Docker-Image bauen / build (push) Successful in 1m7s
Der Testlauf war zu langsam. Gemessen statt geraten: 0,68 s Vorbereitung pro Test, bei 81 Tests praktisch die ganze Laufzeit. Ursache war bcrypt. Pro Test wird ein Hash gebildet und geprüft, und das ist absichtlich langsam - genau das soll es im Betrieb sein. Der Aufwand ist jetzt über BCRYPT_ROUNDS einstellbar (Standard bleibt 12) und in den Tests auf 4 gesetzt: dort geht es um die Ablauflogik, nicht um die Stärke des Hashes. Der Dekompressionsbomben-Test erzeugte ein Bild mit 400 Megapixeln, allein dafür 4,8 s. Jetzt wird stattdessen die Grenze heruntergesetzt und ein kleines Bild verwendet - dieselbe Codestelle, ohne die Wartezeit. Ausserdem installierten Test- und Anwendungs-Image dieselben Abhängigkeiten zweimal. Dockerfile.test setzt nun auf dem gebauten Anwendungs-Image auf, der Testschritt kommt entsprechend danach. Dabei zwei Fallen, die beide auffielen, weil die Tests plötzlich wieder langsam waren: - Das Basis-Image bringt seinen eigenen Stand von app/ mit. Ohne erneutes Kopieren prüfen die Tests den Code des Basis-Images - ist es veraltet, läuft alles gegen alten Code und meldet Erfolg. - Der ENTRYPOINT des Anwendungs-Images startet eine Datenbank-Migration. Für Tests weder nötig noch erwünscht, darum geleert. Ganze Kette lokal durchgespielt: bauen, testen, Image prüfen - 22 s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GR4bNaj9GtRu57J4Niii8o
81 lines
3.6 KiB
Python
81 lines
3.6 KiB
Python
"""Einstellungen, ausschliesslich aus Umgebungsvariablen.
|
|
|
|
Nichts davon steht im Code oder im Image: Passwort und Pfade kommen beim
|
|
Start von aussen. Ohne gesetztes Passwort bleibt der Erfassungsbereich
|
|
gesperrt (siehe Phase 3) - ein mitgeliefertes Standardpasswort wäre
|
|
öffentlich bekannt und damit keines.
|
|
"""
|
|
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
|
|
from pydantic import Field
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Einstellungen(BaseSettings):
|
|
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
|
|
|
# Die Namen der Umgebungsvariablen sind ausdrücklich angegeben und
|
|
# englisch - sie stehen in docker-compose.yml und in der Anleitung, und
|
|
# dort wären deutsche Namen neben ADMIN_PASSWORD & Co. verwirrend.
|
|
|
|
# Ablageorte. Im Container zeigen beide auf eingehängte Volumes, damit
|
|
# Datenbank und Bilder einen Neustart überleben.
|
|
datenbank_pfad: Path = Field(default=Path("data/kleiderboerse.sqlite"),
|
|
validation_alias="DATABASE_PATH")
|
|
upload_pfad: Path = Field(default=Path("uploads"), validation_alias="UPLOAD_PATH")
|
|
|
|
# Passwort für den Erfassungsbereich. Leer = gesperrt.
|
|
admin_passwort: str = Field(default="", validation_alias="ADMIN_PASSWORD")
|
|
admin_passwort_hash: str = Field(default="", validation_alias="ADMIN_PASSWORD_HASH")
|
|
|
|
# Schlüssel, mit dem das Sitzungs-Cookie signiert wird. Ohne festen Wert
|
|
# wird beim Start einer erzeugt - dann ist man nach jedem Neustart
|
|
# abgemeldet. Im Betrieb also setzen.
|
|
secret_key: str = Field(default="", validation_alias="SECRET_KEY")
|
|
|
|
# Secure-Kennzeichen am Sitzungs-Cookie. Standard an; nur zum Testen
|
|
# über http://localhost abschalten, sonst schickt der Browser das
|
|
# Cookie gar nicht erst mit.
|
|
cookie_nur_https: bool = Field(default=True, validation_alias="COOKIE_HTTPS_ONLY")
|
|
|
|
# Grösse eines einzelnen Bild-Uploads. Greift zusätzlich zur Prüfung in
|
|
# images.py; ein Handyfoto liegt heute bei 3-12 MB.
|
|
max_upload_bytes: int = Field(default=10 * 1024 * 1024,
|
|
validation_alias="MAX_UPLOAD_BYTES")
|
|
|
|
# Rate-Limits (pro IP). Reservieren ist ohne Anmeldung möglich, also
|
|
# steht hier nichts anderes zwischen einem Skript und dem Bestand.
|
|
limit_reservieren: str = Field(default="5/hour", validation_alias="RATE_LIMIT_RESERVE")
|
|
limit_anmelden: str = Field(default="10/hour", validation_alias="RATE_LIMIT_LOGIN")
|
|
|
|
# Rechenaufwand für das Passwort-Hashing. 12 ist der sinnvolle
|
|
# Standard; bcrypt ist absichtlich langsam, damit sich Passwörter
|
|
# nicht durchprobieren lassen. In Tests wird der Wert gesenkt -
|
|
# sonst kostet jeder Test rund eine halbe Sekunde nur dafür.
|
|
bcrypt_runden: int = Field(default=12, validation_alias="BCRYPT_ROUNDS")
|
|
|
|
# Nach dieser Zeit ohne Aktivität wird abgemeldet.
|
|
sitzung_max_ruhe_sekunden: int = Field(default=7200,
|
|
validation_alias="SESSION_IDLE_SECONDS")
|
|
|
|
@property
|
|
def datenbank_url(self) -> str:
|
|
return f"sqlite:///{self.datenbank_pfad}"
|
|
|
|
@property
|
|
def admin_aktiv(self) -> bool:
|
|
"""Ohne hinterlegtes Passwort bleibt der Erfassungsbereich gesperrt.
|
|
|
|
Bewusst kein mitgeliefertes Standardpasswort: das stünde in der
|
|
Anleitung und wäre damit allgemein bekannt.
|
|
"""
|
|
return bool(self.admin_passwort_hash or self.admin_passwort)
|
|
|
|
|
|
@lru_cache
|
|
def einstellungen() -> Einstellungen:
|
|
"""Einmal lesen und merken - die Werte ändern sich zur Laufzeit nicht."""
|
|
return Einstellungen()
|