{ config, ... }: # sops-nix wiring for mars. Encrypted values in ../../secrets/mars.yaml, # decrypted with mars's own SSH host key (recipient in ../../.sops.yaml). # The host key is pre-generated on the laptop and shipped at install # (nixos-anywhere --extra-files -> /etc/ssh/ssh_host_ed25519_key). { sops.defaultSopsFile = ../../secrets/mars.yaml; sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; sops.secrets.darman_password.neededForUsers = true; users.users.darman.hashedPasswordFile = config.sops.secrets.darman_password.path; sops.secrets.tailscale_authkey = { }; # Credentials file for the //jupiter/data cifs mount (see configuration.nix). # Same value as jupiter's own samba_password (services/network/samba.nix) — # mars authenticates as the same smb user, mirroring terra's setup. sops.secrets.samba_password = { }; sops.templates."jupiter-smb.credentials".content = '' username=darman password=${config.sops.placeholder.samba_password} ''; # Hermes Agent (hermes-agent.nix) — moved here from jupiter (see that # host's git history); same Telegram bot token, opencode key, and # Authentik OIDC client secret, so no new bot/app to provision. sops.secrets.opencode_go_api_key = { }; sops.secrets.telegram_bot_token = { }; sops.secrets.hermes_dashboard_oidc_client_secret = { }; # Same value as in secrets/jupiter.yaml (the sending side), stored WITHOUT a # trailing newline — a stray newline would change the key the HMAC is # computed with and fail every delivery. `scripts/edit_secrets` writes a # bare value. hermes-agent.nix trims one anyway, belt and braces. # # This is NOT in the container's env any more. It used to be, because # hermes-agent-webhook-route ran `hermes webhook subscribe` inside the # container and read the secret back out of its environment — which meant # podman-hermes-agent had to be restarted first on rotation, or the # subscription silently pinned the stale value. The route config is now # written host-side (hermes-agent-webhook-routes reads this file directly), # so that ordering constraint is gone and the secret no longer sits in an # env var luna can read with `env`. sops.secrets.gitea_hermes_webhook_secret = { restartUnits = [ "hermes-agent-webhook-routes.service" ]; }; sops.templates."hermes-agent.env".content = '' OPENCODE_GO_API_KEY=${config.sops.placeholder.opencode_go_api_key} TELEGRAM_BOT_TOKEN=${config.sops.placeholder.telegram_bot_token} TELEGRAM_HOME_CHANNEL=15151223 TELEGRAM_ALLOWED_USERS=15151223 WEBHOOK_ENABLED=true WEBHOOK_PORT=8644 HERMES_DASHBOARD_OIDC_CLIENT_SECRET=${config.sops.placeholder.hermes_dashboard_oidc_client_secret} ''; # luna's own gitea push token (services/dev/gitea.nix provisions the # account + PR-tier repo access on jupiter; this is the per-user token # generated once via `gitea admin user generate-access-token --username # luna --scopes write:repository,read:user` on jupiter — read:user is # required, `tea logins add` fails without it). Read directly by # hermes-agent.nix's prepare-dirs oneshot (default root:root owner is # fine — that oneshot already runs as root) to set up a git # credential-store file and a `tea` login, both written into hermesHome # so they're visible inside the container at /opt/data/.... # restartUnits re-provisions both on rotation, without a full mars deploy. sops.secrets.gitea_luna_token.restartUnits = [ "hermes-agent-prepare-dirs.service" ]; # livesync-bridge (livesync-bridge.nix) — luna's Obsidian vault, mirrored # out of CouchDB on jupiter. Both values are consumed by the rendered # config.json rather than read directly, so the sops default of root:root # 0400 is correct here; only the TEMPLATE needs an owner (set where it is # defined, next to the vault path it references). # # couchdb_luna_password holds jupiter's `obsidian` ADMIN password — the same # value as secrets/jupiter.yaml's couchdb_admin_password — and # obsidian_luna_passphrase is the same passphrase as the personal vault. # That is a deliberate choice to reuse what already existed, but it is worth # being clear about what it costs: mars can decrypt and read EVERY vault # database, not just luna's, and mars is the box running an autonomous # agent. The two are independent to fix, cheapest first: # # 1. A vault-specific passphrase (re-encrypts luna's remote database, but # leaves the personal vault's contents unreadable from here). # 2. A CouchDB account scoped to luna's database via _security (three curl # calls, in README -> "Obsidian vaults"), which also stops mars from # reaching the other databases at all. # # Neither is required for the bridge to work; both shrink the blast radius # if mars is ever compromised. sops.secrets.couchdb_luna_password = { }; # The E2EE passphrase for luna's vault, as entered in the Obsidian plugin. # Vault passphrases otherwise never leave the clients (see the note in # services/dev/obsidian-livesync.nix) — this one has to be here because mars # IS a client: it decrypts in order to write real markdown to disk. Path # obfuscation uses the same passphrase in the plugin, so the bridge's # separate obfuscatePassphrase field is fed from this one value. sops.secrets.obsidian_luna_passphrase = { }; }