diff --git a/deploy/install.sh b/deploy/install.sh index a7b8de8..7dbd312 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -11,6 +11,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="$SCRIPT_DIR/.env" +EXAMPLE_FILE="$SCRIPT_DIR/.env.example" ASSUME_YES=0 [[ "${1:-}" == "--yes" || "${1:-}" == "-y" ]] && ASSUME_YES=1 @@ -105,14 +106,57 @@ ensure_docker() { fi } +# --- Beispiel-.env sicherstellen ---------------------------------------------- +ensure_example() { + if [[ -f "$EXAMPLE_FILE" ]]; then + return + fi + info "Lege Beispiel-Konfiguration an: $EXAMPLE_FILE" + cat > "$EXAMPLE_FILE" <<'EOF' +# Kopiere diese Datei zu ".env" und passe die Werte an. +# install.sh erzeugt eine .env automatisch mit sicheren Zufallswerten. + +# Port, unter dem die Web-UI erreichbar ist +WEB_PORT=8080 + +# Datenbank +POSTGRES_USER=project_good +POSTGRES_PASSWORD=project_good +POSTGRES_DB=project_good + +# Sicherheit – im Betrieb unbedingt ändern! +JWT_SECRET=change-me-please + +# Erster Admin-Benutzer (wird nur beim allerersten Start angelegt) +ADMIN_USERNAME=admin +ADMIN_PASSWORD=changeme + +# CORS (bei Betrieb hinter dem mitgelieferten nginx nicht nötig) +CORS_ORIGINS=* +EOF +} + +# --- Hinweis auf die Konfigurationsdatei -------------------------------------- +env_hint() { + echo + info "Konfiguration liegt in: $ENV_FILE" + echo " Vorlage/Beispiel: $EXAMPLE_FILE" + warn "Dort kannst du bei Bedarf Werte anpassen: WEB_PORT (Port der Web-UI)," + warn "ADMIN_USERNAME/ADMIN_PASSWORD sowie die Datenbank- und JWT-Secrets." + warn "Nach Änderungen an der .env neu starten: $COMPOSE --env-file .env up -d" +} + # --- .env erzeugen ------------------------------------------------------------ create_env() { + ensure_example + if [[ -f "$ENV_FILE" ]]; then info ".env existiert bereits – bestehende Konfiguration wird verwendet." + env_hint return fi - info "Erzeuge Konfiguration (.env) …" + info "Erzeuge Konfiguration (.env) aus den Beispiel-Vorgaben …" local web_port="8080" local admin_user="admin" @@ -141,6 +185,25 @@ EOF GENERATED_ADMIN_USER="$admin_user" GENERATED_ADMIN_PASS="$admin_pass" GENERATED_WEB_PORT="$web_port" + + env_hint +} + +# --- Warten bis das Backend über die Web-UI erreichbar ist -------------------- +wait_for_backend() { + local port="$1" tries=30 i + ensure_curl + info "Warte darauf, dass das Backend bereit ist …" + for ((i = 1; i <= tries; i++)); do + if curl -fsS "http://localhost:${port}/api/health" >/dev/null 2>&1; then + info "Backend ist erreichbar. ✅" + return 0 + fi + sleep 2 + done + warn "Backend antwortet nach ~$((tries * 2))s noch nicht – das erklärt einen 502 in der Web-UI." + warn "Bitte die Logs prüfen: ( cd \"$SCRIPT_DIR\" && $COMPOSE logs backend )" + return 1 } # --- Start -------------------------------------------------------------------- @@ -161,6 +224,8 @@ main() { web_port="$(grep -E '^WEB_PORT=' "$ENV_FILE" | cut -d= -f2-)" admin_user="$(grep -E '^ADMIN_USERNAME=' "$ENV_FILE" | cut -d= -f2-)" + wait_for_backend "$web_port" || true + echo info "Fertig! 🎉" echo " Web-UI: http://localhost:${web_port} (bzw. http://:${web_port})" diff --git a/web/src/api.js b/web/src/api.js index 2696feb..f9b8232 100644 --- a/web/src/api.js +++ b/web/src/api.js @@ -49,14 +49,22 @@ async function request(path, { method = "GET", body, form } = {}) { } if (!resp.ok) { - const detail = - (data && data.detail) || - (typeof data === "string" ? data : null) || - `Fehler ${resp.status}`; - throw new ApiError( - Array.isArray(detail) ? detail.map((d) => d.msg).join(", ") : detail, - resp.status - ); + let detail; + if (data && data.detail) { + detail = Array.isArray(data.detail) + ? data.detail.map((d) => d.msg).join(", ") + : data.detail; + } else if (resp.status === 502 || resp.status === 503 || resp.status === 504) { + detail = `Server nicht erreichbar (${resp.status}). Läuft der Backend-Dienst? Prüfe: docker compose logs backend`; + } else if (typeof data === "string" && data.trim().startsWith("<")) { + // HTML-Fehlerseite (z.B. von nginx) nicht roh anzeigen + detail = `Server-Fehler (${resp.status}).`; + } else if (typeof data === "string" && data) { + detail = data; + } else { + detail = `Fehler ${resp.status}`; + } + throw new ApiError(detail, resp.status); } return data; }