Sternprodukt · Atlas
← Werkstatt recherche/settings-umbau.md

Umbau: Konfiguration über dry-system settings

Erledigt am 14.08.2026, Bauform C. Diese Datei bleibt als Befund von vorher stehen; was dabei herauskam, steht in entscheidungen-webanwendung.md unter „Und dann kauft dry-system doch noch etwas“. Zwei Stellen hier sind durch die Messung überholt und im Text unten markiert: das „Hindernis“ gab es nicht — ein nicht finalisierter Container löst faul auf —, und plugin :sessions nimmt kein Callable. Wer den Stand sucht, liest die Entscheidungsdatei, nicht diese.

Vorbereitet am 14.08.2026 auf feat/ruby-web-app. Gedacht als eigener PR nach dem Merge des Webanwendungs-PRs. Diese Datei ist die Übergabe: Befund, Bauformen, Hindernis, und was nicht kaputtgehen darf.

Kein brennender Fehler — funktionierender, getesteter Code wird abgelöst, weil die handgeschriebene Fassung drei Dinge schlechter macht als die vorhandene Bibliothek.

Was heute da ist

Drei Umgebungswerte, alle Pflicht, keiner mit Vorgabe:

Variable Form
ATLAS_KONTO Kontoname
ATLAS_PASSWORT_HASH bcrypt-Hash, base64-kodiert
ATLAS_SESSION_SECRET Hex, ≥ 64 Zeichen

Sie werden an zwei Stellen von Hand behandelt:

Was der Settings-Provider besser macht

Gelesen in dry-system-1.2.5/lib/dry/system/provider_sources/settings/, nicht aus der Doku übernommen — die Doku schweigt zu den zwei interessanten Punkten.

  1. Alle Fehler auf einmal. Config.load sammelt und wirft einmal:

    raise InvalidSettingsError, errors unless errors.empty?
    # "Could not load settings. The following settings were invalid:
    #  atlas_konto: … / atlas_session_secret: …"
    

    Auth.env! bricht beim ersten fehlenden Wert ab. Man repariert sie einzeln.

  2. Die Prüfung wird deklarativ, und dry-types braucht es dafür nicht — ein Lambda genügt als constructor:. Nachgeprüft:

    setting :atlas_passwort_hash, constructor: ->(v) {
      h = begin; v.to_s.unpack1('m0'); rescue ArgumentError; raise ArgumentError, 'kein gültiges Base64'; end
      raise ArgumentError, 'dekodiert kein bcrypt-Hash' unless h.to_s.match?(BCRYPT)
      h
    }
    # "Müll" → kein gültiges Base64 · gültiger Wert → dekodierter Hash
    
  3. Eine dokumentierte .env-Kette, die es sonst nirgends gibt (providers/settings/loader.rb):

    [".env.#{env}.local", ".env.local" (außer test), ".env.#{env}", ".env"]
    

    Das ist die Antwort auf die Frage, ob es eine local- oder development-Variante geben sollte: In docker compose nein — es liest nur .env, gemessen. Hier ja. Die Kette kommt über dotenv; fehlt das Gem, tut der Loader nichts (rescue LoadError).

dry-configurable liegt bereits im Lock (1.4.0, transitiv über dry-system).

Das Hindernis, und es ist das eigentliche Stück Arbeit

Überholt. Es gab beides nicht. plugin :sessions weist ein Callable ab (sessions.rb:169 prüft auf String, in self.configure), und es braucht auch keines: ein nicht finalisierter dry-system-Container löst faul auf und startet den Provider dabei, Container['settings'] liefert also vor finalize!. Die Startreihenfolge von AuthApp blieb unangetastet.

web/auth.rb liest beim Laden der Klasse:

class AuthApp < Roda
  DB = Auth.database                                   # ← ENV zur Ladezeit
  plugin :sessions, secret: Auth.env!('ATLAS_SESSION_SECRET')   # ← ebenso

Settings liegen im Container und stehen erst nach Container.finalize! bereit — das passiert in config.ru nach require_relative 'web/auth'. Ein Umstieg heißt also: die Startreihenfolge von AuthApp umbauen, damit Geheimnis und Datenbank erst beim ersten Zugriff geholt werden. Roda-Plugins werden zur Ladezeit konfiguriert; plugin :sessions nimmt aber auch ein Callable für secret, das ist vermutlich der Hebel — ungeprüft, bitte selbst nachsehen.

Das ist der sicherheitsrelevante Pfad. Nicht nebenbei umbauen.

Bauformen, mit Preis

  Gems Was bleibt handgeschrieben
A nur dry-configurable 0 ENV-Lesen und .env bleiben bei mir; nur die Deklaration wandert
B Settings-Provider ohne dotenv 0 mein config.ru-Loader bleibt daneben stehen — zwei Stellen lesen Umgebung
C Settings-Provider mit dotenv 1 nichts; der Loader fliegt raus, die .env-Kette kommt mit

Empfehlung: C. B lässt zwei Mechanismen nebeneinander stehen, und genau das ist die Sorte Doppelung, die dieses Projekt an anderer Stelle schon zweimal eingesammelt hat (die neun CDN-Adressen, die zwei Wächter-Listen).

Was nicht kaputtgehen darf

Alles gemessen und in Tests eingezäunt — wer den Umbau macht, prüft das gegen:

Nachziehen, wenn es steht

Verifikation

bundle exec ruby -Itest test/web_test.rb        # 28 runs

# der dokumentierte Weg, ohne exportierte Variablen
cp .env.example .env && $EDITOR .env
bundle exec puma                                # → http://localhost:9292/

# jeder Wert einzeln fehlend: laut scheitern, mit brauchbarer Meldung
env -u ATLAS_KONTO bundle exec puma
# roher statt base64-kodierter Hash: ebenso
docker compose --profile local up dev