105 lines
4.6 KiB
Bash
105 lines
4.6 KiB
Bash
#!/bin/sh
|
|
# GerbilManager — TrueNAS Custom-App Deploy/Redeploy (laeuft auf dem NAS-HOST)
|
|
# ===========================================================================
|
|
# Legt die App als TrueNAS "Custom App" an (sichtbar unter Apps) bzw. rollt sie
|
|
# neu aus. Wird vom Gitea-Actions-Deploy-Job per SSH aufgerufen UND kann jederzeit
|
|
# manuell auf dem NAS-Host ausgefuehrt werden:
|
|
#
|
|
# sh /opt/gerbilmanager/deploy/truenas/scripts/truenas-deploy.sh
|
|
#
|
|
# Voraussetzungen auf dem Host:
|
|
# * /opt/gerbilmanager/deploy/truenas/custom-app.compose.yaml (Vorlage)
|
|
# * /opt/gerbilmanager/deploy/truenas/.env (mit POSTGRES_PASSWORD etc. — NICHT im Repo)
|
|
# * midclt + docker im PATH (auf TrueNAS SCALE gegeben)
|
|
#
|
|
# Env-Overrides: COMPOSE_DIR, ENV_FILE, HEALTH_TIMEOUT
|
|
set -eu
|
|
|
|
APP_NAME=gerbilmanager
|
|
# TrueNAS Goldeye: / (inkl. /opt) ist read-only -> App-Home liegt auf dem Pool.
|
|
COMPOSE_DIR="${COMPOSE_DIR:-/mnt/JailStorage/DockerVolumes/gerbilmanager}"
|
|
DEPLOY_DIR="$COMPOSE_DIR/deploy/truenas"
|
|
ENV_FILE="${ENV_FILE:-$DEPLOY_DIR/.env}"
|
|
TEMPLATE="$DEPLOY_DIR/custom-app.compose.yaml"
|
|
HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-300}"
|
|
BASE=/mnt/JailStorage/DockerVolumes/gerbilmanager
|
|
|
|
log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"; }
|
|
fail() { log "FEHLER: $*"; exit 1; }
|
|
|
|
[ -f "$TEMPLATE" ] || fail "Vorlage fehlt: $TEMPLATE"
|
|
[ -f "$ENV_FILE" ] || fail ".env fehlt: $ENV_FILE (aus .env.example erstellen)"
|
|
command -v midclt >/dev/null 2>&1 || fail "midclt nicht im PATH"
|
|
command -v docker >/dev/null 2>&1 || fail "docker nicht im PATH"
|
|
|
|
# --- .env laden ---
|
|
set -a; . "$ENV_FILE"; set +a
|
|
: "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD fehlt in .env}"
|
|
export REGISTRY="${REGISTRY:-git.rismer.de/gulum}"
|
|
export TAG="${TAG:-latest}"
|
|
export PORT="${PORT:-8090}"
|
|
|
|
log "=== Deploy $APP_NAME (TAG=$TAG, PORT=$PORT) ==="
|
|
|
|
# --- Host-Verzeichnisse sicherstellen ---
|
|
for d in pgdata photos keys backups publicsite scripts; do mkdir -p "$BASE/$d"; done
|
|
chown -R 999:999 "$BASE/pgdata" 2>/dev/null || true
|
|
chmod 700 "$BASE/pgdata" 2>/dev/null || true
|
|
# Backup-Sidecar-Skripte an den absoluten Mount-Pfad kopieren
|
|
cp "$DEPLOY_DIR/scripts/backup.sh" "$DEPLOY_DIR/scripts/entrypoint.sh" \
|
|
"$DEPLOY_DIR/scripts/restore.sh" "$BASE/scripts/" 2>/dev/null || true
|
|
|
|
# --- Frische Images ziehen (garantiert :latest, unabhaengig vom pull_policy) ---
|
|
log "Ziehe Images ..."
|
|
docker pull "$REGISTRY/gerbilmanager-api:$TAG" || fail "docker pull api fehlgeschlagen"
|
|
docker pull "$REGISTRY/gerbilmanager-frontend:$TAG" || fail "docker pull frontend fehlgeschlagen"
|
|
|
|
# --- Vorlage rendern + midclt-Payload bauen (python: sichere Escapes) ---
|
|
PAYLOAD_FILE="$(mktemp)"
|
|
trap 'rm -f "$PAYLOAD_FILE"' EXIT
|
|
python3 - "$TEMPLATE" > "$PAYLOAD_FILE" <<'PY'
|
|
import sys, os, json
|
|
tmpl = open(sys.argv[1], encoding="utf-8").read()
|
|
repl = {
|
|
"__POSTGRES_PASSWORD__": os.environ["POSTGRES_PASSWORD"],
|
|
"__REGISTRY__": os.environ.get("REGISTRY", "git.rismer.de/gulum"),
|
|
"__TAG__": os.environ.get("TAG", "latest"),
|
|
"__PORT__": os.environ.get("PORT", "8090"),
|
|
"__AI_BASEURL__": os.environ.get("AI__BaseUrl", ""),
|
|
"__AI_APIKEY__": os.environ.get("AI__ApiKey", ""),
|
|
"__AI_MODEL__": os.environ.get("AI__Model", "gemini-flash-latest"),
|
|
}
|
|
for k, v in repl.items():
|
|
tmpl = tmpl.replace(k, v)
|
|
print(json.dumps({"custom_app": True, "app_name": "gerbilmanager",
|
|
"custom_compose_config_string": tmpl}))
|
|
PY
|
|
|
|
# --- Existiert die App schon? ---
|
|
EXISTS="$(midclt call app.query "[[\"name\",\"=\",\"$APP_NAME\"]]" 2>/dev/null | python3 -c 'import sys,json; print(len(json.load(sys.stdin)))' 2>/dev/null || echo 0)"
|
|
|
|
if [ "$EXISTS" -ge 1 ]; then
|
|
log "App existiert -> redeploy (frische Images sind bereits gezogen)"
|
|
midclt call app.redeploy "$APP_NAME" >/dev/null || fail "app.redeploy fehlgeschlagen"
|
|
else
|
|
log "App fehlt -> app.create (Custom App)"
|
|
midclt call app.create "$(cat "$PAYLOAD_FILE")" >/dev/null || fail "app.create fehlgeschlagen"
|
|
fi
|
|
|
|
# --- Auf Gesundheit warten (Frontend proxyt /api/health -> API) ---
|
|
log "Warte auf /api/health (max ${HEALTH_TIMEOUT}s) ..."
|
|
elapsed=0
|
|
while :; do
|
|
if curl -fsS "http://localhost:$PORT/api/health" 2>/dev/null | grep -qi 'healthy'; then
|
|
log "App ist gesund."; break
|
|
fi
|
|
[ "$elapsed" -ge "$HEALTH_TIMEOUT" ] && { \
|
|
log "Letzte App-Logs:"; midclt call app.query "[[\"name\",\"=\",\"$APP_NAME\"]]" >/dev/null 2>&1 || true; \
|
|
fail "App wurde nicht innerhalb ${HEALTH_TIMEOUT}s gesund"; }
|
|
sleep 5; elapsed=$((elapsed + 5))
|
|
done
|
|
|
|
log "Aufraeumen ungenutzter Images ..."
|
|
docker image prune -f >/dev/null 2>&1 || true
|
|
log "=== Deploy erfolgreich ($APP_NAME, TAG=$TAG) ==="
|