From 73cd6b7aae2eb0836ed1d8ab5060ee0be865fe4c Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Sat, 25 Jul 2026 00:08:45 -0700 Subject: [PATCH] infra: mirror LAN dev's rendered secrets under $HOME for snap-confined Docker Docker on the LAN box is the snap package, sandboxed to $HOME (plus a short allowlist) -- it can't see /etc/thermograph.env at all, so env_file: /etc/thermograph.env silently loads nothing for every container there, no matter how correctly the vault renders. Confirmed directly: `docker run --env-file /etc/thermograph.env` fails with "no such file or directory" on a file the shell reads fine; the same file under $HOME loads correctly. render-secrets.sh gains an opt-in second write (THERMOGRAPH_SECRETS_ENV_FILE_MIRROR), deploy-dev.sh points it at infra/deploy/dev-secrets.env (gitignored, re-rendered every deploy), and docker-compose.dev.yml adds it as a second env_file entry for backend/daemon/lake -- compose appends env_file lists across overlays, so this is additive and prod/beta (which never set the mirror var) are unaffected. --- infra/.gitignore | 4 ++++ infra/deploy/deploy-dev.sh | 9 +++++++++ infra/deploy/render-secrets.sh | 15 +++++++++++++++ infra/docker-compose.dev.yml | 17 +++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/infra/.gitignore b/infra/.gitignore index c3ac61f..c6b008f 100644 --- a/infra/.gitignore +++ b/infra/.gitignore @@ -27,3 +27,7 @@ __pycache__/ deploy/.image-tags.env deploy/.deploy.lock deploy/.stack-image-tags.env + +# LAN dev's mirror of the rendered secrets, for snap-confined Docker (see +# deploy-dev.sh / render-secrets.sh) -- plaintext, re-rendered every deploy. +deploy/dev-secrets.env diff --git a/infra/deploy/deploy-dev.sh b/infra/deploy/deploy-dev.sh index 08c501f..8149d8b 100755 --- a/infra/deploy/deploy-dev.sh +++ b/infra/deploy/deploy-dev.sh @@ -72,6 +72,15 @@ export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-thermograph-dev}" # genuinely needs, and nothing else. export THERMOGRAPH_SECRETS_SKIP_COMMON=1 +# (1b) Docker on this box is the snap package, confined to $HOME (plus a short +# allowlist) -- it silently can't see /etc/thermograph.env at all, so +# env_file: /etc/thermograph.env resolves to nothing for every container no +# matter how correctly it's rendered. Mirror the render to a path under +# $APP_DIR too; docker-compose.dev.yml points env_file at this copy instead of +# /etc/thermograph.env. Untracked (see infra/.gitignore); re-rendered on every +# deploy, never committed. +export THERMOGRAPH_SECRETS_ENV_FILE_MIRROR="$APP_DIR/infra/deploy/dev-secrets.env" + # (2) The age key is read from wherever it is READABLE, not necessarily # /etc/thermograph/age.key. render-secrets.sh falls back to `sudo cat` for a # root-owned 0400 key, and this box has no passwordless sudo -- under the Forgejo diff --git a/infra/deploy/render-secrets.sh b/infra/deploy/render-secrets.sh index b5de8fc..2db9cb4 100755 --- a/infra/deploy/render-secrets.sh +++ b/infra/deploy/render-secrets.sh @@ -127,6 +127,21 @@ render_thermograph_secrets() { echo "!! cannot write /etc/thermograph.env (need file write access or passwordless sudo)" >&2 rc=1 fi + + # Optional second copy under $HOME, for a host whose docker CLI cannot read + # /etc/thermograph.env at all -- the LAN dev box's snap-packaged Docker is + # confined to $HOME (plus a short allowlist) and silently treats anything + # under /etc as nonexistent, so env_file: /etc/thermograph.env resolves to + # nothing for every container even though the file is right there and the + # shell that rendered it can read it fine. deploy-dev.sh sets this; + # prod/beta (apt-installed Docker, no snap confinement) leave it unset and + # this is a no-op for them. + if [ "$rc" = 0 ] && [ -n "${THERMOGRAPH_SECRETS_ENV_FILE_MIRROR:-}" ]; then + mkdir -p "$(dirname "$THERMOGRAPH_SECRETS_ENV_FILE_MIRROR")" \ + && install -m 0600 "$tmp" "$THERMOGRAPH_SECRETS_ENV_FILE_MIRROR" \ + || { echo "!! cannot write mirror at $THERMOGRAPH_SECRETS_ENV_FILE_MIRROR" >&2; rc=1; } + fi + rm -f "$tmp" return "$rc" } diff --git a/infra/docker-compose.dev.yml b/infra/docker-compose.dev.yml index 9c12a9b..86f9c53 100644 --- a/infra/docker-compose.dev.yml +++ b/infra/docker-compose.dev.yml @@ -27,12 +27,23 @@ # prod's backend=4 / frontend=2 / db=2 allocation. `!reset` drops the base # value (both the top-level `cpus:` and the Swarm-style `deploy.resources` # block the base file carries for parity). +# 4. backend/daemon/lake get a second env_file entry pointing at a copy of the +# render under $APP_DIR (deploy-dev.sh sets THERMOGRAPH_SECRETS_ENV_FILE_MIRROR +# to produce it). This box's Docker is the snap package, confined to $HOME -- +# it can't see /etc/thermograph.env at all (not a permissions error, it just +# doesn't exist as far as snap-confined Docker is concerned), so the base +# file's env_file entry silently loads nothing here. compose appends env_file +# lists across overlays (last-wins on duplicate keys), so this is additive: +# prod/beta never set the mirror var, so they only ever get the base entry. services: backend: ports: !override - "8137:8137" cpus: !reset null deploy: !reset null + env_file: + - path: ./deploy/dev-secrets.env + required: false frontend: ports: !reset null cpus: !reset null @@ -42,6 +53,9 @@ services: daemon: cpus: !reset null deploy: !reset null + env_file: + - path: ./deploy/dev-secrets.env + required: false db: cpus: !reset null deploy: !reset null @@ -53,3 +67,6 @@ services: # the default DB_MEMORY (raise DB_MEMORY to give dev more); shm_size stays (it's # required shared memory for parallel query, not a limit). mem_limit: !reset null + env_file: + - path: ./deploy/dev-secrets.env + required: false