diff --git a/docker-compose.yml b/docker-compose.yml index 5867597..c3cb240 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1068,6 +1068,9 @@ services: # discord.py expects. Legacy DISCORD_TOKEN inline alias is dropped — # use SOPS at secrets/discord_token.sops. - DISCORD_BOT_TOKEN_FILE=/run/secrets/discord_token + # Backup-repo PAT (git push to ordo-hermes-backup). SOPS-managed; the + # entrypoint bridges GITHUB_BACKUP_PAT_FILE -> GITHUB_BACKUP_PAT env var. + - GITHUB_BACKUP_PAT_FILE=/run/secrets/github_backup_pat - DISCORD_ALLOWED_USERS=${DISCORD_ALLOWED_USERS:-} - DISCORD_ALLOWED_CHANNELS=${DISCORD_ALLOWED_CHANNELS:-} - DISCORD_ALLOWED_ROLES=${DISCORD_ALLOWED_ROLES:-} @@ -1097,6 +1100,7 @@ services: - ${BASE_PATH:-.}/..:${HERMES_HOST_DEV_MOUNT:-/projects}:rw secrets: - discord_token + - github_backup_pat healthcheck: # gateway_state.json is written by `hermes gateway` on startup (Docker-mode # doesn't create a gateway.pid — that's only for systemd/launchd installs). @@ -1223,6 +1227,8 @@ secrets: file: ${HOME}/.ai-toolkit/runtime/secrets/discord_token github_pat: file: ${HOME}/.ai-toolkit/runtime/secrets/github_pat + github_backup_pat: + file: ${HOME}/.ai-toolkit/runtime/secrets/github_backup_pat hf_token: file: ${HOME}/.ai-toolkit/runtime/secrets/hf_token civitai_token: diff --git a/docs/runbooks/secrets.md b/docs/runbooks/secrets.md index 684102a..f4c903d 100644 --- a/docs/runbooks/secrets.md +++ b/docs/runbooks/secrets.md @@ -27,7 +27,12 @@ Two delivery paths, both fed from `~/.ai-toolkit/runtime/` (produced by `SEARXNG_SECRET`, `N8N_OWNER_*`, …). `make up` always passes both. - **File-form** (`secrets/.sops` → `runtime/secrets/`): mounted as Docker secrets at `/run/secrets/`, so they never appear in - `docker inspect`. + `docker inspect`. Where an app SDK expects a plain env var, the consumer's + entrypoint **bridges** `_FILE` → a `` env var (e.g. hermes-gateway: + `DISCORD_BOT_TOKEN`, `GITHUB_BACKUP_PAT`). So agents read the token from their + environment — they never see, need, or look for a plaintext secret in `.env`. + The `ordo-hermes-backup` git remote authenticates the same way: a credential + helper reads the SOPS-decrypted token, so no token is ever embedded in a URL. ### ops-controller recreates with real secrets diff --git a/hermes/entrypoint.sh b/hermes/entrypoint.sh index e152269..df66a3e 100644 --- a/hermes/entrypoint.sh +++ b/hermes/entrypoint.sh @@ -46,6 +46,14 @@ if [ -n "${DISCORD_BOT_TOKEN_FILE:-}" ] && [ -f "$DISCORD_BOT_TOKEN_FILE" ]; the export DISCORD_BOT_TOKEN fi +# Same bridge for the backup-repo PAT: SOPS Docker secret at +# /run/secrets/github_backup_pat -> GITHUB_BACKUP_PAT env var that Hermes and +# git expect. Secrets live in SOPS, never in .env; this is how they reach the env. +if [ -n "${GITHUB_BACKUP_PAT_FILE:-}" ] && [ -f "$GITHUB_BACKUP_PAT_FILE" ]; then + GITHUB_BACKUP_PAT="$(cat "$GITHUB_BACKUP_PAT_FILE")" + export GITHUB_BACKUP_PAT +fi + HERMES_BIN=/opt/hermes-agent/.venv/bin/hermes # Seed model + MCP endpoints to Docker-network DNS. hermes config set is idempotent diff --git a/scripts/secrets/decrypt.sh b/scripts/secrets/decrypt.sh index 2b25335..03fb1dc 100644 --- a/scripts/secrets/decrypt.sh +++ b/scripts/secrets/decrypt.sh @@ -36,6 +36,7 @@ echo "==> ${RUNTIME_DIR}/.env (env-form internal tokens)" # File-form: decrypt each high-value token to its own file. for src in secrets/discord_token.sops \ secrets/github_pat.sops \ + secrets/github_backup_pat.sops \ secrets/hf_token.sops \ secrets/civitai_token.sops \ secrets/n8n_api_key.sops; do diff --git a/secrets/README.md b/secrets/README.md index fee62d7..92794c2 100644 --- a/secrets/README.md +++ b/secrets/README.md @@ -16,6 +16,10 @@ with the age private key at `~/.config/sops/age/keys.txt`. - `github_pat.sops` — GitHub fine-grained PAT. Mounted on `mcp-gateway` and `comfyui` (the latter as `GITHUB_TOKEN_FILE` for ComfyUI-Manager). +- `github_backup_pat.sops` — classic GitHub PAT for `git push` to the + `ordo-hermes-backup` private repo. Mounted on `hermes-gateway`; the + entrypoint bridges it to the `GITHUB_BACKUP_PAT` env var, and the backup + repo's credential helper reads it. Not used by the stack services themselves. - `hf_token.sops` — HuggingFace token (gated model downloads). Mounted on `ops-controller`, `dashboard`, `gguf-puller`, and the comfyui model puller. diff --git a/secrets/github_backup_pat.sops b/secrets/github_backup_pat.sops new file mode 100644 index 0000000..03c4095 --- /dev/null +++ b/secrets/github_backup_pat.sops @@ -0,0 +1,20 @@ +{ + "data": "ENC[AES256_GCM,data:whjXnID5PQh/mlkpS7/5XDuYgxRQyGM5ry7+sQcxFcrlLCDXk0hV3Q==,iv:9VOjPZQB5+rZO82bc5QBgWF3gLqVY+AahQHN4U0vF7g=,tag:ForGDI3m4LsCCjC7azZlDw==,type:str]", + "sops": { + "kms": null, + "gcp_kms": null, + "azure_kv": null, + "hc_vault": null, + "age": [ + { + "recipient": "age1egt7028wtwpf3g9fqe5xvf80ptmhze25tltw0ff6nwv5hf6v6grqpl2qar", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAyZlpneGVUZ2V0a200NVE2\neTRrNGFqOEtWdlFkcWQ0VE1keEt0YjJ1OWlvCnplTm1MYWVDQWlMM0pISFRhWHlt\nQ3hxeStBL1BXQTYxRVdoOVNjcTNBZkkKLS0tIHBxdG1iWHgvdytkdEpZeC83Q2tM\nQmRkaXZCNDRmTldpU0JVTU9RTkg0VHMKErCgr6lhx5ICdemS3dEzkFFDvAHjMObO\nLvLtTexE3rSuX62x/XEHT5mPHV+K5RG/pvBYI5V78ghS3CCHKy7P7w==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2026-06-26T14:16:58Z", + "mac": "ENC[AES256_GCM,data:2N50+k13OFK0rhvei/vfVib5Psad5iB9jhSiTItePnTUfRaw+ViHUB7n2FMrSl1i6oAphfwkU4Un/3hp6oANVHpg8pLBn2EaYm1QY841R2Q81kL3zYmQANMIIW7YHm/9HihMBRKTD5jNU7k4RuhBem5PTJjCt8AMyYfcJKJZNF0=,iv:rzndofwrUJI2QAHW4vyfy6CoshO5+qa5FMDDtYqKbI4=,tag:n0hR1nMZaI+KM/zYDZ2hNQ==,type:str]", + "pgp": null, + "unencrypted_suffix": "_unencrypted", + "version": "3.7.3" + } +} \ No newline at end of file