169 lines
6.0 KiB
Bash
Executable File
169 lines
6.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
DIR="/opt/hx-ki/com2-stack"
|
|
F="$DIR/docker-compose.yml"
|
|
NET="hxki-internal"
|
|
TS="$(date +%Y%m%d-%H%M%S)"
|
|
BK="$DIR/backup-$TS"
|
|
mkdir -p "$BK"
|
|
|
|
echo "=== COM2 REPAIR ONE-SHOT ==="
|
|
echo "Autorität: $F"
|
|
[ -f "$F" ] || { echo "FEHLT: $F"; exit 1; }
|
|
|
|
cp -a "$F" "$BK/docker-compose.yml.pre"
|
|
|
|
# 0) hxki-internal muss als external existieren
|
|
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null
|
|
|
|
# 1) Service hxki-web wiederherstellen aus letztem Backup das hxki-web enthält
|
|
LATEST_WEB_SRC="$(ls -1t "$DIR"/backup-*/docker-compose.yml.orig 2>/dev/null | head -n1 || true)"
|
|
if ! grep -qE '^\s{2}hxki-web:\s*$' "$F"; then
|
|
echo "[1] hxki-web fehlt -> versuche Restore aus Backup"
|
|
if [ -z "${LATEST_WEB_SRC:-}" ] || ! grep -qE '^\s{2}hxki-web:\s*$' "$LATEST_WEB_SRC"; then
|
|
echo "FAIL: Kein Backup mit hxki-web gefunden. Lege eine Quelle bereit (z.B. altes Compose)."
|
|
echo " Erwartet: $DIR/backup-*/docker-compose.yml.orig mit ' hxki-web:' drin."
|
|
exit 1
|
|
fi
|
|
|
|
python3 - <<PY
|
|
import re, pathlib
|
|
dst = pathlib.Path("$F").read_text()
|
|
src = pathlib.Path("$LATEST_WEB_SRC").read_text()
|
|
|
|
def extract_service(text, name):
|
|
m = re.search(r'(?ms)^services:\s*\n(.*?)(?=^\S|\Z)', text)
|
|
if not m: raise SystemExit("FEHLT services: im Source")
|
|
block = m.group(1)
|
|
pat = rf'(?ms)^\s{{2}}{re.escape(name)}:\s*\n(?:^\s{{4}}.*\n|^\s{{6}}.*\n|^\s{{8}}.*\n|^\s*\n)*?(?=^\s{{2}}[A-Za-z0-9_.-]+:\s*$|\Z)'
|
|
mm = re.search(pat, block)
|
|
if not mm: raise SystemExit(f"Service {name} nicht im Source gefunden")
|
|
return mm.group(0)
|
|
|
|
web = extract_service(src, "hxki-web")
|
|
|
|
m = re.search(r'(?ms)^services:\s*\n', dst)
|
|
if not m: raise SystemExit("FEHLT services: im Ziel")
|
|
# direkt nach "services:\n" einfügen
|
|
dst2 = re.sub(r'(?ms)^(services:\s*\n)', r'\1' + web + "\n", dst, count=1)
|
|
pathlib.Path("$F").write_text(dst2)
|
|
print("OK: hxki-web aus Backup wieder eingefügt.")
|
|
PY
|
|
else
|
|
echo "[1] OK: hxki-web ist vorhanden"
|
|
fi
|
|
|
|
# 2) Caddy Ports nach außen sicherstellen (damit es "reagiert")
|
|
python3 - <<PY
|
|
import re, pathlib
|
|
p = pathlib.Path("$F")
|
|
s = p.read_text()
|
|
|
|
def ensure_ports_for(service, ports_lines):
|
|
# finde service block
|
|
m = re.search(r'(?ms)^services:\s*\n(.*?)(?=^\S|\Z)', s)
|
|
if not m: raise SystemExit("FEHLT services:")
|
|
block = m.group(1)
|
|
|
|
pat = rf'(?ms)^\s{{2}}{re.escape(service)}:\s*\n(?:^\s{{4}}.*\n|^\s{{6}}.*\n|^\s{{8}}.*\n|^\s*\n)*?(?=^\s{{2}}[A-Za-z0-9_.-]+:\s*$|\Z)'
|
|
mm = re.search(pat, block)
|
|
if not mm: raise SystemExit(f"FEHLT Service {service}")
|
|
sb = mm.group(0)
|
|
|
|
if re.search(r'(?m)^\s{4}ports:\s*$', sb):
|
|
# ports block vorhanden -> ergänze fehlende mappings
|
|
def has(line): return re.search(r'(?m)^\s{6}-\s*' + re.escape(line) + r'\s*$', sb)
|
|
add = [l for l in ports_lines if not has(l)]
|
|
if add:
|
|
sb2 = re.sub(r'(?m)^(\s{4}ports:\s*\n)', r'\1' + ''.join([f' - "{l}"\n' for l in add]), sb, count=1)
|
|
else:
|
|
sb2 = sb
|
|
else:
|
|
# kein ports block -> vor networks oder am ende einfügen
|
|
ins = " ports:\n" + ''.join([f' - "{l}"\n' for l in ports_lines])
|
|
if re.search(r'(?m)^\s{4}networks:\s*', sb):
|
|
sb2 = re.sub(r'(?m)^(\s{4}networks:\s*)', ins + r'\1', sb, count=1)
|
|
else:
|
|
sb2 = sb.rstrip() + "\n" + ins
|
|
return pat, sb, sb2
|
|
|
|
ports = ["80:80","443:443","443:443/udp","2019:2019"]
|
|
pat, old, new = ensure_ports_for("hx-caddy", ports)
|
|
if old != new:
|
|
s2 = re.sub(pat, new, s, count=1)
|
|
p.write_text(s2)
|
|
print("OK: hx-caddy ports nach außen gesetzt/ergänzt.")
|
|
else:
|
|
print("OK: hx-caddy ports waren schon da.")
|
|
PY
|
|
|
|
# 3) n8n muss im Container auf 0.0.0.0 lauschen (damit Caddy reinkommt)
|
|
python3 - <<PY
|
|
import re, pathlib
|
|
p = pathlib.Path("$F")
|
|
s = p.read_text()
|
|
|
|
def patch_env(service, key, val):
|
|
m = re.search(r'(?ms)^services:\s*\n(.*?)(?=^\S|\Z)', s)
|
|
if not m: raise SystemExit("FEHLT services:")
|
|
block = m.group(1)
|
|
pat = rf'(?ms)^\s{{2}}{re.escape(service)}:\s*\n(?:^\s{{4}}.*\n|^\s{{6}}.*\n|^\s{{8}}.*\n|^\s*\n)*?(?=^\s{{2}}[A-Za-z0-9_.-]+:\s*$|\Z)'
|
|
mm = re.search(pat, block)
|
|
if not mm: raise SystemExit(f"FEHLT Service {service}")
|
|
sb = mm.group(0)
|
|
|
|
if re.search(r'(?m)^\s{6}'+re.escape(key)+r':', sb):
|
|
sb2 = re.sub(r'(?m)^(\s{6}'+re.escape(key)+r':\s*).*$' , r'\1'+val, sb)
|
|
else:
|
|
if re.search(r'(?m)^\s{4}environment:\s*$', sb):
|
|
sb2 = re.sub(r'(?m)^(\s{4}environment:\s*\n)', r'\1' + f' {key}: {val}\n', sb, count=1)
|
|
else:
|
|
# env block vor volumes/networks einfügen
|
|
env = " environment:\n" + f" {key}: {val}\n"
|
|
if re.search(r'(?m)^\s{4}volumes:\s*', sb):
|
|
sb2 = re.sub(r'(?m)^(\s{4}volumes:\s*)', env + r'\1', sb, count=1)
|
|
elif re.search(r'(?m)^\s{4}networks:\s*', sb):
|
|
sb2 = re.sub(r'(?m)^(\s{4}networks:\s*)', env + r'\1', sb, count=1)
|
|
else:
|
|
sb2 = sb.rstrip() + "\n" + env
|
|
return pat, sb, sb2
|
|
|
|
pat, old, new = patch_env("hxki-n8n", "N8N_LISTEN_ADDRESS", "0.0.0.0")
|
|
if old != new:
|
|
s2 = re.sub(pat, new, s, count=1)
|
|
p.write_text(s2)
|
|
print("OK: hxki-n8n N8N_LISTEN_ADDRESS=0.0.0.0 gesetzt.")
|
|
else:
|
|
print("OK: hxki-n8n N8N_LISTEN_ADDRESS war schon ok.")
|
|
PY
|
|
|
|
# 4) Validate (harte Wahrheit)
|
|
echo "[V] Validate: docker compose config"
|
|
docker compose -f "$F" config >/dev/null
|
|
echo "OK: Compose valide."
|
|
|
|
# 5) Orchester neu starten
|
|
cd "$DIR"
|
|
docker compose down --remove-orphans || true
|
|
docker compose up -d --remove-orphans
|
|
|
|
# 6) n8n Listen-Check im Container
|
|
echo "[T] n8n lauscht?"
|
|
for i in {1..60}; do
|
|
if docker exec hxki-n8n sh -lc "ss -lnt | grep -q ':5678'"; then
|
|
echo "OK: n8n lauscht auf 5678."
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# 7) Caddy -> n8n intern
|
|
echo "[T] Caddy -> n8n?"
|
|
docker exec -it hx-caddy sh -lc 'wget -qO- http://hxki-n8n:5678/ >/dev/null && echo OK_CADDY_TO_N8N || echo FAIL_CADDY_TO_N8N'
|
|
|
|
# 8) Status
|
|
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}'
|
|
|
|
echo "=== ENDE COM2 REPAIR ONE-SHOT ==="
|