#!/usr/bin/env bash # # Certain Finance – nativer Installer für einen Debian/Ubuntu-LXC (Proxmox). # Richtet Python-venv, gebautes Frontend, systemd-Dienst und Konfiguration ein. # HTTPS macht dein vorhandener Nginx Proxy Manager – hier wird KEIN Reverse-Proxy # installiert; der Dienst lauscht nur intern auf 127.0.0.1:. # # Nutzung (als root im geklonten Repo): # ./deploy/install.sh interaktiv (fragt Port/Admin) # ./deploy/install.sh --yes nicht-interaktiv (Zufalls-Admin-Passwort) # set -euo pipefail APP_SLUG="certain-finance" # Einzige Stelle mit dem Betriebs-Slug (Dienst/Pfade) DEFAULT_PORT=8071 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" ETC_DIR="/etc/$APP_SLUG" ENV_FILE="$ETC_DIR/env" DATA_DIR="/var/lib/$APP_SLUG" SERVICE_USER="$APP_SLUG" UNIT_FILE="/etc/systemd/system/$APP_SLUG.service" ASSUME_YES=0 [[ "${1:-}" == "--yes" || "${1:-}" == "-y" ]] && ASSUME_YES=1 info() { printf '\033[0;32m==>\033[0m %s\n' "$*"; } warn() { printf '\033[0;33m[!]\033[0m %s\n' "$*"; } error() { printf '\033[0;31m[x]\033[0m %s\n' "$*" >&2; } rand() { LC_ALL=C tr -dc 'A-Za-z0-9' /dev/null 2>&1; then error "Dieses Skript ist für Debian/Ubuntu (apt) gedacht. Kein apt-get gefunden." exit 1 fi # --- Systempakete ------------------------------------------------------------ info "Installiere Systempakete (Python, Build-Tools, git, curl) …" export DEBIAN_FRONTEND=noninteractive apt-get update -y apt-get install -y python3 python3-venv python3-dev build-essential curl ca-certificates git # --- Node.js (nur zum Bauen des Frontends nötig) ----------------------------- ensure_node() { local major=0 if command -v node >/dev/null 2>&1; then major="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)" fi if [[ "$major" -ge 18 ]]; then info "Node.js gefunden: $(node --version)" return fi warn "Node.js fehlt oder ist zu alt – installiere Node 20 LTS (NodeSource) …" curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt-get install -y nodejs info "Node.js installiert: $(node --version)" } ensure_node # --- Dienst-Benutzer und Verzeichnisse -------------------------------------- if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then info "Lege Dienst-Benutzer '$SERVICE_USER' an …" useradd --system --user-group --home-dir "$DATA_DIR" --shell /usr/sbin/nologin "$SERVICE_USER" fi mkdir -p "$DATA_DIR" "$ETC_DIR" chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR" # --- Python-venv + Abhängigkeiten ------------------------------------------- info "Richte Python-venv ein und installiere Abhängigkeiten …" python3 -m venv "$REPO_DIR/backend/.venv" "$REPO_DIR/backend/.venv/bin/pip" install --upgrade pip >/dev/null "$REPO_DIR/backend/.venv/bin/pip" install -r "$REPO_DIR/backend/requirements.txt" # --- Frontend bauen ---------------------------------------------------------- info "Baue das Frontend (Node wird nur hier gebraucht) …" ( cd "$REPO_DIR/web" if [[ -f package-lock.json ]]; then npm ci; else npm install; fi npm run build ) # --- Konfiguration (.env) ---------------------------------------------------- GIT_SHA="$(git -C "$REPO_DIR" rev-parse --short HEAD 2>/dev/null || echo '')" if [[ -f "$ENV_FILE" ]]; then info "Konfiguration existiert bereits: $ENV_FILE (unverändert übernommen)." # Git-Commit aktualisieren (für /api/version). if grep -q '^APP_GIT_SHA=' "$ENV_FILE"; then sed -i "s|^APP_GIT_SHA=.*|APP_GIT_SHA=$GIT_SHA|" "$ENV_FILE" else echo "APP_GIT_SHA=$GIT_SHA" >> "$ENV_FILE" fi ADMIN_USER="$(grep -E '^APP_ADMIN_USERNAME=' "$ENV_FILE" | cut -d= -f2-)" GENERATED_PASS="" else local_port="$DEFAULT_PORT" admin_user="admin" admin_pass="$(rand 16)" if [[ "$ASSUME_YES" -eq 0 ]]; then read -r -p "Interner Port [$DEFAULT_PORT]: " x; local_port="${x:-$DEFAULT_PORT}" read -r -p "Admin-Benutzername [admin]: " x; admin_user="${x:-admin}" read -r -s -p "Admin-Passwort [leer = zufällig]: " x; echo [[ -n "$x" ]] && admin_pass="$x" fi info "Erzeuge Konfiguration: $ENV_FILE" cat > "$ENV_FILE" < "$UNIT_FILE" </dev/null 2>&1 || true info "Starte Dienst … (wendet Migrationen an und legt ggf. den ersten Admin an)" systemctl restart "$APP_SLUG" # --- Gesundheitscheck -------------------------------------------------------- PORT="$(grep -E '^APP_PORT=' "$ENV_FILE" | cut -d= -f2-)" PORT="${PORT:-$DEFAULT_PORT}" info "Warte auf das Backend (127.0.0.1:$PORT) …" ok=0 for _ in $(seq 1 30); do if curl -fsS "http://127.0.0.1:$PORT/api/health" >/dev/null 2>&1; then ok=1; break; fi sleep 2 done echo if [[ "$ok" -eq 1 ]]; then info "Fertig – Certain Finance läuft. 🎉" else warn "Backend antwortet noch nicht. Logs: journalctl -u $APP_SLUG -e" fi echo " Interner Dienst: http://127.0.0.1:$PORT (nur lokal)" echo " Admin-Login: ${ADMIN_USER:-admin}" if [[ -n "${GENERATED_PASS:-}" ]]; then echo " Admin-Passwort: ${GENERATED_PASS}" warn "Bitte dieses Passwort notieren – es wird nicht erneut angezeigt." fi echo echo " Nginx Proxy Manager: neuen Proxy-Host anlegen ->" echo " Forward Hostname/IP: " echo " Forward Port: $PORT" echo " SSL: Let's Encrypt aktivieren; 'Block Common Exploits' ok." echo " (Websockets nicht nötig. Für spätere Uploads client_max_body_size >= 20M.)" echo echo " Logs: journalctl -u $APP_SLUG -f" echo " Neustart: systemctl restart $APP_SLUG" echo " Update: ./deploy/update.sh"