#!/usr/bin/env bash # build-and-deploy.sh — PIR-Server auf Hetzner bauen und deployen # # Voraussetzungen (auf dem Server): # - /srv/pir-build/pir-service-example/ → geklont # - /srv/pir-build/swift-homomorphic-encryption/ → geklont # - /srv/pir-server/data/ → existiert # - /srv/pir-server/config/ → existiert # - /etc/environment enthält INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET # # Was dieses Script macht: # 1. Infisical-Token holen (Universal Auth) # 2. PIR_AUTH_TOKEN aus Infisical staging lesen # 3. service-config.json mit realem Token nach /srv/pir-server/config/ schreiben # 3b. Quell-Patches auf pir-service-example anwenden (patches/*.patch) # 4. Docker-Image bauen (Multi-Stage, ~20-30 Min wegen Swift-Compile) # 5. Test-input.txtpb generieren falls keine echte DB vorhanden # 6. PIRProcessDatabase ausführen (innerhalb Container) # 7. Container starten / neu starten (inkl. PIR_ISSUER_REQUEST_URI env) set -euo pipefail BUILD_DIR="/srv/pir-build" DATA_DIR="/srv/pir-server/data" CONFIG_DIR="/srv/pir-server/config" IMAGE_NAME="pir-service-staging" CONTAINER_NAME="pir-service-staging" PORT="8090" INFISICAL_PROJECT_ID="14b11b35-ef59-4b8a-a16b-398f0cc3ad93" PROCESS_CONFIG_SRC="$(dirname "$0")/process-config.json" PATCH_DIR="$(dirname "$0")/patches" PIR_SERVICE_SRC="${BUILD_DIR}/pir-service-example" # Public-URL des PIR-Servers. issuer-request-uri MUSS absolut sein (RFC 9578 §6), # sonst kann der NEURLFilter-Client keinen Privacy-Pass-Token holen. PIR_PUBLIC_URL="https://pir.staging.rebreak.org" PIR_ISSUER_REQUEST_URI="${PIR_PUBLIC_URL}/issue" log() { echo "[pir-deploy] $(date '+%H:%M:%S') $*"; } log_err() { echo "[pir-deploy:err] $(date '+%H:%M:%S') $*" >&2; } log "=== PIR-Server Deploy gestartet ===" # 0. Environment laden if [[ -f /etc/environment ]]; then export $(grep -v "^#" /etc/environment | grep -v "^$" | xargs) fi # 1. Infisical-Token holen log "Step 1: Infisical Universal-Auth Login..." INFISICAL_TOKEN=$(infisical login \ --method=universal-auth \ --client-id="${INFISICAL_CLIENT_ID}" \ --client-secret="${INFISICAL_CLIENT_SECRET}" \ --silent --plain 2>/dev/null) if [[ -z "$INFISICAL_TOKEN" ]]; then log_err "Infisical login fehlgeschlagen" exit 1 fi log "Infisical Login ok" # 2. PIR_AUTH_TOKEN holen log "Step 2: PIR_AUTH_TOKEN aus Infisical (staging) holen..." PIR_AUTH_TOKEN=$(infisical secrets get PIR_AUTH_TOKEN \ --env=staging \ --projectId="${INFISICAL_PROJECT_ID}" \ --token="${INFISICAL_TOKEN}" \ --plain 2>/dev/null) if [[ -z "$PIR_AUTH_TOKEN" ]]; then log_err "PIR_AUTH_TOKEN nicht in Infisical gefunden (env=staging, project=${INFISICAL_PROJECT_ID})" log_err "Bitte via Infisical-Dashboard anlegen: Key=PIR_AUTH_TOKEN, Env=staging" exit 1 fi log "PIR_AUTH_TOKEN geladen (${#PIR_AUTH_TOKEN} Zeichen)" # 3. service-config.json schreiben (Token inline, nicht in Datei committed) log "Step 3: service-config.json nach ${CONFIG_DIR}/ schreiben..." # Shard-Count aus vorhandenen url-N.bin-Artifacts ableiten. # Bugfix: vorher fix `SHARD_COUNT=1` — bei Re-Runs ohne PIRProcessDatabase (Step 6 # wird übersprungen wenn Artifacts existieren) regressierte das einen 4-Shard-Server # fälschlich auf shardCount=1. SHARD_COUNT=$(ls "${DATA_DIR}"/url-*.bin 2>/dev/null | wc -l | tr -d ' ') [[ "$SHARD_COUNT" -lt 1 ]] && SHARD_COUNT=1 log "Shard-Count aus ${DATA_DIR}: ${SHARD_COUNT}" cat > "${CONFIG_DIR}/service-config.json" << EOF { "users": [ { "tier": "tier1", "tokens": ["${PIR_AUTH_TOKEN}"] } ], "usecases": [ { "name": "org.rebreak.app.url.filtering", "fileStem": "/data/url", "shardCount": ${SHARD_COUNT} } ] } EOF log "service-config.json geschrieben" # 3b. Quell-Patches auf pir-service-example anwenden (idempotent) log "Step 3b: Quell-Patches anwenden..." if [[ -d "$PATCH_DIR" ]] && compgen -G "${PATCH_DIR}/*.patch" > /dev/null; then # Clone auf sauberen Stand bringen, dann alle Patches anwenden git -C "$PIR_SERVICE_SRC" checkout -- . 2>/dev/null || true for p in "${PATCH_DIR}"/*.patch; do log " apply $(basename "$p")" git -C "$PIR_SERVICE_SRC" apply "$p" done else log " keine Patches gefunden — übersprungen" fi # 4. Docker-Image bauen log "Step 4: Docker-Image bauen (Kontext: ${BUILD_DIR})..." log "WARNUNG: Swift-Build dauert ~20-30 Min, RAM-Nutzung hoch (~2-3 GB). Swap wird benötigt." docker build \ -t "${IMAGE_NAME}:latest" \ -f "$(dirname "$0")/Dockerfile" \ "${BUILD_DIR}" log "Docker-Image gebaut: ${IMAGE_NAME}:latest" # 5. Test-input.txtpb generieren falls keine echte DB log "Step 5: Input-DB prüfen..." bash "$(dirname "$0")/gen-test-input.sh" "${DATA_DIR}/input.txtpb" # 6. PIRProcessDatabase ausführen (im Container, mounted /data) log "Step 6: PIRProcessDatabase ausführen..." # Prüfe ob DB-Artifacts schon vorhanden (von vorherigem Run) if [[ -f "${DATA_DIR}/url-0.bin" ]] && [[ -f "${DATA_DIR}/url-0.params.txtpb" ]]; then log "DB-Artifacts bereits vorhanden (url-0.bin + url-0.params.txtpb) — überspringe PIRProcessDatabase" log "Für Rebuild: rm ${DATA_DIR}/url-0.bin ${DATA_DIR}/url-0.params.txtpb && re-run" else # process-config.json in data-dir kopieren (PIRProcessDatabase braucht absolute Pfade) cp "${PROCESS_CONFIG_SRC}" "${DATA_DIR}/process-config.json" docker run --rm \ -v "${DATA_DIR}:/data" \ "${IMAGE_NAME}:latest" \ PIRProcessDatabase /data/process-config.json log "PIRProcessDatabase abgeschlossen" # Shard-Count aus Output ermitteln SHARD_COUNT=$(ls "${DATA_DIR}"/url-*.bin 2>/dev/null | wc -l) log "Erkannte Shards: ${SHARD_COUNT}" # service-config.json mit korrektem shardCount neu schreiben if [[ "$SHARD_COUNT" -gt 1 ]]; then log "Mehr als 1 Shard — service-config.json updaten..." cat > "${CONFIG_DIR}/service-config.json" << EOF { "users": [ { "tier": "tier1", "tokens": ["${PIR_AUTH_TOKEN}"] } ], "usecases": [ { "name": "org.rebreak.app.url.filtering", "fileStem": "/data/url", "shardCount": ${SHARD_COUNT} } ] } EOF log "service-config.json mit shardCount=${SHARD_COUNT} aktualisiert" fi fi # 7. Container starten / neu starten log "Step 7: Container starten..." # Alten Container stoppen falls vorhanden if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then log "Stoppe alten Container ${CONTAINER_NAME}..." docker stop "${CONTAINER_NAME}" 2>/dev/null || true docker rm "${CONTAINER_NAME}" 2>/dev/null || true fi docker run -d \ --name "${CONTAINER_NAME}" \ --restart unless-stopped \ -p "127.0.0.1:${PORT}:8090" \ -v "${DATA_DIR}:/data:ro" \ -v "${CONFIG_DIR}:/config:ro" \ -e "PIR_ISSUER_REQUEST_URI=${PIR_ISSUER_REQUEST_URI}" \ "${IMAGE_NAME}:latest" log "Container ${CONTAINER_NAME} gestartet auf 127.0.0.1:${PORT}" # 8. Health-Check log "Step 8: Health-Check (warte 5s auf Start)..." sleep 5 # Issuer-Directory muss erreichbar sein UND eine ABSOLUTE issuer-request-uri liefern. # (PrivateToken-Auth lässt sich ohne echten Privacy-Pass-Token nicht testen — der # PIR_AUTH_TOKEN ist nur der User-Token für /issue, nicht der /config-Token.) DIRECTORY=$(curl -s "http://127.0.0.1:${PORT}/.well-known/private-token-issuer-directory" 2>/dev/null || echo "") if echo "$DIRECTORY" | grep -q '"issuer-request-uri":"https://'; then log "Health-Check OK — Issuer-Directory liefert absolute issuer-request-uri" log "=== Deploy erfolgreich abgeschlossen ===" else log_err "Health-Check fehlgeschlagen — Issuer-Directory unerwartet:" log_err "$DIRECTORY" log_err "Container-Logs:" docker logs "${CONTAINER_NAME}" --tail 30 log_err "Deploy FEHLGESCHLAGEN — manueller Check erforderlich" exit 1 fi