feat: Phase 0 – Gerüst (FastAPI+SQLite, Multi-User-Login, React-App-Shell, Deploy)

Grundgerüst für Certain Finance:
- Backend: FastAPI + SQLAlchemy + SQLite, Alembic-Migrationen (Batch-Mode),
  argon2-Passwort-Hashing, server-seitige Sitzungen im Cookie, Erst-Admin,
  Benutzerverwaltung mit Temporär-Passwort, Einstellungen je Benutzer.
- Ein Prozess, ein Port: uvicorn liefert API (/api) und gebautes SPA aus,
  inkl. SPA-Deeplink-Fallback.
- Frontend: React 18 + Vite + JSX, Design-Tokens (Light/Dark), i18n DE/EN,
  Inline-SVG-Icons (keine Emojis), Login, Passwort-ändern, Dashboard-Platzhalter,
  Benutzer- und Einstellungsseiten.
- Deploy: nativer LXC-Installer + Update-Skript (systemd), env-Vorlage, Doku.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-19 18:27:42 +02:00
co-authored by Claude Opus 4.8
commit 2c795a57c8
59 changed files with 6419 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env bash
#
# Certain Finance – Update-Skript.
# Holt den neuesten Stand aus Git, baut das Frontend neu, sichert die Datenbank
# und startet den Dienst neu (der beim Start die Migrationen anwendet).
#
# Nutzung (als root im geklonten Repo):
# ./deploy/update.sh Update holen, bauen, neu starten
# ./deploy/update.sh --no-pull nur neu bauen (ohne git pull)
# ./deploy/update.sh --no-build ohne Frontend-Neubau
#
set -euo pipefail
APP_SLUG="certain-finance"
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"
DO_PULL=1
DO_BUILD=1
for arg in "$@"; do
case "$arg" in
--no-pull) DO_PULL=0 ;;
--no-build) DO_BUILD=0 ;;
-h|--help) sed -n '2,12p' "${BASH_SOURCE[0]}"; exit 0 ;;
*) echo "Unbekannte Option: $arg" >&2; exit 1 ;;
esac
done
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; }
if [[ "$(id -u)" -ne 0 ]]; then
error "Bitte als root ausführen (z. B. 'sudo ./deploy/update.sh')."
exit 1
fi
if [[ ! -f "$ENV_FILE" ]]; then
error "Keine Konfiguration in $ENV_FILE – bitte zuerst ./deploy/install.sh ausführen."
exit 1
fi
# --- Git-Update --------------------------------------------------------------
if [[ "$DO_PULL" -eq 1 ]]; then
if [[ ! -d "$REPO_DIR/.git" ]]; then
warn "$REPO_DIR ist kein Git-Repository – überspringe git pull."
else
info "Hole Updates aus Git …"
before="$(git -C "$REPO_DIR" rev-parse --short HEAD 2>/dev/null || echo '?')"
if ! git -C "$REPO_DIR" pull --ff-only; then
error "git pull fehlgeschlagen (lokale Änderungen?)."
echo " Stand ansehen: git -C \"$REPO_DIR\" status"
exit 1
fi
after="$(git -C "$REPO_DIR" rev-parse --short HEAD)"
[[ "$before" == "$after" ]] && info "Bereits aktuell ($after)." || info "Aktualisiert: $before -> $after"
fi
fi
# --- Datenbank sichern (vor der Migration) ----------------------------------
if [[ -f "$DATA_DIR/app.db" ]]; then
mkdir -p "$DATA_DIR/backups"
ts="$(date +%Y%m%d-%H%M%S)"
cp "$DATA_DIR/app.db" "$DATA_DIR/backups/pre-update-$ts.db"
info "Datenbank gesichert: backups/pre-update-$ts.db"
fi
# --- Python-Abhängigkeiten ---------------------------------------------------
info "Aktualisiere Python-Abhängigkeiten …"
"$REPO_DIR/backend/.venv/bin/pip" install -r "$REPO_DIR/backend/requirements.txt" >/dev/null
# --- Frontend neu bauen ------------------------------------------------------
if [[ "$DO_BUILD" -eq 1 ]]; then
info "Baue das Frontend neu …"
( cd "$REPO_DIR/web"; if [[ -f package-lock.json ]]; then npm ci; else npm install; fi; npm run build )
fi
# --- Git-Commit in die Konfiguration schreiben (für /api/version) -----------
GIT_SHA="$(git -C "$REPO_DIR" rev-parse --short HEAD 2>/dev/null || echo '')"
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
# --- Neu starten (wendet Migrationen an) ------------------------------------
info "Starte Dienst neu …"
systemctl restart "$APP_SLUG"
PORT="$(grep -E '^APP_PORT=' "$ENV_FILE" | cut -d= -f2-)"
PORT="${PORT:-8071}"
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
if [[ "$ok" -eq 1 ]]; then
info "Update fertig – Dienst läuft."
else
warn "Backend antwortet noch nicht. Logs: journalctl -u $APP_SLUG -e"
fi