* Die Wortmarke stand nur bei TESM -- sie hing an der Funktionszeile, und die
hatte der Lizenzserver nicht. Jetzt zeigen beide die Marke; die Zeile
darunter ist optional und beim Lizenzserver "LIZENZIEREN".
* Der Kartenkopf ist weg: eine Kachel mit zwei Buchstaben, die Ueberschrift
"Anmelden" und der Anwendungsname darunter sagten dasselbe wie die Marke
darueber und der Knopf darunter. Die Ueberschrift bleibt fuer die
Titelzeile des Browsers.
* Marke und Karte stehen jetzt uebereinander im Fluss statt uebereinander
gestapelt: auf einem flachen Fenster schnitt die Karte mitten durch den
Schriftzug.
* Die Marke ist kuerzer und symmetrisch um die Mitte aufgebaut -- Schriftzug
ueber text-anchor, Zacken spiegelbildlich, Linie links so lang wie der Punkt
rechts. Volle Deckkraft, weil sie kein Wasserzeichen hinter der Karte mehr
ist.
* Funktionszeile gekuerzt auf "UEBERWACHEN . BOOTEN . ANBINDEN" -- mit echtem
Umlaut. "(PXE)" stand fuer etwas, das es noch nicht gibt.
* Der Farbverlauf im Hintergrund hat wieder Geometrie und Staerke der Fassung
1.x. Der zweite Kreis lag auf --info und 10 %: ein kaltes Blau, das man auf
dunklem Grund nicht sah und das nicht zur Marke gehoert.
* "Bitte melden Sie sich an." stand auf einer Seite aus Anmeldefeld,
Passwortfeld und einem Knopf "Anmelden". Statt dessen erfaehrt jetzt der,
dessen Sitzung gerade ablief, warum er wieder hier ist.
Dabei drei Dinge gefunden, die lautlos nichts taten:
* __version__ stand fest verdrahtet in drei __init__.py. Die Fusszeile zeigte
vier Releases lang 2.0.2, waehrend die Anwendung 2.0.6 war. Die Nummer kommt
jetzt aus den Metadaten des installierten Pakets; Quelle ist pyproject.toml.
* "gap: var(--sp-7)" -- die Skala kennt kein --sp-7 (1 bis 6, dann 8, 10, 12,
16). Eine undefinierte Variable verwirft die ganze Deklaration ohne Meldung:
der Abstand war "normal", und Marke und Karte stiessen auf den Pixel
aneinander.
* Ein Test dagegen fand sofort ein zweites: --surface-0 wird dreimal benutzt
und nirgends definiert. Die Schrift auf der Kachel "kritisch" blieb
dunkelgrau auf Rot, die Dateivorschau hatte keinen Grund, und das Feld
"Recht nicht vorhanden" in der Rechtematrix war durchsichtig -- also so hell
wie die Karte, obwohl es dunkler sein sollte.
Version 2.0.7.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die laufenden Installationen hiessen "tesm-opus" und "tesm-license-opus" --
ein Instanzname aus der Zeit, in der sie neben der Fassung 1.x getestet wurden.
Jetzt heissen sie "tesm" und "tesm-license".
Umbenennen ist mehr als ein verschobenes Verzeichnis, und drei Stellen sind
leicht zu uebersehen:
* SITE_KEY in der Umgebungsdatei -- daraus entsteht der Name des
Sitzungscookies. Bleibt er alt, heisst das Cookie weiter wie die alte
Instanz: unauffaellig und falsch.
* web_cert_path/web_key_path/web_acme_webroot in der Datenbank enthalten den
Namen. Ein Zertifikat unter dem alten Pfad findet nginx nicht mehr.
* server_name der nginx-Site. install.sh schreibt die Datei aus seiner Vorlage
neu, sobald sich die alias-Pfade aendern -- und die aendern sich hier
zwangslaeufig. Ohne den Namen aus der Datenbank stehen danach zwei Sites mit
"server_name _" auf Port 80, und die erste gewinnt. web-setup braucht dafuer
--apply; ohne ihn speichert es nur.
Zwei Fehler hat erst der Lauf am System gezeigt:
* Das venv wandert nicht mit. Seine Konsolenskripte tragen den absoluten Pfad
im Shebang, und danach zeigt venv/bin/gunicorn auf einen Interpreter, den es
nicht mehr gibt -- systemd meldet "No such file or directory" fuer eine
Datei, die sichtbar da ist. Es wird jetzt verworfen und neu gebaut.
* Den Zielnamen belegt nicht nur /srv/<name>, sondern auch /var/log/<name>,
der ACME-Pfad und die logrotate-Datei. Der Umzug brach mitten drin ab. Jetzt
wandert alles, was den Namen belegt, in ein Archiv -- geloescht wird nichts,
die Units und die Site der alten Fassung liegen darin unter systemd-alt/.
Ein Test vergleicht die Pfade, die install.sh unter dem Namen anlegt, mit
denen, die rename.sh kennt. Genau diese Luecke war der zweite Fehler; ohne den
Test faellt der naechste erst wieder auf einem Server auf.
Version 2.0.6.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Das Banner der Vorgaengerfassung hing an /srv/tesm/motd_banner.py und
importierte dafuer die ganze Flask-Anwendung samt deren venv. Nach dem
Umstieg zeigte es damit die Zahlen einer Datenbank, die niemand mehr fuellt --
eingefroren, aber unverdaechtig. Aufgefallen ist es erst, als der alte
Ueberwachungsdienst abgeschaltet wurde.
deploy/tesm-motd loest das anders: **nur Standardbibliothek**, kein venv, kein
Import der Anwendung, die Datenbank wird nur lesend geoeffnet (mode=ro). Ein
Lauf dauert Millisekunden statt eines Flask-Starts. install.sh legt das Skript
nach /usr/local/lib/tesm/ und schreibt /etc/update-motd.d/50-<name>, abgesichert
mit "timeout 3" und "|| true" -- ein Banner darf eine Anmeldung nie blockieren.
Ein Banner der Vorgaengerfassung wird entfernt, aber nur, wenn es genau auf
motd_banner.py zeigt.
Damit es nicht zwei Wahrheiten ueber "online" gibt, steht die Zaehlung der
Statusuebersicht jetzt als Konstante DASHBOARD_COUNT_SQL in monitor.py; das
Banner traegt sie wortgleich, und ein Hygienetest vergleicht beide. Dasselbe
fuer die Zeile unter der Wortmarke. Ein weiterer Test verbietet dem Banner
jeden Import ausserhalb der Standardbibliothek -- genau dieser Import war die
Ursache.
Der Lizenzserver bekommt sein eigenes Banner (Kunden und ausgestellte
Lizenzen). Eine Tagline hat er bewusst nicht: die Funktionsliste von TESM waere
dort falsch, und eine erfundene waere schlimmer als keine.
Version 2.0.5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Abmeldung hat weiterhin nicht ausgeloest. Zwei Gruende, beide fuer sich
ausreichend:
* **save_session** schreibt ``idle_expires_at`` bei jeder Anfrage, die die
Sitzung anfasst -- ein neu erzeugtes CSRF-Token genuegt. Die Pruefung in
``open_session`` lief damit ins Leere. Sitzungsdaten werden weiter
gespeichert, nur die Fristen bleiben stehen. Dieselbe Stelle benutzte
ausserdem ``idle_minutes`` statt der einstellbaren Zeit; bei der Vorgabe von
dreissig Minuten fiel das nicht auf.
* **Der Kopf allein reicht nicht.** Er setzt voraus, dass der Browser das
aktuelle Skript faehrt -- ein Tab, der seit gestern Abend offen ist, faehrt
das alte und schickt ihn nicht. Genau dieser Tab ist der Fall, um den es
geht. Der Server weiss es ohne Zutun des Browsers: die Endpunkte, die eine
Kachel oder ein Protokoll nachlaedt, sind als Aktualisierungsausschnitt
gekennzeichnet (``@fragment``) und gelten damit immer als automatisch.
``request.endpoint`` steht in ``open_session`` noch nicht -- Flask oeffnet
die Sitzung vor dem Routing --, deshalb sieht die Erkennung dort selbst nach.
Dass es auf dem Lizenzserver zuverlaessig funktionierte, war der Hinweis: dort
pollt keine Kachel.
Dazu ein Kopf ``X-Session-Expired`` fuer Seiten, die auch oeffentlich
antworten. Die Statusuebersicht liefert nach dem Ablauf eine 200 mit den
oeffentlichen Kacheln -- am Status ist der Verlust der Sitzung also nicht zu
erkennen, und der offene Tab zeigte weiter eine Oberflaeche, als sei alles in
Ordnung. Jetzt laedt er neu und zeigt die Anmeldemaske.
Version 2.0.4.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
upgrade.sh -- ein Weg vom Stand SONNET5 auf diese Fassung, mit Daten. Der
Ablauf braucht keine Schalter: alte Installation sichern, abschalten, neu
installieren, exportieren, uebersetzen, einspielen. Die Sicherung steht
bewusst vor dem Export -- ohne --instance installiert install.sh in dasselbe
Verzeichnis, und wer erst exportiert und dann sichert, sichert im
schlechtesten Fall nichts mehr. Gelesen wird nur aus der Kopie.
Dafuer zwei neue Befehle: "legacy-export" liest eine alte Installation
unmittelbar (der Vorgaenger kann nur ueber die Weboberflaeche exportieren --
fuer ein Upgrade der falsche Weg, und unmoeglich, wenn die alte Anwendung nicht
mehr startet) und schreibt eine Datei im alten Format, die deshalb auch als
Rueckweg taugt. "legacy-import" liest eine Datei im neuen Format ein, ueber
transfer.apply_import -- dieselbe Funktion wie die Seite "Sicherung", kein
zweiter Schreibpfad.
Am Lizenzserver hat der Lauf gegen echte Altdaten zwei Fehler gezeigt, die
jede Uebernahme unmoeglich machten:
* Der alte Signaturschluessel ist URL-sicher kodiert (- und _). Das neue
Protokoll prueft Base64 streng -- richtig so, aber beim Lesen einer alten
Datei brach die Uebernahme mit einem Traceback ueber "kein gueltiges Base64"
ab. Umkodiert wird jetzt an der Grenze zum Altformat; der kanonische Decoder
bleibt streng.
* Der Anbieterblock wurde aus den Einstellungen der *neuen* Installation
gebaut, und ohne Endpunkt brach die Uebernahme ab. Bei einem Upgrade ist die
neue Installation frisch -- dort steht nichts. Endpunkt und Anbieterdaten
kommen jetzt aus der alten Installation, aber nur dort, wo neu nichts steht.
Der alte Signaturschluessel wird uebernommen: sonst muesste jeder Kunde
einen Ausstellerwechsel bestaetigen.
Leerlaufabmeldung: sie hat nie ausgeloest. Ein offener Tab fragt seine
Kacheln alle 15 Sekunden neu ab, und jede Abfrage verlaengerte das
Leerlauffenster -- nachweisbar an einer Sitzung, deren last_seen_at eine
Minute alt war, obwohl seit zehn Stunden niemand am Rechner sass. Der Client
kennzeichnet automatische Anfragen jetzt mit einem Kopf, der Server
verlaengert daraufhin nichts, und last_seen_at bleibt, was der Name sagt.
Faellt die Sitzung waehrend des Pollens, antwortet der Server mit 401 statt
mit einer Umleitung: der Tab laedt neu und zeigt die Anmeldemaske, statt
stumm mit alten Zahlen stehenzubleiben.
Im Audit ist der Name der Mitschrift jetzt ein Verweis auf die Datei -- es ist
derselbe Weg wie zuvor, nur einen Klick weit. Verlinkt wird nur, wenn die
Datei existiert und der Betrachter Protokolle lesen darf; der Rest des
Eintrags wird maskiert, denn ein Auditeintrag enthaelt Fremdeingaben.
Ausserdem: Version 2.0.3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Fehler, die erst beim Aktualisieren auffallen -- also genau dann, wenn
niemand sie sehen will:
* update.sh zeigte auf "gitea.example.invalid" und den Besitzer "wis". Auf
keinem Host konnte das funktionieren, und die Doku erwaehnte die Variablen
nicht, mit denen man es haette umlenken koennen. Die Vorgaben zeigen jetzt
dorthin, wo die Releases liegen; das Repository heisst wie die Anwendung.
* update.sh gab nichts an install.sh weiter. Eine benannte Instanz war damit
nicht aktualisierbar: ohne --instance entsteht neben "tesm-opus" eine
zweite Installation namens "tesm", mit eigener Datenbank und eigener
nginx-Site.
* install.sh setzte bei einer Instanz die Ports auf 8080/8443 zurueck, auch
wenn sie seit der Installation auf Port 80 lief. Ein Reverse Proxy davor
haette danach ins Leere gezeigt. Bestehende Ports werden jetzt aus
Umgebungsdatei und nginx-Site uebernommen -- dieselbe Regel wie beim
Secure-Flag: eine Aktualisierung nimmt nichts weg, was nicht ausdruecklich
neu gesetzt wird.
Zwei Tests fuehren dazu die echten Zeilen aus den Skripten aus, statt sie
nachzubauen. Beide fallen ohne die Korrektur durch.
Ausserdem: die vier subprocess-Aufrufe im Textmodus nageln die Kodierung auf
UTF-8 fest. "text=True" allein nimmt die Locale des Prozesses -- unter systemd
haeufig C/ASCII --, und das Lesen bricht ab, sobald ein Werkzeug einen Umlaut
ausgibt. Der Fehler entstand im Leser-Thread von subprocess und damit weit weg
von seiner Ursache; im Testlauf war er als Warnung sichtbar.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Echte Umlaute in allen Texten der Oberflaeche und in den Markdown-Dateien.
Die Einbindung von Dateifreigaben haengt jetzt an der Sitzung: wird sie
widerrufen oder laeuft sie ab, verschwinden die Freigaben mit ihr. Neu
einstellbar ist die automatische Abmeldung bei Leerlauf (Vorgabe 30 Minuten).
Ein Geraet, das nicht antwortet, bietet nur noch "Einschalten (PoE)" an --
Neustart ueber SSH oder RPC braucht ein erreichbares Geraet. Der Zeitgeber der
naechsten Pruefung und die Restlaufzeit der Lizenz stehen jetzt oben in der
Kopfzeile; der doppelte Knopf darunter ist weg. Millisekunden werden ab einer
Sekunde als Sekunden und ab einer Minute als m:ss dargestellt.
Ausserdem: Release-Tarball nicht mehr im Repository -- er haengt am Release.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
In der Spalte "Quelle" stand das Kuerzel des Protokolls: "tofu". Das ist
Fachjargon (Trust On First Use) und als Wert in einer Tabelle unbrauchbar --
es sieht aus wie ein Tippfehler.
Jetzt: "Erstkontakt" (beim ersten Einspielen automatisch uebernommen) und
"Bestaetigt" (Ausstellerwechsel von einem Administrator freigegeben).
Die Uebersetzung liegt in tesm_core.ui als ISSUER_SOURCES mit zwei
Jinja-Filtern, nicht in zwei Vorlagen: der Lizenzserver zeigt dieselbe Angabe
unter "Eigene Lizenz", und zwei Texte fuer denselben Sachverhalt laufen
auseinander. Unbekannte Werte bleiben unveraendert sichtbar statt hinter einem
Platzhalter zu verschwinden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Freigaben mit kurzem Hostnamen (\S2025\Share) scheiterten mit "could not
resolve address for S2025". Ursache: dieser Server hat keine Suchdomaene
(search . in resolv.conf), ein Windows-Client hat eine. Der Name ist also
richtig, nur hier nicht auflösbar. Die AD-Domaene ist aus den Einstellungen
des Verzeichnisdienstes bekannt und wird jetzt zum Vervollstaendigen benutzt;
laesst sich der Name gar nicht aufloesen, sagt die Meldung was zu tun ist
statt "Unknown error". Auf POETEST gegen echtes DNS geprueft:
\S2025\LDAP_Share -> \S2025.ad.eertmoed.net\LDAP_Share, und dieser Pfad
bindet ein.
Protokollansichten standen beim Oeffnen oben. Die neueste Zeile steht aber
unten, und weil die Aktualisierung nur mitzieht, wenn man ohnehin am Ende ist,
zog sie danach nie mit -- es sah aus wie ein kaputter Autoscroll, war aber nur
die falsche Anfangsposition. Die Protokollansichten sind jetzt per data-tail
markiert und springen beim Laden nach unten. Konfigurationsvorschauen (nginx,
Kea) und der Offline-Code sind bewusst nicht markiert: die beginnen oben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Knopf im Dateifeld kommt vom Browser und ignoriert jede Aussenfarbe: im
dunklen Thema ein hellgraues Kaestchen mit dunkler Schrift, kaum zu lesen und
passend zu nichts. Jetzt explizit wie ein Sekundaerknopf gestaltet -- einmal
zentral, denn es betrifft vier Felder (Sicherung, Lizenz, Profilbild,
Dateifreigaben).
Dazu zwei neue Hinweise fuer den Windows-Neustart. "Could not initialise pipe
winreg / NT_STATUS_OBJECT_NAME_NOT_FOUND" liest sich wie ein technisches
Detail, hat aber fast immer eine Ursache: das Konto ist auf dem Zielgeraet kein
Administrator. An einem echten Fall nachgemessen -- die Anmeldung gelang, aber
schon "net rpc info" und der Dienstmanager antworteten mit ACCESS_DENIED. Ein
Standardkonto bekommt die winreg-Pipe gar nicht zu sehen. Die Meldung sagt das
jetzt und nennt den Pruefbefehl.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Vollstaendiger Neubau der Anwendung. Der vorherige Stand bleibt unveraendert
im Zweig SONNET5 erhalten.
Aufbau: apps/tesm (Anwendung), packages/tesm-core (gemeinsamer Kern),
packages/tesm-licensing (Lizenzprotokoll), deploy (Installation, systemd,
privilegierter Helfer), docs, tests. Der Lizenzserver liegt in seinem eigenen
Repository; beide Repositorien bringen die gemeinsamen Pakete mit, damit sich
jedes allein installieren laesst.
Die wichtigsten Unterschiede zum Vorgaenger, jeweils an der Stelle im Code
kommentiert, an der der Fehler entstanden ist:
* Der Webprozess laeuft unprivilegiert. Alles, was Root braucht, geht ueber
einen einzigen Helfer mit Positivlisten fuer jedes Argument.
* CSRF-Schutz ueberhaupt -- der Vorgaenger hatte keinen.
* Rechte werden serverseitig geprueft, nicht nur im Template ausgeblendet.
* Die nginx-Site wird bei jedem Lauf inhaltlich verglichen und erneuert.
* Jede erzeugte Konfiguration wird vor dem Uebernehmen geprueft (nginx, Kea).
* Kein Hostname im Lizenz-Fingerabdruck.
* Zwei Installationen auf einem Host stoeren sich nicht (eigener SITE_KEY).
* Verschachtelte Datenbankverbindungen sind ein Fehler, kein Deadlock.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>