2dd5ed5877
check / flake-check (pull_request) Has been cancelled
- sops-nix flake input, age + ssh-to-age in devShell, .sops.yaml with per-host recipients (real pubkeys still TODO until lars bootstraps). - nix/modules/gebos-secrets.nix centralises per-host secret subsets and renders /run/secrets/gebos-env via sops.templates. Service modules now read EnvironmentFile= from services.gebos.secrets.envFile instead of the placeholder /etc/gebos/secrets.env. - Falls back to a writeText env file when nix/secrets/secrets.yaml is absent, so `nix flake check` and first-boot eval work pre-bootstrap. - nix/secrets/README.md walks through age key + sops bootstrap. - Dev defaults: ingester binary falls back to local tcp/postgres URLs, process-compose adds mosquitto and wires GEBOS_MQTT_BROKER / GEBOS_POSTGRES_URL / GEBOS_POSTGRES_PASSWORD + VITE_SUPABASE_URL / VITE_SUPABASE_ANON_KEY, frontend defaults to 127.0.0.1:8000 in DEV. Refs #23 (comment #110).
Secrets
Layout
.sops.yaml(repo root) — recipients and per-file creation rules.nix/secrets/secrets.yaml.example— plaintext template (committed).nix/secrets/secrets.yaml— sops-encrypted real values (committed once created).
Plaintext values never enter the Nix store or disk outside tmpfs. sops-nix
decrypts on activation into /run/secrets/<name> with declared owner/mode,
then renders gebos-env from those placeholders for systemd EnvironmentFile=.
First-time bootstrap
# 1. Generate your personal age key (keep this private, NEVER commit).
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
# → "Public key: age1abc..." ← paste this into .sops.yaml as &dev_<you>
# 2. Get each host's age pubkey (one per host).
# On each host as root:
ssh-to-age < /etc/ssh/ssh_host_ed25519_key.pub
# → "age1xyz..." ← paste into .sops.yaml as &host_<name>
# 3. Edit .sops.yaml — replace every "age1TODO_..." with the real pubkeys above.
# 4. Create the real secrets file from the template, fill in real values, encrypt.
cp nix/secrets/secrets.yaml.example nix/secrets/secrets.yaml
$EDITOR nix/secrets/secrets.yaml
sops -e -i nix/secrets/secrets.yaml
# 5. Commit .sops.yaml + the encrypted secrets.yaml.
git add .sops.yaml nix/secrets/secrets.yaml
git commit -m "secrets: initial sops bootstrap"
Editing later
sops nix/secrets/secrets.yaml # opens $EDITOR, re-encrypts on save
Rotation
- Leaked value →
sops nix/secrets/secrets.yaml, change value, redeploy. - New host → add its age pubkey to
.sops.yaml, thensops updatekeys nix/secrets/secrets.yaml. - Compromised dev key → remove from
.sops.yaml,sops updatekeys, redeploy.
Generating value material
| Field | How |
|---|---|
supabase_postgres_password |
openssl rand -hex 32 |
supabase_jwt_secret |
openssl rand -hex 32 (must be ≥32 chars) |
supabase_anon_key |
JWT signed with jwt_secret, role: anon, long expiry |
supabase_service_role_key |
JWT signed with jwt_secret, role: service_role |
supabase_dashboard_password |
openssl rand -hex 16 (Studio basic-auth, username gebos) |
ingester_postgres_password |
openssl rand -hex 32 |
postgres_admin_password |
openssl rand -hex 32 |
Self-hosted Supabase docs have a one-liner for signing the two JWTs offline.
Local development
You do not need to bootstrap secrets for nix run .#dev. The dev process-compose
stack and the ingester binary both default to local-only values
(GEBOS_POSTGRES_URL, GEBOS_MQTT_BROKER, GEBOS_POSTGRES_PASSWORD, plus the
JWT/anon-key pair the frontend needs). See nix/dev/process-compose.nix.