#!/usr/bin/env bash # Generic: merge a site snippet into a Caddyfile + ACME http:// list + ports comment. # # Laptop (mirror only, no reload): # bash scripts/caddy/caddy-apply.sh \ # --config configs/caddy/Caddyfile \ # --snippet configs/caddy/galene.hacktivism.ch.caddy \ # --site galene.hacktivism.ch --port 9029 --label galene \ # --no-reload # # Koopa live (root, Freigabe): # sudo bash …/caddy-apply.sh \ # --snippet /home/hernani/koopa-admin-log/configs/caddy/galene.hacktivism.ch.caddy \ # --site galene.hacktivism.ch --port 9029 --label galene set -euo pipefail CADDY=/etc/caddy/Caddyfile SNIPPET="" SITE="" PORT="" LABEL="" INSERT_AFTER="decidim.hacktivism.ch" ACME_ANCHOR="http://decidim.hacktivism.ch" NO_RELOAD=0 DRY=0 OLD_SITES=() usage() { sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//' echo "Options: --config PATH --snippet PATH --site HOST --port N --label NAME" echo " --insert-after HOST --acme-anchor http://HOST --old-site HOST" echo " --no-reload --dry-run" } while [[ $# -gt 0 ]]; do case "$1" in --config) CADDY=$2; shift 2 ;; --snippet) SNIPPET=$2; shift 2 ;; --site) SITE=$2; shift 2 ;; --port) PORT=$2; shift 2 ;; --label) LABEL=$2; shift 2 ;; --insert-after) INSERT_AFTER=$2; shift 2 ;; --acme-anchor) ACME_ANCHOR=$2; shift 2 ;; --old-site) OLD_SITES+=("$2"); shift 2 ;; --no-reload) NO_RELOAD=1; shift ;; --dry-run) DRY=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "ERROR: unknown arg: $1" >&2; usage >&2; exit 2 ;; esac done [[ -n "$SNIPPET" && -n "$SITE" ]] || { echo "ERROR: --snippet and --site required" >&2; exit 2; } [[ -f "$CADDY" ]] || { echo "ERROR: missing $CADDY" >&2; exit 1; } [[ -f "$SNIPPET" ]] || { echo "ERROR: missing $SNIPPET" >&2; exit 1; } LABEL="${LABEL:-$SITE}" if [[ "$CADDY" == /etc/caddy/Caddyfile && "$(id -u)" -ne 0 && "$DRY" -eq 0 ]]; then echo "ERROR: live Caddyfile needs root (or --dry-run / --config mirror)" >&2 exit 1 fi ts=$(date +%Y%m%d-%H%M%S) if [[ "$DRY" -eq 0 ]]; then cp -a "$CADDY" "${CADDY}.bak-caddy-apply-${ts}" echo "backup ${CADDY}.bak-caddy-apply-${ts}" fi export CADDY SNIPPET SITE PORT LABEL INSERT_AFTER ACME_ANCHOR DRY export OLD_SITES_CSV OLD_SITES_CSV=$(IFS=,; echo "${OLD_SITES[*]-}") python3 - <<'PY' import os, re, sys from pathlib import Path caddy = Path(os.environ["CADDY"]) snip = Path(os.environ["SNIPPET"]).read_text().rstrip() + "\n\n" site = os.environ["SITE"] port = os.environ.get("PORT") or "" label = os.environ.get("LABEL") or site insert_after = os.environ["INSERT_AFTER"] acme_anchor = os.environ["ACME_ANCHOR"] dry = os.environ.get("DRY") == "1" old_sites = [s for s in os.environ.get("OLD_SITES_CSV", "").split(",") if s] text = caddy.read_text() orig = text def drop_site_block(text: str, old_site: str) -> str: marker_old = f"{old_site} {{" i = text.find(marker_old) if i < 0: return text j = text.find("\n}", i) if j < 0: return text end = j + 2 while end < len(text) and text[end] == "\n": end += 1 text = text[:i] + text[end:] text = text.replace(f", http://{old_site}", "").replace(f"http://{old_site}, ", "") text = text.replace(f"http://{old_site}", "") print(f"OK: removed duplicate old block {old_site}") return text for old_site in old_sites: if old_site in text and site not in text: text = text.replace(old_site, site) print(f"OK: renamed {old_site} → {site}") elif old_site in text and site in text: text = drop_site_block(text, old_site) marker = f"{site} {{" if marker in text: print(f"OK: site block already present: {site}") else: insert_at = None after = text.find(f"{insert_after} {{") if after >= 0: j = text.find("\n}", after) if j >= 0: insert_at = j + 2 while insert_at < len(text) and text[insert_at] == "\n": insert_at += 1 if insert_at is None: acme = text.find("http://taler.hacktivism.ch") insert_at = acme if acme >= 0 else len(text) text = text[:insert_at] + snip + text[insert_at:] print(f"OK: inserted site block {site}") http_tok = f"http://{site}" if http_tok in text: print(f"OK: ACME http list already has {http_tok}") else: # Prefer exact "anchor {" form; else append before " {" of the long http list old = f"{acme_anchor} {{" new = f"{acme_anchor}, {http_tok} {{" if old in text: text = text.replace(old, new, 1) print(f"OK: added {http_tok} to ACME http list (anchor)") else: # Find the shared ACME line (starts with http://taler…) m = re.search(r"(http://taler\.hacktivism\.ch[^\n]*?)(\s*\{)", text) if not m: raise SystemExit("ERROR: ACME http list not found") line = m.group(1) if http_tok in line: print(f"OK: ACME http list already has {http_tok}") else: text = text[: m.start(1)] + line.rstrip() + f", {http_tok}" + text[m.end(1) :] print(f"OK: added {http_tok} to ACME http list (append)") # Ports comment header (first matching line starting with "# 9020 castopod") if port: token = f"{port} {label}" def upd_hdr(line: str) -> str: if token in line or f"| {port} " in line or f"| {port}|" in line: return line # insert before " | 9200 forgejo-ssh" if present, else before end if "9200 forgejo-ssh" in line: return line.replace(" | 9200 forgejo-ssh", f" | {token} | 9200 forgejo-ssh", 1) if line.rstrip().endswith("forgejo-ssh"): return line.rstrip() + f" | {token}\n" return line.rstrip() + f" | {token}\n" lines = text.splitlines(keepends=True) changed = False for i, line in enumerate(lines): if line.startswith("# 9020 castopod"): new_line = upd_hdr(line) if new_line != line: lines[i] = new_line if new_line.endswith("\n") else new_line + "\n" print(f"OK: updated ports comment (+{token})") changed = True else: print(f"OK: ports comment already has {token}") break if changed: text = "".join(lines) if text != orig: if dry: print(f"DRY: would write {caddy} ({len(text) - len(orig):+d} bytes)") else: caddy.write_text(text) print(f"wrote {caddy}") else: print("no Caddyfile change") PY if [[ "$DRY" -eq 1 ]]; then echo "dry-run done (no validate/reload)" exit 0 fi if command -v caddy >/dev/null 2>&1; then echo "== validate ==" caddy validate --config "$CADDY" else echo "WARN: caddy binary missing — skip validate" >&2 fi if [[ "$NO_RELOAD" -eq 1 ]]; then echo "skip reload (--no-reload)" exit 0 fi if [[ "$CADDY" == /etc/caddy/Caddyfile ]] && command -v systemctl >/dev/null 2>&1; then echo "== reload ==" if systemctl is-active --quiet caddy; then systemctl reload caddy else echo "WARN: caddy unit not active — start it yourself" >&2 fi else echo "skip reload (not live path or no systemctl)" fi