This repository has been archived on 2026-09-17. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
nixos-config/secrets
pijukka c8cfdcd88e host(pinix): add live intent and ereader viewer services
Both run on pinix, published only through the pinix Tailnet namespace and
Traefik's web-internal entrypoint (unbound local-data + Headscale split
DNS for intent./ereader.domain2). Live Intent reads its OpenAI key from
the new pinix-only agenix secret.

ereaderViewer is a path: flake input for now; the repo split plan moves
it into its own repository on cgit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:18:55 +02:00
..
agentCalendarPw.age updatet secret mit pw agentCalendarPw.age 2026-08-01 12:16:25 +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
bindAcmeTsig.age host(pinix): Wildcard-Zertifikate per DNS-01 gegen das eigene BIND 2026-09-12 19:10:55 +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
financeXmpp.age pinix: prepare finance-agent XMPP bot, add codex package 2026-07-31 16:17:41 +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
icecastPw.age Add sound.0ff.space: Icecast + Liquidsoap internet radio on pinix 2026-08-04 08:42:36 +02:00
ipv4.age auto 2025-07-06 20:46:19 +02:00
ipv6.age auto 2025-07-06 20:46:19 +02:00
liveIntentOpenaiApiKey.age host(pinix): add live intent and ereader viewer services 2026-09-17 12:18:55 +02:00
lldapAdmin.age secrets: fix LLDAP password format 2026-07-28 02:03:07 +02:00
mailPW.age updated secret 2026-08-01 23:24:00 +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
oidcTrek.age feat(blunix): add Dex OIDC client for Trek 2026-07-30 14:00:11 +02:00
oidcVikunja.age secrets: remove plaintext credentials 2026-07-28 01:26:10 +02:00
openrouterApiKey.age secrets: add OpenRouter key for board agents 2026-09-08 23:01:44 +02:00
pinix-wifi.age secrets(pinix-wifi): rotate WLAN passphrase to four-word passphrase 2026-09-13 14:48:28 +02:00
pinixHeadscalePreAuthKey.age tailnet: move main nodes to pinix user lukas 2026-09-14 16:52:53 +02:00
pinixLldapAdmin.age host(pinix): commit the Dex + Headscale + LLDAP identity stack 2026-09-13 16:38:02 +02:00
README.md host(pinix): record identity DNS bootstrap for dex and head 2026-09-14 12:55:11 +02:00
resticPW.age auto 2025-07-06 20:46:19 +02:00
secrets.nix host(pinix): add live intent and ereader viewer services 2026-09-17 12:18:55 +02:00
secrets.yaml auto 2025-06-24 20:58:08 +02:00
traefikBasicAuth.age secrets: set intent-board basic auth password 2026-09-05 13:16:00 +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 host(tnix): switch wifi to iPhone hotspot, allow enp0s31f6 without default route 2026-09-12 10:05:43 +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
financeXmpp.age pinix finance-xmpp-listener/finance-agent-worker systemd services Raw XMPP account password for finance@bot.0ff.space
agentCalendarPw.age blunix agent-codex-worker (via the calendar-add-event wrapper) Raw LDAP password for the existing calendar LLDAP account, used for CalDAV writes to calendar/public on Radicale
go-sendxmpp.age pinix webhook notifications Raw XMPP account password
headscalePreAuthKey.age Tailscale clients on tnix and pinix Raw Headscale pre-authentication key
icecastPw.age pinix icecast-radio/liquidsoap-radio systemd services Raw password, shared by Icecast's source/admin login and Liquidsoap's outgoing source connection
lldapAdmin.age LLDAP, Dex, and Radicale on blunix/pinix 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 on blunix/pinix 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
oidcTrek.age Dex client for Trek Raw OIDC client secret
oidcVikunja.age Dex and Vikunja Raw OIDC client secret
openrouterApiKey.age blunix board-agent-worker and agent-codex-builder systemd services Raw OpenRouter API key, used as the openrouter runtime's OPENROUTER_API_KEY
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 view a secret's plaintext instead of editing it, see „agenix on blunix is ragenix" below — agenix -d doesn't work on blunix, there's a different command for that.

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>

Only ever run this yourself, as a human, in your own terminal session. The XMPP agent@/builder@ bots and any interactive coding assistant must never run a decrypt command and print the result — see the Sicherheitsrichtlinien in agents/shared.md. If an agent needs to hand you a decrypted value (e.g. an OIDC client secret meant for another admin), it gives you this command to run yourself, never the plaintext.

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 .