feat(deploy): publicsite-Container intern (Port 8081) + app.update im Deploy-Skript
- custom-app.compose.yaml: publicsite (nginx) ergaenzt, LAN-only, statisches HTML aus publicsite-Volume (POST /api/publish); PUBLICSITE_PORT-Platzhalter - truenas-deploy.sh: bei existierender App app.update (statt nur redeploy), damit Compose-Aenderungen (neue Dienste) uebernommen werden - CLAUDE.md: 5. Dienst dokumentiert Verifiziert: publicsite Up auf 8081, publish -> HTTP 200 (inkl. abgabetiere-Seite). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,8 +42,11 @@ Prod läuft als **TrueNAS SCALE Custom-App** `gerbilmanager` auf dem Host **`tru
|
|||||||
- **Sichtbar unter Apps**, weil via `midclt call app.create {custom_app:true, app_name:"gerbilmanager",
|
- **Sichtbar unter Apps**, weil via `midclt call app.create {custom_app:true, app_name:"gerbilmanager",
|
||||||
custom_compose_config_string:<yaml>}` angelegt — **nicht** via `docker compose` (das taucht in Apps
|
custom_compose_config_string:<yaml>}` angelegt — **nicht** via `docker compose` (das taucht in Apps
|
||||||
NICHT auf). Container: `ix-gerbilmanager-{db,api,frontend,backup}-1`.
|
NICHT auf). Container: `ix-gerbilmanager-{db,api,frontend,backup}-1`.
|
||||||
- **4 Dienste:** `db` (postgres:18) · `api` (.NET, EF-Migrationen laufen beim Start) · `frontend`
|
- **5 Dienste:** `db` (postgres:18) · `api` (.NET, EF-Migrationen laufen beim Start) · `frontend`
|
||||||
(nginx: SPA + `/api`-Proxy → `api:8080`) · `backup` (täglich 03:00 pg_dump + Foto-Archiv, Shell-Scheduler).
|
(nginx: SPA + `/api`-Proxy → `api:8080`, Host-Port 8090) · `publicsite` (nginx, **nur intern**,
|
||||||
|
Host-Port **8081** — serviert statisches HTML aus dem `publicsite`-Volume, das die API via
|
||||||
|
`POST /api/publish` schreibt; ohne Publish leer → 404) · `backup` (täglich 03:00 pg_dump +
|
||||||
|
Foto-Archiv, Shell-Scheduler).
|
||||||
- **App-Home auf dem Pool:** `/mnt/JailStorage/DockerVolumes/gerbilmanager/` mit
|
- **App-Home auf dem Pool:** `/mnt/JailStorage/DockerVolumes/gerbilmanager/` mit
|
||||||
`pgdata/ photos/ keys/ backups/ scripts/ publicsite/` und `deploy/truenas/{custom-app.compose.yaml,.env,scripts}`.
|
`pgdata/ photos/ keys/ backups/ scripts/ publicsite/` und `deploy/truenas/{custom-app.compose.yaml,.env,scripts}`.
|
||||||
Images aus der Gitea-Registry `git.rismer.de/gulum/gerbilmanager-{api,frontend}:latest`.
|
Images aus der Gitea-Registry `git.rismer.de/gulum/gerbilmanager-{api,frontend}:latest`.
|
||||||
|
|||||||
@@ -79,6 +79,22 @@ services:
|
|||||||
api:
|
api:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
|
# --- Oeffentliche Seite (WEB-2), vorerst nur INTERN gehostet ---
|
||||||
|
# Serviert NUR statisches HTML aus dem publicsite-Volume (live/), das die API via
|
||||||
|
# POST /api/publish schreibt. Kein Proxy auf api/frontend. Solange nichts publiziert
|
||||||
|
# wurde, ist das Volume leer -> 404 (erwartet). Extern (Domain/TLS) ist ein spaeterer
|
||||||
|
# Schritt ueber Julians Reverse-Proxy (siehe deploy/truenas/vhost-snippet.conf).
|
||||||
|
publicsite:
|
||||||
|
image: nginx:alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "__PUBLICSITE_PORT__:80"
|
||||||
|
volumes:
|
||||||
|
- /mnt/JailStorage/DockerVolumes/gerbilmanager/publicsite:/usr/share/nginx/html:ro
|
||||||
|
- /mnt/JailStorage/DockerVolumes/gerbilmanager/deploy/truenas/nginx/publicsite.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
|
||||||
# --- Backup-Sidecar (taeglicher pg_dump + Foto-Archiv + Rotation) ---
|
# --- Backup-Sidecar (taeglicher pg_dump + Foto-Archiv + Rotation) ---
|
||||||
backup:
|
backup:
|
||||||
image: postgres:18
|
image: postgres:18
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ set -a; . "$ENV_FILE"; set +a
|
|||||||
export REGISTRY="${REGISTRY:-git.rismer.de/gulum}"
|
export REGISTRY="${REGISTRY:-git.rismer.de/gulum}"
|
||||||
export TAG="${TAG:-latest}"
|
export TAG="${TAG:-latest}"
|
||||||
export PORT="${PORT:-8090}"
|
export PORT="${PORT:-8090}"
|
||||||
|
export PUBLICSITE_PORT="${PUBLICSITE_PORT:-8081}"
|
||||||
|
|
||||||
log "=== Deploy $APP_NAME (TAG=$TAG, PORT=$PORT) ==="
|
log "=== Deploy $APP_NAME (TAG=$TAG, PORT=$PORT) ==="
|
||||||
|
|
||||||
@@ -54,36 +55,49 @@ log "Ziehe Images ..."
|
|||||||
docker pull "$REGISTRY/gerbilmanager-api:$TAG" || fail "docker pull api fehlgeschlagen"
|
docker pull "$REGISTRY/gerbilmanager-api:$TAG" || fail "docker pull api fehlgeschlagen"
|
||||||
docker pull "$REGISTRY/gerbilmanager-frontend:$TAG" || fail "docker pull frontend fehlgeschlagen"
|
docker pull "$REGISTRY/gerbilmanager-frontend:$TAG" || fail "docker pull frontend fehlgeschlagen"
|
||||||
|
|
||||||
# --- Vorlage rendern + midclt-Payload bauen (python: sichere Escapes) ---
|
# --- Vorlage rendern (python: sichere Substitution) ---
|
||||||
PAYLOAD_FILE="$(mktemp)"
|
RENDERED="$(mktemp)"
|
||||||
trap 'rm -f "$PAYLOAD_FILE"' EXIT
|
trap 'rm -f "$RENDERED"' EXIT
|
||||||
python3 - "$TEMPLATE" > "$PAYLOAD_FILE" <<'PY'
|
python3 - "$TEMPLATE" > "$RENDERED" <<'PY'
|
||||||
import sys, os, json
|
import sys, os
|
||||||
tmpl = open(sys.argv[1], encoding="utf-8").read()
|
tmpl = open(sys.argv[1], encoding="utf-8").read()
|
||||||
repl = {
|
repl = {
|
||||||
"__POSTGRES_PASSWORD__": os.environ["POSTGRES_PASSWORD"],
|
"__POSTGRES_PASSWORD__": os.environ["POSTGRES_PASSWORD"],
|
||||||
"__REGISTRY__": os.environ.get("REGISTRY", "git.rismer.de/gulum"),
|
"__REGISTRY__": os.environ.get("REGISTRY", "git.rismer.de/gulum"),
|
||||||
"__TAG__": os.environ.get("TAG", "latest"),
|
"__TAG__": os.environ.get("TAG", "latest"),
|
||||||
"__PORT__": os.environ.get("PORT", "8090"),
|
"__PORT__": os.environ.get("PORT", "8090"),
|
||||||
|
"__PUBLICSITE_PORT__": os.environ.get("PUBLICSITE_PORT", "8081"),
|
||||||
"__AI_BASEURL__": os.environ.get("AI__BaseUrl", ""),
|
"__AI_BASEURL__": os.environ.get("AI__BaseUrl", ""),
|
||||||
"__AI_APIKEY__": os.environ.get("AI__ApiKey", ""),
|
"__AI_APIKEY__": os.environ.get("AI__ApiKey", ""),
|
||||||
"__AI_MODEL__": os.environ.get("AI__Model", "gemini-flash-latest"),
|
"__AI_MODEL__": os.environ.get("AI__Model", "gemini-flash-latest"),
|
||||||
}
|
}
|
||||||
for k, v in repl.items():
|
for k, v in repl.items():
|
||||||
tmpl = tmpl.replace(k, v)
|
tmpl = tmpl.replace(k, v)
|
||||||
print(json.dumps({"custom_app": True, "app_name": "gerbilmanager",
|
sys.stdout.write(tmpl)
|
||||||
"custom_compose_config_string": tmpl}))
|
|
||||||
PY
|
PY
|
||||||
|
|
||||||
|
# midclt-Payload bauen: create braucht app_name+custom_app, update nur die Compose.
|
||||||
|
build_payload() { # $1 = create|update
|
||||||
|
python3 - "$RENDERED" "$1" <<'PY'
|
||||||
|
import sys, json
|
||||||
|
compose = open(sys.argv[1], encoding="utf-8").read()
|
||||||
|
if sys.argv[2] == "create":
|
||||||
|
print(json.dumps({"custom_app": True, "app_name": "gerbilmanager",
|
||||||
|
"custom_compose_config_string": compose}))
|
||||||
|
else:
|
||||||
|
print(json.dumps({"custom_compose_config_string": compose}))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
# --- Existiert die App schon? ---
|
# --- 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)"
|
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
|
if [ "$EXISTS" -ge 1 ]; then
|
||||||
log "App existiert -> redeploy (frische Images sind bereits gezogen)"
|
log "App existiert -> app.update (Compose-Aenderungen uebernehmen, frische Images gezogen)"
|
||||||
midclt call app.redeploy "$APP_NAME" >/dev/null || fail "app.redeploy fehlgeschlagen"
|
midclt call app.update "$APP_NAME" "$(build_payload update)" >/dev/null || fail "app.update fehlgeschlagen"
|
||||||
else
|
else
|
||||||
log "App fehlt -> app.create (Custom App)"
|
log "App fehlt -> app.create (Custom App)"
|
||||||
midclt call app.create "$(cat "$PAYLOAD_FILE")" >/dev/null || fail "app.create fehlgeschlagen"
|
midclt call app.create "$(build_payload create)" >/dev/null || fail "app.create fehlgeschlagen"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Auf Gesundheit warten (Frontend proxyt /api/health -> API) ---
|
# --- Auf Gesundheit warten (Frontend proxyt /api/health -> API) ---
|
||||||
|
|||||||
Reference in New Issue
Block a user