cryodev/docs/services/sops.md
steffen 5ba78886d2 Add SD image pipeline, documentation overhaul, and fix module issues
- Add automatic SD image builds for Raspberry Pi via Forgejo Actions
- Enable binfmt emulation on cryodev-main for aarch64 cross-builds
- Add sd-image.nix module to cryodev-pi configuration
- Create comprehensive docs/ structure with installation guides
- Split installation docs into: first-install (server), reinstall, new-client (Pi)
- Add lib/utils.nix and apps/rebuild from synix
- Fix headplane module for new upstream API (tale/headplane)
- Fix various module issues (mailserver stateVersion, option conflicts)
- Add placeholder secrets.yaml files for both hosts
- Remove old INSTRUCTIONS.md (content moved to docs/)
2026-03-11 08:41:58 +01:00

3.3 KiB

SOPS Secret Management

Atomic secret provisioning for NixOS using sops-nix.

Overview

Secrets are encrypted with age using SSH host keys, ensuring:

  • No plaintext secrets in the repository
  • Secrets are decrypted at activation time
  • Each host can only decrypt its own secrets

Setup

1. Get Host's Age Public Key

After a host is installed, extract its age key from the SSH host key:

nix-shell -p ssh-to-age --run 'ssh-keyscan -t ed25519 <HOST_IP> | ssh-to-age'

Or locally on the host:

nix-shell -p ssh-to-age --run 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'

2. Configure .sops.yaml

Add the host key to .sops.yaml:

keys:
  - &admin_key age1e8p35795htf7twrejyugpzw0qja2v33awcw76y4gp6acnxnkzq0s935t4t
  - &main_key age1...  # cryodev-main
  - &pi_key age1...    # cryodev-pi

creation_rules:
  - path_regex: hosts/cryodev-main/secrets.yaml$
    key_groups:
      - age:
        - *admin_key
        - *main_key
        
  - path_regex: hosts/cryodev-pi/secrets.yaml$
    key_groups:
      - age:
        - *admin_key
        - *pi_key

3. Create Secrets File

sops hosts/<hostname>/secrets.yaml

This opens your editor. Add secrets in YAML format:

tailscale:
  auth-key: "tskey-..."
  
some-service:
  password: "secret123"

Usage in Modules

Declaring Secrets

{ config, ... }:
{
  sops.secrets.my-secret = {
    # Optional: set owner/group
    owner = "myservice";
    group = "myservice";
  };
}

Using Secrets

Reference the secret path in service configuration:

{
  services.myservice = {
    passwordFile = config.sops.secrets.my-secret.path;
  };
}

Using Templates

For secrets that need to be embedded in config files:

{
  sops.secrets."netdata/stream-api-key" = { };
  
  sops.templates."netdata-stream.conf" = {
    content = ''
      [stream]
      enabled = yes
      api key = ${config.sops.placeholder."netdata/stream-api-key"}
    '';
    owner = "netdata";
  };
  
  services.netdata.configDir."stream.conf" = 
    config.sops.templates."netdata-stream.conf".path;
}

Common Secrets

cryodev-main

mailserver:
  accounts:
    forgejo: "$2y$05$..."  # bcrypt hash
    admin: "$2y$05$..."

forgejo-runner:
  token: "..."

headplane:
  cookie_secret: "..."       # openssl rand -hex 16
  agent_pre_authkey: "..."   # headscale preauthkey

tailscale:
  auth-key: "tskey-..."

cryodev-pi

tailscale:
  auth-key: "tskey-..."

netdata:
  stream:
    child-uuid: "..."  # uuidgen

Generating Secret Values

Secret Command
Mailserver password nix-shell -p mkpasswd --run 'mkpasswd -sm bcrypt'
Random hex token nix-shell -p openssl --run 'openssl rand -hex 16'
UUID uuidgen
Tailscale preauth sudo headscale preauthkeys create --expiration 99y --reusable --user default

Updating Keys

After modifying .sops.yaml, update existing secrets files:

sops --config .sops.yaml updatekeys hosts/<hostname>/secrets.yaml

Troubleshooting

"No matching keys found"

Ensure the host's age key is in .sops.yaml and you've run updatekeys.

Secret not decrypting on host

Check that /etc/ssh/ssh_host_ed25519_key exists and matches the public key in .sops.yaml.