#!/usr/bin/env bash set -euo pipefail DIR="/opt/hx-ki/com2-stack" F="$DIR/docker-compose.yml" ENVF="$DIR/.env" NET="hxki-internal" TS="$(date +%Y%m%d-%H%M%S)" BK="/opt/hx-ki/backups/com2-ferrari-$TS" mkdir -p "$BK" echo "=== COM2 · FERRARI REPAIR ONE-SHOT (no guessing) ===" echo "Compose: $F" echo "Env: $ENVF" echo "Backup: $BK" echo [ -f "$F" ] || { echo "FAIL: FEHLT $F"; exit 1; } cp -a "$F" "$BK/docker-compose.yml.pre" || true [ -f "$ENVF" ] && cp -a "$ENVF" "$BK/.env.pre" || true # --- helpers --------------------------------------------------------------- die(){ echo "FAIL: $*" ; exit 1; } # find candidates from many places, newest first collect_kv_candidates() { local key="$1" # search in .env files + compose backups + docker-compose.yml* (only small grep) find /opt/hx-ki -maxdepth 6 -type f \ \( -iname ".env" -o -iname "*.env" -o -iname "docker-compose*.yml" -o -iname "docker-compose*.yml.*" -o -iname "*.yml.bak*" \) \ -printf "%T@ %p\n" 2>/dev/null | sort -nr | awk '{ $1=""; sub(/^ /,""); print }' | while read -r p; do [ -f "$p" ] || continue # exact KEY=VALUE lines only awk -v k="$key" -F= ' $1==k { v=$0; sub(/^[^=]+= /,"",v); sub(/^[^=]+=/,"",v); gsub(/\r/,"",v); if (v!="" && v !~ /CHANGE_ME|CHANGEME|changeme/) { print v } }' "$p" 2>/dev/null | head -n 5 done | awk 'NF' | awk '!seen[$0]++' | head -n 30 } ensure_net() { docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null } stop_host_caddy() { if systemctl is-active --quiet caddy 2>/dev/null; then echo "[0] Stoppe host-caddy (systemd) – Ports 80/443 frei" systemctl stop caddy fi } compose_down() { cd "$DIR" docker compose down --remove-orphans || true } compose_up() { cd "$DIR" docker compose up -d --remove-orphans } # Test Postgres password by real connection pg_try_pw() { local user="$1" pw="$2" docker exec -e PGPASSWORD="$pw" hxki-postgres sh -lc "psql -U '$user' -d postgres -tAc 'select 1' >/dev/null 2>&1" } # Ensure DB exists (create if missing) using admin user pg_ensure_db() { local admin_user="$1" admin_pw="$2" db="$3" docker exec -e PGPASSWORD="$admin_pw" hxki-postgres sh -lc \ "psql -U '$admin_user' -d postgres -tAc \"select 1 from pg_database where datname='${db}'\" | grep -q 1 || psql -U '$admin_user' -d postgres -c \"create database \\\"${db}\\\"\" >/dev/null" } # Ensure role exists + password (only if missing) pg_ensure_role() { local admin_user="$1" admin_pw="$2" role="$3" role_pw="$4" docker exec -e PGPASSWORD="$admin_pw" hxki-postgres sh -lc \ "psql -U '$admin_user' -d postgres -tAc \"select 1 from pg_roles where rolname='${role}'\" | grep -q 1 || psql -U '$admin_user' -d postgres -c \"create role \\\"${role}\\\" login password '${role_pw}'\" >/dev/null" } # grant owner if needed pg_set_owner() { local admin_user="$1" admin_pw="$2" db="$3" owner="$4" docker exec -e PGPASSWORD="$admin_pw" hxki-postgres sh -lc \ "psql -U '$admin_user' -d postgres -c \"alter database \\\"${db}\\\" owner to \\\"${owner}\\\"\" >/dev/null 2>&1 || true" } # MariaDB root auth test (real) my_root_try_pw() { local pw="$1" docker exec hxki-mariadb sh -lc "mysqladmin ping -uroot -p'$pw' --silent >/dev/null 2>&1" } # MariaDB user auth test my_user_try_pw() { local user="$1" pw="$2" db="$3" docker exec hxki-mariadb sh -lc "mysql -u'$user' -p'$pw' -D'$db' -e 'select 1' >/dev/null 2>&1" } # Ensure mautic db/user exists (create only if missing; uses root) my_ensure_mautic() { local rootpw="$1" db="$2" user="$3" userpw="$4" docker exec hxki-mariadb sh -lc " mysql -uroot -p'$rootpw' -e \"CREATE DATABASE IF NOT EXISTS \\\`$db\\\`;\" >/dev/null && mysql -uroot -p'$rootpw' -e \"CREATE USER IF NOT EXISTS '$user'@'%' IDENTIFIED BY '$userpw';\" >/dev/null && mysql -uroot -p'$rootpw' -e \"GRANT ALL PRIVILEGES ON \\\`$db\\\`.* TO '$user'@'%'; FLUSH PRIVILEGES;\" >/dev/null " } # --- start --------------------------------------------------------------- [ -d "$DIR" ] || die "FEHLT: $DIR" stop_host_caddy ensure_net echo "[1] Orchester runter (sauber)" compose_down echo "[2] Starte NUR DBs (damit wir Credentials echt testen können)" cd "$DIR" docker compose up -d hxki-postgres hxki-mariadb || die "DB-Start fehlgeschlagen" # --- discover postgres admin user (from container env) ---------------------- PG_ADMIN_USER="$(docker inspect hxki-postgres --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '$1=="POSTGRES_USER"{print $2}' | tail -n 1)" [ -n "$PG_ADMIN_USER" ] || die "POSTGRES_USER nicht gefunden in hxki-postgres Env" # Candidate passwords from old state echo "[3] Suche Postgres-Passwort (no guessing: scan + real login test)" mapfile -t PG_PWS < <(collect_kv_candidates "POSTGRES_PASSWORD") # also accept PG_PASSWORD style keys mapfile -t PG_PWS2 < <(collect_kv_candidates "PG_PASSWORD") PG_PWS+=( "${PG_PWS2[@]}" ) # de-dupe PG_PWS=( $(printf "%s\n" "${PG_PWS[@]}" | awk 'NF' | awk '!seen[$0]++') ) PG_ADMIN_PW="" for pw in "${PG_PWS[@]}"; do if pg_try_pw "$PG_ADMIN_USER" "$pw"; then PG_ADMIN_PW="$pw"; break fi done [ -n "$PG_ADMIN_PW" ] || die "Kein funktionierendes Postgres-Passwort gefunden (scan+test)." echo " OK: Postgres auth funktioniert für user=$PG_ADMIN_USER" # --- discover mariadb root pw --------------------------------------------- echo "[4] Suche MariaDB root Passwort (scan + real ping test)" mapfile -t MYROOT_PWS < <(collect_kv_candidates "MARIADB_ROOT_PASSWORD") mapfile -t MYROOT_PWS2 < <(collect_kv_candidates "MYSQL_ROOT_PASSWORD") MYROOT_PWS+=( "${MYROOT_PWS2[@]}" ) MYROOT_PWS=( $(printf "%s\n" "${MYROOT_PWS[@]}" | awk 'NF' | awk '!seen[$0]++') ) MY_ROOT_PW="" for pw in "${MYROOT_PWS[@]}"; do if my_root_try_pw "$pw"; then MY_ROOT_PW="$pw"; break fi done [ -n "$MY_ROOT_PW" ] || die "Kein funktionierendes MariaDB root Passwort gefunden (scan+test)." echo " OK: MariaDB root auth funktioniert" # --- read target vars from compose/env (but align with OLD secrets) --------- # Extract current target DB names from compose env placeholders (.env) # If not present, fall back to container env default PG_DB="${PG_DB:-}" PG_USER="${PG_USER:-}" if [ -f "$ENVF" ]; then # shellcheck disable=SC1090 set -a; source "$ENVF" 2>/dev/null || true; set +a fi # Ensure defaults if env missing : "${PG_USER:=$PG_ADMIN_USER}" : "${PG_DB:=n8n}" # Mautic target vars : "${MAUTIC_DB_NAME:=mautic}" : "${MAUTIC_DB_USER:=mautic}" # try recover MAUTIC_DB_PASSWORD from old state too mapfile -t MAUTIC_PWS < <(collect_kv_candidates "MAUTIC_DB_PASSWORD") MAUTIC_DB_PASSWORD="${MAUTIC_DB_PASSWORD:-${MAUTIC_PWS[0]:-}}" # If not found, we do NOT invent: we only proceed if we can validate, else stop. if [ -z "${MAUTIC_DB_PASSWORD:-}" ]; then die "MAUTIC_DB_PASSWORD nicht gefunden im alten Zustand. (Bitte in alten .env/Backups vorhanden machen.)" fi echo "[5] Schreibe kanonische .env (nur funktionierende Secrets, kein CHANGE_ME)" cp -a "$ENVF" "$BK/.env.prewrite" 2>/dev/null || true cat > "$ENVF" </dev/null || die "Compose ist nicht valide." echo "[9] Orchester hoch" docker compose up -d --remove-orphans echo echo "=== HARD CHECKS (facts) ===" echo "[A] Container Status" docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-' || true echo echo "[B] Logs (tail 30)" echo "--- hxki-n8n ---" docker logs --tail=30 hxki-n8n || true echo "--- hxki-mautic ---" docker logs --tail=30 hxki-mautic || true echo "--- hxki-web ---" docker logs --tail=30 hxki-web || true echo echo "[C] Caddy -> Upstreams (internal reachability)" 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' || true docker exec -it hx-caddy sh -lc 'wget -qO- http://hxki-mautic/ >/dev/null && echo OK_CADDY_TO_MAUTIC || echo FAIL_CADDY_TO_MAUTIC' || true docker exec -it hx-caddy sh -lc 'wget -qO- http://hxki-web/ >/dev/null && echo OK_CADDY_TO_WEB || echo FAIL_CADDY_TO_WEB' || true echo echo "=== DONE ===" echo "Backup: $BK"