initial COM2 system snapshot

This commit is contained in:
gitea
2026-03-06 15:22:40 +00:00
commit 9c0fa49baf
4377 changed files with 273033 additions and 0 deletions

132
COM2_CANONICAL_WRITE_AND_UP.sh Executable file
View File

@@ -0,0 +1,132 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
F="$DIR/docker-compose.yml"
ENVF="$DIR/.env"
NET="hxki-internal"
mkdir -p "$DIR"
echo "=== COM2 · CANONICAL WRITE + UP ==="
# Netzwerk muss existieren (external)
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null
# .env (Passwörter hier)
cat > "$ENVF" <<'ENV'
PG_USER=hxki
PG_PASSWORD=CHANGE_ME_STRONG
PG_DB=n8n
MAUTIC_DB_ROOT_PASSWORD=CHANGE_ME_STRONG
MAUTIC_DB_NAME=mautic
MAUTIC_DB_USER=mautic
MAUTIC_DB_PASSWORD=CHANGE_ME_STRONG
N8N_HOST=n8n.hx-ki.com
N8N_PROTOCOL=https
ENV
# Compose: autark, ohne Grafana
cat > "$F" <<'YML'
services:
hxki-postgres:
image: postgres:16
container_name: hxki-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ${PG_USER}
POSTGRES_PASSWORD: ${PG_PASSWORD}
POSTGRES_DB: ${PG_DB}
volumes:
- /opt/hx-ki/postgres:/var/lib/postgresql/data
networks: [hxki-internal]
ports: ["5432:5432"]
hxki-mariadb:
image: mariadb:10.11
container_name: hxki-mariadb
restart: unless-stopped
command: ["--character-set-server=utf8mb4","--collation-server=utf8mb4_unicode_ci"]
environment:
MARIADB_ROOT_PASSWORD: ${MAUTIC_DB_ROOT_PASSWORD}
MARIADB_DATABASE: ${MAUTIC_DB_NAME}
MARIADB_USER: ${MAUTIC_DB_USER}
MARIADB_PASSWORD: ${MAUTIC_DB_PASSWORD}
volumes:
- /opt/hx-ki/mautic/db:/var/lib/mysql
networks: [hxki-internal]
ports: ["3306:3306"]
hxki-mautic:
image: mautic/mautic:5-apache
container_name: hxki-mautic
restart: unless-stopped
depends_on: [hxki-mariadb]
environment:
MAUTIC_DB_HOST: hxki-mariadb
MAUTIC_DB_USER: ${MAUTIC_DB_USER}
MAUTIC_DB_PASSWORD: ${MAUTIC_DB_PASSWORD}
MAUTIC_DB_NAME: ${MAUTIC_DB_NAME}
volumes:
- /opt/hx-ki/mautic/app:/var/www/html
networks: [hxki-internal]
ports: ["8080:80"]
hxki-n8n:
image: docker.n8n.io/n8nio/n8n:latest
container_name: hxki-n8n
restart: unless-stopped
depends_on: [hxki-postgres]
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: hxki-postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: ${PG_DB}
DB_POSTGRESDB_USER: ${PG_USER}
DB_POSTGRESDB_PASSWORD: ${PG_PASSWORD}
N8N_HOST: ${N8N_HOST}
N8N_PROTOCOL: ${N8N_PROTOCOL}
N8N_PORT: 5678
N8N_EDITOR_BASE_URL: ${N8N_PROTOCOL}://${N8N_HOST}
WEBHOOK_URL: ${N8N_PROTOCOL}://${N8N_HOST}
volumes:
- /data/HXKI_WORKSPACE/router:/home/node/.n8n
- /data/HXKI_WORKSPACE:/data/HXKI_WORKSPACE
networks: [hxki-internal]
ports: ["5678:5678"]
# Caddy auf COM2 Cluster-Client: Container bleibt drin.
# Cluster-spezifische Settings kommen über DEIN Caddy-Cluster-Mechanismus (Volumes/Env/Config),
# den wir nicht raten, sondern aus deinem vorhandenen COM2-Caddy Setup übernehmen.
hx-caddy:
image: caddy:2
container_name: hx-caddy
restart: unless-stopped
networks: [hxki-internal]
ports:
- "80:80"
- "443:443"
- "443:443/udp"
- "2019:2019"
volumes:
- /opt/hx-ki/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
- /opt/hx-ki/caddy/data:/data
- /opt/hx-ki/caddy/config:/config
networks:
hxki-internal:
external: true
YML
echo "[1] Validate"
( cd "$DIR" && docker compose --env-file .env config >/dev/null )
echo "OK: compose valide."
echo "[2] Up"
( cd "$DIR" && docker compose --env-file .env up -d )
echo "[3] Status"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-caddy' || true
echo "=== ENDE ==="

31
COM2_CAUSE_DOCTOR_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
cd /opt/hx-ki/com2-stack
echo "=== COM2 CAUSE DOCTOR (one-shot) ==="
echo
echo "[1] Status"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-caddy' || true
echo
echo "[2] Logs (n8n/mautic/postgres/mariadb) letzte 80"
for c in hxki-n8n hxki-mautic hxki-postgres hxki-mariadb; do
echo; echo "--- $c ---"
docker logs --tail=80 "$c" 2>&1 || true
done
echo
echo "[3] Listen-Check im Container (muss LISTEN zeigen)"
docker exec -it hxki-n8n sh -lc '(ss -lntp 2>/dev/null || true) | egrep "(:5678\\b|LISTEN)" || true'
docker exec -it hxki-postgres sh -lc '(ss -lntp 2>/dev/null || true) | egrep "(:5432\\b|LISTEN)" || true'
docker exec -it hxki-mariadb sh -lc '(ss -lntp 2>/dev/null || true) | egrep "(:3306\\b|LISTEN)" || true'
docker exec -it hxki-mautic sh -lc '(ss -lntp 2>/dev/null || true) | egrep "(:80\\b|LISTEN)" || true'
echo
echo "[4] Caddy -> Upstreams (nur Test, kein Fix)"
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'
docker exec -it hx-caddy sh -lc 'wget -qO- http://hxki-mautic/ >/dev/null && echo OK_CADDY_TO_MAUTIC || echo FAIL_CADDY_TO_MAUTIC'
echo
echo "=== ENDE ==="

View File

@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
cd "$DIR"
echo "=== COM2 · CLEAN NAME CONFLICTS (container-only, no data loss) ==="
echo "[1] Zeige laufende hxki-Container (harte Fakten)"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}}' | egrep '^(hxki-|hx-caddy)' || true
echo
echo "[2] Versuche COM2 sauber runterzufahren (falls Projekt schon existiert)"
docker compose down --remove-orphans || true
echo
echo "[3] Entferne nur Konflikt-Container (NUR Container, keine Volumes/Dirs)"
for n in hxki-mariadb hxki-postgres hxki-n8n hxki-mautic hxki-web hx-caddy hxki-grafana; do
if docker ps -a --format '{{.Names}}' | grep -qx "$n"; then
echo " - rm -f $n"
docker rm -f "$n" >/dev/null || true
fi
done
echo
echo "[4] COM2 hochfahren"
docker compose up -d --remove-orphans
echo
echo "[5] Status"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-caddy' || true
echo "=== DONE ==="

218
COM2_DB_ALIGN_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,218 @@
#!/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)"
BKDIR="/opt/hx-ki/backups/com2-db-align-$TS"
mkdir -p "$BKDIR"
echo "=== COM2 DB ALIGN (one-shot) ==="
echo "Compose: $F"
echo "Env: $ENVF"
echo "Backup: $BKDIR"
[ -f "$F" ] || { echo "FEHLT: $F"; exit 1; }
[ -f "$ENVF" ] || { echo "FEHLT: $ENVF"; exit 1; }
cp -a "$F" "$BKDIR/docker-compose.yml"
cp -a "$ENVF" "$BKDIR/.env"
# Netzwerk muss existieren (external)
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null
# --- .env laden (ohne zu crashen wenn Leerzeilen/Kommentare) ---
set -a
# shellcheck disable=SC1090
source <(grep -v '^\s*#' "$ENVF" | sed '/^\s*$/d')
set +a
need_real_pw() {
local k="$1"; local v="${!k:-}"
if [[ -z "$v" || "$v" =~ CHANGE_ME|CHANGEME|changeme ]]; then return 0; fi
return 1
}
echo
echo "[0] Secrets prüfen (keine Platzhalter erlaubt)"
if need_real_pw PG_PASSWORD || need_real_pw MARIADB_ROOT_PASSWORD || need_real_pw MAUTIC_DB_PASSWORD; then
echo "WARN: In $ENVF sind Platzhalter/fehlende Werte."
echo " Ich frage jetzt EINMAL nach echten Passwörtern und schreibe sie nach $ENVF."
echo
if need_real_pw PG_PASSWORD; then
read -r -s -p "PG_PASSWORD (für Postgres User hxki): " PG_PASSWORD; echo
fi
if need_real_pw MARIADB_ROOT_PASSWORD; then
read -r -s -p "MARIADB_ROOT_PASSWORD (neues Root-PW für MariaDB): " MARIADB_ROOT_PASSWORD; echo
fi
if need_real_pw MAUTIC_DB_PASSWORD; then
read -r -s -p "MAUTIC_DB_PASSWORD (für MariaDB User mautic): " MAUTIC_DB_PASSWORD; echo
fi
# Minimaler .env Patch (nur Keys, die wir brauchen)
# Backup ist schon in BKDIR.
awk -v pg="$PG_PASSWORD" -v mr="$MARIADB_ROOT_PASSWORD" -v mp="$MAUTIC_DB_PASSWORD" '
BEGIN{done_pg=done_mr=done_mp=0}
/^PG_PASSWORD=/{print "PG_PASSWORD="pg; done_pg=1; next}
/^MARIADB_ROOT_PASSWORD=/{print "MARIADB_ROOT_PASSWORD="mr; done_mr=1; next}
/^MAUTIC_DB_PASSWORD=/{print "MAUTIC_DB_PASSWORD="mp; done_mp=1; next}
{print}
END{
if(!done_pg) print "PG_PASSWORD="pg
if(!done_mr) print "MARIADB_ROOT_PASSWORD="mr
if(!done_mp) print "MAUTIC_DB_PASSWORD="mp
}
' "$ENVF" > "$ENVF.tmp" && mv "$ENVF.tmp" "$ENVF"
echo "OK: $ENVF aktualisiert (Backup: $BKDIR/.env)"
fi
# Reload .env after potential patch
set -a
# shellcheck disable=SC1090
source <(grep -v '^\s*#' "$ENVF" | sed '/^\s*$/d')
set +a
: "${PG_USER:=hxki}"
: "${PG_DB:=n8n}"
: "${MAUTIC_DB_USER:=mautic}"
: "${MAUTIC_DB_NAME:=mautic}"
echo
echo "[1] Orchester: sicher hoch (damit DB-Container laufen)"
cd "$DIR"
docker compose up -d --remove-orphans
echo
echo "[2] POSTGRES align (User+DB+PW) Volume bleibt"
# Wir gehen als postgres-OS-User rein (nicht als DB-User), damit kein Passwort-Raten nötig ist.
docker exec -u postgres -i hxki-postgres psql -d postgres <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${PG_USER}') THEN
CREATE ROLE ${PG_USER} LOGIN;
END IF;
ALTER ROLE ${PG_USER} WITH PASSWORD '${PG_PASSWORD}';
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = '${PG_DB}') THEN
CREATE DATABASE ${PG_DB} OWNER ${PG_USER};
END IF;
END
\$\$;
SQL
echo "OK: Postgres User/DB/PW aligned: ${PG_USER}/${PG_DB}"
echo
echo "[3] MARIADB Root-Reset (Recovery-Container) + Mautic User/DB fix Volume bleibt"
# Volume/Bind für /var/lib/mysql ermitteln
MOUNT_SRC="$(docker inspect hxki-mariadb --format '{{range .Mounts}}{{if eq .Destination "/var/lib/mysql"}}{{.Source}}{{end}}{{end}}')"
MOUNT_NAME="$(docker inspect hxki-mariadb --format '{{range .Mounts}}{{if eq .Destination "/var/lib/mysql"}}{{.Name}}{{end}}{{end}}')"
IMG="$(docker inspect hxki-mariadb --format '{{.Config.Image}}')"
if [[ -n "$MOUNT_NAME" ]]; then
VOLARG="-v ${MOUNT_NAME}:/var/lib/mysql"
elif [[ -n "$MOUNT_SRC" ]]; then
VOLARG="-v ${MOUNT_SRC}:/var/lib/mysql"
else
echo "FAIL: Konnte MariaDB /var/lib/mysql Mount nicht ermitteln."
exit 1
fi
# Mautic stoppen damit es nicht dauernd hämmert
docker stop hxki-mautic >/dev/null 2>&1 || true
# MariaDB stoppen (damit Volume frei ist)
docker stop hxki-mariadb >/dev/null 2>&1 || true
# Recovery-Container starten (ohne Grants, ohne Network)
docker rm -f hxki-mariadb-recover >/dev/null 2>&1 || true
docker run -d --name hxki-mariadb-recover $VOLARG --entrypoint mysqld "$IMG" \
--skip-grant-tables --skip-networking --socket=/tmp/mysqld.sock >/dev/null
# Warten bis Socket da ist
for i in {1..40}; do
if docker exec hxki-mariadb-recover sh -lc 'test -S /tmp/mysqld.sock'; then break; fi
sleep 0.25
done
docker exec hxki-mariadb-recover sh -lc 'test -S /tmp/mysqld.sock' || { echo "FAIL: MariaDB recover socket nicht bereit"; docker logs --tail=80 hxki-mariadb-recover; exit 1; }
# Root-PW setzen + Mautic DB/User fixen
docker exec -i hxki-mariadb-recover sh -lc "mariadb --protocol=socket -uroot -S /tmp/mysqld.sock" <<SQL
FLUSH PRIVILEGES;
ALTER USER IF EXISTS 'root'@'localhost' IDENTIFIED BY '${MARIADB_ROOT_PASSWORD}';
ALTER USER IF EXISTS 'root'@'%' IDENTIFIED BY '${MARIADB_ROOT_PASSWORD}';
CREATE DATABASE IF NOT EXISTS ${MAUTIC_DB_NAME};
CREATE USER IF NOT EXISTS '${MAUTIC_DB_USER}'@'%' IDENTIFIED BY '${MAUTIC_DB_PASSWORD}';
ALTER USER '${MAUTIC_DB_USER}'@'%' IDENTIFIED BY '${MAUTIC_DB_PASSWORD}';
GRANT ALL PRIVILEGES ON ${MAUTIC_DB_NAME}.* TO '${MAUTIC_DB_USER}'@'%';
FLUSH PRIVILEGES;
SQL
# Recovery runter
docker rm -f hxki-mariadb-recover >/dev/null
# MariaDB wieder hoch über Compose (mit NEUEM Root-PW in .env)
cd "$DIR"
docker compose up -d --remove-orphans hxki-mariadb
# kurz warten
sleep 2
# Mautic wieder hoch
docker compose up -d --remove-orphans hxki-mautic
echo "OK: MariaDB Root-PW + Mautic DB/User aligned."
echo
echo "[4] Services neu hoch (n8n/web/caddy) und harte Checks"
docker compose up -d --remove-orphans
echo
echo "[A] Container Status"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-caddy' || true
echo
echo "[B] Netzwerk-Mitglieder ($NET)"
docker network inspect "$NET" --format '{{range $id,$c := .Containers}}{{println $c.Name}}{{end}}' | sort
echo
echo "[C] Caddy -> Upstreams (intern, 10s Retry)"
check_up() {
local name="$1" url="$2"
for i in {1..20}; do
if docker exec hx-caddy sh -lc "wget -qO- '$url' >/dev/null 2>&1"; then
echo "OK_CADDY_TO_${name}"
return 0
fi
sleep 0.5
done
echo "FAIL_CADDY_TO_${name}"
return 1
}
FAIL=0
check_up WEB "http://hxki-web:3000/" || FAIL=1
check_up N8N "http://hxki-n8n:5678/" || FAIL=1
check_up MAUTIC "http://hxki-mautic:80/" || FAIL=1
echo
echo "[D] Logs (kurz)"
echo "--- n8n ---"
docker logs --tail=40 hxki-n8n || true
echo "--- mautic ---"
docker logs --tail=40 hxki-mautic || true
echo "--- web ---"
docker logs --tail=40 hxki-web || true
echo "--- caddy ---"
docker logs --tail=40 hx-caddy || true
echo
if [[ "$FAIL" -eq 0 ]]; then
echo "=== OK: COM2 ist wieder stabil (DBs aligned, Upstreams erreichbar) ==="
else
echo "=== WARN: DBs aligned, aber mind. ein Upstream ist noch nicht erreichbar (siehe FAIL_*) ==="
echo "Backup liegt hier: $BKDIR"
fi

152
COM2_DB_ALIGN_TO_ENV_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,152 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
ENVF="$DIR/.env"
NET="hxki-internal"
BK="/opt/hx-ki/backups/com2-db-align-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BK"
echo "=== COM2 · DB ALIGN TO ENV (ONE-SHOT, NO DATA LOSS) ==="
echo "Env: $ENVF"
echo "Backup: $BK"
echo
[ -f "$ENVF" ] || { echo "FAIL: FEHLT $ENVF"; exit 1; }
if grep -qE 'CHANGE_ME|CHANGEME|changeme' "$ENVF"; then
echo "FAIL: In $ENVF sind noch Platzhalter (CHANGE_ME...)."; exit 1
fi
# .env laden (nur simple KEY=VALUE Zeilen)
set -a
. "$ENVF"
set +a
# Pflicht-Keys Postgres
: "${PG_USER:?fehlend in .env}"
: "${PG_PASSWORD:?fehlend in .env}"
: "${PG_DB:?fehlend in .env}"
# Pflicht-Keys MariaDB/Mautic (passen zu deinem Compose ggf. in .env ergänzen)
: "${MYSQL_ROOT_PASSWORD:?fehlend in .env}"
: "${MAUTIC_DB_NAME:?fehlend in .env}"
: "${MAUTIC_DB_USER:?fehlend in .env}"
: "${MAUTIC_DB_PASSWORD:?fehlend in .env}"
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null
cd "$DIR"
echo "[1] DB-Container sauber runter (nur DBs)"
docker compose rm -sf hxki-postgres hxki-mariadb >/dev/null 2>&1 || true
echo "[2] Postgres hoch (nur DB)"
docker compose up -d hxki-postgres
echo "[3] Wait: Postgres ready (pg_isready)"
for i in $(seq 1 60); do
if docker exec -u postgres hxki-postgres pg_isready -q >/dev/null 2>&1; then
echo "OK: Postgres ready."
break
fi
sleep 1
if [ "$i" = "60" ]; then
echo "FAIL: Postgres wird nicht ready."; exit 1
fi
done
echo "[4] Postgres: Role/DB auf .env angleichen (ohne pg_hba-Hacks)"
# WICHTIG: als OS-User 'postgres' im Container -> lokaler Socket -> kein Passwort nötig
docker exec -u postgres hxki-postgres psql -v ON_ERROR_STOP=1 -d postgres <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${PG_USER}') THEN
CREATE ROLE "${PG_USER}" LOGIN;
END IF;
END \$\$;
ALTER ROLE "${PG_USER}" WITH PASSWORD '${PG_PASSWORD}';
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = '${PG_DB}') THEN
CREATE DATABASE "${PG_DB}" OWNER "${PG_USER}";
END IF;
END \$\$;
ALTER DATABASE "${PG_DB}" OWNER TO "${PG_USER}";
SQL
echo "OK: Postgres aligned."
echo "[5] MariaDB: Reset-Container im 'skip-grant-tables' Modus (NO DATA LOSS)"
# Image von vorhandener Definition nehmen (falls schon bekannt), sonst mariadb:10.11
IMG="$(docker inspect hxki-mariadb --format '{{.Config.Image}}' 2>/dev/null || true)"
[ -n "$IMG" ] || IMG="mariadb:10.11"
# Sicherheit: falls noch ein Reset-Container existiert
docker rm -f hxki-mariadb-reset >/dev/null 2>&1 || true
# Reset-Container starten (gleicher Bind-Mount wie dein echtes Setup!)
docker run -d --name hxki-mariadb-reset \
--network "$NET" \
-v /opt/hx-ki/mautic/db:/var/lib/mysql \
"$IMG" \
--skip-networking --skip-grant-tables >/dev/null
echo "[6] Wait: MariaDB Reset ready"
for i in $(seq 1 60); do
if docker exec hxki-mariadb-reset sh -lc "mariadb -uroot -e 'SELECT 1' >/dev/null 2>&1"; then
echo "OK: MariaDB reset ready."
break
fi
sleep 1
if [ "$i" = "60" ]; then
echo "FAIL: MariaDB reset wird nicht ready."; docker logs --tail=80 hxki-mariadb-reset || true; exit 1
fi
done
echo "[7] MariaDB: root-PW setzen + Mautic DB/User sicherstellen"
docker exec hxki-mariadb-reset sh -lc "mariadb -uroot <<'SQL'
FLUSH PRIVILEGES;
-- Root Passwort (für localhost und %)
ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}';
CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
-- Mautic DB + User
CREATE DATABASE IF NOT EXISTS \`${MAUTIC_DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '${MAUTIC_DB_USER}'@'%' IDENTIFIED BY '${MAUTIC_DB_PASSWORD}';
GRANT ALL PRIVILEGES ON \`${MAUTIC_DB_NAME}\`.* TO '${MAUTIC_DB_USER}'@'%';
FLUSH PRIVILEGES;
SQL"
echo "OK: MariaDB aligned."
echo "[8] Reset-Container stoppen"
docker rm -f hxki-mariadb-reset >/dev/null
echo "[9] Echtes Orchester hoch (DBs + Apps)"
docker compose up -d --remove-orphans
echo
echo "[A] Quick checks"
echo "- Postgres Auth (mit .env User/PW):"
docker exec -e PGPASSWORD="${PG_PASSWORD}" hxki-postgres sh -lc "psql -U '${PG_USER}' -d '${PG_DB}' -c 'select 1' >/dev/null" \
&& echo "OK_PG_AUTH" || echo "FAIL_PG_AUTH"
echo "- MariaDB Auth (Mautic User):"
docker exec hxki-mariadb sh -lc "mariadb -u'${MAUTIC_DB_USER}' -p'${MAUTIC_DB_PASSWORD}' -e 'SELECT 1' '${MAUTIC_DB_NAME}' >/dev/null" \
&& echo "OK_MY_AUTH" || echo "FAIL_MY_AUTH"
echo "- n8n -> localhost:5678 im Container:"
docker exec hxki-n8n sh -lc "wget -qO- http://127.0.0.1:5678/ >/dev/null && echo OK_N8N_LISTEN || echo FAIL_N8N_LISTEN" || true
echo "- Caddy -> n8n/mautic/web intern:"
docker exec 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 hx-caddy sh -lc "wget -qO- http://hxki-mautic/ >/dev/null && echo OK_CADDY_TO_MAUTIC || echo FAIL_CADDY_TO_MAUTIC" || true
docker exec 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 dir: $BK"

View File

@@ -0,0 +1,232 @@
#!/usr/bin/env bash
set -euo pipefail
# === Autorität ===
COMPOSE_DIR="/opt/hx-ki/com2-stack"
COMPOSE_FILE="${COMPOSE_DIR}/docker-compose.yml"
ENV_FILE="${COMPOSE_DIR}/.env"
# Bind-Mount Pfade (IST-Zustand, von dir geliefert)
PG_BIND="/opt/hx-ki/postgres"
MY_BIND="/opt/hx-ki/mautic/db"
# Container-Namen (IST)
PG_C="hxki-postgres"
MY_C="hxki-mariadb"
TS="$(date +%Y%m%d-%H%M%S)"
BK_DIR="/opt/hx-ki/backups/com2-one-shot-${TS}"
mkdir -p "$BK_DIR"
echo "=== COM2 ONE-SHOT · RECOVER + FIX (no guessing) ==="
echo "Compose: $COMPOSE_FILE"
echo "Env: $ENV_FILE"
echo "Backup: $BK_DIR"
echo
# --- Preconditions ---
[ -f "$COMPOSE_FILE" ] || { echo "FAIL: FEHLT $COMPOSE_FILE"; exit 1; }
[ -d "$PG_BIND" ] || { echo "FAIL: FEHLT Postgres Bind $PG_BIND"; exit 1; }
[ -d "$MY_BIND" ] || { echo "FAIL: FEHLT MariaDB Bind $MY_BIND"; exit 1; }
cp -a "$COMPOSE_FILE" "$BK_DIR/docker-compose.yml.bak"
[ -f "$ENV_FILE" ] && cp -a "$ENV_FILE" "$BK_DIR/.env.bak" || true
# --- Helper: finde letzte echte Secrets (ohne CHANGE_ME) ---
find_last_secret() {
local key="$1"
local base="/opt/hx-ki"
# Suche in .env / compose / backups
# Priorität: neueste Datei gewinnt
local hit
hit="$(find "$base" -maxdepth 6 -type f \
\( -iname ".env" -o -iname "*.env" -o -iname "docker-compose*.yml" -o -iname "*.bak*" -o -iname "*.yml" \) \
-printf '%T@ %p\n' 2>/dev/null \
| sort -nr \
| awk '{print $2}' \
| while read -r f; do
# KEY=VALUE Zeile
if grep -qE "^${key}=" "$f" 2>/dev/null; then
v="$(grep -E "^${key}=" "$f" | tail -n 1 | cut -d= -f2- | tr -d '\r')"
# skip empty / placeholder
if [ -n "$v" ] && ! echo "$v" | grep -qiE 'CHANGE_ME|CHANGEME|changeme|__REPLACE__'; then
echo "$v"
exit 0
fi
fi
done)" || true
echo "$hit"
}
# --- 1) ENV befüllen aus altem Zustand (nur wenn gefunden) ---
echo "[1] Recover Secrets aus /opt/hx-ki (no guessing)"
PG_PASSWORD="$(find_last_secret "PG_PASSWORD")"
MARIADB_ROOT_PASSWORD="$(find_last_secret "MARIADB_ROOT_PASSWORD")"
MAUTIC_DB_PASSWORD="$(find_last_secret "MAUTIC_DB_PASSWORD")"
# Optional Defaults (nur wenn vorhanden)
PG_USER="$(find_last_secret "PG_USER")"; PG_USER="${PG_USER:-hxki}"
PG_DB="$(find_last_secret "PG_DB")"; PG_DB="${PG_DB:-n8n}"
MAUTIC_DB_USER="$(find_last_secret "MAUTIC_DB_USER")"; MAUTIC_DB_USER="${MAUTIC_DB_USER:-mautic}"
MAUTIC_DB_NAME="$(find_last_secret "MAUTIC_DB_NAME")"; MAUTIC_DB_NAME="${MAUTIC_DB_NAME:-mautic}"
# N8N Host/Proto (optional)
N8N_HOST="$(find_last_secret "N8N_HOST")"; N8N_HOST="${N8N_HOST:-n8n.hx-ki.com}"
N8N_PROTOCOL="$(find_last_secret "N8N_PROTOCOL")"; N8N_PROTOCOL="${N8N_PROTOCOL:-https}"
missing=0
[ -n "$PG_PASSWORD" ] || { echo "FAIL: PG_PASSWORD nicht gefunden (alter Zustand fehlt)"; missing=1; }
[ -n "$MARIADB_ROOT_PASSWORD" ] || { echo "FAIL: MARIADB_ROOT_PASSWORD nicht gefunden (alter Zustand fehlt)"; missing=1; }
[ -n "$MAUTIC_DB_PASSWORD" ] || { echo "FAIL: MAUTIC_DB_PASSWORD nicht gefunden (alter Zustand fehlt)"; missing=1; }
if [ "$missing" -ne 0 ]; then
echo
echo "STOP: Ich rate keine Passwörter."
echo "Gib mir die alten Values oder lege die alte .env Datei in /opt/hx-ki/ ab."
exit 1
fi
cat > "$ENV_FILE" <<ENV
PG_USER=${PG_USER}
PG_PASSWORD=${PG_PASSWORD}
PG_DB=${PG_DB}
MARIADB_ROOT_PASSWORD=${MARIADB_ROOT_PASSWORD}
MAUTIC_DB_USER=${MAUTIC_DB_USER}
MAUTIC_DB_PASSWORD=${MAUTIC_DB_PASSWORD}
MAUTIC_DB_NAME=${MAUTIC_DB_NAME}
N8N_HOST=${N8N_HOST}
N8N_PROTOCOL=${N8N_PROTOCOL}
ENV
echo "OK: .env konsistent aus altem Zustand gesetzt."
echo
# --- 2) Stack runter (sauber) ---
echo "[2] Orchester down"
cd "$COMPOSE_DIR"
docker compose down --remove-orphans || true
# --- 3) Postgres: PW + DB fix via temporär trust (bind-mount pg_hba.conf) ---
echo "[3] Postgres Repair (bind-mount, deterministisch)"
PG_HBA="${PG_BIND}/pg_hba.conf"
[ -f "$PG_HBA" ] || { echo "FAIL: FEHLT $PG_HBA"; exit 1; }
cp -a "$PG_HBA" "$BK_DIR/pg_hba.conf.bak"
# Setze ALLE host/local auf trust (temporär), damit wir ohne Passwort reinkommen
# Danach setzen wir wieder auf scram-sha-256 zurück.
python3 - <<'PY'
from pathlib import Path
p = Path("/opt/hx-ki/postgres/pg_hba.conf")
s = p.read_text()
out=[]
for line in s.splitlines():
if line.strip().startswith("#") or line.strip()=="":
out.append(line); continue
parts=line.split()
if len(parts)>=5 and parts[0] in ("local","host","hostssl","hostnossl"):
# method ist letztes token
parts[-1]="trust"
out.append(" ".join(parts))
else:
out.append(line)
p.write_text("\n".join(out)+ "\n")
PY
# Starte nur Postgres
docker compose up -d "$PG_C"
# Warten bis Postgres bereit
for i in $(seq 1 60); do
if docker exec -i "$PG_C" sh -lc "pg_isready -U '$PG_USER' -d postgres >/dev/null 2>&1"; then
break
fi
sleep 1
done
# Setze Passwort + DB erstellen (wenn fehlt)
docker exec -i "$PG_C" sh -lc "
psql -U '$PG_USER' -d postgres -v ON_ERROR_STOP=1 <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname='${PG_DB}') THEN
CREATE DATABASE \"${PG_DB}\";
END IF;
END
\$\$;
ALTER ROLE \"${PG_USER}\" WITH PASSWORD '${PG_PASSWORD}';
SQL
"
# Restore pg_hba.conf (Original wiederherstellen)
cp -a "$BK_DIR/pg_hba.conf.bak" "$PG_HBA"
# Restart Postgres (scram wieder aktiv)
docker compose restart "$PG_C"
echo "OK: Postgres repaired (PW + DB) und auth wieder wie vorher."
echo
# --- 4) MariaDB: root + mautic-user fix via temporary skip-grant-tables ---
echo "[4] MariaDB Repair (bind-mount, deterministisch)"
# Stop mariadb, starte temporär mit skip-grant-tables (nur lokal im container)
docker compose stop "$MY_C" || true
TMP_C="hxki-mariadb-repair-${TS}"
# gleicher bind mount, aber ohne Auth
docker run -d --rm \
--name "$TMP_C" \
-v "${MY_BIND}:/var/lib/mysql" \
--network none \
mariadb:latest \
--skip-grant-tables --skip-networking
# warten bis mysqld da ist
for i in $(seq 1 60); do
if docker exec -i "$TMP_C" sh -lc "mysqladmin ping --silent >/dev/null 2>&1"; then
break
fi
sleep 1
done
# Set root pw + mautic user + db
docker exec -i "$TMP_C" sh -lc "
mysql -uroot <<SQL
FLUSH PRIVILEGES;
ALTER USER 'root'@'localhost' IDENTIFIED BY '${MARIADB_ROOT_PASSWORD}';
CREATE DATABASE IF NOT EXISTS \`${MAUTIC_DB_NAME}\`;
CREATE USER IF NOT EXISTS '${MAUTIC_DB_USER}'@'%' IDENTIFIED BY '${MAUTIC_DB_PASSWORD}';
GRANT ALL PRIVILEGES ON \`${MAUTIC_DB_NAME}\`.* TO '${MAUTIC_DB_USER}'@'%';
FLUSH PRIVILEGES;
SQL
"
docker stop "$TMP_C" >/dev/null
# normal wieder hoch
docker compose up -d "$MY_C"
echo "OK: MariaDB repaired (root + mautic user/db)."
echo
# --- 5) Full stack up + checks ---
echo "[5] Orchester up"
docker compose up -d --remove-orphans
echo
echo "=== STATUS ==="
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-caddy' || true
echo
echo "=== CHECKS (DB auth) ==="
echo "[A] n8n logs (tail 20)"
docker logs --tail=20 hxki-n8n || true
echo
echo "[B] mautic logs (tail 20)"
docker logs --tail=20 hxki-mautic || true
echo
echo "=== ENDE · ONE-SHOT ==="

91
COM2_DB_CAUSE_FIX_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,91 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
ENVF="$DIR/.env"
NET="hxki-internal"
BKDIR="/opt/hx-ki/backups/com2-db-$(date +%Y%m%d-%H%M%S)"
cd "$DIR"
mkdir -p "$BKDIR"
echo "=== COM2 DB CAUSE FIX (one-shot, deterministisch) ==="
echo "Compose: $DIR/docker-compose.yml"
echo "ENV: $ENVF"
echo "Backup: $BKDIR"
[ -f "$DIR/docker-compose.yml" ] || { echo "FEHLT: $DIR/docker-compose.yml"; exit 1; }
[ -f "$ENVF" ] || { echo "FEHLT: $ENVF"; exit 1; }
# Kein Placeholders
if grep -qE 'CHANGE_ME|CHANGEME|changeme' "$ENVF"; then
echo "FAIL: In $ENVF sind noch Platzhalter (CHANGE_ME...). Erst echte Passwörter setzen."
exit 1
fi
# Netzwerk external muss existieren (keine YAML-Frickelei, nur Host-Fakt)
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null
echo "[1] Orchester runter"
docker compose down --remove-orphans || true
# Helper: Named Volume ermitteln (Destination muss Standard sein)
get_named_volume_for_dest() {
local c="$1" dest="$2"
docker inspect "$c" --format '{{range .Mounts}}{{if and (eq .Type "volume") (eq .Destination "'"$dest"'")}}{{println .Name}}{{end}}{{end}}'
}
backup_volume() {
local vol="$1" name="$2"
echo " -> Backup volume $vol -> $BKDIR/${name}.tar.gz"
docker run --rm -v "${vol}:/v:ro" -v "${BKDIR}:/b" alpine \
sh -lc "cd /v && tar -czf /b/${name}.tar.gz ."
}
PGC="hxki-postgres"
MDBC="hxki-mariadb"
echo "[2] Container check"
docker inspect "$PGC" >/dev/null 2>&1 || { echo "FAIL: Container fehlt: $PGC"; exit 1; }
docker inspect "$MDBC" >/dev/null 2>&1 || { echo "FAIL: Container fehlt: $MDBC"; exit 1; }
echo "[3] Volumes ermitteln"
PGVOL="$(get_named_volume_for_dest "$PGC" "/var/lib/postgresql/data" || true)"
MDBVOL="$(get_named_volume_for_dest "$MDBC" "/var/lib/mysql" || true)"
echo " Postgres volume: ${PGVOL:-<NONE>}"
echo " MariaDB volume: ${MDBVOL:-<NONE>}"
[ -n "${PGVOL:-}" ] || { echo "FAIL: Postgres nutzt kein Named Volume auf /var/lib/postgresql/data"; exit 1; }
[ -n "${MDBVOL:-}" ] || { echo "FAIL: MariaDB nutzt kein Named Volume auf /var/lib/mysql"; exit 1; }
echo "[4] Backup DB-Volumes (Beweis/Absicherung)"
backup_volume "$PGVOL" "postgres_${PGVOL}"
backup_volume "$MDBVOL" "mariadb_${MDBVOL}"
echo "[5] Volume-Reset (Ursache fixen: Secrets-Drift eliminieren)"
docker volume rm "$PGVOL" "$MDBVOL"
echo "[6] Orchester hoch (DBs initialisieren NEU aus .env)"
docker compose up -d --remove-orphans
echo
echo "[7] Hard checks (nur Fakten)"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-postgres|hxki-mariadb|hxki-n8n|hxki-mautic|hxki-web|hx-caddy' || true
echo
echo "[8] DB Init-Fakten (Logs kurz)"
echo "--- postgres ---"
docker logs --tail=40 hxki-postgres || true
echo "--- mariadb ---"
docker logs --tail=40 hxki-mariadb || true
echo
echo "[9] App-Fakten (Logs kurz)"
echo "--- n8n ---"
docker logs --tail=60 hxki-n8n || true
echo "--- mautic ---"
docker logs --tail=60 hxki-mautic || true
echo "=== ENDE ==="
echo "Backups: $BKDIR"

104
COM2_DB_VOLUME_REPAIR_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
ENVF="$DIR/.env"
echo "=== COM2 · DB VOLUME REPAIR (one-shot, deterministisch) ==="
[ -f "$ENVF" ] || { echo "FEHLT: $ENVF"; exit 1; }
# .env laden
set -a
# shellcheck disable=SC1090
source "$ENVF"
set +a
# Hard stop bei Platzhaltern
if grep -qE 'CHANGE_ME|CHANGEME|changeme' "$ENVF"; then
echo "FAIL: In $ENVF sind noch Platzhalter (CHANGE_ME...). Erst echte Passwörter setzen."
exit 1
fi
# Erwartete Variablen (min.)
: "${PG_USER:?PG_USER fehlt}"
: "${PG_PASSWORD:?PG_PASSWORD fehlt}"
: "${PG_DB:?PG_DB fehlt}"
: "${MAUTIC_DB:?MAUTIC_DB fehlt}"
: "${MAUTIC_DB_USER:?MAUTIC_DB_USER fehlt}"
: "${MAUTIC_DB_PASSWORD:?MAUTIC_DB_PASSWORD fehlt}"
: "${MARIADB_ROOT_PASSWORD:?MARIADB_ROOT_PASSWORD fehlt}"
echo "[1] POSTGRES fix im bestehenden Volume (User/Pass/DB)"
# In der offiziellen postgres-image ist "local" i.d.R. trust -> psql ohne Passwort im Container möglich.
# Wir connecten als der initiale Admin-User (POSTGRES_USER im Container), der bei dir "hxki" ist.
if ! docker exec -i hxki-postgres sh -lc "psql -U '${PG_USER}' -d postgres -tAc \"SELECT 1\" >/dev/null" 2>/dev/null; then
echo "FAIL: Kann Postgres im Container nicht per local-socket als ${PG_USER} erreichen."
echo " Ursache: entweder Container nicht running oder local auth ist nicht trust."
exit 1
fi
# Passwort angleichen + DB sicherstellen
docker exec -i hxki-postgres sh -lc "
psql -U '${PG_USER}' -d postgres -v ON_ERROR_STOP=1 <<SQL
DO \$\$
BEGIN
-- User sicherstellen
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='${PG_USER}') THEN
CREATE ROLE ${PG_USER} LOGIN;
END IF;
END
\$\$;
ALTER ROLE ${PG_USER} WITH PASSWORD '${PG_PASSWORD}';
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname='${PG_DB}') THEN
CREATE DATABASE ${PG_DB} OWNER ${PG_USER};
END IF;
END
\$\$;
SQL
" >/dev/null
echo "OK: Postgres User/Pass/DB synchronisiert (Volume bleibt intakt)"
echo
echo "[2] MARIADB fix im bestehenden Volume (root erreichbar? user/db/grants)"
# Versuch 1: root via unix_socket (ohne Passwort) klappt bei manchen Setups
if docker exec -i hxki-mariadb sh -lc "mysql -uroot -e 'SELECT 1' >/dev/null" 2>/dev/null; then
MYSQL_AUTH="mysql -uroot"
else
# Versuch 2: root mit Passwort aus .env
MYSQL_AUTH="mysql -uroot -p'${MARIADB_ROOT_PASSWORD}'"
if ! docker exec -i hxki-mariadb sh -lc "${MYSQL_AUTH} -e 'SELECT 1' >/dev/null" 2>/dev/null; then
echo "FAIL: MariaDB root login schlägt fehl (weder socket noch Passwort)."
echo " Das heißt: Volume hat ein anderes root-PW als in .env."
echo " Ferrari-like Optionen: (A) altes root-PW wiederfinden ODER (B) Volume kontrolliert neu initialisieren."
exit 1
fi
fi
# DB + User + Grants für Mautic
docker exec -i hxki-mariadb sh -lc "
${MYSQL_AUTH} <<SQL
CREATE DATABASE IF NOT EXISTS \`${MAUTIC_DB}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '${MAUTIC_DB_USER}'@'%' IDENTIFIED BY '${MAUTIC_DB_PASSWORD}';
GRANT ALL PRIVILEGES ON \`${MAUTIC_DB}\`.* TO '${MAUTIC_DB_USER}'@'%';
FLUSH PRIVILEGES;
SQL
" >/dev/null
echo "OK: MariaDB mautic DB/User/Grants synchronisiert (Volume bleibt intakt)"
echo
echo "[3] Services neu starten (damit sie die jetzt korrekten Credentials nutzen)"
cd "$DIR"
docker compose restart hxki-postgres hxki-mariadb hxki-n8n hxki-mautic >/dev/null || true
echo
echo "[4] Hard Checks (nur Fakten)"
echo "--- n8n should stop DB-auth loop ---"
docker logs --tail=25 hxki-n8n || true
echo "--- mautic should stop DB-auth loop ---"
docker logs --tail=25 hxki-mautic || true
echo "=== ENDE ==="

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="/opt/hx-ki"
COMPOSE="/opt/hx-ki/com2-stack/docker-compose.yml"
PG_C="hxki-postgres"
MY_C="hxki-mariadb"
echo "=== COM2 · DISCOVER + VERIFY DB CREDS (one-shot, no guessing) ==="
[ -f "$COMPOSE" ] || { echo "FAIL: missing $COMPOSE"; exit 1; }
echo "[0] Start only DB containers (for verification tests)"
docker compose -f "$COMPOSE" up -d "$PG_C" "$MY_C" >/dev/null 2>&1 || true
echo "[1] Read current container ENV (ground truth candidates)"
PG_USER="$(docker inspect "$PG_C" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^POSTGRES_USER=/{print $2}' | tail -n1 || true)"
PG_DB="$(docker inspect "$PG_C" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^POSTGRES_DB=/{print $2}' | tail -n1 || true)"
MY_ROOT_PW_ENV="$(docker inspect "$MY_C" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^(MARIADB_ROOT_PASSWORD|MYSQL_ROOT_PASSWORD)=/{print $2}' | tail -n1 || true)"
echo " PG_USER=${PG_USER:-<unknown>} PG_DB=${PG_DB:-<unknown>}"
echo " MY_ROOT_PW_ENV=${MY_ROOT_PW_ENV:+<set>} ${MY_ROOT_PW_ENV:+"(not shown)"}"
echo "[2] Collect password candidates from existing files (no guessing)"
# We only scan /opt/hx-ki for known keys; no invention.
mapfile -t CANDIDATES < <(
grep -RInh --binary-files=without-match -E \
'(^|[[:space:]])(POSTGRES_PASSWORD|PG_PASSWORD|DB_POSTGRESDB_PASSWORD|MARIADB_ROOT_PASSWORD|MYSQL_ROOT_PASSWORD|MAUTIC_DB_PASSWORD|MAUTIC_DB_ROOT_PASSWORD)[[:space:]]*[:=][[:space:]]*[^[:space:]]+' \
"$ROOT" 2>/dev/null \
| sed -E 's/.*[:=][[:space:]]*//' \
| sed -E "s/^['\"]//; s/['\"]$//" \
| awk 'NF' \
| sort -u
)
# Add current env passwords as candidates (if set)
if [ -n "${MY_ROOT_PW_ENV:-}" ]; then
CANDIDATES+=("$MY_ROOT_PW_ENV")
fi
# De-dup again
mapfile -t CANDIDATES < <(printf "%s\n" "${CANDIDATES[@]}" | awk 'NF' | sort -u)
echo " Found candidates: ${#CANDIDATES[@]}"
mask() { local s="$1"; local n="${#s}"; if [ "$n" -le 4 ]; then echo "****"; else echo "****${s: -4}"; fi; }
echo "[3] Verify Postgres password by REAL login test (no assumptions)"
PG_OK=""
if [ -z "${PG_USER:-}" ]; then
echo " FAIL: PG_USER not detectable from container env"
else
for pw in "${CANDIDATES[@]}"; do
# Try connecting to default DBs first; existence of PG_DB may vary.
if docker exec -e PGPASSWORD="$pw" "$PG_C" sh -lc \
"psql -U '$PG_USER' -d postgres -tAc 'SELECT 1' >/dev/null 2>&1 || psql -U '$PG_USER' -d template1 -tAc 'SELECT 1' >/dev/null 2>&1"; then
PG_OK="$pw"
echo " OK: Postgres login works with password $(mask "$pw")"
break
fi
done
[ -n "$PG_OK" ] || echo " FAIL: No candidate password could log in to Postgres as user '$PG_USER'"
fi
echo "[4] Verify MariaDB root password by REAL login test (no assumptions)"
MY_OK=""
for pw in "${CANDIDATES[@]}"; do
if docker exec "$MY_C" sh -lc "mysql -uroot -p'$pw' -e 'SELECT 1' >/dev/null 2>&1"; then
MY_OK="$pw"
echo " OK: MariaDB root login works with password $(mask "$pw")"
break
fi
done
[ -n "$MY_OK" ] || echo " FAIL: No candidate password could log in to MariaDB as root"
echo
echo "=== RESULT (verifiziert oder nicht auffindbar) ==="
if [ -n "$PG_OK" ]; then
echo "POSTGRES_USER=$PG_USER"
echo "POSTGRES_PASSWORD=<VERIFIED $(mask "$PG_OK")>"
else
echo "POSTGRES: VERIFIED PASSWORD NOT FOUND IN /opt/hx-ki SOURCES"
fi
if [ -n "$MY_OK" ]; then
echo "MYSQL_ROOT_PASSWORD=<VERIFIED $(mask "$MY_OK")>"
else
echo "MARIADB: VERIFIED ROOT PASSWORD NOT FOUND IN /opt/hx-ki SOURCES"
fi
echo
echo "If one of them is NOT found: the only deterministic path is a controlled password reset (auth bypass), because plaintext cannot be recovered from the data directories."

137
COM2_DOCTOR_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,137 @@
#!/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"
echo "=== COM2 DOCTOR (one-shot, deterministisch) ==="
echo "Autorität: $F"
echo "Backup: $BK"
mkdir -p "$BK"
# 0) Preconditions
[ -f "$F" ] || { echo "FEHLT: $F"; exit 1; }
cp -a "$F" "$BK/docker-compose.yml.orig"
# 1) Host-Caddy darf NICHT Port 80/443 blocken, wenn Docker-Caddy laufen soll
if systemctl is-active --quiet caddy 2>/dev/null; then
echo "[1] Stoppe host-caddy (systemd), damit Docker-Caddy 80/443 bekommt"
systemctl stop caddy
else
echo "[1] OK: host-caddy ist nicht aktiv"
fi
# 2) Docker Netzwerk sicherstellen (external)
docker network inspect "$NET" >/dev/null 2>&1 || { echo "[2] Erzeuge Docker-Netzwerk: $NET"; docker network create "$NET" >/dev/null; }
echo "[2] OK: Netzwerk existiert: $NET"
# 3) Compose YAML MUSS valide sein sonst: Restore und STOP mit Zeilenanzeige
echo "[3] Validate Compose YAML"
if ! docker compose -f "$F" config >/dev/null 2>&1; then
echo "FAIL: Compose ist NICHT valide."
echo "---- Parser-Fehler ----"
docker compose -f "$F" config 2>&1 | tail -n 30 || true
echo "---- Zeilen um die vermutete Fehlerstelle (45-80) ----"
nl -ba "$F" | sed -n '45,80p' || true
echo
echo "Auto-Restore auf Backup und STOP."
cp -a "$BK/docker-compose.yml.orig" "$F"
exit 1
fi
echo "[3] OK: Compose ist valide"
# 4) Pflicht-Services auf COM2 (laut deiner Vorgabe: autark)
echo "[4] Prüfe Pflicht-Services im Compose (ohne Umbau)"
python3 - <<'PY'
import re, pathlib, sys
p = pathlib.Path("/opt/hx-ki/com2-stack/docker-compose.yml")
s = p.read_text()
m = re.search(r'(?ms)^services:\s*\n(.*?)(?=^\S|\Z)', s)
if not m:
print("FAIL: Kein Top-Level 'services:' Block.")
sys.exit(1)
block = m.group(1)
names = re.findall(r'(?m)^\s{2}([A-Za-z0-9_.-]+):\s*$', block)
need = ["hx-caddy","hxki-n8n","hxki-postgres","hxki-mariadb","hxki-mautic"]
missing = [x for x in need if x not in names]
print("Services gefunden:", ", ".join(names))
if missing:
print("FAIL: Pflicht-Services fehlen:", ", ".join(missing))
sys.exit(2)
print("OK: Pflicht-Services vorhanden.")
PY
# 5) hxki-internal als external:true MUSS im Compose vorhanden sein (sonst knallt network-ref)
echo "[5] Prüfe: networks/hxki-internal external:true"
python3 - <<'PY'
from pathlib import Path
import re, sys
p = Path("/opt/hx-ki/com2-stack/docker-compose.yml")
s = p.read_text()
# sehr konservativ: nur prüfen, nicht "raten"
if not re.search(r'(?ms)^networks:\s*\n(?:.*\n)*?\s{2}hxki-internal:\s*\n(?:.*\n)*?\s{4}external:\s*true\s*$', s):
print("FAIL: networks: hxki-internal: external:true fehlt oder ist anders.")
print("Zeilen-Hinweis:")
import subprocess, shlex
subprocess.run(["bash","-lc",f"nl -ba {p} | grep -nE '^(\\s*networks:|\\s{{2}}hxki-internal:|\\s{{4}}external:)' -n || true"])
sys.exit(3)
print("OK: networks/hxki-internal external:true gefunden.")
PY
# 6) n8n config MUSS gültiges JSON sein (dein log beweist, dass es kaputt war)
# Wir reparieren NUR das deterministisch: config als JSON resetten, ohne Workflows/DB anzufassen.
echo "[6] Fix: n8n config JSON (nur wenn kaputt)"
MOUNT_SRC="/data/HXKI_WORKSPACE/router"
CFG="$MOUNT_SRC/config"
if [ -d "$MOUNT_SRC" ]; then
docker stop hxki-n8n >/dev/null 2>&1 || true
if [ -f "$CFG" ]; then
if python3 - <<PY >/dev/null 2>&1
import json
json.load(open("$CFG","r"))
PY
then
echo "OK: $CFG ist gültiges JSON (nichts zu tun)"
else
echo "WARN: $CFG ist KEIN gültiges JSON -> schreibe minimal gültig + Backup"
cp -a "$CFG" "$CFG.bak.$TS"
cat > "$CFG" <<JSON
{"encryptionKey":"","instanceId":""}
JSON
chown 1000:1000 "$CFG" || true
chmod 600 "$CFG" || true
fi
else
echo "INFO: $CFG existiert nicht -> wird von n8n beim Start erzeugt"
fi
# Rechte auf Mount (konservativ)
chown -R 1000:1000 "$MOUNT_SRC" || true
chmod -R u+rwX "$MOUNT_SRC" || true
chmod -R g+rwX "$MOUNT_SRC" || true
docker start hxki-n8n >/dev/null 2>&1 || true
else
echo "WARN: $MOUNT_SRC fehlt -> n8n Mount passt nicht (keine Reparatur)"
fi
# 7) Orchester hoch (alles oder nichts)
echo "[7] Orchester UP (alles zusammen)"
cd "$DIR"
docker compose up -d --remove-orphans
echo "[8] Status (Container)"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-' || true
echo "[9] Netzwerk-Mitglieder: $NET"
docker network inspect "$NET" --format '{{range $id,$c := .Containers}}{{println $c.Name}}{{end}}' | sort || true
# 10) Caddy -> n8n intern test (nur intern, ohne "lokal 127.0.0.1")
echo "[10] Caddy->n8n Service-Test (intern)"
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
echo "=== ENDE ==="

59
COM2_DOCTOR_PORTS_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env bash
set -euo pipefail
NET="hxki-internal"
SVC_LIST=("hxki-web:80" "hxki-n8n:5678" "hxki-mautic:80")
echo "=== COM2 DOCTOR · PORTS / LISTEN / LOGS ==="
echo "[0] Netzwerk"
docker network inspect "$NET" >/dev/null 2>&1 && echo "OK: $NET existiert" || { echo "FAIL: $NET fehlt"; exit 1; }
docker network inspect "$NET" --format '{{range $id,$c := .Containers}}{{println $c.Name}}{{end}}' | sort
echo
echo "[1] Container-Status"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-caddy' || true
echo
echo "[2] Listen-Checks IN jedem Service-Container (ss/netstat)"
for entry in "${SVC_LIST[@]}"; do
c="${entry%%:*}"
p="${entry##*:}"
echo
echo "--- $c (soll lauschen auf :$p) ---"
docker exec -it "$c" sh -lc "
echo 'id:'; id || true
echo 'ps:'; ps aux | head -n 20 || true
echo 'ss:'; (ss -lntp 2>/dev/null || netstat -lntp 2>/dev/null || true) | egrep '(:$p\\b|LISTEN)' || true
echo 'localhost test:'; (wget -qO- http://127.0.0.1:$p/ >/dev/null && echo OK_LOCALHOST || echo FAIL_LOCALHOST) || true
" || echo "WARN: docker exec in $c fehlgeschlagen"
done
echo
echo "[3] Caddy -> Upstreams (mit Retry 30s)"
for entry in "${SVC_LIST[@]}"; do
host="${entry%%:*}"
port="${entry##*:}"
echo
echo "--- hx-caddy -> $host:$port ---"
docker exec -it hx-caddy sh -lc "
i=0
while [ \$i -lt 30 ]; do
wget -qO- http://$host:$port/ >/dev/null 2>&1 && { echo OK; exit 0; }
i=\$((i+1)); sleep 1
done
echo FAIL
exit 0
" || true
done
echo
echo "[4] Logs (letzte 120 Zeilen)"
for c in hxki-web hxki-n8n hxki-mautic hx-caddy; do
echo
echo "--- logs: $c ---"
docker logs --tail=120 "$c" 2>&1 || true
done
echo
echo "=== ENDE ==="

258
COM2_FERRARI_REPAIR_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,258 @@
#!/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" <<ENV
# === CANONICAL (recovered + validated) ===
# Postgres (n8n)
PG_USER=${PG_USER}
PG_PASSWORD=${PG_ADMIN_PW}
PG_DB=${PG_DB}
# Mautic (MariaDB)
MAUTIC_DB_NAME=${MAUTIC_DB_NAME}
MAUTIC_DB_USER=${MAUTIC_DB_USER}
MAUTIC_DB_PASSWORD=${MAUTIC_DB_PASSWORD}
MAUTIC_DB_ROOT_PASSWORD=${MY_ROOT_PW}
# n8n public vars (leave if you already had them in compose via defaults)
N8N_HOST=${N8N_HOST:-localhost}
N8N_PROTOCOL=${N8N_PROTOCOL:-http}
ENV
echo "[6] Postgres: stelle sicher, dass DB für n8n existiert (create only if missing)"
pg_ensure_db "$PG_ADMIN_USER" "$PG_ADMIN_PW" "$PG_DB"
pg_ensure_role "$PG_ADMIN_USER" "$PG_ADMIN_PW" "$PG_USER" "$PG_ADMIN_PW" || true
pg_set_owner "$PG_ADMIN_USER" "$PG_ADMIN_PW" "$PG_DB" "$PG_USER" || true
echo " OK: Postgres DB/Role geprüft"
echo "[7] MariaDB: stelle sicher, dass mautic DB/User passt (create/grant only if needed)"
# if mautic user auth already works, we don't touch grants
if my_user_try_pw "$MAUTIC_DB_USER" "$MAUTIC_DB_PASSWORD" "$MAUTIC_DB_NAME"; then
echo " OK: mautic user auth OK keine Änderungen an MariaDB nötig"
else
echo " INFO: mautic user auth FAIL setze DB/User/Grants deterministisch via root"
my_ensure_mautic "$MY_ROOT_PW" "$MAUTIC_DB_NAME" "$MAUTIC_DB_USER" "$MAUTIC_DB_PASSWORD" || die "MariaDB repair failed"
echo " OK: MariaDB DB/User/Grants gesetzt"
fi
echo "[8] Validate Compose"
cd "$DIR"
docker compose config >/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"

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
MA="hxki-mautic"
CA="hx-caddy"
echo "=== COM2 · FIX MAUTIC ONLY (no rebuild, no guessing) ==="
echo "[1] Stoppe NUR Mautic"
docker stop "$MA" >/dev/null || true
echo "[2] Starte NUR Mautic neu"
docker start "$MA" >/dev/null
echo "[3] Warte bis Mautic auf :80 lauscht (max 60s)"
for i in {1..60}; do
if docker exec "$MA" sh -lc "ss -lnt | grep -q ':80 '"; then
echo "OK: Mautic lauscht auf Port 80"
break
fi
sleep 1
if [ "$i" -eq 60 ]; then
echo "FAIL: Mautic lauscht NICHT auf Port 80"
docker logs --tail=120 "$MA"
exit 1
fi
done
echo "[4] Test: Caddy -> Mautic"
if docker exec "$CA" sh -lc "wget -qO- http://hxki-mautic/ >/dev/null"; then
echo "OK_CADDY_TO_MAUTIC"
else
echo "FAIL_CADDY_TO_MAUTIC"
docker logs --tail=120 "$MA"
exit 1
fi
echo "=== DONE: COM2 IST SAUBER ==="

199
COM2_FIX_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,199 @@
#!/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="$DIR/backup-fix-$TS"
mkdir -p "$BK"
echo "=== COM2 FIX ONE-SHOT ==="
echo "Autorität: $F"
[ -f "$F" ] || { echo "FEHLT: $F"; exit 1; }
cp -a "$F" "$BK/docker-compose.yml.pre"
# Netzwerk muss als external existieren (Host)
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null
# .env laden (falls vorhanden)
if [ -f "$ENVF" ]; then
set -a
# shellcheck disable=SC1090
. "$ENVF"
set +a
fi
# Helper: ENV aus Container ziehen
get_env_from_container() {
local c="$1" key="$2"
docker inspect "$c" --format '{{range .Config.Env}}{{println .}}{{end}}' \
| awk -F= -v k="$key" '$1==k{print substr($0, index($0,"=")+1)}' \
| head -n1
}
echo
echo "[1] Container vorhanden?"
for c in hxki-postgres hxki-mariadb hxki-n8n hxki-mautic; do
docker inspect "$c" >/dev/null 2>&1 || { echo "FEHLT Container: $c"; exit 1; }
done
echo
echo "[2] Postgres: setze/angleiche User/DB/PW (damit n8n startet)"
# Werte entweder aus .env oder aus n8n-Container ENV
PG_USER="${PG_USER:-$(get_env_from_container hxki-n8n DB_POSTGRESDB_USER)}"
PG_PASSWORD="${PG_PASSWORD:-$(get_env_from_container hxki-n8n DB_POSTGRESDB_PASSWORD)}"
PG_DB="${PG_DB:-$(get_env_from_container hxki-n8n DB_POSTGRESDB_DATABASE)}"
[ -n "${PG_USER:-}" ] || { echo "FEHLT: PG_USER (in $ENVF oder n8n ENV)"; exit 1; }
[ -n "${PG_PASSWORD:-}" ] || { echo "FEHLT: PG_PASSWORD (in $ENVF oder n8n ENV)"; exit 1; }
[ -n "${PG_DB:-}" ] || { echo "FEHLT: PG_DB (in $ENVF oder n8n ENV)"; exit 1; }
docker exec -u postgres -i hxki-postgres psql -v ON_ERROR_STOP=1 -d postgres <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='${PG_USER}') THEN
CREATE ROLE ${PG_USER} LOGIN PASSWORD '${PG_PASSWORD}';
ELSE
ALTER ROLE ${PG_USER} WITH LOGIN PASSWORD '${PG_PASSWORD}';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname='${PG_DB}') THEN
CREATE DATABASE ${PG_DB} OWNER ${PG_USER};
END IF;
END
\$\$;
GRANT ALL PRIVILEGES ON DATABASE ${PG_DB} TO ${PG_USER};
SQL
echo "OK: Postgres User/DB/PW angeglichen."
echo
echo "[3] MariaDB: setze/angleiche Mautic DB/User/PW (damit mautic startet)"
# Root-PW aus MariaDB Container ENV
MDB_ROOT_PW="$(get_env_from_container hxki-mariadb MYSQL_ROOT_PASSWORD || true)"
[ -n "${MDB_ROOT_PW:-}" ] || MDB_ROOT_PW="$(get_env_from_container hxki-mariadb MARIADB_ROOT_PASSWORD || true)"
[ -n "${MDB_ROOT_PW:-}" ] || { echo "FEHLT: MariaDB Root-Passwort (MYSQL_ROOT_PASSWORD/MARIADB_ROOT_PASSWORD im Container)"; exit 1; }
# Mautic Zielwerte: aus Mautic Container ENV (Quelle der Wahrheit)
MAUTIC_DB_HOST="$(get_env_from_container hxki-mautic MAUTIC_DB_HOST || true)"
MAUTIC_DB_USER="$(get_env_from_container hxki-mautic MAUTIC_DB_USER || true)"
MAUTIC_DB_PASSWORD="$(get_env_from_container hxki-mautic MAUTIC_DB_PASSWORD || true)"
MAUTIC_DB_NAME="$(get_env_from_container hxki-mautic MAUTIC_DB_NAME || true)"
# Fallbacks (falls Image andere Keys nutzt)
[ -n "${MAUTIC_DB_USER:-}" ] || MAUTIC_DB_USER="$(get_env_from_container hxki-mautic MYSQL_USER || true)"
[ -n "${MAUTIC_DB_PASSWORD:-}" ] || MAUTIC_DB_PASSWORD="$(get_env_from_container hxki-mautic MYSQL_PASSWORD || true)"
[ -n "${MAUTIC_DB_NAME:-}" ] || MAUTIC_DB_NAME="$(get_env_from_container hxki-mautic MYSQL_DATABASE || true)"
[ -n "${MAUTIC_DB_USER:-}" ] || { echo "FEHLT: MAUTIC_DB_USER (im hxki-mautic ENV)"; exit 1; }
[ -n "${MAUTIC_DB_PASSWORD:-}" ] || { echo "FEHLT: MAUTIC_DB_PASSWORD (im hxki-mautic ENV)"; exit 1; }
[ -n "${MAUTIC_DB_NAME:-}" ] || { echo "FEHLT: MAUTIC_DB_NAME (im hxki-mautic ENV)"; exit 1; }
docker exec -i hxki-mariadb mysql -uroot -p"${MDB_ROOT_PW}" <<SQL
CREATE DATABASE IF NOT EXISTS \`${MAUTIC_DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '${MAUTIC_DB_USER}'@'%' IDENTIFIED BY '${MAUTIC_DB_PASSWORD}';
ALTER USER '${MAUTIC_DB_USER}'@'%' IDENTIFIED BY '${MAUTIC_DB_PASSWORD}';
GRANT ALL PRIVILEGES ON \`${MAUTIC_DB_NAME}\`.* TO '${MAUTIC_DB_USER}'@'%';
FLUSH PRIVILEGES;
SQL
echo "OK: MariaDB User/DB/PW angeglichen."
echo
echo "[4] hxki-web: repariere Service-Definition aus vorhandenem Backup (damit /app/package.json wieder stimmt)"
# Quelle suchen (du hast welche gefunden)
SRC=""
for cand in \
/opt/hx-ki/com-stack/docker-compose.yml \
/opt/hx-ki/com-stack/docker-compose.yml.bak.* \
/opt/hx-ki/docker/docker-compose.yml.bak_* \
/opt/hx-ki/com2-stack/docker-compose.yml.bak.* ; do
for f in $cand; do
[ -f "$f" ] || continue
if grep -qE '^[[:space:]]{2}hxki-web:[[:space:]]*$' "$f"; then
SRC="$f"
break 2
fi
done
done
if [ -z "$SRC" ]; then
echo "WARN: Keine Quelle mit hxki-web gefunden -> überspringe web-restore."
else
echo "SRC(web)=$SRC"
cp -a "$SRC" "$BK/docker-compose.yml.web-src"
python3 - <<PY
import re
from pathlib import Path
dst = Path("$F")
src = Path("$SRC")
d = dst.read_text()
s = src.read_text()
def extract_block(text, name):
# block: " name:" bis zum nächsten " <service>:"
m = re.search(rf'(?ms)^\\s{{2}}{re.escape(name)}:\\s*\\n.*?(?=^\\s{{2}}[A-Za-z0-9_.-]+:\\s*$|\\Z)', text)
return m.group(0) if m else None
web = extract_block(s, "hxki-web")
if not web:
raise SystemExit("SRC hat keinen hxki-web Block obwohl grep ihn fand (unerwartet).")
# dst services-block finden
ms = re.search(r'(?ms)^services:\\s*\\n', d)
if not ms:
raise SystemExit("DST hat keinen top-level services:-Block.")
# wenn dst schon hxki-web hat -> ersetzen, sonst einfügen
if re.search(r'(?m)^\\s{2}hxki-web:\\s*$', d):
d2 = re.sub(r'(?ms)^\\s{2}hxki-web:\\s*\\n.*?(?=^\\s{2}[A-Za-z0-9_.-]+:\\s*$|\\Z)', web, d)
else:
# nach services: direkt einfügen (oben)
d2 = re.sub(r'(?ms)^(services:\\s*\\n)', r'\\1' + web + "\\n", d, count=1)
# sicherstellen, dass hxki-web im hxki-internal hängt (wenn nicht, adden)
if "hxki-web:" in d2 and "networks: [hxki-internal]" not in web and "networks:" not in web:
# falls web-block gar keine networks hat: am Ende des blocks ergänzen
d2 = re.sub(r'(?ms)(^\\s{2}hxki-web:\\s*\\n.*?)(?=^\\s{2}[A-Za-z0-9_.-]+:\\s*$|\\Z)',
lambda m: m.group(1).rstrip()+"\\n networks: [hxki-internal]\\n",
d2, count=1)
dst.write_text(d2)
PY
echo "OK: hxki-web Block restored/patched."
fi
echo
echo "[5] Validate + Restart Orchester"
cd "$DIR"
docker compose -f "$F" config >/dev/null
docker compose down --remove-orphans
docker compose up -d --remove-orphans
echo
echo "[6] Hard Checks"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-caddy' || true
docker network inspect "$NET" --format '{{range $id,$c := .Containers}}{{println $c.Name}}{{end}}' | sort
echo
echo "[7] Caddy -> Upstreams"
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
# web: probiere 80 und 3000
docker exec -it hx-caddy sh -lc '(wget -qO- http://hxki-web/ >/dev/null && echo OK_CADDY_TO_WEB_80) || (wget -qO- http://hxki-web:3000/ >/dev/null && echo OK_CADDY_TO_WEB_3000) || echo FAIL_CADDY_TO_WEB' || true
echo
echo "[8] Logs kurz"
docker logs --tail=30 hxki-n8n 2>&1 || true
docker logs --tail=30 hxki-mautic 2>&1 || true
docker logs --tail=30 hxki-web 2>&1 || true
echo "=== ENDE ==="

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env bash
set -euo pipefail
C="hxki-postgres"
echo "=== FIX POSTGRES ADMIN (one-shot) ==="
docker inspect "$C" >/dev/null 2>&1 || { echo "FEHLT: $C"; exit 1; }
IMG="$(docker inspect "$C" --format '{{.Config.Image}}')"
echo "[0] Image: $IMG"
# Admin-User/PW/DB aus ENV lesen (Autorität: Container selbst)
get_env() {
local key="$1"
docker inspect "$C" --format '{{range .Config.Env}}{{println .}}{{end}}' \
| awk -F= -v k="$key" '$1==k{print substr($0, index($0,"=")+1)}' | head -n1
}
ADMIN_USER="$(get_env POSTGRES_USER || true)"
ADMIN_PW="$(get_env POSTGRES_PASSWORD || true)"
ADMIN_DB="$(get_env POSTGRES_DB || true)"
# Bitnami-Fallbacks
[ -n "${ADMIN_USER:-}" ] || ADMIN_USER="$(get_env POSTGRESQL_USERNAME || true)"
[ -n "${ADMIN_PW:-}" ] || ADMIN_PW="$(get_env POSTGRESQL_PASSWORD || true)"
[ -n "${ADMIN_DB:-}" ] || ADMIN_DB="$(get_env POSTGRESQL_DATABASE || true)"
# Default-DB-Fallback
[ -n "${ADMIN_DB:-}" ] || ADMIN_DB="postgres"
echo "[1] Detected ADMIN_USER=${ADMIN_USER:-<leer>} ADMIN_DB=${ADMIN_DB:-<leer>}"
[ -n "${ADMIN_USER:-}" ] || { echo "FAIL: Konnte ADMIN_USER nicht aus Container ENV lesen."; exit 1; }
[ -n "${ADMIN_PW:-}" ] || { echo "FAIL: Konnte ADMIN_PW nicht aus Container ENV lesen."; exit 1; }
# Zielwerte für n8n (aus n8n-Container-ENV Autorität!)
N8N="hxki-n8n"
docker inspect "$N8N" >/dev/null 2>&1 || { echo "FEHLT: $N8N"; exit 1; }
get_env_n8n() {
local key="$1"
docker inspect "$N8N" --format '{{range .Config.Env}}{{println .}}{{end}}' \
| awk -F= -v k="$key" '$1==k{print substr($0, index($0,"=")+1)}' | head -n1
}
PG_USER="$(get_env_n8n DB_POSTGRESDB_USER || true)"
PG_PW="$(get_env_n8n DB_POSTGRESDB_PASSWORD || true)"
PG_DB="$(get_env_n8n DB_POSTGRESDB_DATABASE || true)"
[ -n "${PG_USER:-}" ] || { echo "FAIL: n8n ENV fehlt DB_POSTGRESDB_USER"; exit 1; }
[ -n "${PG_PW:-}" ] || { echo "FAIL: n8n ENV fehlt DB_POSTGRESDB_PASSWORD"; exit 1; }
[ -n "${PG_DB:-}" ] || { echo "FAIL: n8n ENV fehlt DB_POSTGRESDB_DATABASE"; exit 1; }
echo "[2] Target (für n8n): user=$PG_USER db=$PG_DB"
echo "[3] Apply in Postgres (create/alter role + create db)"
docker exec -i "$C" sh -lc "export PGPASSWORD='${ADMIN_PW}'; psql -v ON_ERROR_STOP=1 -U '${ADMIN_USER}' -d '${ADMIN_DB}'" <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='${PG_USER}') THEN
CREATE ROLE ${PG_USER} LOGIN PASSWORD '${PG_PW}';
ELSE
ALTER ROLE ${PG_USER} WITH LOGIN PASSWORD '${PG_PW}';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_database WHERE datname='${PG_DB}') THEN
CREATE DATABASE ${PG_DB} OWNER ${PG_USER};
END IF;
END
\$\$;
GRANT ALL PRIVILEGES ON DATABASE ${PG_DB} TO ${PG_USER};
SQL
echo "[4] Quick check: kann sich n8n-user anmelden?"
docker exec -i "$C" sh -lc "export PGPASSWORD='${PG_PW}'; psql -v ON_ERROR_STOP=1 -U '${PG_USER}' -d '${PG_DB}' -c 'select 1;' >/dev/null"
echo "OK: Postgres Credentials passen jetzt für n8n."
echo "=== ENDE ==="

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
F="$DIR/docker-compose.yml"
C="hxki-postgres"
TS="$(date +%Y%m%d-%H%M%S)"
BK="/opt/hx-ki/backups/com2-pg-semicolon-$TS"
mkdir -p "$BK"
echo "=== COM2 · FIX POSTGRES ; + VERIFY (one-shot) ==="
echo "Compose: $F"
echo "Backup: $BK"
echo
[ -f "$F" ] || { echo "FAIL: missing $F"; exit 1; }
cp -a "$F" "$BK/docker-compose.yml.pre"
echo "[1] Show offending lines (with trailing ';')"
grep -nE 'POSTGRES_(USER|PASSWORD|DB)=.*;$' "$F" || echo "OK: no trailing ';' in compose"
echo
echo "[2] Patch ONLY trailing ';' for POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB (compose is authority)"
# supports both list-form " - POSTGRES_USER=..." and mapping-form "POSTGRES_USER: ..."
sed -i -E \
-e 's/^(\s*-\s*POSTGRES_USER=[^;]*);$/\1/' \
-e 's/^(\s*-\s*POSTGRES_PASSWORD=[^;]*);$/\1/' \
-e 's/^(\s*-\s*POSTGRES_DB=[^;]*);$/\1/' \
-e 's/^(\s*POSTGRES_USER:\s*[^;]*);$/\1/' \
-e 's/^(\s*POSTGRES_PASSWORD:\s*[^;]*);$/\1/' \
-e 's/^(\s*POSTGRES_DB:\s*[^;]*);$/\1/' \
"$F"
echo
echo "[3] Validate compose"
docker compose -f "$F" config >/dev/null
echo "OK: compose valid"
echo
echo "[4] Recreate postgres container to re-inject ENV"
cd "$DIR"
docker compose up -d --force-recreate --no-deps "$C" >/dev/null
echo
echo "[5] Read ENV actually inside container (ground truth)"
docker inspect "$C" --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^POSTGRES_(USER|PASSWORD|DB)=' || true
PG_USER="$(docker inspect "$C" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^POSTGRES_USER=/{print $2}' | tail -n1)"
PG_DB="$(docker inspect "$C" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^POSTGRES_DB=/{print $2}' | tail -n1)"
PG_PW="$(docker inspect "$C" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^POSTGRES_PASSWORD=/{print $2}' | tail -n1)"
echo
echo "[6] Wait until Postgres accepts connections (max 30s)"
for i in $(seq 1 30); do
if docker exec -e PGPASSWORD="$PG_PW" "$C" psql -U "$PG_USER" -d "$PG_DB" -c "SELECT 1;" >/dev/null 2>&1; then
echo "OK: login works with CURRENT ENV"
OK_MODE="CURRENT_ENV"
break
fi
sleep 1
done
if [ "${OK_MODE:-}" != "CURRENT_ENV" ]; then
echo "WARN: login failed with CURRENT ENV. Testing legacy ';' variants deterministically."
# Variant B: user+db with ';' and password with ';'
if docker exec -e PGPASSWORD="${PG_PW};" "$C" psql -U "${PG_USER};" -d "${PG_DB};" -c "SELECT 1;" >/dev/null 2>&1; then
echo "OK: legacy semicolon credentials work (user/db/pw had ';' at init time)"
OK_MODE="LEGACY_SEMICOLON"
else
echo "FAIL: Neither CURRENT_ENV nor LEGACY_SEMICOLON credentials can authenticate."
echo "=> Then the only deterministic path is an auth-reset inside the data dir (no guessing), because plaintext can't be recovered."
exit 2
fi
fi
echo
echo "=== RESULT ==="
echo "MODE=$OK_MODE"
echo "POSTGRES_USER=$PG_USER"
echo "POSTGRES_DB=$PG_DB"
echo "POSTGRES_PASSWORD=$PG_PW"
echo "Backup: $BK"
echo "=== DONE ==="

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
COMPOSE="/opt/hx-ki/com2-stack/docker-compose.yml"
ENVF="/opt/hx-ki/com2-stack/.env"
MY="hxki-mariadb"
MA="hxki-mautic"
CA="hx-caddy"
echo "=== COM2 · MAUTIC FIX (run on server only) ==="
[ -f "$COMPOSE" ] || { echo "FAIL: missing $COMPOSE"; exit 1; }
[ -f "$ENVF" ] || { echo "FAIL: missing $ENVF"; exit 1; }
# load .env (simple KEY=VALUE)
set -a
. "$ENVF"
set +a
# bring up mariadb first
docker compose -f "$COMPOSE" up -d "$MY"
# root pw from .env or container env
ROOTPW="${MARIADB_ROOT_PASSWORD-${MYSQL_ROOT_PASSWORD-}}"
if [ -z "${ROOTPW:-}" ]; then
ROOTPW="$(docker inspect "$MY" --format '{{range .Config.Env}}{{println .}}{{end}}' \
| awk -F= '/^(MARIADB_ROOT_PASSWORD|MYSQL_ROOT_PASSWORD)=/{print $2}' | tail -n1 || true)"
fi
[ -n "${ROOTPW:-}" ] || { echo "FAIL: cannot determine MariaDB root password"; exit 1; }
# mautic creds STRICTLY from .env (must exist)
DB_NAME="${MAUTIC_DB_NAME-${MYSQL_DATABASE-mautic}}"
DB_USER="${MAUTIC_DB_USER-${MYSQL_USER-mautic}}"
DB_PASS="${MAUTIC_DB_PASSWORD-${MYSQL_PASSWORD-}}"
[ -n "${DB_USER:-}" ] || { echo "FAIL: MAUTIC_DB_USER or MYSQL_USER missing in .env"; exit 1; }
[ -n "${DB_PASS:-}" ] || { echo "FAIL: MAUTIC_DB_PASSWORD or MYSQL_PASSWORD missing in .env"; exit 1; }
echo "DB_NAME=$DB_NAME"
echo "DB_USER=$DB_USER"
echo "DB_PASS=***"
# wait mariadb ready
for i in {1..40}; do
if docker exec -i "$MY" sh -lc "mariadb -uroot -p\"$ROOTPW\" -e 'SELECT 1' >/dev/null 2>&1"; then
echo "OK: MariaDB ready"
break
fi
sleep 1
[ "$i" -eq 40 ] && { echo "FAIL: MariaDB not ready"; exit 1; }
done
# align user/db/grants (no data loss)
docker exec -i "$MY" sh -lc "mariadb -uroot -p\"$ROOTPW\" <<SQL
CREATE DATABASE IF NOT EXISTS \\\`$DB_NAME\\\`;
CREATE USER IF NOT EXISTS '$DB_USER'@'%' IDENTIFIED BY '$DB_PASS';
ALTER USER '$DB_USER'@'%' IDENTIFIED BY '$DB_PASS';
GRANT ALL PRIVILEGES ON \\\`$DB_NAME\\\`.* TO '$DB_USER'@'%';
FLUSH PRIVILEGES;
SQL"
echo "OK: grants aligned"
# restart mautic and caddy (if present in compose)
docker compose -f "$COMPOSE" up -d --force-recreate "$MA" "$CA" || true
echo "=== STATUS ==="
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-caddy' || true
echo "=== CADDY -> MAUTIC CHECK ==="
if docker exec -i "$CA" sh -lc "wget -qO- http://$MA/ >/dev/null"; then
echo "OK_CADDY_TO_MAUTIC"
else
echo "FAIL_CADDY_TO_MAUTIC"
echo "--- mautic logs (tail 120) ---"
docker logs --tail=120 "$MA" || true
exit 1
fi

View File

@@ -0,0 +1,159 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
COMPOSE="$DIR/docker-compose.yml"
ENVF="$DIR/.env"
[ -f "$COMPOSE" ] || { echo "FAIL: missing $COMPOSE"; exit 1; }
[ -f "$ENVF" ] || { echo "FAIL: missing $ENVF"; exit 1; }
cd "$DIR"
echo "=== COM2 · MAUTIC INSTALL (ONE-SHOT, NO REBUILD, NO GUESSING) ==="
# Load .env as authority (only simple KEY=VALUE lines)
set -a
. "$ENVF"
set +a
# Resolve service names from compose (authority: compose)
MAUTIC_SVC="$(python3 - <<'PY'
import re, pathlib
p=pathlib.Path("/opt/hx-ki/com2-stack/docker-compose.yml")
s=p.read_text()
m=re.search(r'(?ms)^services:\s*\n(.*?)(?=^\S|\Z)', s)
if not m: raise SystemExit("FAIL: no services: block")
blk=m.group(1)
# find service with container_name: hxki-mautic
cur=None
for line in blk.splitlines():
mm=re.match(r'^\s{2}([A-Za-z0-9_.-]+):\s*$', line)
if mm: cur=mm.group(1); continue
if cur and re.match(r'^\s{4}container_name:\s*hxki-mautic\s*$', line):
print(cur); raise SystemExit(0)
raise SystemExit("FAIL: could not find service for container_name: hxki-mautic")
PY
)"
DB_SVC="$(python3 - <<'PY'
import re, pathlib
p=pathlib.Path("/opt/hx-ki/com2-stack/docker-compose.yml")
s=p.read_text()
m=re.search(r'(?ms)^services:\s*\n(.*?)(?=^\S|\Z)', s)
if not m: raise SystemExit("FAIL: no services: block")
blk=m.group(1)
# find service with container_name: hxki-mariadb
cur=None
for line in blk.splitlines():
mm=re.match(r'^\s{2}([A-Za-z0-9_.-]+):\s*$', line)
if mm: cur=mm.group(1); continue
if cur and re.match(r'^\s{4}container_name:\s*hxki-mariadb\s*$', line):
print(cur); raise SystemExit(0)
raise SystemExit("FAIL: could not find service for container_name: hxki-mariadb")
PY
)"
MA="hxki-mautic"
MY="hxki-mariadb"
CA="hx-caddy"
echo "[1] Bring up DB + Mautic (no orphans change)"
docker compose up -d "$DB_SVC" "$MAUTIC_SVC" >/dev/null
echo "[2] Hard requirements from .env (NO guessing)"
req() { local k="$1"; [ -n "${!k:-}" ] || { echo "FAIL: missing $k in $ENVF"; exit 1; }; }
req MAUTIC_DB_HOST
req MAUTIC_DB_NAME
req MAUTIC_DB_USER
req MAUTIC_DB_PASSWORD
req MAUTIC_ADMIN_USERNAME
req MAUTIC_ADMIN_PASSWORD
req MAUTIC_ADMIN_EMAIL
echo " DB_HOST=$MAUTIC_DB_HOST DB_NAME=$MAUTIC_DB_NAME DB_USER=$MAUTIC_DB_USER DB_PASS=***"
echo " ADMIN_USER=$MAUTIC_ADMIN_USERNAME ADMIN_EMAIL=$MAUTIC_ADMIN_EMAIL ADMIN_PASS=***"
echo "[3] Read MariaDB root password (NO guessing: container env)"
ROOTPW="$(docker inspect "$MY" --format '{{range .Config.Env}}{{println .}}{{end}}' \
| awk -F= '/^(MARIADB_ROOT_PASSWORD|MYSQL_ROOT_PASSWORD)=/{print $2}' | tail -n1 || true)"
[ -n "${ROOTPW:-}" ] || { echo "FAIL: MariaDB root password not found in container env"; exit 1; }
echo "[4] Wait MariaDB ready (max 60s) + ensure DB exists (no data loss)"
for i in {1..60}; do
if docker exec -i "$MY" sh -lc "mariadb -uroot -p\"$ROOTPW\" -e 'SELECT 1' >/dev/null 2>&1"; then
break
fi
sleep 1
[ "$i" -eq 60 ] && { echo "FAIL: MariaDB not ready"; docker logs --tail=120 "$MY" || true; exit 1; }
done
docker exec -i "$MY" sh -lc "mariadb -uroot -p\"$ROOTPW\" <<SQL
CREATE DATABASE IF NOT EXISTS \\\`$MAUTIC_DB_NAME\\\`;
SQL" >/dev/null
# If Mautic uses non-root user, ensure it can log in + has grants (still no data loss)
if [ "$MAUTIC_DB_USER" != "root" ]; then
docker exec -i "$MY" sh -lc "mariadb -uroot -p\"$ROOTPW\" <<SQL
CREATE USER IF NOT EXISTS '$MAUTIC_DB_USER'@'%' IDENTIFIED BY '$MAUTIC_DB_PASSWORD';
ALTER USER '$MAUTIC_DB_USER'@'%' IDENTIFIED BY '$MAUTIC_DB_PASSWORD';
GRANT ALL PRIVILEGES ON \\\`$MAUTIC_DB_NAME\\\`.* TO '$MAUTIC_DB_USER'@'%';
FLUSH PRIVILEGES;
SQL" >/dev/null
fi
echo "[5] If already installed -> stop here (local.php present)"
if docker exec -i "$MA" sh -lc "test -f app/config/local.php"; then
echo "OK: already installed (app/config/local.php exists)"
else
echo "[6] Run Mautic installer inside container (deterministic CLI)"
# detect console path
CONSOLE=""
if docker exec -i "$MA" sh -lc "test -x bin/console"; then
CONSOLE="bin/console"
elif docker exec -i "$MA" sh -lc "test -x app/console"; then
CONSOLE="app/console"
else
echo "FAIL: cannot find bin/console or app/console inside $MA"
docker exec -i "$MA" sh -lc "ls -la" || true
exit 1
fi
# Run installer (no interaction)
docker exec -i "$MA" sh -lc "
php $CONSOLE mautic:install \
--db_driver=pdo_mysql \
--db_host=\"$MAUTIC_DB_HOST\" \
--db_port=3306 \
--db_name=\"$MAUTIC_DB_NAME\" \
--db_user=\"$MAUTIC_DB_USER\" \
--db_password=\"$MAUTIC_DB_PASSWORD\" \
--admin_firstname=HXKI \
--admin_lastname=Admin \
--admin_username=\"$MAUTIC_ADMIN_USERNAME\" \
--admin_password=\"$MAUTIC_ADMIN_PASSWORD\" \
--admin_email=\"$MAUTIC_ADMIN_EMAIL\" \
--no-interaction
" || { echo "FAIL: mautic:install failed"; docker logs --tail=200 "$MA" || true; exit 1; }
echo "[7] Verify local.php created"
docker exec -i "$MA" sh -lc "test -f app/config/local.php" || { echo "FAIL: install did not create app/config/local.php"; exit 1; }
echo "OK: installed (local.php present)"
fi
echo "[8] Restart Mautic (clean) and verify Caddy can reach it"
docker compose up -d --force-recreate "$MAUTIC_SVC" >/dev/null
# wait for http from caddy
for i in {1..60}; do
if docker exec -i "$CA" sh -lc "wget -qO- http://$MA/ >/dev/null"; then
echo "OK_CADDY_TO_MAUTIC"
echo "=== DONE ==="
exit 0
fi
sleep 1
done
echo "FAIL: Caddy still cannot reach Mautic after install"
docker logs --tail=200 "$MA" || true
exit 1

131
COM2_MAUTIC_INSTALL_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,131 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
ENVF="$DIR/.env"
COMPOSE="$DIR/docker-compose.yml"
MA="hxki-mautic"
MY="hxki-mariadb"
echo "=== COM2 · MAUTIC INSTALL ONE-SHOT (no guessing) ==="
echo "Compose: $COMPOSE"
echo "Env: $ENVF"
echo
[ -f "$COMPOSE" ] || { echo "FAIL: missing $COMPOSE"; exit 1; }
[ -f "$ENVF" ] || { echo "FAIL: missing $ENVF"; exit 1; }
docker inspect "$MA" >/dev/null 2>&1 || { echo "FAIL: missing container $MA"; exit 1; }
docker inspect "$MY" >/dev/null 2>&1 || { echo "FAIL: missing container $MY"; exit 1; }
# --- [1] Ground truth DB params from mautic container env -------------------
DB_HOST="$(docker inspect "$MA" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^MAUTIC_DB_HOST=/{print $2}' | tail -n1)"
DB_USER="$(docker inspect "$MA" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^MAUTIC_DB_USER=/{print $2}' | tail -n1)"
DB_PASS="$(docker inspect "$MA" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^MAUTIC_DB_PASSWORD=/{print $2}' | tail -n1)"
DB_NAME="$(docker inspect "$MA" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^MAUTIC_DB_NAME=/{print $2}' | tail -n1)"
[ -n "${DB_HOST:-}" ] || { echo "FAIL: MAUTIC_DB_HOST missing in container env"; exit 1; }
[ -n "${DB_USER:-}" ] || { echo "FAIL: MAUTIC_DB_USER missing in container env"; exit 1; }
[ -n "${DB_PASS:-}" ] || { echo "FAIL: MAUTIC_DB_PASSWORD missing in container env"; exit 1; }
[ -n "${DB_NAME:-}" ] || { echo "FAIL: MAUTIC_DB_NAME missing in container env"; exit 1; }
echo "[1] DB (ground truth): host=$DB_HOST db=$DB_NAME user=$DB_USER pass=***"
# --- [2] MariaDB root password from MariaDB container env (no guessing) -----
ROOTPW="$(docker inspect "$MY" --format '{{range .Config.Env}}{{println .}}{{end}}' \
| awk -F= '/^(MARIADB_ROOT_PASSWORD|MYSQL_ROOT_PASSWORD)=/{print $2}' | tail -n1 || true)"
[ -n "${ROOTPW:-}" ] || { echo "FAIL: MariaDB root PW not found in container env"; exit 1; }
echo "[2] MariaDB root PW present (not shown)"
# Wait until MariaDB accepts root connections
echo "[2a] Wait MariaDB ready (max 60s)"
for i in {1..60}; do
if docker exec -i "$MY" sh -lc "mariadb -uroot -p\"$ROOTPW\" -e 'SELECT 1' >/dev/null 2>&1"; then
echo " OK: MariaDB ready"
break
fi
sleep 1
[ "$i" -eq 60 ] && { echo "FAIL: MariaDB not ready"; docker logs --tail=120 "$MY" || true; exit 1; }
done
# Ensure DB + user + grants match the *existing* state (no data loss)
echo "[2b] Align DB user/grants to existing DB (no data loss)"
docker exec -i "$MY" sh -lc "mariadb -uroot -p\"$ROOTPW\" <<SQL
CREATE DATABASE IF NOT EXISTS \\\`$DB_NAME\\\`;
CREATE USER IF NOT EXISTS '$DB_USER'@'%' IDENTIFIED BY '$DB_PASS';
ALTER USER '$DB_USER'@'%' IDENTIFIED BY '$DB_PASS';
GRANT ALL PRIVILEGES ON \\\`$DB_NAME\\\`.* TO '$DB_USER'@'%';
FLUSH PRIVILEGES;
SQL"
echo " OK: grants aligned"
# --- [3] Ensure MAUTIC_ADMIN_* exists in .env (generate once, then persist) ---
echo "[3] Ensure MAUTIC_ADMIN_* in $ENVF (no guessing: generate+persist if missing)"
# Load env
set -a
# shellcheck disable=SC1090
. "$ENVF" || true
set +a
gen_pw() { tr -dc 'A-Za-z0-9!@#%^_-.' </dev/urandom | head -c 24; echo; }
if [ -z "${MAUTIC_ADMIN_USERNAME:-}" ]; then
MAUTIC_ADMIN_USERNAME="admin"
fi
if [ -z "${MAUTIC_ADMIN_PASSWORD:-}" ]; then
MAUTIC_ADMIN_PASSWORD="$(gen_pw)"
fi
if [ -z "${MAUTIC_ADMIN_EMAIL:-}" ]; then
MAUTIC_ADMIN_EMAIL="admin@hxki.local"
fi
# Persist into .env (idempotent)
grep -q '^MAUTIC_ADMIN_USERNAME=' "$ENVF" && sed -i "s/^MAUTIC_ADMIN_USERNAME=.*/MAUTIC_ADMIN_USERNAME=$MAUTIC_ADMIN_USERNAME/" "$ENVF" || echo "MAUTIC_ADMIN_USERNAME=$MAUTIC_ADMIN_USERNAME" >> "$ENVF"
grep -q '^MAUTIC_ADMIN_PASSWORD=' "$ENVF" && sed -i "s/^MAUTIC_ADMIN_PASSWORD=.*/MAUTIC_ADMIN_PASSWORD=$MAUTIC_ADMIN_PASSWORD/" "$ENVF" || echo "MAUTIC_ADMIN_PASSWORD=$MAUTIC_ADMIN_PASSWORD" >> "$ENVF"
grep -q '^MAUTIC_ADMIN_EMAIL=' "$ENVF" && sed -i "s/^MAUTIC_ADMIN_EMAIL=.*/MAUTIC_ADMIN_EMAIL=$MAUTIC_ADMIN_EMAIL/" "$ENVF" || echo "MAUTIC_ADMIN_EMAIL=$MAUTIC_ADMIN_EMAIL" >> "$ENVF"
echo " OK: MAUTIC_ADMIN_USERNAME=$MAUTIC_ADMIN_USERNAME"
echo " OK: MAUTIC_ADMIN_EMAIL=$MAUTIC_ADMIN_EMAIL"
echo " OK: MAUTIC_ADMIN_PASSWORD=*** (written to .env)"
# --- [4] Recreate mautic so it receives new env ----------------------------
echo "[4] Recreate $MA to inject admin env"
cd "$DIR"
docker compose up -d --force-recreate "$MA" >/dev/null
# --- [5] Install if not installed -----------------------------------------
echo "[5] Install check"
if docker exec "$MA" test -f app/config/local.php; then
echo "OK: already installed (local.php exists)"
else
echo "Not installed yet -> trying CLI install (only if available)"
# Find console path
CONSOLE=""
if docker exec "$MA" test -x /var/www/html/bin/console; then CONSOLE="/var/www/html/bin/console"; fi
if [ -z "$CONSOLE" ] && docker exec "$MA" test -x /var/www/html/app/console; then CONSOLE="/var/www/html/app/console"; fi
[ -n "$CONSOLE" ] || { echo "FAIL: cannot find Mautic console (bin/console or app/console)"; exit 1; }
# Verify command exists
if ! docker exec "$MA" sh -lc "php $CONSOLE list 2>/dev/null | grep -q 'mautic:install'"; then
echo "FAIL: mautic:install command not available in this image -> cannot do scripted install without guessing image internals."
exit 1
fi
# Run install with explicit params (no guessing)
docker exec "$MA" sh -lc "php $CONSOLE mautic:install --force \
--db_host='$DB_HOST' --db_name='$DB_NAME' --db_user='$DB_USER' --db_password='$DB_PASS' \
--admin_username='$MAUTIC_ADMIN_USERNAME' --admin_password='$MAUTIC_ADMIN_PASSWORD' --admin_email='$MAUTIC_ADMIN_EMAIL'"
echo "OK: install command executed"
fi
echo
echo "=== DONE ==="
echo "Admin user: $MAUTIC_ADMIN_USERNAME"
echo "Admin email: $MAUTIC_ADMIN_EMAIL"
echo "Admin pass: (steht in $ENVF)"

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
COMPOSE="$DIR/docker-compose.yml"
ENVF="$DIR/.env"
MA_CONTAINER="hxki-mautic"
echo "=== COM2 · MAUTIC RECREATE (service-resolve, no guessing) ==="
[ -f "$COMPOSE" ] || { echo "FAIL: missing $COMPOSE"; exit 1; }
[ -f "$ENVF" ] || { echo "FAIL: missing $ENVF"; exit 1; }
# 1) Find service name that owns container_name: hxki-mautic (authority: compose)
SVC="$(
python3 - <<'PY'
import re, pathlib
p = pathlib.Path("/opt/hx-ki/com2-stack/docker-compose.yml")
s = p.read_text()
m = re.search(r'(?ms)^services:\s*\n(.*?)(?=^\S|\Z)', s)
if not m:
print("")
raise SystemExit(0)
blk = m.group(1)
# parse services by 2-space indentation
services = []
cur = None
cur_lines = []
for line in blk.splitlines(True):
m2 = re.match(r'^ ([A-Za-z0-9_.-]+):\s*$', line)
if m2:
if cur:
services.append((cur, "".join(cur_lines)))
cur = m2.group(1)
cur_lines = []
else:
if cur is not None:
cur_lines.append(line)
if cur:
services.append((cur, "".join(cur_lines)))
# priority 1: container_name match
for name, body in services:
if re.search(r'(?m)^\s{4}container_name:\s*hxki-mautic\s*$', body):
print(name); raise SystemExit(0)
# priority 2: image contains mautic
for name, body in services:
if re.search(r'(?m)^\s{4}image:\s*.*mautic.*$', body, re.I):
print(name); raise SystemExit(0)
print("")
PY
)"
if [ -z "$SVC" ]; then
echo "FAIL: Konnte Mautic-Service-Namen in $COMPOSE nicht finden."
echo "Check: grep -n 'container_name: hxki-mautic' $COMPOSE"
exit 1
fi
echo "OK: Mautic service name in compose = $SVC"
# 2) Ensure admin vars exist in .env (no guessing; generate if missing) - safe charset (no tr range bug)
set -a
# shellcheck disable=SC1090
. "$ENVF" || true
set +a
gen_pw() { tr -dc 'A-Za-z0-9@#%+=:,.!' </dev/urandom | head -c 24; echo; }
: "${MAUTIC_ADMIN_USERNAME:=admin}"
: "${MAUTIC_ADMIN_PASSWORD:=$(gen_pw)}"
: "${MAUTIC_ADMIN_EMAIL:=admin@hxki.local}"
grep -q '^MAUTIC_ADMIN_USERNAME=' "$ENVF" && sed -i "s/^MAUTIC_ADMIN_USERNAME=.*/MAUTIC_ADMIN_USERNAME=$MAUTIC_ADMIN_USERNAME/" "$ENVF" || echo "MAUTIC_ADMIN_USERNAME=$MAUTIC_ADMIN_USERNAME" >> "$ENVF"
grep -q '^MAUTIC_ADMIN_PASSWORD=' "$ENVF" && sed -i "s/^MAUTIC_ADMIN_PASSWORD=.*/MAUTIC_ADMIN_PASSWORD=$MAUTIC_ADMIN_PASSWORD/" "$ENVF" || echo "MAUTIC_ADMIN_PASSWORD=$MAUTIC_ADMIN_PASSWORD" >> "$ENVF"
grep -q '^MAUTIC_ADMIN_EMAIL=' "$ENVF" && sed -i "s/^MAUTIC_ADMIN_EMAIL=.*/MAUTIC_ADMIN_EMAIL=$MAUTIC_ADMIN_EMAIL/" "$ENVF" || echo "MAUTIC_ADMIN_EMAIL=$MAUTIC_ADMIN_EMAIL" >> "$ENVF"
echo "OK: Admin ENV ensured in .env (password stored in file)"
# 3) Recreate using SERVICE name (not container name)
cd "$DIR"
docker compose up -d --force-recreate "$SVC"
echo "=== DONE ==="
echo "If Mautic still not ready, run:"
echo " docker logs --tail=200 $MA_CONTAINER"

98
COM2_REBUILD_FROM_EXISTING.sh Executable file
View File

@@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -euo pipefail
BASE="/opt/hx-ki/com-stack/docker-compose.yml"
OUTDIR="/opt/hx-ki/com2-stack"
OUT="$OUTDIR/docker-compose.yml"
TS="$(date +%Y%m%d-%H%M%S)"
echo "=== COM2 REBUILD (Autorität wird: $OUT) ==="
# 0) Basis muss existieren (dein aktueller „Wahrheits-Compose“)
[ -f "$BASE" ] || { echo "FEHLT: $BASE"; exit 1; }
mkdir -p "$OUTDIR"
echo "[1] Backup vom aktuellen com-stack Compose"
cp -a "$BASE" "${BASE}.bak.${TS}"
echo "[2] Erzeuge kanonische YAML aus Basis-Compose"
TMP_CANON="/tmp/com2.base.${TS}.yml"
docker compose -f "$BASE" config > "$TMP_CANON"
echo "[3] Entferne Service: grafana (falls vorhanden)"
TMP_NOGRAF="/tmp/com2.nograf.${TS}.yml"
awk '
BEGIN{skip=0}
/^ grafana:$/ {skip=1; next}
skip==1 && /^ [A-Za-z0-9_.-]+:$/ {skip=0}
skip==0 {print}
' "$TMP_CANON" > "$TMP_NOGRAF"
echo "[4] Suche vorhandene Mautic-Compose-Datei auf dem Server"
MAUTIC_FILE="$(grep -RIl --exclude-dir='.git' --include='*.yml' --include='*.yaml' -E '^[[:space:]]*mautic:' /opt/hx-ki 2>/dev/null | head -n1 || true)"
if [ -z "${MAUTIC_FILE:-}" ]; then
echo "WARN: Keine vorhandene 'mautic:' Definition unter /opt/hx-ki gefunden."
echo " -> COM2-Compose wird OHNE Mautic geschrieben (weil du 0% Vermutung willst)."
cp -a "$TMP_NOGRAF" "$OUT"
else
echo "OK: Mautic-Definition gefunden in: $MAUTIC_FILE"
TMP_MAUTIC_CANON="/tmp/com2.mautic.${TS}.yml"
docker compose -f "$MAUTIC_FILE" config > "$TMP_MAUTIC_CANON"
echo "[5] Extrahiere nur den Mautic-Service-Block (deterministisch)"
TMP_MAUTIC_BLOCK="/tmp/com2.mautic.block.${TS}.yml"
awk '
BEGIN{inservices=0; grab=0}
/^services:$/ {inservices=1; next}
inservices==1 && /^ mautic:$/ {grab=1; print " mautic:"; next}
grab==1 && /^ [A-Za-z0-9_.-]+:$/ {grab=0}
grab==1 {print}
' "$TMP_MAUTIC_CANON" > "$TMP_MAUTIC_BLOCK"
if ! grep -q '^ mautic:$' "$TMP_MAUTIC_BLOCK"; then
echo "WARN: Konnte mautic-Block nicht extrahieren -> schreibe ohne Mautic (0% Vermutung)."
cp -a "$TMP_NOGRAF" "$OUT"
else
echo "[6] Füge Mautic in COM2-Compose ein (unter services:)"
# Insert mautic block right after 'services:' line
awk -v block="$TMP_MAUTIC_BLOCK" '
BEGIN{inserted=0}
/^services:$/ {
print
if (inserted==0) {
while ((getline line < block) > 0) print line
close(block)
inserted=1
}
next
}
{print}
' "$TMP_NOGRAF" > "$OUT"
fi
fi
echo "[7] Host-Caddy stoppen (damit Docker-Caddy Port 80/443 bekommt), falls aktiv"
if systemctl is-active --quiet caddy 2>/dev/null; then
systemctl stop caddy
fi
echo "[8] hxki-internal sicherstellen"
if ! docker network ls --format '{{.Name}}' | grep -q '^hxki-internal$'; then
docker network create hxki-internal >/dev/null
fi
echo "[9] COM2 Orchester starten NUR aus $OUT"
cd "$OUTDIR"
docker compose down --remove-orphans || true
docker compose up -d --remove-orphans
echo
echo "=== COM2 STATUS (nur Fakten) ==="
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}'
echo
echo "hxki-internal Mitglieder:"
docker network inspect hxki-internal --format '{{range $id,$c := .Containers}}{{println $c.Name}}{{end}}' | sort
echo "=== ENDE ==="

145
COM2_REPAIR_CANONICAL_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,145 @@
#!/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-repair-$TS"
mkdir -p "$BK"
echo "=== COM2 REPAIR · CANONICAL 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"
# 0) Netzwerk sicherstellen (external)
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null
# 1) Alte Secrets/DB-Namen aus vorhandenen Dateien RECOVERn (keine Fantasie)
# Priorität: Inventory/Backups -> dann bestehende .env
echo "[1] Recover old state (secrets/dbnames) from /opt/hx-ki …"
# helper: first match from file list
first_match() { local re="$1"; shift; for f in "$@"; do [ -f "$f" ] || continue; local v; v="$(grep -Eo "$re" "$f" | head -n1 || true)"; [ -n "$v" ] && { echo "$v"; return 0; }; done; return 1; }
# Kandidatenquellen (du hast sie bereits im System)
SRC_FILES=(
"/opt/hx-ki/inventory/"*.txt
"/opt/hx-ki/com2-stack/"*.bak.*
"/opt/hx-ki/com-stack/docker-compose.yml"
"/opt/hx-ki/com-stack/"*.bak.*
"/opt/hx-ki/docker/"docker-compose.yml*
"$ENVF"
)
# Postgres: Passwort + DB
PG_PASS_LINE="$(first_match 'POSTGRES_PASSWORD=[^[:space:]]+' "${SRC_FILES[@]}" || true)"
PG_DB_LINE="$(first_match 'POSTGRES_DB=[^[:space:]]+' "${SRC_FILES[@]}" || true)"
PG_USER_LINE="$(first_match 'POSTGRES_USER=[^[:space:]]+' "${SRC_FILES[@]}" || true)"
# Mautic/MariaDB: root oder mautic creds
MY_ROOT_LINE="$(first_match '(MARIADB_ROOT_PASSWORD|MYSQL_ROOT_PASSWORD)=[^[:space:]]+' "${SRC_FILES[@]}" || true)"
MAUTIC_USER_LINE="$(first_match 'MAUTIC_DB_USER=[^[:space:]]+' "${SRC_FILES[@]}" || true)"
MAUTIC_PASS_LINE="$(first_match 'MAUTIC_DB_PASSWORD=[^[:space:]]+' "${SRC_FILES[@]}" || true)"
MAUTIC_NAME_LINE="$(first_match 'MAUTIC_DB_NAME=[^[:space:]]+' "${SRC_FILES[@]}" || true)"
MAUTIC_ROOT_LINE="$(first_match 'MAUTIC_DB_ROOT_PASSWORD=[^[:space:]]+' "${SRC_FILES[@]}" || true)"
# Minimalanforderungen
if [ -z "${PG_PASS_LINE:-}" ] || [ -z "${PG_DB_LINE:-}" ] || [ -z "${PG_USER_LINE:-}" ]; then
echo "FAIL: Postgres Altzustand nicht eindeutig (POSTGRES_USER/PASSWORD/DB)."
echo " Liefere eine Quelle mit diesen Keys (inventory txt oder altes .env)."
exit 1
fi
# MariaDB/Mautic: entweder ROOT oder MAUTIC_* muss vorhanden sein
if [ -z "${MY_ROOT_LINE:-}" ] && [ -z "${MAUTIC_ROOT_LINE:-}" ] && ( [ -z "${MAUTIC_USER_LINE:-}" ] || [ -z "${MAUTIC_PASS_LINE:-}" ] ); then
echo "FAIL: MariaDB/Mautic Altzustand nicht eindeutig (root oder mautic creds fehlen)."
exit 1
fi
echo " OK Postgres: $PG_USER_LINE $PG_DB_LINE (PW recovered)"
echo " OK MariaDB: $( [ -n "${MY_ROOT_LINE:-}" ] && echo "$MY_ROOT_LINE" || echo "$MAUTIC_ROOT_LINE" ) (root PW recovered)"
echo
# 2) Schreibe .env KANONISCH (nur Werte, die wir wirklich kennen)
echo "[2] Write canonical .env (aligned to old DB state)"
cp -a "${ENVF:-/dev/null}" "$BK/.env.pre" 2>/dev/null || true
PG_USER="${PG_USER_LINE#POSTGRES_USER=}"
PG_PASSWORD="${PG_PASS_LINE#POSTGRES_PASSWORD=}"
PG_DB="${PG_DB_LINE#POSTGRES_DB=}"
# Mautic Defaults (wenn nicht vorhanden, bleiben leer -> Compose muss dann nicht drauf referenzieren)
MAUTIC_DB_USER="${MAUTIC_USER_LINE#MAUTIC_DB_USER=}"
MAUTIC_DB_PASSWORD="${MAUTIC_PASS_LINE#MAUTIC_DB_PASSWORD=}"
MAUTIC_DB_NAME="${MAUTIC_NAME_LINE#MAUTIC_DB_NAME=}"
MAUTIC_DB_ROOT_PASSWORD="${MAUTIC_ROOT_LINE#MAUTIC_DB_ROOT_PASSWORD=}"
# root pw aus MARIADB_ROOT_PASSWORD falls vorhanden
if [ -n "${MY_ROOT_LINE:-}" ]; then
ROOTPW="${MY_ROOT_LINE#*=}"
else
ROOTPW="${MAUTIC_DB_ROOT_PASSWORD:-}"
fi
cat > "$ENVF" <<ENV
# === CANONICAL COM2 ENV (recovered from old state) ===
PG_USER=$PG_USER
PG_PASSWORD=$PG_PASSWORD
PG_DB=$PG_DB
# MariaDB/Mautic
MARIADB_ROOT_PASSWORD=$ROOTPW
MAUTIC_DB_ROOT_PASSWORD=$ROOTPW
MAUTIC_DB_USER=${MAUTIC_DB_USER:-root}
MAUTIC_DB_PASSWORD=${MAUTIC_DB_PASSWORD:-$ROOTPW}
MAUTIC_DB_NAME=${MAUTIC_DB_NAME:-mautic}
# n8n public host/proto (falls du es nutzt; ansonsten bleibt's neutral)
N8N_HOST=${N8N_HOST:-localhost}
N8N_PROTOCOL=${N8N_PROTOCOL:-http}
ENV
echo " OK: .env written"
# 3) Validate Compose
echo "[3] Validate compose"
docker compose -f "$F" --env-file "$ENVF" config >/dev/null
echo " OK: compose valid"
# 4) Orchester hoch
echo "[4] Up"
cd "$DIR"
docker compose --env-file "$ENVF" down --remove-orphans || true
docker compose --env-file "$ENVF" up -d --remove-orphans
# 5) Hard checks: DB login, then services listen, then caddy->upstreams
echo
echo "[5] HARD CHECKS"
echo "--- Postgres auth check (inside container) ---"
docker exec -e PGPASSWORD="$PG_PASSWORD" -it hxki-postgres psql -U "$PG_USER" -d "$PG_DB" -c "SELECT 1;" >/dev/null && echo "OK_PG_AUTH" || echo "FAIL_PG_AUTH"
echo "--- MariaDB auth check (inside container) ---"
docker exec -it hxki-mariadb sh -lc "mysql -uroot -p'$ROOTPW' -e 'SELECT 1;' >/dev/null 2>&1 && echo OK_MY_AUTH || echo FAIL_MY_AUTH"
echo "--- n8n logs (tail) ---"
docker logs --tail=20 hxki-n8n || true
echo "--- mautic logs (tail) ---"
docker logs --tail=40 hxki-mautic || true
echo "--- caddy -> upstreams ---"
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
echo
echo "=== DONE ==="
echo "Backup: $BK"

168
COM2_REPAIR_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,168 @@
#!/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 ==="

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT="/opt/hx-ki"
DIR="$ROOT/com2-stack"
COMPOSE_DST="$DIR/docker-compose.yml"
NET="hxki-internal"
TS="$(date +%Y%m%d-%H%M%S)"
BK="$ROOT/backups/com2-original-restore-$TS"
mkdir -p "$BK"
echo "=== COM2 · RESTORE ORIGINAL STATE (one-shot, no guessing) ==="
echo "DST compose: $COMPOSE_DST"
echo "Backup: $BK"
echo
[ -f "$COMPOSE_DST" ] || { echo "FAIL: missing $COMPOSE_DST"; exit 1; }
cp -a "$COMPOSE_DST" "$BK/docker-compose.yml.pre" || true
echo "[1] Suche Compose-Quelle mit hxki-web (ohne Raten)…"
pick_src() {
# COM2 Backups (neueste zuerst)
for f in $(ls -1t /opt/hx-ki/com2-stack/docker-compose.yml.bak.* 2>/dev/null); do
grep -qE '^[[:space:]]{2}hxki-web:[[:space:]]*$' "$f" && { echo "$f"; return 0; }
done
# COM1 compose + backups
for f in /opt/hx-ki/com-stack/docker-compose.yml /opt/hx-ki/com-stack/docker-compose.yml.bak.*; do
[ -f "$f" ] || continue
grep -qE '^[[:space:]]{2}hxki-web:[[:space:]]*$' "$f" && { echo "$f"; return 0; }
done
# docker Backups
for f in /opt/hx-ki/docker/docker-compose.yml*; do
[ -f "$f" ] || continue
grep -qE '^[[:space:]]{2}hxki-web:[[:space:]]*$' "$f" && { echo "$f"; return 0; }
done
return 1
}
SRC="$(pick_src || true)"
[ -n "${SRC:-}" ] || { echo "FAIL: keine Compose-Quelle mit hxki-web gefunden"; exit 1; }
echo "OK: SRC=$SRC"
cp -a "$SRC" "$BK/docker-compose.source.used"
cp -a "$SRC" "$COMPOSE_DST"
echo "[2] hxki-internal als external sicherstellen (nur ergänzen, nichts raten)"
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null
# Falls am Ende kein networks:-Block existiert: minimal anhängen
if ! grep -qE '^networks:' "$COMPOSE_DST"; then
cat >> "$COMPOSE_DST" <<NETS
networks:
hxki-internal:
external: true
NETS
else
# Wenn networks existiert, aber hxki-internal nicht: minimal ergänzen
if ! grep -qE '^[[:space:]]{2}hxki-internal:' "$COMPOSE_DST"; then
echo "WARN: networks: existiert, aber hxki-internal fehlt -> bitte manuell prüfen (ich ändere hier nicht kreativ)."
fi
fi
echo "[3] Validate compose"
docker compose -f "$COMPOSE_DST" config >/dev/null
echo "OK: compose valid"
echo "DONE. Backup: $BK"

68
COM2_RESTORE_WEB_ONE_SHOT.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
DST="/opt/hx-ki/com2-stack/docker-compose.yml"
[ -f "$DST" ] || { echo "FEHLT: $DST"; exit 1; }
pick_src() {
# 1) COM2 Backups (neueste zuerst)
for f in $(ls -1t /opt/hx-ki/com2-stack/docker-compose.yml.bak.* 2>/dev/null); do
grep -qE '^[[:space:]]{2}hxki-web:[[:space:]]*$' "$f" && { echo "$f"; return 0; }
done
# 2) COM-Stack
for f in /opt/hx-ki/com-stack/docker-compose.yml /opt/hx-ki/com-stack/docker-compose.yml.bak.*; do
[ -f "$f" ] || continue
grep -qE '^[[:space:]]{2}hxki-web:[[:space:]]*$' "$f" && { echo "$f"; return 0; }
done
# 3) docker Backups
for f in /opt/hx-ki/docker/docker-compose.yml*; do
[ -f "$f" ] || continue
grep -qE '^[[:space:]]{2}hxki-web:[[:space:]]*$' "$f" && { echo "$f"; return 0; }
done
return 1
}
SRC="$(pick_src)"
[ -n "$SRC" ] || { echo "FAIL: Keine Quelle mit hxki-web gefunden"; exit 1; }
export SRC DST
echo "=== COM2 RESTORE hxki-web ==="
echo "SRC=$SRC"
echo "DST=$DST"
python3 <<'PY'
import re, pathlib, os
src = pathlib.Path(os.environ["SRC"]).read_text()
dstp = pathlib.Path(os.environ["DST"])
dst = dstp.read_text()
m = re.search(r'(?ms)^services:\s*\n(.*?)(?=^\S|\Z)', src)
if not m:
raise SystemExit("FEHLT: services: im Source")
block = m.group(1)
pat = r'(?ms)^\s{2}hxki-web:\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("FEHLT: hxki-web Block")
web = mm.group(0)
if 'hxki-web:' in dst:
print("OK: hxki-web bereits vorhanden")
raise SystemExit(0)
dst = re.sub(r'(?m)^(services:\s*\n)', r'\1' + web + '\n', dst, count=1)
dstp.write_text(dst)
print("OK: hxki-web eingefügt")
PY
docker compose -f "$DST" config >/dev/null
echo "OK: Compose valide."

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
F="$DIR/docker-compose.yml"
C="hxki-postgres"
TS="$(date +%Y%m%d-%H%M%S)"
BK="/opt/hx-ki/backups/com2-strip-pg-$TS"
mkdir -p "$BK"
echo "=== COM2 · STRIP PG ';' + REVERIFY (one-shot) ==="
echo "Compose: $F"
echo "Backup: $BK"
echo
[ -f "$F" ] || { echo "FAIL: missing $F"; exit 1; }
cp -a "$F" "$BK/docker-compose.yml.pre"
echo "[1] Strip trailing ';' from POSTGRES_USER/DB/PASSWORD values (list + mapping, quoted/unquoted)"
python3 - <<'PY'
from pathlib import Path
import re
p = Path("/opt/hx-ki/com2-stack/docker-compose.yml")
s = p.read_text()
keys = ["POSTGRES_USER", "POSTGRES_PASSWORD", "POSTGRES_DB"]
# list-form: - KEY=value;
for k in keys:
s = re.sub(rf'^(\s*-\s*{k}=)(.*?);(\s*)$', r'\1\2\3', s, flags=re.M)
# mapping-form: KEY: value;
for k in keys:
s = re.sub(rf'^(\s*{k}:\s*)(.*?);(\s*)$', r'\1\2\3', s, flags=re.M)
# also handle quoted values: KEY: "value;";
for k in keys:
s = re.sub(rf'^(\s*{k}:\s*")(.*?);("(\s*)$)', r'\1\2\3', s, flags=re.M)
p.write_text(s)
PY
echo
echo "[2] Validate compose"
docker compose -f "$F" config >/dev/null
echo "OK: compose valid"
echo
echo "[3] Recreate postgres to re-inject sanitized ENV"
cd "$DIR"
docker compose up -d --force-recreate --no-deps "$C" >/dev/null
echo
echo "[4] Ground truth ENV inside container (must NOT end with ';')"
docker inspect "$C" --format '{{range .Config.Env}}{{println .}}{{end}}' | egrep '^POSTGRES_(USER|PASSWORD|DB)='
PG_USER="$(docker inspect "$C" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^POSTGRES_USER=/{print $2}' | tail -n1)"
PG_DB="$(docker inspect "$C" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^POSTGRES_DB=/{print $2}' | tail -n1)"
PG_PW="$(docker inspect "$C" --format '{{range .Config.Env}}{{println .}}{{end}}' | awk -F= '/^POSTGRES_PASSWORD=/{print $2}' | tail -n1)"
for x in "$PG_USER" "$PG_DB" "$PG_PW"; do
if [[ "$x" == *";"* ]]; then
echo "FAIL: still contains ';' in ENV -> $x"
exit 3
fi
done
echo "OK: no semicolons in ENV"
echo
echo "[5] Verify REAL login (max 30s)"
for i in $(seq 1 30); do
if docker exec -e PGPASSWORD="$PG_PW" "$C" psql -U "$PG_USER" -d "$PG_DB" -c "SELECT 1;" >/dev/null 2>&1; then
echo "OK: Postgres auth works with CURRENT ENV"
echo "=== DONE ==="
exit 0
fi
sleep 1
done
echo "FAIL: Postgres still rejects CURRENT ENV."
echo "=> That means the DATA DIR was initialized with different credentials."
echo "=> Next deterministic step is a controlled auth-reset (one-time), or you provide the real old password."
exit 2

38
COM2_UP_AND_HARD_CHECKS.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
DIR="/opt/hx-ki/com2-stack"
F="$DIR/docker-compose.yml"
NET="hxki-internal"
echo "=== COM2 UP + HARD CHECKS ==="
[ -f "$F" ] || { echo "FEHLT: $F"; exit 1; }
# 0) Netzwerk muss external existieren
docker network inspect "$NET" >/dev/null 2>&1 || docker network create "$NET" >/dev/null
# 1) Orchester neu hoch
cd "$DIR"
docker compose down --remove-orphans
docker compose up -d --remove-orphans
echo
echo "[A] Container Status"
docker ps --format 'NAME={{.Names}} STATUS={{.Status}} PORTS={{.Ports}}' | egrep 'hxki-|hx-caddy' || true
echo
echo "[B] Netzwerk-Mitglieder ($NET)"
docker network inspect "$NET" --format '{{range $id,$c := .Containers}}{{println $c.Name}}{{end}}' | sort
echo
echo "[C] Caddy -> Service Reachability (intern)"
docker exec -it hx-caddy sh -lc 'wget -qO- http://hxki-web/ >/dev/null && echo OK_CADDY_TO_WEB || echo FAIL_CADDY_TO_WEB'
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'
docker exec -it hx-caddy sh -lc 'wget -qO- http://hxki-mautic/ >/dev/null && echo OK_CADDY_TO_MAUTIC || echo FAIL_CADDY_TO_MAUTIC'
echo
echo "[D] n8n Logs (letzte 40)"
docker logs --tail=40 hxki-n8n || true
echo
echo "=== ENDE ==="

131
agent/hxki_agent.py Executable file
View File

@@ -0,0 +1,131 @@
#!/usr/bin/env python3
import os
import json
import subprocess
from datetime import datetime
from pathlib import Path
TS = datetime.utcnow().strftime("%Y%m%d-%H%M%S")
BASE_DIR = Path("/opt/hx-ki")
INVENTORY_DIR = BASE_DIR / "inventory"
INVENTORY_DIR.mkdir(parents=True, exist_ok=True)
# .env laden (HXKI_NODE_NAME etc.)
ENV_FILE = BASE_DIR / "env" / ".env"
if ENV_FILE.exists():
for line in ENV_FILE.read_text().splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
os.environ.setdefault(k.strip(), v.strip())
HOST = subprocess.getoutput("hostname")
NODE = os.environ.get("HXKI_NODE_NAME", "")
def run(cmd: str) -> str:
try:
out = subprocess.check_output(
cmd, shell=True, stderr=subprocess.DEVNULL, text=True
)
return out.strip()
except Exception:
return ""
def bytes_to_mb(b: int) -> float:
return round(b / 1024 / 1024, 2)
def workspace_info():
ws_path = Path(os.environ.get("HXKI_WORKSPACE", "/data/HXKI_WORKSPACE"))
info = {"path": str(ws_path), "exists": ws_path.exists()}
if ws_path.exists():
total_size = 0
count = 0
for root, dirs, files in os.walk(ws_path):
for name in files:
try:
p = Path(root) / name
total_size += p.stat().st_size
count += 1
except Exception:
pass
info["file_count"] = count
info["total_bytes"] = total_size
info["total_mb"] = bytes_to_mb(total_size)
return info
def telemetry_info():
tel_path = Path(os.environ.get("HXKI_TELEMETRY_LOG", "/opt/hx-ki/logs/autoindexer_events.log"))
info = {"path": str(tel_path), "exists": tel_path.exists()}
if tel_path.exists():
try:
sz = tel_path.stat().st_size
info["size_bytes"] = sz
info["size_mb"] = bytes_to_mb(sz)
info["tail_10"] = run(f"tail -n 10 {tel_path}")
except Exception:
pass
return info
summary = {
"timestamp_utc": TS,
"host": HOST,
"node_name": NODE,
"sections": {}
}
summary["sections"]["system"] = {
"hostnamectl": run("hostnamectl || echo ''"),
"cpu": run("lscpu || echo ''"),
"memory": run("free -h || echo ''"),
"disk_root": run("df -h / || echo ''"),
}
summary["sections"]["workspace"] = workspace_info()
summary["sections"]["telemetry"] = telemetry_info()
summary["sections"]["docker"] = {
"ps": run("docker ps || echo ''"),
"images": run("docker images || echo ''"),
}
hx_vars = {}
for k, v in os.environ.items():
if k.startswith("HXKI_") or k in ("CHROMA_HOST", "CHROMA_PORT", "POSTGRES_HOST", "POSTGRES_DB"):
hx_vars[k] = v
summary["sections"]["env"] = {
"env_file": str(ENV_FILE),
"present": ENV_FILE.exists(),
"hx_vars": hx_vars,
}
summary["sections"]["load"] = {"uptime": run("uptime || echo ''")}
summary["sections"]["disk_detail"] = {"df_all": run("df -h || echo ''")}
log_path = INVENTORY_DIR / f"hxki_agent_{TS}.log"
json_path = INVENTORY_DIR / f"hxki_agent_{TS}.json"
with log_path.open("w", encoding="utf-8") as f:
f.write("==== HX-KI AGENT INVENTAR ====\n")
f.write(f"Zeit (UTC): {TS}\n")
f.write(f"Host: {HOST}\n")
f.write(f"Node-Name: {NODE}\n")
f.write("====================================\n\n")
for section, data in summary["sections"].items():
f.write(f"---- {section.upper()} ----\n")
if isinstance(data, dict):
f.write(json.dumps(data, indent=2, ensure_ascii=False))
else:
f.write(str(data))
f.write("\n\n")
with json_path.open("w", encoding="utf-8") as jf:
json.dump(summary, jf, indent=2, ensure_ascii=False)
print("========================================================")
print("HX-KI AGENT RUN COMPLETE")
print("Log: ", log_path)
print("JSON: ", json_path)
print("========================================================")

28
agents/json_homepage_agent.py Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
import json
import os
WORKSPACE = "/opt/hx-ki/workspaces/homepage"
FILE = os.path.join(WORKSPACE, "homepage.json")
os.makedirs(WORKSPACE, exist_ok=True)
content = {
"hero_title": "HX-KI Dynamischer JSON-Agent",
"hero_sub": "Content kommt aus dem Workspace auf Falkenstein kein Zugriff auf Gehirn oder Motor.",
"sections": [
{
"title": "Agent-Status",
"text": "Dieser JSON-Agent kann von Events, Cron, KI oder Mautic getriggert werden."
},
{
"title": "Architektur-Sicherheit",
"text": "Falkenstein liest nur den Workspace. Nürnberg & Helsinki bleiben intern."
}
]
}
with open(FILE, "w") as f:
json.dump(content, f, indent=2)
print(f"✔ homepage.json durch json_homepage_agent.py erzeugt/aktualisiert: {FILE}")

View File

View File

@@ -0,0 +1 @@
GITEA_CUSTOM=/data/gitea

View File

@@ -0,0 +1 @@
ref: refs/heads/main

View File

@@ -0,0 +1,4 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true

View File

@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini post-receive

View File

@@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info

View File

@@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:

View File

@@ -0,0 +1,49 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff-index --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

View File

@@ -0,0 +1,13 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit"
:

View File

@@ -0,0 +1,53 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
while read local_ref local_oid remote_ref remote_oid
do
if test "$local_oid" = "$zero"
then
# Handle delete
:
else
if test "$remote_oid" = "$zero"
then
# New branch, examine all commits
range="$local_oid"
else
# Update to existing branch, examine new commits
range="$remote_oid..$local_oid"
fi
# Check for WIP commit
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
if test -n "$commit"
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0

View File

@@ -0,0 +1,169 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini pre-receive

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".
if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi

View File

@@ -0,0 +1,42 @@
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".
# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output. It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
# case "$COMMIT_SOURCE,$SHA1" in
# ,|template,)
# /usr/bin/perl -i.bak -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
# *) ;;
# esac
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
# if test -z "$COMMIT_SOURCE"
# then
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
# fi

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini proc-receive

View File

@@ -0,0 +1,78 @@
#!/bin/sh
# An example hook script to update a checked-out tree on a git push.
#
# This hook is invoked by git-receive-pack(1) when it reacts to git
# push and updates reference(s) in its repository, and when the push
# tries to update the branch that is currently checked out and the
# receive.denyCurrentBranch configuration variable is set to
# updateInstead.
#
# By default, such a push is refused if the working tree and the index
# of the remote repository has any difference from the currently
# checked out commit; when both the working tree and the index match
# the current commit, they are updated to match the newly pushed tip
# of the branch. This hook is to be used to override the default
# behaviour; however the code below reimplements the default behaviour
# as a starting point for convenient modification.
#
# The hook receives the commit with which the tip of the current
# branch is going to be updated:
commit=$1
# It can exit with a non-zero status to refuse the push (when it does
# so, it must not modify the index or the working tree).
die () {
echo >&2 "$*"
exit 1
}
# Or it can make any necessary changes to the working tree and to the
# index to bring them to the desired state when the tip of the current
# branch is updated to the new commit, and exit with a zero status.
#
# For example, the hook can simply run git read-tree -u -m HEAD "$1"
# in order to emulate git fetch that is run in the reverse direction
# with git push, as the two-tree form of git read-tree -u -m is
# essentially the same as git switch or git checkout that switches
# branches while keeping the local changes in the working tree that do
# not interfere with the difference between the branches.
# The below is a more-or-less exact translation to shell of the C code
# for the default behaviour for git's push-to-checkout hook defined in
# the push_to_deploy() function in builtin/receive-pack.c.
#
# Note that the hook will be executed from the repository directory,
# not from the working tree, so if you want to perform operations on
# the working tree, you will have to adapt your code accordingly, e.g.
# by adding "cd .." or using relative paths.
if ! git update-index -q --ignore-submodules --refresh
then
die "Up-to-date check failed"
fi
if ! git diff-files --quiet --ignore-submodules --
then
die "Working directory has unstaged changes"
fi
# This is a rough translation of:
#
# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
if git cat-file -e HEAD 2>/dev/null
then
head=HEAD
else
head=$(git hash-object -t tree --stdin </dev/null)
fi
if ! git diff-index --quiet --cached --ignore-submodules $head --
then
die "Working directory has staged changes"
fi
if ! git read-tree -u -m "$commit"
then
die "Could not update working tree to new HEAD"
fi

View File

@@ -0,0 +1,77 @@
#!/bin/sh
# An example hook script to validate a patch (and/or patch series) before
# sending it via email.
#
# The hook should exit with non-zero status after issuing an appropriate
# message if it wants to prevent the email(s) from being sent.
#
# To enable this hook, rename this file to "sendemail-validate".
#
# By default, it will only check that the patch(es) can be applied on top of
# the default upstream branch without conflicts in a secondary worktree. After
# validation (successful or not) of the last patch of a series, the worktree
# will be deleted.
#
# The following config variables can be set to change the default remote and
# remote ref that are used to apply the patches against:
#
# sendemail.validateRemote (default: origin)
# sendemail.validateRemoteRef (default: HEAD)
#
# Replace the TODO placeholders with appropriate checks according to your
# needs.
validate_cover_letter () {
file="$1"
# TODO: Replace with appropriate checks (e.g. spell checking).
true
}
validate_patch () {
file="$1"
# Ensure that the patch applies without conflicts.
git am -3 "$file" || return
# TODO: Replace with appropriate checks for this patch
# (e.g. checkpatch.pl).
true
}
validate_series () {
# TODO: Replace with appropriate checks for the whole series
# (e.g. quick build, coding style checks, etc.).
true
}
# main -------------------------------------------------------------------------
if test "$GIT_SENDEMAIL_FILE_COUNTER" = 1
then
remote=$(git config --default origin --get sendemail.validateRemote) &&
ref=$(git config --default HEAD --get sendemail.validateRemoteRef) &&
worktree=$(mktemp --tmpdir -d sendemail-validate.XXXXXXX) &&
git worktree add -fd --checkout "$worktree" "refs/remotes/$remote/$ref" &&
git config --replace-all sendemail.validateWorktree "$worktree"
else
worktree=$(git config --get sendemail.validateWorktree)
fi || {
echo "sendemail-validate: error: failed to prepare worktree" >&2
exit 1
}
unset GIT_DIR GIT_WORK_TREE
cd "$worktree" &&
if grep -q "^diff --git " "$1"
then
validate_patch "$1"
else
validate_cover_letter "$1"
fi &&
if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL"
then
git config --unset-all sendemail.validateWorktree &&
trap 'git worktree remove -ff "$worktree"' EXIT &&
validate_series
fi

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0/..)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
"${hook}" $1 $2 $3
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini update $1 $2 $3

View File

@@ -0,0 +1,128 @@
#!/bin/sh
#
# An example hook script to block unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
# hooks.allowdeletetag
# This boolean sets whether deleting tags will be allowed in the
# repository. By default they won't be.
# hooks.allowmodifytag
# This boolean sets whether a tag may be modified after creation. By default
# it won't be.
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Config
allowunannotated=$(git config --type=bool hooks.allowunannotated)
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
echo "*** Project description file hasn't been set" >&2
exit 1
;;
esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
case "$refname","$newrev_type" in
refs/tags/*,commit)
# un-annotated tag
short_refname=${refname##refs/tags/}
if [ "$allowunannotated" != "true" ]; then
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
exit 1
fi
;;
refs/tags/*,delete)
# delete tag
if [ "$allowdeletetag" != "true" ]; then
echo "*** Deleting a tag is not allowed in this repository" >&2
exit 1
fi
;;
refs/tags/*,tag)
# annotated tag
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
then
echo "*** Tag '$refname' already exists." >&2
echo "*** Modifying a tag is not allowed in this repository." >&2
exit 1
fi
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/remotes/*,commit)
# tracking branch
;;
refs/remotes/*,delete)
# delete tracking branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
exit 1
fi
;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
exit 1
;;
esac
# --- Finished
exit 0

View File

@@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View File

@@ -0,0 +1 @@
ref: refs/heads/main

View File

@@ -0,0 +1,4 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true

View File

@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini post-receive

View File

@@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info

View File

@@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:

View File

@@ -0,0 +1,49 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff-index --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

View File

@@ -0,0 +1,13 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit"
:

View File

@@ -0,0 +1,53 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
while read local_ref local_oid remote_ref remote_oid
do
if test "$local_oid" = "$zero"
then
# Handle delete
:
else
if test "$remote_oid" = "$zero"
then
# New branch, examine all commits
range="$local_oid"
else
# Update to existing branch, examine new commits
range="$remote_oid..$local_oid"
fi
# Check for WIP commit
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
if test -n "$commit"
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0

View File

@@ -0,0 +1,169 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini pre-receive

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to make use of push options.
# The example simply echoes all push options that start with 'echoback='
# and rejects all pushes when the "reject" push option is used.
#
# To enable this hook, rename this file to "pre-receive".
if test -n "$GIT_PUSH_OPTION_COUNT"
then
i=0
while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
esac
i=$((i + 1))
done
fi

View File

@@ -0,0 +1,42 @@
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source. The hook's purpose is to edit the commit
# message file. If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".
# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output. It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited. This is rarely a good idea.
COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3
/usr/bin/perl -i.bak -ne 'print unless(m/^. Please enter the commit message/..m/^#$/)' "$COMMIT_MSG_FILE"
# case "$COMMIT_SOURCE,$SHA1" in
# ,|template,)
# /usr/bin/perl -i.bak -pe '
# print "\n" . `git diff --cached --name-status -r`
# if /^#/ && $first++ == 0' "$COMMIT_MSG_FILE" ;;
# *) ;;
# esac
# SOB=$(git var GIT_COMMITTER_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# git interpret-trailers --in-place --trailer "$SOB" "$COMMIT_MSG_FILE"
# if test -z "$COMMIT_SOURCE"
# then
# /usr/bin/perl -i.bak -pe 'print "\n" if !$first_line++' "$COMMIT_MSG_FILE"
# fi

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini proc-receive

View File

@@ -0,0 +1,78 @@
#!/bin/sh
# An example hook script to update a checked-out tree on a git push.
#
# This hook is invoked by git-receive-pack(1) when it reacts to git
# push and updates reference(s) in its repository, and when the push
# tries to update the branch that is currently checked out and the
# receive.denyCurrentBranch configuration variable is set to
# updateInstead.
#
# By default, such a push is refused if the working tree and the index
# of the remote repository has any difference from the currently
# checked out commit; when both the working tree and the index match
# the current commit, they are updated to match the newly pushed tip
# of the branch. This hook is to be used to override the default
# behaviour; however the code below reimplements the default behaviour
# as a starting point for convenient modification.
#
# The hook receives the commit with which the tip of the current
# branch is going to be updated:
commit=$1
# It can exit with a non-zero status to refuse the push (when it does
# so, it must not modify the index or the working tree).
die () {
echo >&2 "$*"
exit 1
}
# Or it can make any necessary changes to the working tree and to the
# index to bring them to the desired state when the tip of the current
# branch is updated to the new commit, and exit with a zero status.
#
# For example, the hook can simply run git read-tree -u -m HEAD "$1"
# in order to emulate git fetch that is run in the reverse direction
# with git push, as the two-tree form of git read-tree -u -m is
# essentially the same as git switch or git checkout that switches
# branches while keeping the local changes in the working tree that do
# not interfere with the difference between the branches.
# The below is a more-or-less exact translation to shell of the C code
# for the default behaviour for git's push-to-checkout hook defined in
# the push_to_deploy() function in builtin/receive-pack.c.
#
# Note that the hook will be executed from the repository directory,
# not from the working tree, so if you want to perform operations on
# the working tree, you will have to adapt your code accordingly, e.g.
# by adding "cd .." or using relative paths.
if ! git update-index -q --ignore-submodules --refresh
then
die "Up-to-date check failed"
fi
if ! git diff-files --quiet --ignore-submodules --
then
die "Working directory has unstaged changes"
fi
# This is a rough translation of:
#
# head_has_history() ? "HEAD" : EMPTY_TREE_SHA1_HEX
if git cat-file -e HEAD 2>/dev/null
then
head=HEAD
else
head=$(git hash-object -t tree --stdin </dev/null)
fi
if ! git diff-index --quiet --cached --ignore-submodules $head --
then
die "Working directory has staged changes"
fi
if ! git read-tree -u -m "$commit"
then
die "Could not update working tree to new HEAD"
fi

View File

@@ -0,0 +1,77 @@
#!/bin/sh
# An example hook script to validate a patch (and/or patch series) before
# sending it via email.
#
# The hook should exit with non-zero status after issuing an appropriate
# message if it wants to prevent the email(s) from being sent.
#
# To enable this hook, rename this file to "sendemail-validate".
#
# By default, it will only check that the patch(es) can be applied on top of
# the default upstream branch without conflicts in a secondary worktree. After
# validation (successful or not) of the last patch of a series, the worktree
# will be deleted.
#
# The following config variables can be set to change the default remote and
# remote ref that are used to apply the patches against:
#
# sendemail.validateRemote (default: origin)
# sendemail.validateRemoteRef (default: HEAD)
#
# Replace the TODO placeholders with appropriate checks according to your
# needs.
validate_cover_letter () {
file="$1"
# TODO: Replace with appropriate checks (e.g. spell checking).
true
}
validate_patch () {
file="$1"
# Ensure that the patch applies without conflicts.
git am -3 "$file" || return
# TODO: Replace with appropriate checks for this patch
# (e.g. checkpatch.pl).
true
}
validate_series () {
# TODO: Replace with appropriate checks for the whole series
# (e.g. quick build, coding style checks, etc.).
true
}
# main -------------------------------------------------------------------------
if test "$GIT_SENDEMAIL_FILE_COUNTER" = 1
then
remote=$(git config --default origin --get sendemail.validateRemote) &&
ref=$(git config --default HEAD --get sendemail.validateRemoteRef) &&
worktree=$(mktemp --tmpdir -d sendemail-validate.XXXXXXX) &&
git worktree add -fd --checkout "$worktree" "refs/remotes/$remote/$ref" &&
git config --replace-all sendemail.validateWorktree "$worktree"
else
worktree=$(git config --get sendemail.validateWorktree)
fi || {
echo "sendemail-validate: error: failed to prepare worktree" >&2
exit 1
}
unset GIT_DIR GIT_WORK_TREE
cd "$worktree" &&
if grep -q "^diff --git " "$1"
then
validate_patch "$1"
else
validate_cover_letter "$1"
fi &&
if test "$GIT_SENDEMAIL_FILE_COUNTER" = "$GIT_SENDEMAIL_FILE_TOTAL"
then
git config --unset-all sendemail.validateWorktree &&
trap 'git worktree remove -ff "$worktree"' EXIT &&
validate_series
fi

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0/..)}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
"${hook}" $1 $2 $3
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini update $1 $2 $3

View File

@@ -0,0 +1,128 @@
#!/bin/sh
#
# An example hook script to block unannotated tags from entering.
# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
#
# To enable this hook, rename this file to "update".
#
# Config
# ------
# hooks.allowunannotated
# This boolean sets whether unannotated tags will be allowed into the
# repository. By default they won't be.
# hooks.allowdeletetag
# This boolean sets whether deleting tags will be allowed in the
# repository. By default they won't be.
# hooks.allowmodifytag
# This boolean sets whether a tag may be modified after creation. By default
# it won't be.
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
# hooks.denycreatebranch
# This boolean sets whether remotely creating branches will be denied
# in the repository. By default this is allowed.
#
# --- Command line
refname="$1"
oldrev="$2"
newrev="$3"
# --- Safety check
if [ -z "$GIT_DIR" ]; then
echo "Don't run this script from the command line." >&2
echo " (if you want, you could supply GIT_DIR then run" >&2
echo " $0 <ref> <oldrev> <newrev>)" >&2
exit 1
fi
if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
echo "usage: $0 <ref> <oldrev> <newrev>" >&2
exit 1
fi
# --- Config
allowunannotated=$(git config --type=bool hooks.allowunannotated)
allowdeletebranch=$(git config --type=bool hooks.allowdeletebranch)
denycreatebranch=$(git config --type=bool hooks.denycreatebranch)
allowdeletetag=$(git config --type=bool hooks.allowdeletetag)
allowmodifytag=$(git config --type=bool hooks.allowmodifytag)
# check for no description
projectdesc=$(sed -e '1q' "$GIT_DIR/description")
case "$projectdesc" in
"Unnamed repository"* | "")
echo "*** Project description file hasn't been set" >&2
exit 1
;;
esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git cat-file -t $newrev)
fi
case "$refname","$newrev_type" in
refs/tags/*,commit)
# un-annotated tag
short_refname=${refname##refs/tags/}
if [ "$allowunannotated" != "true" ]; then
echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
exit 1
fi
;;
refs/tags/*,delete)
# delete tag
if [ "$allowdeletetag" != "true" ]; then
echo "*** Deleting a tag is not allowed in this repository" >&2
exit 1
fi
;;
refs/tags/*,tag)
# annotated tag
if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
then
echo "*** Tag '$refname' already exists." >&2
echo "*** Modifying a tag is not allowed in this repository." >&2
exit 1
fi
;;
refs/heads/*,commit)
# branch
if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
echo "*** Creating a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/heads/*,delete)
# delete branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a branch is not allowed in this repository" >&2
exit 1
fi
;;
refs/remotes/*,commit)
# tracking branch
;;
refs/remotes/*,delete)
# delete tracking branch
if [ "$allowdeletebranch" != "true" ]; then
echo "*** Deleting a tracking branch is not allowed in this repository" >&2
exit 1
fi
;;
*)
# Anything else (is there anything else?)
echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
exit 1
;;
esac
# --- Finished
exit 0

View File

@@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View File

@@ -0,0 +1 @@
ref: refs/heads/main

View File

@@ -0,0 +1,4 @@
[core]
repositoryformatversion = 0
filemode = true
bare = true

View File

@@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View File

@@ -0,0 +1,15 @@
#!/bin/sh
#
# An example hook script to check the commit log message taken by
# applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit. The hook is
# allowed to edit the commit message file.
#
# To enable this hook, rename this file to "applypatch-msg".
. git-sh-setup
commitmsg="$(git rev-parse --git-path hooks/commit-msg)"
test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"}
:

View File

@@ -0,0 +1,24 @@
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".
# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
# This example catches duplicate Signed-off-by lines.
test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini post-receive

View File

@@ -0,0 +1,8 @@
#!/bin/sh
#
# An example hook script to prepare a packed repository for use over
# dumb transports.
#
# To enable this hook, rename this file to "post-update".
exec git update-server-info

View File

@@ -0,0 +1,14 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed
# by applypatch from an e-mail message.
#
# The hook should exit with non-zero status after issuing an
# appropriate message if it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-applypatch".
. git-sh-setup
precommit="$(git rev-parse --git-path hooks/pre-commit)"
test -x "$precommit" && exec "$precommit" ${1+"$@"}
:

View File

@@ -0,0 +1,49 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git commit" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message if
# it wants to stop the commit.
#
# To enable this hook, rename this file to "pre-commit".
if git rev-parse --verify HEAD >/dev/null 2>&1
then
against=HEAD
else
# Initial commit: diff against an empty tree object
against=$(git hash-object -t tree /dev/null)
fi
# If you want to allow non-ASCII filenames set this variable to true.
allownonascii=$(git config --type=bool hooks.allownonascii)
# Redirect output to stderr.
exec 1>&2
# Cross platform projects tend to avoid non-ASCII filenames; prevent
# them from being added to the repository. We exploit the fact that the
# printable range starts at the space character and ends with tilde.
if [ "$allownonascii" != "true" ] &&
# Note that the use of brackets around a tr range is ok here, (it's
# even required, for portability to Solaris 10's /usr/bin/tr), since
# the square bracket bytes happen to fall in the designated range.
test $(git diff-index --cached --name-only --diff-filter=A -z $against |
LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
then
cat <<\EOF
Error: Attempt to add a non-ASCII file name.
This can cause problems if you want to work with people on other platforms.
To be portable it is advisable to rename the file.
If you know what you are doing you can disable this check using:
git config hooks.allownonascii true
EOF
exit 1
fi
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --

View File

@@ -0,0 +1,13 @@
#!/bin/sh
#
# An example hook script to verify what is about to be committed.
# Called by "git merge" with no arguments. The hook should
# exit with non-zero status after issuing an appropriate message to
# stderr if it wants to stop the merge commit.
#
# To enable this hook, rename this file to "pre-merge-commit".
. git-sh-setup
test -x "$GIT_DIR/hooks/pre-commit" &&
exec "$GIT_DIR/hooks/pre-commit"
:

View File

@@ -0,0 +1,53 @@
#!/bin/sh
# An example hook script to verify what is about to be pushed. Called by "git
# push" after it has checked the remote status, but before anything has been
# pushed. If this script exits with a non-zero status nothing will be pushed.
#
# This hook is called with the following parameters:
#
# $1 -- Name of the remote to which the push is being done
# $2 -- URL to which the push is being done
#
# If pushing without using a named remote those arguments will be equal.
#
# Information about the commits which are being pushed is supplied as lines to
# the standard input in the form:
#
# <local ref> <local oid> <remote ref> <remote oid>
#
# This sample shows how to prevent push of commits where the log message starts
# with "WIP" (work in progress).
remote="$1"
url="$2"
zero=$(git hash-object --stdin </dev/null | tr '[0-9a-f]' '0')
while read local_ref local_oid remote_ref remote_oid
do
if test "$local_oid" = "$zero"
then
# Handle delete
:
else
if test "$remote_oid" = "$zero"
then
# New branch, examine all commits
range="$local_oid"
else
# Update to existing branch, examine new commits
range="$remote_oid..$local_oid"
fi
# Check for WIP commit
commit=$(git rev-list -n 1 --grep '^WIP' "$range")
if test -n "$commit"
then
echo >&2 "Found WIP commit in $local_ref, not pushing"
exit 1
fi
fi
done
exit 0

View File

@@ -0,0 +1,169 @@
#!/bin/sh
#
# Copyright (c) 2006, 2008 Junio C Hamano
#
# The "pre-rebase" hook is run just before "git rebase" starts doing
# its job, and can prevent the command from running by exiting with
# non-zero status.
#
# The hook is called with the following parameters:
#
# $1 -- the upstream the series was forked from.
# $2 -- the branch being rebased (or empty when rebasing the current branch).
#
# This sample shows how to prevent topic branches that are already
# merged to 'next' branch from getting rebased, because allowing it
# would result in rebasing already published history.
publish=next
basebranch="$1"
if test "$#" = 2
then
topic="refs/heads/$2"
else
topic=`git symbolic-ref HEAD` ||
exit 0 ;# we do not interrupt rebasing detached HEAD
fi
case "$topic" in
refs/heads/??/*)
;;
*)
exit 0 ;# we do not interrupt others.
;;
esac
# Now we are dealing with a topic branch being rebased
# on top of master. Is it OK to rebase it?
# Does the topic really exist?
git show-ref -q "$topic" || {
echo >&2 "No such branch $topic"
exit 1
}
# Is topic fully merged to master?
not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
if test -z "$not_in_master"
then
echo >&2 "$topic is fully merged to master; better remove it."
exit 1 ;# we could allow it, but there is no point.
fi
# Is topic ever merged to next? If so you should not be rebasing it.
only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
only_next_2=`git rev-list ^master ${publish} | sort`
if test "$only_next_1" = "$only_next_2"
then
not_in_topic=`git rev-list "^$topic" master`
if test -z "$not_in_topic"
then
echo >&2 "$topic is already up to date with master"
exit 1 ;# we could allow it, but there is no point.
else
exit 0
fi
else
not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
/usr/bin/perl -e '
my $topic = $ARGV[0];
my $msg = "* $topic has commits already merged to public branch:\n";
my (%not_in_next) = map {
/^([0-9a-f]+) /;
($1 => 1);
} split(/\n/, $ARGV[1]);
for my $elem (map {
/^([0-9a-f]+) (.*)$/;
[$1 => $2];
} split(/\n/, $ARGV[2])) {
if (!exists $not_in_next{$elem->[0]}) {
if ($msg) {
print STDERR $msg;
undef $msg;
}
print STDERR " $elem->[1]\n";
}
}
' "$topic" "$not_in_next" "$not_in_master"
exit 1
fi
<<\DOC_END
This sample hook safeguards topic branches that have been
published from being rewound.
The workflow assumed here is:
* Once a topic branch forks from "master", "master" is never
merged into it again (either directly or indirectly).
* Once a topic branch is fully cooked and merged into "master",
it is deleted. If you need to build on top of it to correct
earlier mistakes, a new topic branch is created by forking at
the tip of the "master". This is not strictly necessary, but
it makes it easier to keep your history simple.
* Whenever you need to test or publish your changes to topic
branches, merge them into "next" branch.
The script, being an example, hardcodes the publish branch name
to be "next", but it is trivial to make it configurable via
$GIT_DIR/config mechanism.
With this workflow, you would want to know:
(1) ... if a topic branch has ever been merged to "next". Young
topic branches can have stupid mistakes you would rather
clean up before publishing, and things that have not been
merged into other branches can be easily rebased without
affecting other people. But once it is published, you would
not want to rewind it.
(2) ... if a topic branch has been fully merged to "master".
Then you can delete it. More importantly, you should not
build on top of it -- other people may already want to
change things related to the topic as patches against your
"master", so if you need further changes, it is better to
fork the topic (perhaps with the same name) afresh from the
tip of "master".
Let's look at this example:
o---o---o---o---o---o---o---o---o---o "next"
/ / / /
/ a---a---b A / /
/ / / /
/ / c---c---c---c B /
/ / / \ /
/ / / b---b C \ /
/ / / / \ /
---o---o---o---o---o---o---o---o---o---o---o "master"
A, B and C are topic branches.
* A has one fix since it was merged up to "next".
* B has finished. It has been fully merged up to "master" and "next",
and is ready to be deleted.
* C has not merged to "next" at all.
We would want to allow C to be rebased, refuse A, and encourage
B to be deleted.
To compute (1):
git rev-list ^master ^topic next
git rev-list ^master next
if these match, topic has not merged in next at all.
To compute (2):
git rev-list master..topic
if this is empty, it is fully merged to "master".
DOC_END

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
data=$(cat)
exitcodes=""
hookname=$(basename $0)
GIT_DIR=${GIT_DIR:-$(dirname $0)/..}
for hook in ${GIT_DIR}/hooks/${hookname}.d/*; do
test -x "${hook}" && test -f "${hook}" || continue
echo "${data}" | "${hook}"
exitcodes="${exitcodes} $?"
done
for i in ${exitcodes}; do
[ ${i} -eq 0 ] || exit ${i}
done

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# AUTO GENERATED BY GITEA, DO NOT MODIFY
/usr/local/bin/gitea hook --config=/data/gitea/conf/app.ini pre-receive

Some files were not shown because too many files have changed in this diff Show More