mvest braucht eine öffentlich erreichbare Web-UI für die Konsumation der Agent-Outputs.
Die Domain mvest.malte.io steht zur Verfügung (DNS zeigt bereits auf den
Server 148.251.126.27). Der Server hostet bereits andere nginx-basierte
Anwendungen. Da Trades nicht ausgeführt werden, ist die Angriffsfläche begrenzt, aber
Portfolio-Hypothesen, Watchlists und Empfehlungs-Historie sind nicht öffentlich gedacht.
mvest.malte.io (A-Record auf 148.251.126.27).certbot --nginx. Zertifikat eingerichtet (gültig bis 2026-08-13). Auto-Renewal über certbot.timer.nginx als Reverse-Proxy. Pfad-Aufteilung:
/adr/ → statisches Verzeichnis (/home/malte/mvest/adr/), public lesbar ohne Auth. ADRs sind dokumentarisch, nicht sensibel./ → 302-Redirect auf /adr/ (vorläufig). Später: Proxy auf die FastAPI-App./app/ oder /) → Proxy auf 127.0.0.1:8000 (uvicorn), Auth durch die App selbst./home/malte/mvest. Pragmatisch für Single-Dev-Setup; Dev-Loop simpel. Migration nach /opt/mvest bei Bedarf möglich.malte. Kein dedizierter mvest-User in v1 (würde Permissions-Management für SQLite-Datei und Logs verkomplizieren). Bei Bedarf später nachzuholen.mvest.service startet uvicorn auf 127.0.0.1:8000, restart on failure, läuft als malte.journalctl -u mvest).git pull && uv sync && systemctl restart mvest. Kein CI/CD nötig solange Single-Dev.git pull macht sie sofort live.Auth erfolgt in der Webapp selbst — kein nginx-Auth-Layer, keine externen IdPs in v1.
data/users.yaml. Vergleich über secrets.compare_digest() (constant-time, gegen Timing-Attacken). Begründung: private Hobby-Anwendung, minimale Angriffsfläche, File-Permission 0600 als zentrale Schutzwand. Migration zu argon2id ist trivial nachzuziehen, wenn Multi-User oder Public-Use kommt.data/users.yaml (gitignored, chmod 600 beim Schreiben). Lade beim Start in den Speicher, schreibe bei jeder Änderung. Atomic write via os.replace. Kein SQLite für User in v1.session_id → SessionData. Session-ID ist 32 Byte URL-safe random, im HttpOnly + Secure + SameSite=Lax-Cookie. Bei Server-Restart sind alle ausgeloggt (akzeptiert für v1).expires_at). Periodic-Cleanup expired Sessions im Hintergrund.itsdangerous), wird vom Server gegen Session validiert.data/users.yaml (gitignored, chmod 600)users:
- username: malte
password: "klartext-passwort"
is_active: true
created_at: 2026-05-15T10:30:00Z
last_login_at: null
SessionData(BaseModel):
session_id: str # 32 bytes urlsafe random
username: str
created_at: datetime
expires_at: datetime
last_used_at: datetime
# Storage: dict[session_id, SessionData], in-memory only
Warum YAML statt SQLite für Users? Für v1 ist die User-Anzahl winzig (1), Änderungen sind selten. Eine Textdatei ist trivial zu inspizieren, manuell zu editieren und zu sichern. Sessions hingegen sind kurzlebig und volatil — in-memory ist hier angemessen. Migration zu SQLite ist möglich, wenn Multi-User in v2 kommt.
Beim ersten Boot prüft die App, ob die users-Tabelle leer ist. Wenn ja:
liest sie die Env-Vars MVEST_INITIAL_USER und MVEST_INITIAL_PASSWORD,
legt den User an und logged in. Anschließend werden die Env-Vars bei jedem weiteren Boot
ignoriert (Bedingung: users ist nicht leer).
Sicherheit: Initial-Passwort steht im Klartext in .env.
Nach erstem erfolgreichem Login sollte das Passwort über die Settings-Seite in der Web-UI
geändert und anschließend die beiden Env-Vars aus .env entfernt werden.
users leeren — danach greift wieder der ENV-Bootstrap).| Komponente | Status |
|---|---|
DNS mvest.malte.io | ✓ live |
| nginx Site-Config | ✓ live |
| TLS via Let's Encrypt | ✓ live (bis 2026-08-13, Auto-Renewal aktiv) |
| HTTP→HTTPS Redirect | ✓ live |
/adr/ als Static-Serving | ✓ live |
| FastAPI-App + Auth-Implementierung | ⏳ ausstehend (siehe ADR-0013) |
systemd-Unit mvest.service | ⏳ ausstehend (folgt mit App) |