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.