4 Entwicklung
julian edited this page 2026-11-04 21:41:54 +01:00

Entwicklung

Diese Seite beschreibt Aufbau, lokale Einrichtung, Tests, Migrationen und den Release-Bau.

Architekturüberblick

Browser ──HTTP──> FastAPI (ein Worker, Session-Cookie, CSRF)
                    ├─ SQLAlchemy 2 / SQLite (WAL) in data/database/app.db
                    ├─ MSAL (Authorization Code Flow + PKCE, ein Cache je Konto)
                    └─ asyncio-Subprozess: imapsync 2.326
                          ├─ liest Token/Passwort aus Dateien (0600)
                          └─ schreibt stdout/stderr redigiert nach data/logs
  • Ein Prozess. Kein Redis, kein Celery, kein zweiter Worker. Die Warteschlange ist die Tabelle runs; ein asyncio-Task arbeitet sie ab.
  • Eine Instanz pro Datenverzeichnis, erzwungen über data/queue.lock.
  • Keine Secrets in argv. imapsync erhält Dateipfade, keine Werte.
  • Keine Löschoperationen an Quell- oder Zielpostfächern.

Code-Struktur

backend/app/
  api/          auth, setup, tenants, identities, oauth, sources, targets,
                jobs, runs, queue, system
  services/     msal, token, imapsync, migration_runner, queue, folder_preview,
                imap_test, secret_files, secret, single_instance, process_probe
  models.py     SQLAlchemy-Modelle (tenants, identities, targets, sources, jobs, runs)
  schemas.py    Pydantic-Schemata
  security.py   Argon2id, Session-Cookie, CSRF
  runtime.py    Pfade und Umgebung der portablen Laufzeit
  launcher.py   Portauswahl, uvicorn-Start, Browserstart
  updater.py    Release-Prüfung, Signatur, Austausch, Rollback
  cli.py        check, init, bootstrap, set-password
backend/migrations/   Alembic-Umgebung und Versionen
backend/tests/        pytest-Suite, Fake-imapsync, Test-IMAP-Server
frontend/src/         api, pages, components, hooks, lib, test
scripts/              setup.ps1, provision_runtime.py, build_release.py,
                      sign_release.py, publish_release.py

Lokale Einrichtung

Voraussetzungen: Python 3.12, uv; Node.js nur für das Frontend.

uv venv --python 3.12 .venv
uv pip install --python .venv/Scripts/python.exe -r backend/requirements.txt
cp .env.example .env

In der .env müssen ADMIN_PASSWORD_HASH, APP_ENCRYPTION_KEY und SESSION_SECRET gesetzt sein; ohne sie startet die Anwendung nicht. Die Werte lassen sich mit argon2-cffi (Hash), base64.urlsafe_b64encode(os.urandom(32)) (Fernet-Schlüssel) und secrets.token_urlsafe(48) (Session-Secret) erzeugen.

Backend starten (Migrationen laufen beim Start, manuell mit python -m app.cli init):

cd backend
../.venv/Scripts/python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8080
  • API: http://127.0.0.1:8080/api/v1, OpenAPI: http://127.0.0.1:8080/api/docs, Health: GET /api/v1/system/health.

Frontend im Entwicklungsmodus (Proxy auf 8080):

cd frontend
npm ci
npm run dev        # http://127.0.0.1:5173
npm run types      # Typen aus der laufenden API erzeugen

Tests

cd backend
../.venv/Scripts/python.exe -m pytest tests -q
cd frontend
npm run test          # vitest run
npm run typecheck     # tsc -b --noEmit
npm run build         # Produktionsbuild nach dist/

Letzter dokumentierter Stand: Backend 305 passed, 3 skipped; Frontend 35 Tests in 7 Dateien; TypeScript ohne Fehler; Build erfolgreich. Die drei übersprungenen Tests prüfen POSIX-Dateirechte und laufen nur unter Linux.

Abgedeckt: Auth/CSRF/Throttling, CRUD für Tenants, Konten, Ziele, Migrationen, OAuth mit MSAL-Fake, imapsync-Erkennung, Runner mit echtem Subprozess und Fake-CLI, IMAP-Test gegen echten TLS-Server, Warteschlange mit Parallelität/Retry/Recovery, Oberfläche mit Vitest und API-Mock. Nicht abgedeckt: echte Migration gegen Microsoft 365, echter Token-Ablauf, Linux-Betrieb.

Datenbankmigrationen

Die Versionen liegen unter backend/migrations/versions und sind fortlaufend nummeriert (0001_initial bis 0005_imap_sources). Neue Migration erzeugen:

cd backend
../.venv/Scripts/alembic.exe revision --autogenerate -m "kurze beschreibung"

Bestehende Migrationen nicht ändern, sondern eine neue anlegen; Upgrade und Downgrade implementieren. backend/tests/test_migrations.py prüft beides. Die Anwendung führt beim Start alembic upgrade head aus; app.cli check vergleicht die Datenbankrevision mit dem Head.

Release bauen

python scripts/build_release.py --build-frontend

Ergebnis in dist/release/: M365-to-Exchange-Sync-Windows.zip, M365-to-Exchange-Sync-Linux.tar.gz (identischer Inhalt, POSIX-Rechte), SHA256SUMS und MANIFEST.txt (SHA-256 je Datei).

Der Bau ist whitelist-basiert: node_modules, .venv, Tests, .env, Datenbanken, Logs, Schlüssel und .git werden nie eingepackt. Nach dem Packen prüft das Skript, dass frontend/dist/index.html enthalten ist, und bricht sonst mit Exit-Code 1 ab.

python scripts/sign_release.py      # erzeugt SHA256SUMS.sig (Ed25519)
export FORGEJO_TOKEN=<token>
python scripts/publish_release.py --version 0.9.0-beta.1 --prerelease --dry-run
python scripts/publish_release.py --version 0.9.0-beta.1 --prerelease

Der private Signaturschlüssel liegt außerhalb des Repositories; der öffentliche Schlüssel steht in backend/app/release_key.py. Ohne ihn akzeptiert der Updater kein Release. Kein Release ohne grüne Tests, Installationstest aus dem Archiv und einen echten lokalen imapsync-Lauf; Vorabversionen werden als Prerelease markiert, VERSION und APP_VERSION gemeinsam gepflegt. Neue API-Felder zuerst im Pydantic-Schema anlegen, dann Frontend-Typen generieren; das Fehlerformat {detail: {code, message, fields?}} bleibt bestehen, neue Endpunkte brauchen Tests für Erfolg und Fehlerfall.