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> |
||
|---|---|---|
| .. | ||
| agentCalendarPw.age | ||
| agentXmpp.age | ||
| bindAcmeTsig.age | ||
| bindPW.age | ||
| builderXmpp.age | ||
| dns-challenge.age | ||
| domain.age | ||
| financeXmpp.age | ||
| forgejoToken.age | ||
| go-sendxmpp.age | ||
| headscalePreAuthKey.age | ||
| icecastPw.age | ||
| ipv4.age | ||
| ipv6.age | ||
| liveIntentOpenaiApiKey.age | ||
| lldapAdmin.age | ||
| mailPW.age | ||
| motd.age | ||
| oauth2Cookie.age | ||
| oidcAudiobookshelf.age | ||
| oidcForgejo.age | ||
| oidcGTS.age | ||
| oidcGTSEnv.age | ||
| oidcHeadscale.age | ||
| oidcOauth2.age | ||
| oidcQuantum.age | ||
| oidcTrek.age | ||
| oidcVikunja.age | ||
| openrouterApiKey.age | ||
| pinix-wifi.age | ||
| pinixHeadscalePreAuthKey.age | ||
| pinixLldapAdmin.age | ||
| README.md | ||
| resticPW.age | ||
| secrets.nix | ||
| secrets.yaml | ||
| traefikBasicAuth.age | ||
| traefikDashboardPassword.age | ||
| webdav.age | ||
| wirelessSecretsFile.age | ||
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.nixhosts/tnix/localServices/secrets.nixhosts/pinix/localServices/secrets.nixhosts/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:
- Add its recipient list to
secrets/secrets.nix. - Create the encrypted file with
agenix -e. - Declare its runtime owner and path in the appropriate NixOS secret module.
- Pass only the resulting runtime path to the service.
- 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 .