From 3990ae8535a6cac5d78757ca8849e05274ead25b Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 22 Jun 2026 22:57:51 -0700 Subject: [PATCH] Add stack-deploy.sh + envparse.py: safe env loading via Python for all stacks --- .woodpecker.yml | 4 ++-- deploy/envparse.py | 27 +++++++++++++++++++++++++++ deploy/safe-deploy.sh | 36 ------------------------------------ deploy/stack-deploy.sh | 24 ++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 38 deletions(-) create mode 100644 deploy/envparse.py delete mode 100755 deploy/safe-deploy.sh create mode 100755 deploy/stack-deploy.sh diff --git a/.woodpecker.yml b/.woodpecker.yml index 362f9fd..ab2eab5 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -168,14 +168,14 @@ steps: - | YAML_FILES=$(echo "${CI_COMMIT_CHANGED_FILES}" | tr ',' '\n' | grep '\.yaml$' || true) [ -z "$YAML_FILES" ] && echo "No yaml files changed" && exit 0 - rsync -av -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa" deploy/safe-deploy.sh root@192.168.4.32:/tmp/sd.sh + rsync -av -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa" deploy/ root@192.168.4.32:/volume1/docker/compose-files/deploy/ for f in $YAML_FILES; do [ -f "$f" ] || continue STACK=$(basename "$f" .yaml) rsync -av -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa" \ "$f" root@192.168.4.32:/volume1/docker/compose-files/ ssh -o StrictHostKeyChecking=no root@192.168.4.32 \ - "python3 /tmp/sd.sh ${STACK}" \ + "bash /volume1/docker/compose-files/deploy/stack-deploy.sh $STACK" \ && echo " OK $STACK" || { echo " FAIL $STACK"; exit 1; } done secrets: [ ssh_key ] diff --git a/deploy/envparse.py b/deploy/envparse.py new file mode 100644 index 0000000..50f0ea7 --- /dev/null +++ b/deploy/envparse.py @@ -0,0 +1,27 @@ +import sys, re + +def parse_env(path): + pairs = [] + with open(path) as f: + for line in f: + line = line.strip() + if not line or line.startswith('#') or '=' not in line: + continue + k, _, v = line.partition('=') + k = k.strip(); v = v.strip() + if (v.startswith("'") and v.endswith("'")) or \ + (v.startswith('"') and v.endswith('"')): + v = v[1:-1] + pairs.append((k, v)) + return pairs + +mode = sys.argv[1] +if mode == 'export': + for k, v in parse_env(sys.argv[2]): + print('export {}={}'.format(k, repr(v))) +elif mode == 'vars': + print(' '.join('$' + k for k, v in parse_env(sys.argv[2]))) +elif mode == 'strip': + t = sys.stdin.read() + t = re.sub(r'[ \t]*env_file:[ \t]*\n([ \t]+-[^\n]*\n)+', '', t) + sys.stdout.write(t) diff --git a/deploy/safe-deploy.sh b/deploy/safe-deploy.sh deleted file mode 100755 index 04a798c..0000000 --- a/deploy/safe-deploy.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -# safe-deploy.sh — Safe Docker Stack deployer with .env support -# Usage: python3 deploy/safe-deploy.sh [compose-dir] -# Parses .env safely — no shell interpretation of special chars -import subprocess, sys, os - -def parse_env_file(path): - env = {} - with open(path) as f: - for line in f: - line = line.rstrip('\n') - if not line.strip() or line.strip().startswith('#'): continue - if '=' not in line: continue - key, _, value = line.partition('=') - key = key.strip(); value = value.strip() - if len(value) >= 2: - if (value[0]=="'" and value[-1]=="'") or (value[0]=='"' and value[-1]=='"'): - value = value[1:-1] - env[key] = value - return env - -def main(): - if len(sys.argv) < 2: print("Usage: safe-deploy.sh [dir]"); sys.exit(1) - stack = sys.argv[1] - d = sys.argv[2] if len(sys.argv)>2 else "/volume1/docker/compose-files" - yp = os.path.join(d, stack+".yaml") - ep = os.path.join(d, stack+".env") - if not os.path.exists(yp): print(f"ERROR: {yp} not found"); sys.exit(1) - env = dict(os.environ) - if os.path.exists(ep): parsed=parse_env_file(ep); print(f" Loaded {len(parsed)} vars"); env.update(parsed) - else: print(f" No {stack}.env - deploying without overlay") - cmd=["docker","stack","deploy","-c",yp,stack] - print(f" Running: {chr(32).join(cmd)}") - sys.exit(subprocess.run(cmd,env=env,cwd=d).returncode) - -if __name__=="__main__": main() diff --git a/deploy/stack-deploy.sh b/deploy/stack-deploy.sh new file mode 100755 index 0000000..27a7ec5 --- /dev/null +++ b/deploy/stack-deploy.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# stack-deploy.sh - Safe Docker Swarm stack deployer +# Handles special chars in .env (||, backticks, %, *) via Python +set -euo pipefail +STACK="${1:?Usage: stack-deploy.sh }" +DIR="/volume1/docker/compose-files" +YAML="$DIR/${STACK}.yaml" +ENVFILE="$DIR/${STACK}.env" +PY="/volume1/docker/compose-files/deploy/envparse.py" +[ -f "$YAML" ] || { echo "ERROR: $YAML not found"; exit 1; } +echo "==> Deploying stack: $STACK" +if [ -f "$ENVFILE" ]; then + echo " Loading: $ENVFILE" + eval "$(python3 $PY export "$ENVFILE")" + VARS="$(python3 $PY vars "$ENVFILE")" + echo " Vars: $VARS" + envsubst "$VARS" < "$YAML" \ + | python3 $PY strip \ + | docker stack deploy -c - "$STACK" +else + echo " No env file" + docker stack deploy -c "$YAML" "$STACK" +fi +echo "==> Done: $STACK"