#!/usr/bin/env bash # # Startet die Anwendung für die lokale Entwicklung: Datenbank, Worker, Web. # # Das hier ist ausdrücklich **nicht** der Betrieb. Im Betrieb läuft alles als # ein Dockge-Stack, siehe docs/11-betrieb.md. Dieses Skript ist für den Fall, # dass der Stack gerade nicht läuft und man am Code arbeiten will. # # ./start.sh Datenbank, Worker und Web starten # ./start.sh --ohne-worker nur Datenbank und Web (kein Discord nötig) # ./start.sh --nur-db nur die Datenbank hochfahren # # Beenden mit Strg+C, das nimmt alle Kindprozesse mit. set -euo pipefail cd "$(dirname "$0")" DB_CONTAINER="afis-dev-db" DB_PORT="${AFIS_DB_PORT:-5434}" DB_IMAGE="postgres:16-alpine" MIT_WORKER=1 NUR_DB=0 for arg in "$@"; do case "$arg" in --ohne-worker) MIT_WORKER=0 ;; --nur-db) NUR_DB=1 ;; -h|--help) awk 'NR>1 && /^#/ { sub(/^# ?/, ""); print; next } NR>1 { exit }' "$0"; exit 0 ;; *) echo "Unbekannte Option: $arg" >&2; exit 2 ;; esac done info() { printf '\033[36m▸\033[0m %s\n' "$*"; } warn() { printf '\033[33m!\033[0m %s\n' "$*"; } fehler() { printf '\033[31m✗\033[0m %s\n' "$*" >&2; exit 1; } # --- Node aus nvm holen, falls es nicht schon im PATH liegt ------------------- if ! command -v node >/dev/null 2>&1; then if [ -s "$HOME/.nvm/nvm.sh" ]; then # shellcheck disable=SC1091 . "$HOME/.nvm/nvm.sh" >/dev/null nvm use --lts >/dev/null 2>&1 || nvm use node >/dev/null 2>&1 || true fi fi command -v node >/dev/null 2>&1 || fehler "Node nicht gefunden. Node 22 installieren oder nvm laden." if ! command -v pnpm >/dev/null 2>&1; then corepack enable >/dev/null 2>&1 || true fi command -v pnpm >/dev/null 2>&1 || fehler "pnpm nicht gefunden. 'corepack enable' ausführen." command -v docker >/dev/null 2>&1 || fehler "Docker nicht gefunden, die Datenbank läuft im Container." [ -f .env ] || fehler ".env fehlt. Von .env.example abschreiben und die Discord-Werte eintragen." # --- Datenbank --------------------------------------------------------------- if [ -n "$(docker ps -q -f "name=^${DB_CONTAINER}$")" ]; then info "Datenbank läuft bereits" elif [ -n "$(docker ps -aq -f "name=^${DB_CONTAINER}$")" ]; then info "Datenbank starten" docker start "$DB_CONTAINER" >/dev/null else info "Datenbank anlegen (Port ${DB_PORT})" docker run -d --name "$DB_CONTAINER" \ -e POSTGRES_DB=afis -e POSTGRES_USER=afis -e POSTGRES_PASSWORD=afis \ -p "${DB_PORT}:5432" "$DB_IMAGE" >/dev/null \ || fehler "Container nicht gestartet. Läuft schon etwas auf Port ${DB_PORT}?" fi printf ' warte auf Postgres' for _ in $(seq 1 60); do if docker exec "$DB_CONTAINER" pg_isready -U afis -d afis >/dev/null 2>&1; then printf ' bereit\n' break fi printf '.' sleep 1 done docker exec "$DB_CONTAINER" pg_isready -U afis -d afis >/dev/null 2>&1 \ || fehler "Postgres antwortet nicht." # --- Schema und Grundzustand ------------------------------------------------- info "Prisma-Client erzeugen" pnpm --filter @afis/db run build >/dev/null info "Migrationen anwenden" pnpm db:deploy >/dev/null info "Grundzustand einspielen" pnpm db:seed >/dev/null # --- Dateiablage ------------------------------------------------------------- # Im Stack ist das ein Volume, hier ein Verzeichnis. Ohne es scheitert der # erste Upload mit einem Fehler, der nichts erklärt. FILES_DIR_LOKAL="$(grep -E '^FILES_DIR=' .env 2>/dev/null | cut -d= -f2- || true)" FILES_DIR_LOKAL="${FILES_DIR_LOKAL:-/tmp/afis-files}" mkdir -p "${FILES_DIR_LOKAL}/quarantine" "${FILES_DIR_LOKAL}/store" info "Dateiablage unter ${FILES_DIR_LOKAL}" # Die Prüfkette braucht beides. Fehlt eines, bleiben die betroffenen Uploads in # der Quarantäne — sie gehen nicht ungeprüft durch. Siehe docs/07. CLAMAV_SOCKET_LOKAL="$(grep -E '^CLAMAV_SOCKET=' .env 2>/dev/null | cut -d= -f2- || true)" [ -S "${CLAMAV_SOCKET_LOKAL:-/var/run/clamav/clamd.sock}" ] \ || warn "Kein clamd erreichbar: Uploads landen auf 'Prüfung unvollständig' und brauchen die Freigabe von Hand" command -v qpdf >/dev/null \ || warn "qpdf fehlt: PDF-Uploads landen auf 'Prüfung unvollständig' (im Container ist es enthalten)" if [ "$NUR_DB" = "1" ]; then info "Datenbank läuft auf localhost:${DB_PORT}. Beenden mit: docker stop ${DB_CONTAINER}" exit 0 fi # --- Kindprozesse ------------------------------------------------------------ PIDS=() aufraeumen() { echo info "Beenden" for pid in "${PIDS[@]:-}"; do kill "$pid" 2>/dev/null || true done wait 2>/dev/null || true info "Die Datenbank läuft weiter. Anhalten mit: docker stop ${DB_CONTAINER}" } trap aufraeumen INT TERM EXIT if [ "$MIT_WORKER" = "1" ]; then info "Worker starten (Discord-Rollenabgleich)" pnpm worker:dev 2>&1 | sed 's/^/[worker] /' & PIDS+=($!) fi info "Web starten" pnpm dev 2>&1 | sed 's/^/[web] /' & PIDS+=($!) echo info "Anwendung unter http://localhost:3000" [ "$MIT_WORKER" = "1" ] || warn "Ohne Worker: Rollen werden nicht abgeglichen, /api/health meldet das Gateway als gestört" echo wait