217 lines
7 KiB
Bash
Executable file
217 lines
7 KiB
Bash
Executable file
#!/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
|