1
0
Fork 0
forked from lukas/nixos-config
nixos-config/secrets
pijukka ba6d7b8512 agent-codex: wire up Builder handoff (test-stage relay), own XMPP identity
Agent -> Builder handoff pipeline, first cut:

- agents/agent.md: documents the ===BUILDER-HANDOFF=== marker format
  the Agent uses once the user explicitly wants an elaborated spec
  implemented.
- worker.py: extract_handoff() detects the marker, strips it from the
  user-facing reply, and enqueue_builder_job() writes the spec as a new
  job into a separate builder-inbox queue.
- builder.py (new): path-triggered oneshot worker for that queue. Test
  stage only -- runs Codex read-only against the same agents/ role dir
  and is told to relay the received spec back unchanged, to validate
  the pipeline before any real write capability is built. No execution,
  no filesystem changes.
- New Prosody account builder@bot.0ff.space (own XMPP identity, own
  secrets/builderXmpp.age, own go-sendxmpp config) so Builder replies
  are visibly distinct from Agent replies instead of reusing agent@'s
  identity with a text prefix.

Verified end to end against the live services: a real Agent job that
requests a test handoff produces the marker, gets detected, enqueues a
Builder job, and the Builder relays it back -- confirmed in Prosody's
log as a message from builder@bot.0ff.space to lukas@0ff.space.

Builder does not yet get real technical execution capability or a
"build with lukas's permission" dialog loop -- that needs its own
sandbox/approval design and is explicitly not built here.
2026-07-30 10:08:17 +02:00
..
agentXmpp.age prosody: agent@ turntaking; migrate agent listener to codex worker on blunix; calendar personal feed 2026-07-29 23:29:59 +02:00
bindPW.age secrets: remove plaintext credentials 2026-07-28 01:26:10 +02:00
builderXmpp.age agent-codex: wire up Builder handoff (test-stage relay), own XMPP identity 2026-07-30 10:08:17 +02:00
dns-challenge.age auto 2025-07-06 20:46:19 +02:00
domain.age auto 2025-07-06 20:46:19 +02:00
forgejoToken.age feat: add native agenix (ragenix) on blunix, wire Forgejo API token 2026-07-29 18:15:33 +02:00
go-sendxmpp.age cal updates and hyprland added keepass sc 2025-09-20 22:17:37 +02:00
headscalePreAuthKey.age auto 2025-07-06 21:50:06 +02:00
ipv4.age auto 2025-07-06 20:46:19 +02:00
ipv6.age auto 2025-07-06 20:46:19 +02:00
lldapAdmin.age secrets: fix LLDAP password format 2026-07-28 02:03:07 +02:00
mailPW.age auto 2025-07-08 12:24:20 +02:00
motd.age motd 2025-09-07 23:33:54 +02:00
oauth2Cookie.age secrets: remove plaintext credentials 2026-07-28 01:26:10 +02:00
oidcAudiobookshelf.age secrets: rotate DietPi OIDC credentials 2026-07-28 13:05:12 +02:00
oidcForgejo.age auto 2025-09-04 18:49:14 +02:00
oidcGTS.age platform: declarativize pinix services and deployment 2026-07-27 23:46:41 +02:00
oidcGTSEnv.age platform: declarativize pinix services and deployment 2026-07-27 23:46:41 +02:00
oidcHeadscale.age secrets: rotate Headscale OIDC credential 2026-07-28 02:47:18 +02:00
oidcOauth2.age auto 2025-07-06 20:46:19 +02:00
oidcQuantum.age secrets: rotate DietPi OIDC credentials 2026-07-28 13:05:12 +02:00
oidcVikunja.age secrets: remove plaintext credentials 2026-07-28 01:26:10 +02:00
README.md agent-codex: wire up Builder handoff (test-stage relay), own XMPP identity 2026-07-30 10:08:17 +02:00
resticPW.age auto 2025-07-06 20:46:19 +02:00
secrets.nix agent-codex: wire up Builder handoff (test-stage relay), own XMPP identity 2026-07-30 10:08:17 +02:00
secrets.yaml auto 2025-06-24 20:58:08 +02:00
traefikBasicAuth.age secrets: remove plaintext credentials 2026-07-28 01:26:10 +02:00
traefikDashboardPassword.age secrets: remove plaintext credentials 2026-07-28 01:26:10 +02:00
webdav.age updated secret 2025-09-07 19:38:53 +02:00
wirelessSecretsFile.age test5 2025-08-15 12:15:04 +02:00

Secrets

This directory contains credentials encrypted with agenix. Only encrypted *.age files belong in Git. Never commit plaintext passwords, tokens, private keys, recovery codes, or decrypted copies.

secrets/secrets.nix defines which user and host SSH public keys may decrypt each file. The public keys are not secrets. Host-specific ownership and runtime paths are configured in:

  • hosts/globalServices/secretFiles.nix
  • hosts/tnix/localServices/secrets.nix
  • hosts/pinix/localServices/secrets.nix
  • hosts/blunix/localServices/secrets.nix

Active secrets

File Consumer Expected plaintext format
bindPW.age Prosody LDAP authentication Prosody Lua configuration containing the LDAP password
dns-challenge.age Traefik/Let's Encrypt systemd environment file containing the Cloudflare DNS challenge credentials
forgejoToken.age Manual/ad-hoc Forgejo API calls (drone, on any host) Raw Forgejo API access token for the lukas account
agentXmpp.age blunix agent-xmpp-listener systemd service Raw XMPP account password for agent@bot.0ff.space
builderXmpp.age blunix agent-codex-builder systemd service Raw XMPP account password for builder@bot.0ff.space
go-sendxmpp.age pinix webhook notifications Raw XMPP account password
headscalePreAuthKey.age Tailscale clients on tnix and pinix Raw Headscale pre-authentication key
lldapAdmin.age LLDAP, Dex, and Radicale Raw LLDAP administrator password
mailPW.age aerc and msmtp on tnix Raw mailbox password
motd.age Prosody Prosody Lua configuration for the message of the day
oauth2Cookie.age oauth2-proxy protecting 0ff.space Raw cookie encryption secret
oidcAudiobookshelf.age Dex client for Audiobookshelf Raw OIDC client secret
oidcForgejo.age Dex and Forgejo Raw OIDC client secret
oidcGTS.age Dex client for GoToSocial Raw OIDC client secret
oidcGTSEnv.age GoToSocial Environment file containing GTS_OIDC_CLIENT_SECRET=...; the value must match oidcGTS.age
oidcHeadscale.age Dex and Headscale Raw OIDC client secret
oidcOauth2.age Dex and oauth2-proxy protecting 0ff.space Raw OIDC client secret
oidcQuantum.age Dex client for Quantum Raw OIDC client secret
oidcVikunja.age Dex and Vikunja Raw OIDC client secret
resticPW.age Restic on pinix and blunix Raw Restic repository password
traefikBasicAuth.age Traefik dashboard middleware htpasswd line in user:password-hash format
webdav.age WebDAV on pinix Environment file with ENV_USERNAME=... and ENV_PASSWORD=...
wirelessSecretsFile.age wpa_supplicant on tnix and pinix key=value lines referenced through ext:key, currently psk_mango and psk_home

Audiobookshelf and Quantum currently run on DietPi, which is offline. Their OIDC client entries remain prepared in Dex, but the applications are not deployed by the active NixOS hosts. Their rotated client secrets must be entered in the applications when DietPi is next powered on.

Raw passwords and tokens must not end with a newline unless the consuming service explicitly expects one. In particular, lldapAdmin.age is read verbatim when LLDAP resets the administrator password.

Prepared or unused files

These encrypted files currently have no active consumer:

File Intended use or status
domain.age Legacy declaration; no service currently reads it
ipv4.age Legacy file; not declared by agenix or read by a service
ipv6.age Legacy file; not declared by agenix or read by a service
traefikDashboardPassword.age Legacy file replaced by traefikBasicAuth.age

secrets/secrets.yaml is an older SOPS file and is not consumed by the current agenix configuration. Do not add new values to it. It can be removed after confirming that no external workflow still depends on it.

Creating or editing a secret

From the repository root:

agenix -e secrets/<name>.age

To add a new secret:

  1. Add its recipient list to secrets/secrets.nix.
  2. Create the encrypted file with agenix -e.
  3. Declare its runtime owner and path in the appropriate NixOS secret module.
  4. Pass only the resulting runtime path to the service.
  5. Evaluate the affected host before deployment.

Do not interpolate plaintext secrets into Nix strings. Doing so copies them into the world-readable Nix store.

Recipients and key changes

After adding, removing, or replacing a recipient key, re-encrypt all registered files:

agenix -r

Removing a recipient does not invalidate copies that person or host already decrypted. Rotate the underlying credential as well if access must be revoked.

agenix on blunix is ragenix

agenix (from ragenix in hosts/blunix/localServices/packages.nix) is installed on blunix so agenix -e/agenix -r work as documented above. It is a Rust reimplementation and, unlike the original CLI, has no -d/--decrypt flag — running agenix -d on blunix fails with unexpected argument '-d' found. For decrypting to stdout (verification, or reading a secret's value in a script/session), use the original CLI via the pinned flake input instead:

nix run github:ryantm/agenix -- -d secrets/<name>.age -i <private-key>

Safe checks

Validate that every registered age file can be decrypted without printing its content:

for secret in secrets/*.age; do
  nix run github:ryantm/agenix -- -d "$secret" -i <private-key> >/dev/null
done

Scan the working tree for accidentally committed plaintext:

nix run nixpkgs#gitleaks -- dir --redact .