feat(stack-deploy): load global.env as base, stack env overrides on top
ci/woodpecker/push/woodpecker Pipeline was canceled

This commit is contained in:
2026-07-08 21:29:15 -07:00
parent b664e9d41d
commit 2b6e937b57
+34 -8
View File
@@ -12,6 +12,10 @@
# ├── hwaccel.ml.yml # ├── hwaccel.ml.yml
# └── immich.env ← optional, same as flat pattern # └── immich.env ← optional, same as flat pattern
# #
# Env layering:
# deploy/global.env ← always loaded first (DOMAIN, DOMAIN_NAME, ALT_DOMAIN, etc.)
# <stack>.env ← loaded second; stack values override globals
#
# Merge strategy: docker compose ... config | docker stack deploy -c - # Merge strategy: docker compose ... config | docker stack deploy -c -
# All files in the folder matching *.yml or *.yaml are included. # All files in the folder matching *.yml or *.yaml are included.
# Main file (<stack>.yml or <stack>.yaml) is always passed FIRST. # Main file (<stack>.yml or <stack>.yaml) is always passed FIRST.
@@ -22,6 +26,7 @@ set -euo pipefail
STACK="${1:?Usage: stack-deploy.sh <stack-name>}" STACK="${1:?Usage: stack-deploy.sh <stack-name>}"
DIR="/volume1/docker/compose-files" DIR="/volume1/docker/compose-files"
PY="$DIR/deploy/envparse.py" PY="$DIR/deploy/envparse.py"
GLOBAL_ENV="$DIR/deploy/global.env"
# ── Locate compose file(s) ────────────────────────────────────────────────── # ── Locate compose file(s) ──────────────────────────────────────────────────
@@ -75,34 +80,55 @@ for f in "${EXTRAS[@]:-}"; do
[ -n "$f" ] && F_FLAGS+=(-f "$f") [ -n "$f" ] && F_FLAGS+=(-f "$f")
done done
# ── Load env file if present ───────────────────────────────────────────────── # ── Load env (global base + optional stack override) ─────────────────────────
HINT=" Hint: 'secret not found' means Woodpecker hasn't provisioned secrets yet.\n Trigger the pipeline: https://woodpecker.bryanmail.net\n" HINT=" Hint: 'secret not found' means Woodpecker hasn't provisioned secrets yet.\n Trigger the pipeline: https://woodpecker.bryanmail.net\n"
if [ -f "$ENVFILE" ]; then GLOBAL_EXISTS=0
echo " Env: $ENVFILE" STACK_EXISTS=0
[ -f "$GLOBAL_ENV" ] && GLOBAL_EXISTS=1
[ -f "$ENVFILE" ] && STACK_EXISTS=1
if [ "$GLOBAL_EXISTS" -eq 1 ] && [ "$STACK_EXISTS" -eq 1 ]; then
echo " Env: $GLOBAL_ENV + $ENVFILE (stack overrides global)"
eval "$(python3 "$PY" export_merged "$GLOBAL_ENV" "$ENVFILE")"
VARS="$(python3 "$PY" vars_merged "$GLOBAL_ENV" "$ENVFILE")"
elif [ "$GLOBAL_EXISTS" -eq 1 ]; then
echo " Env: $GLOBAL_ENV (no stack env)"
eval "$(python3 "$PY" export "$GLOBAL_ENV")"
VARS="$(python3 "$PY" vars "$GLOBAL_ENV")"
elif [ "$STACK_EXISTS" -eq 1 ]; then
echo " Env: $ENVFILE (no global env)"
eval "$(python3 "$PY" export "$ENVFILE")" eval "$(python3 "$PY" export "$ENVFILE")"
VARS="$(python3 "$PY" vars "$ENVFILE")" VARS="$(python3 "$PY" vars "$ENVFILE")"
else
echo " Env: (none)"
VARS=""
fi
echo " Vars: $VARS" echo " Vars: $VARS"
# ── Deploy ───────────────────────────────────────────────────────────────────
if [ -n "$VARS" ]; then
if [ "${#F_FLAGS[@]}" -gt 2 ]; then if [ "${#F_FLAGS[@]}" -gt 2 ]; then
# Folder mode with extras: use docker compose config to merge, then deploy # Folder mode with extras: merge via docker compose config
docker compose "${F_FLAGS[@]}" config \ docker compose "${F_FLAGS[@]}" config \
| python3 "$PY" strip \ | python3 "$PY" strip \
| envsubst "$VARS" \ | envsubst "$VARS" \
| docker stack deploy -c - "$STACK" \ | docker stack deploy -c - "$STACK" \
|| { printf "\n%b" "$HINT"; exit 1; } || { printf "\n%b" "$HINT"; exit 1; }
else else
# Single file: envsubst directly (preserves existing behavior exactly) # Single file
envsubst "$VARS" < "$MAIN" \ envsubst "$VARS" < "$MAIN" \
| python3 "$PY" strip \ | python3 "$PY" strip \
| docker stack deploy -c - "$STACK" \ | docker stack deploy -c - "$STACK" \
|| { printf "\n%b" "$HINT"; exit 1; } || { printf "\n%b" "$HINT"; exit 1; }
fi fi
else else
echo " Env: (none)"
if [ "${#F_FLAGS[@]}" -gt 2 ]; then if [ "${#F_FLAGS[@]}" -gt 2 ]; then
docker compose "${F_FLAGS[@]}" config \ docker compose "${F_FLAGS[@]}" config \
| python3 "$PY" strip \ | python3 "$PY" strip \