rebreak-monorepo/ops/pir-server/build-and-deploy.sh
chahinebrini 29bbf23405 feat(protection): iOS NEURLFilter-Spike + PIR-Server-Ops
NEURLFilter-Stack (iOS 26): Extension RebreakURLFilter -> URLFilterExtension
umbenannt, url-filter-provider-Entitlement, Bloom-Prefilter-Extension,
PIR-Client-Config (pirServerURL/pirAuthToken via Build-Env).
PIR-Server-Ops unter ops/pir-server/ (Dockerfile, build-and-deploy, Patches,
DTS-Report). backend/scripts/generate-pir-input.ts erzeugt die PIR-Datenbank.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 18:09:42 +02:00

222 lines
7.7 KiB
Bash

#!/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