From 7aa95fa00ae182093476293d2f2bec55c0944cb6 Mon Sep 17 00:00:00 2001 From: ginnoir Date: Tue, 9 Jun 2026 16:03:47 -0500 Subject: [PATCH] refactor: pure env_file stack.env convention across all stacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every service now reads stack.env via env_file with container-exact var names — no ${VAR} parse-time interpolation, no Portainer UI env vars (except stacks/dev, kept on UI-env interpolation as documented exception). - media: drop PUID/PGID/TZ env lines (env_file covers); resolve ${ROOT} - foundry: secrets via env_file; FOUNDRY_PATCH_URLS -> CONTAINER_PATCH_URLS - monitoring: HOMARR_SECRET_ENCRYPTION_KEY -> SECRET_ENCRYPTION_KEY - notify: FRESHRSS_USER composed in stack.env; TZ via env_file - owncloud: container-exact OWNCLOUD_*/MYSQL_* keys; runtime $$ healthcheck - resume/famapp: composites (DATABASE_URL etc.) written out in stack.env - authentik: container-exact keys; image tag pinned in compose - notes: couchdb creds via env_file - remote: gains stack.env (no secrets; convention completeness) - CLAUDE.md: env convention, valhalla-lab registration-only note, correct on-host Portainer working-copy path (/config/portainer/compose/) Validated with docker compose config on valhalla for all 15 stacks. --- CLAUDE.md | 11 ++-- stacks/authentik/docker-compose.yml | 22 ++------ stacks/authentik/stack.env | 15 ++++-- stacks/famapp/docker-compose.yml | 31 ++---------- stacks/famapp/stack.env | 31 +++++++----- stacks/foundry/docker-compose.yml | 8 --- stacks/foundry/stack.env | 5 +- stacks/media/docker-compose.yml | 76 ++++++++-------------------- stacks/media/stack.env | 5 +- stacks/monitoring/docker-compose.yml | 2 - stacks/monitoring/stack.env | 4 +- stacks/notes/docker-compose.yml | 5 +- stacks/notify/docker-compose.yml | 2 - stacks/notify/stack.env | 8 +-- stacks/owncloud/docker-compose.yml | 11 +--- stacks/owncloud/stack.env | 10 ++-- stacks/proxy/docker-compose.yml | 2 - stacks/remote/docker-compose.yml | 4 ++ stacks/remote/stack.env | 4 ++ stacks/resume/docker-compose.yml | 10 ---- stacks/resume/stack.env | 23 +++++++-- 21 files changed, 114 insertions(+), 175 deletions(-) create mode 100644 stacks/remote/stack.env diff --git a/CLAUDE.md b/CLAUDE.md index 5238b8c..0e8c8a1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,7 +50,7 @@ The runner workflow gate-filters on `paths: [Caddyfile, .github/workflows/deploy | Path on valhalla | Purpose | |---|---| | `~/valhalla-lab/portainer-compose.yml` + `.env` | management plane (raw compose; project name `valhalla-lab`) | -| `/data/compose//stacks//` | Portainer's working copy of each app stack (auto-pulled from git) | +| `/config/portainer/compose//stacks//` | Portainer's working copy of each app stack (auto-pulled from git; `/data/compose//...` is the same path *inside* the portainer container, and root-owned on the host) | | `/config//` | SSD-tier persistence: app configs, Postgres data, Redis data | | `/storage1/labdata//` | ZFS-tier persistence: MinIO blob buckets, registry image layers, gitea repos/LFS | | `/storage1/` | media library (Books, Tabletop, plex-style media) | @@ -84,14 +84,14 @@ For per-stack compose ops, pick the file from the container's label: ```powershell ssh ginnoir@valhalla "docker inspect --format '{{ index .Config.Labels \"com.docker.compose.project.config_files\"}}'" -ssh ginnoir@valhalla "docker compose -f /data/compose/14/stacks/owncloud/docker-compose.yml ps" +ssh ginnoir@valhalla "docker compose -f /config/portainer/compose/14/stacks/owncloud/docker-compose.yml ps" # root-owned; needs sudo or a helper container ``` ## Adding a service 1. Pick the stack it belongs to under `stacks//` (or create a new stack and a new Portainer git stack for it). 2. Add the service to `stacks//docker-compose.yml`. Configs → `/config/` bind. Blobs → `/storage1/labdata/` bind. Add `edge` to its `networks:` if Caddy must reach it; otherwise just the per-stack private net. -3. Add secrets to `stacks//stack.env` (Portainer reads this file directly from the git repo on each redeploy). +3. Add secrets to `stacks//stack.env` (Portainer reads this file directly from the git repo on each redeploy). **Env convention:** every service gets `env_file: stack.env`, and var names in `stack.env` are exactly what the container reads — no `${VAR}` interpolation, no Portainer UI env vars. Composite values (e.g. `DATABASE_URL`) are written out in full in `stack.env`; keep embedded components in sync on rotation. Exception: `stacks/dev` still uses `${VAR}` interpolation backed by Portainer UI env vars — when editing its `stack.env`, mirror the change in the stack's UI env vars too. 4. Add a site block to `Caddyfile` (`reverse_proxy :`); `import internal_only` for LAN-only. 5. Run `./scripts/gen-bookmarks.ps1` to regenerate `bookmarks-domains.html` + `bookmarks-ports.html` from the new Caddy block / published ports. 6. `git push`. Portainer redeploys the stack within 5 min; the runner reloads Caddy on the same push if `Caddyfile` changed. @@ -104,6 +104,11 @@ ssh ginnoir@valhalla "docker compose -f /data/compose/14/stacks/owncloud/docker- ## Known quirks / gotchas +- Portainer stack #6 `valhalla-lab` is **registration-only** (visibility in the Portainer UI). It + has no auto-update and no git credentials on purpose — the management plane deploys via + `apply-compose.ps1 -Portainer`, never via Portainer git. Don't "fix" it by re-enabling polling, + and don't click *pull and redeploy* on it (its `docker-compose.yml` path no longer exists at + repo root). - A **GateGuard hook** blocks the first use of `Bash`, and every `Write`/`Edit`, until you state the required facts (the user request + what the operation does/affects). State them, then retry the same call. - `Caddyfile` proxies `matrix.ginnoir.com → localhost:8008`, but there is **no Matrix/Synapse service in compose** — it's external/legacy. Likewise `dev.ginnoir.com → 192.168.1.74:3000` points at a different LAN host. - `watchtower` auto-updates `:latest` images, so a running image can drift ahead of what the last `apply` pulled. diff --git a/stacks/authentik/docker-compose.yml b/stacks/authentik/docker-compose.yml index 79d8b09..a74ed6c 100644 --- a/stacks/authentik/docker-compose.yml +++ b/stacks/authentik/docker-compose.yml @@ -8,7 +8,7 @@ services: authentik-server: container_name: authentik_server - image: ghcr.io/goauthentik/server:${AUTHENTIK_IMAGE_TAG:-2024.12.3} + image: ghcr.io/goauthentik/server:2024.12.3 restart: unless-stopped command: server networks: [authentik, edge] @@ -17,10 +17,6 @@ services: environment: AUTHENTIK_REDIS__HOST: authentik-redis AUTHENTIK_POSTGRESQL__HOST: authentik-db - AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER:-authentik} - AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD} - AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME:-authentik} - AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} AUTHENTIK_ERROR_REPORTING__ENABLED: "false" ports: - "9200:9000" @@ -32,7 +28,7 @@ services: authentik-worker: container_name: authentik_worker - image: ghcr.io/goauthentik/server:${AUTHENTIK_IMAGE_TAG:-2024.12.3} + image: ghcr.io/goauthentik/server:2024.12.3 restart: unless-stopped command: worker networks: [authentik] @@ -41,10 +37,6 @@ services: environment: AUTHENTIK_REDIS__HOST: authentik-redis AUTHENTIK_POSTGRESQL__HOST: authentik-db - AUTHENTIK_POSTGRESQL__USER: ${AUTHENTIK_DB_USER:-authentik} - AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD} - AUTHENTIK_POSTGRESQL__NAME: ${AUTHENTIK_DB_NAME:-authentik} - AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY} AUTHENTIK_ERROR_REPORTING__ENABLED: "false" depends_on: authentik-db: @@ -59,18 +51,10 @@ services: networks: [authentik] env_file: - stack.env - environment: - POSTGRES_USER: ${AUTHENTIK_DB_USER:-authentik} - POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASSWORD} - POSTGRES_DB: ${AUTHENTIK_DB_NAME:-authentik} volumes: - /config/authentik/postgres:/var/lib/postgresql/data healthcheck: - test: - [ - "CMD-SHELL", - "pg_isready -U ${AUTHENTIK_DB_USER:-authentik} -d ${AUTHENTIK_DB_NAME:-authentik}", - ] + test: ["CMD-SHELL", "pg_isready -U authentik -d authentik"] interval: 10s timeout: 5s retries: 5 diff --git a/stacks/authentik/stack.env b/stacks/authentik/stack.env index eb0723c..5adc442 100644 --- a/stacks/authentik/stack.env +++ b/stacks/authentik/stack.env @@ -1,6 +1,11 @@ -# authentik stack secrets -AUTHENTIK_IMAGE_TAG=2024.12.3 -AUTHENTIK_DB_USER=authentik -AUTHENTIK_DB_PASSWORD=ebb7d34ecbc4fc8f95cd16ef491d2162cdf8 -AUTHENTIK_DB_NAME=authentik +# authentik stack secrets — injected into all services via env_file. +# Var names are exactly what each container reads (no ${VAR} interpolation). +# AUTHENTIK_POSTGRESQL__* (server/worker) and POSTGRES_* (db) must stay in sync. +# Image tag is pinned in docker-compose.yml — bump it there via git. +AUTHENTIK_POSTGRESQL__USER=authentik +AUTHENTIK_POSTGRESQL__PASSWORD=ebb7d34ecbc4fc8f95cd16ef491d2162cdf8 +AUTHENTIK_POSTGRESQL__NAME=authentik AUTHENTIK_SECRET_KEY=C33S/oXOVOLrlLnWGMRYadgH9zfEC7Bm1vHtbczkFxHcTOj9j6io397c/OGb +POSTGRES_USER=authentik +POSTGRES_PASSWORD=ebb7d34ecbc4fc8f95cd16ef491d2162cdf8 +POSTGRES_DB=authentik diff --git a/stacks/famapp/docker-compose.yml b/stacks/famapp/docker-compose.yml index 8dcebc4..596f7ca 100644 --- a/stacks/famapp/docker-compose.yml +++ b/stacks/famapp/docker-compose.yml @@ -10,33 +10,15 @@ services: famapp: container_name: famapp - image: ${FAMAPP_IMAGE:-ghcr.io/ginnoir/famapp:latest} - pull_policy: ${FAMAPP_PULL_POLICY:-always} + image: ghcr.io/ginnoir/famapp:latest + pull_policy: always restart: unless-stopped networks: [famapp, edge] env_file: - stack.env environment: NODE_ENV: production - AUTH_URL: ${AUTH_URL} - DATABASE_URL: postgres://${FAMAPP_DB_USER}:${FAMAPP_DB_PASSWORD}@famapp-db:5432/${FAMAPP_DB_NAME} - AUTH_SECRET: ${FAMAPP_AUTH_SECRET} - AUTH_OIDC_ISSUER: ${AUTH_OIDC_ISSUER} - AUTH_OIDC_CLIENT_ID: ${AUTH_OIDC_CLIENT_ID} - AUTH_OIDC_CLIENT_SECRET: ${AUTH_OIDC_CLIENT_SECRET} - VAPID_PUBLIC_KEY: ${VAPID_PUBLIC_KEY} - VAPID_PRIVATE_KEY: ${VAPID_PRIVATE_KEY} - VAPID_SUBJECT: ${VAPID_SUBJECT} - NTFY_URL: ${NTFY_URL:-} - NTFY_TOPIC: ${NTFY_TOPIC:-} - LOG_LEVEL: ${LOG_LEVEL:-info} - RUN_MIGRATIONS: ${RUN_MIGRATIONS:-true} MINIO_ENDPOINT: http://famapp-minio:9000 - MINIO_ROOT_USER: ${famapp_MINIO_ROOT_USER} - MINIO_ROOT_PASSWORD: ${famapp_MINIO_ROOT_PASSWORD} - MINIO_BUCKET: ${famapp_MINIO_BUCKET} - OPENPLANTBOOK_CLIENT_ID: ${famapp_OPENPLANTBOOK_CLIENT_ID} - OPENPLANTBOOK_CLIENT_SECRET: ${famapp_OPENPLANTBOOK_CLIENT_SECRET} ports: - "3010:3000" depends_on: @@ -52,14 +34,10 @@ services: networks: [famapp] env_file: - stack.env - environment: - POSTGRES_USER: ${FAMAPP_DB_USER} - POSTGRES_PASSWORD: ${FAMAPP_DB_PASSWORD} - POSTGRES_DB: ${FAMAPP_DB_NAME} volumes: - /config/famapp/postgres:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${FAMAPP_DB_USER} -d ${FAMAPP_DB_NAME}"] + test: ["CMD-SHELL", "pg_isready -U famapp -d famapp"] interval: 10s timeout: 5s retries: 5 @@ -72,9 +50,6 @@ services: networks: [famapp] env_file: - stack.env - environment: - MINIO_ROOT_USER: ${famapp_MINIO_ROOT_USER} - MINIO_ROOT_PASSWORD: ${famapp_MINIO_ROOT_PASSWORD} volumes: - /storage1/labdata/famapp/minio:/data ports: diff --git a/stacks/famapp/stack.env b/stacks/famapp/stack.env index 1a9ec64..476d2af 100644 --- a/stacks/famapp/stack.env +++ b/stacks/famapp/stack.env @@ -1,16 +1,21 @@ -# famapp stack secrets -FAMAPP_IMAGE=ghcr.io/ginnoir/famapp:latest -FAMAPP_PULL_POLICY=always +# famapp stack secrets — injected into all services via env_file. +# Var names are exactly what each container reads (no ${VAR} interpolation, +# no Portainer UI env vars). POSTGRES_* (db) are embedded in DATABASE_URL +# (app) — keep them in sync on rotation. MINIO_ROOT_* are shared by the app +# and the minio server. Image tag is pinned in docker-compose.yml. RUN_MIGRATIONS=true LOG_LEVEL=info AUTH_URL=https://fam.ginnoir.com -FAMAPP_DB_USER=famapp -FAMAPP_DB_PASSWORD=a9a999d16dd5cdc521bb62eff331265d3c24 -FAMAPP_DB_NAME=famapp -# famapp Auth.js session secret (mapped to the container's AUTH_SECRET) -FAMAPP_AUTH_SECRET=CJsICgBs78YxxSdJfS8KU9Zn9k+QytRoVui9kVtuHtI= +# postgres (db container) + the composed URL the app reads +POSTGRES_USER=famapp +POSTGRES_PASSWORD=a9a999d16dd5cdc521bb62eff331265d3c24 +POSTGRES_DB=famapp +DATABASE_URL=postgres://famapp:a9a999d16dd5cdc521bb62eff331265d3c24@famapp-db:5432/famapp + +# famapp Auth.js session secret +AUTH_SECRET=CJsICgBs78YxxSdJfS8KU9Zn9k+QytRoVui9kVtuHtI= # OIDC via Authentik (public URL through Caddy — no container dependency) AUTH_OIDC_ISSUER=https://auth.ginnoir.com/application/o/famapp/ @@ -25,9 +30,9 @@ NTFY_URL=https://ntfy.ginnoir.com NTFY_TOPIC=famapp # MinIO object storage (plant/container image uploads) -famapp_MINIO_ROOT_USER=famapp -famapp_MINIO_ROOT_PASSWORD=d0fet0th3x -famapp_MINIO_BUCKET=garden +MINIO_ROOT_USER=famapp +MINIO_ROOT_PASSWORD=d0fet0th3x +MINIO_BUCKET=garden -famapp_OPENPLANTBOOK_CLIENT_ID=5JdQLab69RhVaF4er7b7D29BCrkf3fo3OEiDsOQa -famapp_OPENPLANTBOOK_CLIENT_SECRET=kxPTrdQPAVta7sjVuIRf5EGIJ1fKvlMlqRSJpQtFxRftJhg7oeGnXzwH7ls6tluQLFUYVDYRMwISiAuKZGZLvkuJzUwFGCwhZw7UtW8OvcTGy4717WeNUinvuCDSsNXP +OPENPLANTBOOK_CLIENT_ID=5JdQLab69RhVaF4er7b7D29BCrkf3fo3OEiDsOQa +OPENPLANTBOOK_CLIENT_SECRET=kxPTrdQPAVta7sjVuIRf5EGIJ1fKvlMlqRSJpQtFxRftJhg7oeGnXzwH7ls6tluQLFUYVDYRMwISiAuKZGZLvkuJzUwFGCwhZw7UtW8OvcTGy4717WeNUinvuCDSsNXP diff --git a/stacks/foundry/docker-compose.yml b/stacks/foundry/docker-compose.yml index 61c83fc..8687605 100644 --- a/stacks/foundry/docker-compose.yml +++ b/stacks/foundry/docker-compose.yml @@ -17,9 +17,6 @@ services: env_file: - stack.env environment: - - FOUNDRY_USERNAME=${FOUNDRY_USERNAME} - - FOUNDRY_PASSWORD=${FOUNDRY_PASSWORD} - - FOUNDRY_ADMIN_KEY=${FOUNDRY_ADMIN_KEY} - CONTAINER_VERBOSE=true - FOUNDRY_HOSTNAME=foundry.ginnoir.com - FOUNDRY_PROXY_PORT=443 @@ -27,7 +24,6 @@ services: - CONTAINER_PRESERVE_OWNER=/data/Data/storage - FOUNDRY_UID=1000 - FOUNDRY_GID=1000 - - CONTAINER_PATCH_URLS=${FOUNDRY_PATCH_URLS:-} ports: - "30000:30000" @@ -45,9 +41,6 @@ services: env_file: - stack.env environment: - - FOUNDRY_USERNAME=${FOUNDRY_USERNAME} - - FOUNDRY_PASSWORD=${FOUNDRY_PASSWORD} - - FOUNDRY_ADMIN_KEY=${FOUNDRY_ADMIN_KEY} - CONTAINER_VERBOSE=true - FOUNDRY_HOSTNAME=foundry2.ginnoir.com - FOUNDRY_PROXY_PORT=443 @@ -55,7 +48,6 @@ services: - CONTAINER_PRESERVE_OWNER=/data/Data/storage - FOUNDRY_UID=1000 - FOUNDRY_GID=1000 - - CONTAINER_PATCH_URLS=${FOUNDRY_PATCH_URLS:-} ports: - "30001:30000" diff --git a/stacks/foundry/stack.env b/stacks/foundry/stack.env index 2a4b8b7..a00fc17 100644 --- a/stacks/foundry/stack.env +++ b/stacks/foundry/stack.env @@ -1,8 +1,9 @@ -# foundry stack secrets +# foundry stack secrets — injected into both Foundry services via env_file. +# Var names are exactly what the felddy/foundryvtt container reads. FOUNDRY_USERNAME=ginnoir FOUNDRY_PASSWORD=agt-UAU!uky_ykh3juy FOUNDRY_ADMIN_KEY=fuckyouben # NOTE: the old plutonium patch gist 404s (dead URL), which put Foundry into a # crash/backoff loop. Disabled to restore service. To re-enable, set this to a # CURRENT raw gist URL for the plutonium patch script. -FOUNDRY_PATCH_URLS= +CONTAINER_PATCH_URLS= diff --git a/stacks/media/docker-compose.yml b/stacks/media/docker-compose.yml index 5b4a79c..7e14c60 100644 --- a/stacks/media/docker-compose.yml +++ b/stacks/media/docker-compose.yml @@ -2,6 +2,11 @@ # All config/data is bind-mounted to /config and /storage1 already (no named # volumes to migrate). Services join the private `media` net for inter-app # traffic (prowlarr<->sonarr/radarr) and `edge` so Caddy can proxy them. +# +# Env convention: every service gets stack.env via env_file; var names there are +# exactly what the containers read (PUID/PGID/TZ), so no ${VAR} interpolation +# and no Portainer UI env vars are needed. Service-specific settings stay in +# per-service environment blocks. services: deluge: @@ -11,13 +16,9 @@ services: networks: [media, edge] env_file: - stack.env - environment: - - PUID=${PUID} - - PGID=${PGID} - - TZ=${TZ} volumes: - - ${ROOT}/storage1/torrents:/downloads - - ${ROOT}/config/deluge:/config + - /storage1/torrents:/downloads + - /config/deluge:/config ports: - "8112:8112" @@ -28,13 +29,9 @@ services: networks: [media, edge] env_file: - stack.env - environment: - - PUID=${PUID} - - PGID=${PGID} - - TZ=${TZ} volumes: - - ${ROOT}/storage1/complete:/downloads - - ${ROOT}/config/nzbget:/config + - /storage1/complete:/downloads + - /config/nzbget:/config ports: - "6789:6789" @@ -45,16 +42,12 @@ services: networks: [media, edge] env_file: - stack.env - environment: - - PUID=${PUID} - - PGID=${PGID} - - TZ=${TZ} volumes: - /etc/localtime:/etc/localtime:ro - - ${ROOT}/config/sonarr:/config - - ${ROOT}/storage1/Media/TV:/tv - - ${ROOT}/storage1/Media/Anime:/anime - - ${ROOT}/storage1/complete:/downloads + - /config/sonarr:/config + - /storage1/Media/TV:/tv + - /storage1/Media/Anime:/anime + - /storage1/complete:/downloads ports: - "8989:8989" @@ -65,15 +58,11 @@ services: networks: [media, edge] env_file: - stack.env - environment: - - PUID=${PUID} - - PGID=${PGID} - - TZ=${TZ} volumes: - /etc/localtime:/etc/localtime:ro - - ${ROOT}/config/radarr:/config - - ${ROOT}/storage1/Media/Movies:/movies - - ${ROOT}/storage1/complete:/downloads + - /config/radarr:/config + - /storage1/Media/Movies:/movies + - /storage1/complete:/downloads ports: - "7878:7878" @@ -85,15 +74,12 @@ services: env_file: - stack.env environment: - - PUID=${PUID} - - PGID=${PGID} - - TZ=${TZ} - UMASK_SET=022 volumes: - - ${ROOT}/config/bazarr:/config - - ${ROOT}/storage1/Media/Movies:/movies - - ${ROOT}/storage1/Media/TV:/tv - - ${ROOT}/storage1/Media/Anime:/anime + - /config/bazarr:/config + - /storage1/Media/Movies:/movies + - /storage1/Media/TV:/tv + - /storage1/Media/Anime:/anime ports: - "6767:6767" @@ -105,10 +91,7 @@ services: env_file: - stack.env environment: - - PUID=1000 - - PGID=1000 - UMASK=002 - - TZ=${TZ} volumes: - /config/prowlarr:/config ports: @@ -122,10 +105,7 @@ services: env_file: - stack.env environment: - - PUID=1000 - - PGID=1000 - UMASK=002 - - TZ=${TZ} volumes: - /config/overseerr:/app/config ports: @@ -138,13 +118,9 @@ services: networks: [media, edge] env_file: - stack.env - environment: - - PUID=${PUID} - - PGID=${PGID} - - TZ=${TZ} volumes: - - ${ROOT}/config/tautulli:/config - - ${ROOT}/storage1/PMS:/logs + - /config/tautulli:/config + - /storage1/PMS:/logs ports: - "8181:8181" @@ -156,9 +132,6 @@ services: env_file: - stack.env environment: - - PUID=1000 - - PGID=1000 - - TZ=${TZ} - WEBUI_PORT=3232 - TORRENTING_PORT=6881 volumes: @@ -177,10 +150,7 @@ services: env_file: - stack.env environment: - - PUID=1000 - - PGID=1000 - UMASK=002 - - TZ=${TZ} - WEBUI_PORTS=6969/tcp volumes: - /config/whisparr:/config @@ -201,8 +171,6 @@ services: env_file: - stack.env environment: - - PUID=1000 - - PGID=1000 - STASH_STASH=/data/ - STASH_GENERATED=/generated/ - STASH_METADATA=/metadata/ diff --git a/stacks/media/stack.env b/stacks/media/stack.env index 2712830..e0a5085 100644 --- a/stacks/media/stack.env +++ b/stacks/media/stack.env @@ -1,5 +1,6 @@ -# media stack config +# media stack config — injected into every service via env_file. +# Var names are exactly what the containers read; no ${VAR} interpolation, +# no Portainer UI env vars needed. TZ=America/Chicago PUID=1000 PGID=1000 -ROOT=/ diff --git a/stacks/monitoring/docker-compose.yml b/stacks/monitoring/docker-compose.yml index c73a8c4..4f4cab0 100644 --- a/stacks/monitoring/docker-compose.yml +++ b/stacks/monitoring/docker-compose.yml @@ -21,8 +21,6 @@ services: networks: [edge] env_file: - stack.env - environment: - - SECRET_ENCRYPTION_KEY=${HOMARR_SECRET_ENCRYPTION_KEY} ports: - "7575:7575" volumes: diff --git a/stacks/monitoring/stack.env b/stacks/monitoring/stack.env index 80f9d53..2699827 100644 --- a/stacks/monitoring/stack.env +++ b/stacks/monitoring/stack.env @@ -1,3 +1,3 @@ -# monitoring stack secrets +# monitoring stack secrets — injected via env_file; container-exact var names. # Homarr config encryption key. Generate with: openssl rand -hex 32 -HOMARR_SECRET_ENCRYPTION_KEY=98989838bf06c9e42d77944ce848963a22ab316b55a09b0d199652ef0934394f +SECRET_ENCRYPTION_KEY=98989838bf06c9e42d77944ce848963a22ab316b55a09b0d199652ef0934394f diff --git a/stacks/notes/docker-compose.yml b/stacks/notes/docker-compose.yml index 7f106ec..843799a 100644 --- a/stacks/notes/docker-compose.yml +++ b/stacks/notes/docker-compose.yml @@ -25,9 +25,8 @@ services: image: couchdb:3 restart: unless-stopped networks: [notes, edge] - environment: - COUCHDB_USER: ${COUCHDB_USER} - COUCHDB_PASSWORD: ${COUCHDB_PASSWORD} + env_file: + - stack.env volumes: - /config/couchdb/data:/opt/couchdb/data - /config/couchdb/etc:/opt/couchdb/etc/local.d diff --git a/stacks/notify/docker-compose.yml b/stacks/notify/docker-compose.yml index 71e5af3..38e5fe3 100644 --- a/stacks/notify/docker-compose.yml +++ b/stacks/notify/docker-compose.yml @@ -16,7 +16,6 @@ services: env_file: - stack.env environment: - - TZ=${TZ} - NTFY_DEFAULT_HOST=https://ntfy.ginnoir.com - NTFY_BASE_URL=https://ntfy.ginnoir.com - NTFY_CACHE_FILE=/var/lib/ntfy/cache.db @@ -63,7 +62,6 @@ services: - CRON_MIN=3,33 - TRUSTED_PROXY=172.16.0.1/12 192.168.0.1/16 - FRESHRSS_INSTALL=--default-user ginnoir --api-enabled --base-url https://freshrss.ginnoir.com - - FRESHRSS_USER=--api-password ${FRESHRSS_API_PASSWORD} --email ${FRESHRSS_EMAIL} --password ${FRESHRSS_PASSWORD} --user ginnoir vigilant: container_name: vigilant diff --git a/stacks/notify/stack.env b/stacks/notify/stack.env index b5ae5e0..c04a2db 100644 --- a/stacks/notify/stack.env +++ b/stacks/notify/stack.env @@ -1,5 +1,5 @@ -# notify stack secrets +# notify stack secrets — injected via env_file; container-exact var names. TZ=America/Chicago -FRESHRSS_API_PASSWORD=d0fet0th3x -FRESHRSS_PASSWORD=d0fet0th3x -FRESHRSS_EMAIL=3nigma.matt@gmail.com +# FreshRSS first-run user bootstrap (the container reads FRESHRSS_USER as the +# full CLI argument string for its install script). +FRESHRSS_USER=--api-password d0fet0th3x --email 3nigma.matt@gmail.com --password d0fet0th3x --user ginnoir diff --git a/stacks/owncloud/docker-compose.yml b/stacks/owncloud/docker-compose.yml index 40cdda7..e4a77a9 100644 --- a/stacks/owncloud/docker-compose.yml +++ b/stacks/owncloud/docker-compose.yml @@ -19,14 +19,9 @@ services: env_file: - stack.env environment: - - OWNCLOUD_DOMAIN=${OWNCLOUD_DOMAIN} - OWNCLOUD_DB_TYPE=mysql - OWNCLOUD_DB_NAME=owncloud - - OWNCLOUD_DB_USERNAME=${OWNCLOUD_DB_USER} - - OWNCLOUD_DB_PASSWORD=${OWNCLOUD_DB_PASSWORD} - OWNCLOUD_DB_HOST=mariadb - - OWNCLOUD_ADMIN_USERNAME=${OWNCLOUD_ADMIN_USER} - - OWNCLOUD_ADMIN_PASSWORD=${OWNCLOUD_ADMIN_PASSWORD} - OWNCLOUD_MYSQL_UTF8MB4=true - OWNCLOUD_REDIS_ENABLED=true - OWNCLOUD_REDIS_HOST=redis @@ -49,13 +44,11 @@ services: env_file: - stack.env environment: - - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} - - MYSQL_USER=${OWNCLOUD_DB_USER} - - MYSQL_PASSWORD=${OWNCLOUD_DB_PASSWORD} - MYSQL_DATABASE=owncloud command: ["--max-allowed-packet=128M", "--innodb-log-file-size=64M"] healthcheck: - test: ["CMD", "mariadb-admin", "ping", "-u", "root", "--password=${MYSQL_ROOT_PASSWORD}"] + # $$ defers expansion to container runtime (env_file supplies the value) + test: ["CMD-SHELL", "mariadb-admin ping -u root --password=\"$$MYSQL_ROOT_PASSWORD\""] interval: 10s timeout: 5s retries: 5 diff --git a/stacks/owncloud/stack.env b/stacks/owncloud/stack.env index ebcee36..1874f68 100644 --- a/stacks/owncloud/stack.env +++ b/stacks/owncloud/stack.env @@ -1,7 +1,11 @@ -# owncloud stack secrets +# owncloud stack secrets — injected via env_file; container-exact var names. +# OWNCLOUD_* are read by owncloud/server, MYSQL_* by mariadb (cross-injection +# of the other service's vars is harmless — both images ignore unknown env). OWNCLOUD_DOMAIN=files.ginnoir.com -OWNCLOUD_DB_USER=ginnoir +OWNCLOUD_DB_USERNAME=ginnoir OWNCLOUD_DB_PASSWORD=d0fet0th3x -OWNCLOUD_ADMIN_USER=ginnoir +OWNCLOUD_ADMIN_USERNAME=ginnoir OWNCLOUD_ADMIN_PASSWORD=d0fet0th3x MYSQL_ROOT_PASSWORD=d0fet0th3x +MYSQL_USER=ginnoir +MYSQL_PASSWORD=d0fet0th3x diff --git a/stacks/proxy/docker-compose.yml b/stacks/proxy/docker-compose.yml index 6189a17..60d08e8 100644 --- a/stacks/proxy/docker-compose.yml +++ b/stacks/proxy/docker-compose.yml @@ -24,8 +24,6 @@ services: - "443:443" env_file: - stack.env - environment: - - CF_API_TOKEN=${CF_API_TOKEN} volumes: - /config/caddy/Caddyfile:/etc/caddy/Caddyfile - /config/caddy/site:/srv diff --git a/stacks/remote/docker-compose.yml b/stacks/remote/docker-compose.yml index b35a159..fd5353e 100644 --- a/stacks/remote/docker-compose.yml +++ b/stacks/remote/docker-compose.yml @@ -8,6 +8,8 @@ services: image: rustdesk/rustdesk-server:latest command: hbbr restart: unless-stopped + env_file: + - stack.env ports: - "21117:21117" volumes: @@ -18,6 +20,8 @@ services: image: rustdesk/rustdesk-server:latest command: hbbs restart: unless-stopped + env_file: + - stack.env ports: - "21115:21115" - "21116:21116" diff --git a/stacks/remote/stack.env b/stacks/remote/stack.env new file mode 100644 index 0000000..e0c8a48 --- /dev/null +++ b/stacks/remote/stack.env @@ -0,0 +1,4 @@ +# remote stack config — injected into hbbr/hbbs via env_file. +# No secrets today: the RustDesk key pair lives in /config/rustdesk/*/data on +# the host, not in env. Present for stack convention (every stack ships a +# stack.env). Candidate vars if ever needed: ALWAYS_USE_RELAY, RUST_LOG. diff --git a/stacks/resume/docker-compose.yml b/stacks/resume/docker-compose.yml index e8b2d4c..f247cb0 100644 --- a/stacks/resume/docker-compose.yml +++ b/stacks/resume/docker-compose.yml @@ -19,7 +19,6 @@ services: environment: - POSTGRES_DB=postgres - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"] interval: 10s @@ -39,9 +38,6 @@ services: - /storage1/labdata/resume/minio:/data env_file: - stack.env - environment: - - MINIO_ROOT_USER=${MINIO_ROOT_USER} - - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} healthcheck: test: ["CMD-SHELL", "curl -sf http://localhost:9000/minio/health/live || exit 1"] interval: 30s @@ -59,7 +55,6 @@ services: environment: - TIMEOUT=10000 - CONCURRENT=10 - - TOKEN=${CHROME_TOKEN} - EXIT_ON_HEALTH_FAILURE=true - PRE_REQUEST_HEALTH_CHECK=true @@ -85,16 +80,11 @@ services: - APP_URL=https://resume.ginnoir.com - STORAGE_URL=https://storage.j-costa.com/default - PRINTER_ENDPOINT=http://chrome:3000 - - PRINTER_TOKEN=${CHROME_TOKEN} - - DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/postgres - - AUTH_SECRET=${RESUME_AUTH_SECRET} - MAIL_FROM=noreply@localhost - STORAGE_ENDPOINT=resume-minio - STORAGE_PORT=9000 - STORAGE_REGION=us-east-1 - STORAGE_BUCKET=default - - STORAGE_ACCESS_KEY=${MINIO_ROOT_USER} - - STORAGE_SECRET_KEY=${MINIO_ROOT_PASSWORD} - STORAGE_USE_SSL=false - STORAGE_SKIP_BUCKET_CHECK=false diff --git a/stacks/resume/stack.env b/stacks/resume/stack.env index d481159..9350eee 100644 --- a/stacks/resume/stack.env +++ b/stacks/resume/stack.env @@ -1,9 +1,24 @@ -# resume stack secrets +# resume stack secrets — injected into all services via env_file. +# Var names are exactly what each container reads (no ${VAR} interpolation). +# Components are duplicated into composites (DATABASE_URL embeds +# POSTGRES_PASSWORD; STORAGE_* mirror MINIO_*) — keep them in sync on rotation. + +# postgres POSTGRES_PASSWORD=postgres + +# minio (resume-minio service) MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=minioadmin -CHROME_TOKEN=chrome_token -# Auth/JWT signing secret for Reactive Resume (the app reads ${RESUME_AUTH_SECRET}). + +# browserless chrome auth token (chrome reads TOKEN, the app reads PRINTER_TOKEN) +TOKEN=chrome_token +PRINTER_TOKEN=chrome_token + +# reactive-resume app +DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postgres +STORAGE_ACCESS_KEY=minioadmin +STORAGE_SECRET_KEY=minioadmin +# Auth/JWT signing secret for Reactive Resume. # Pinned to the running value so recreating the container keeps sessions valid. # Rotate with: openssl rand -hex 64 -RESUME_AUTH_SECRET=1b7e96f61f080d04d4bf673d9ea1d9349eb7693cddee5772b6c3f153c88032db5b97efd8570087bc6f340d2cd874aabc1e5c681bc22889921240d6a23738bc7f +AUTH_SECRET=1b7e96f61f080d04d4bf673d9ea1d9349eb7693cddee5772b6c3f153c88032db5b97efd8570087bc6f340d2cd874aabc1e5c681bc22889921240d6a23738bc7f