Der Lauf gegen die echte Installation hat zwei Fehler gezeigt, die den
Aktualisierungsweg fuer jede Version unbrauchbar machten:
* Die Suche nach install.sh im entpackten Paket lief mit "-maxdepth 2".
release.sh packt mit dem Praefix "<name>/", das Skript liegt also drei
Ebenen tief -- gefunden wurde nie etwas, und die Meldung schob es auf den
Download ("Im Paket fehlt deploy/install.sh").
* Die CRLF-Pruefung rief "file" auf. Das Paket gehoert nicht zur
Grundinstallation eines Servers; fehlt es, endete die Pipeline in "command
not found", grep bekam nichts zu sehen, der if-Zweig wurde nicht betreten --
und genau der Fehler, den die Pruefung abfangen soll, ging unbemerkt durch.
Der Wagenruecklauf wird jetzt ohne Zusatzpaket gesucht, mit "-U": unter Linux
ohne Wirkung, auf einer Windows-Buildmaschine entscheidend, weil grep dort
den Wagenruecklauf beim Lesen entfernt.
Beide Pruefungen fuehren die echten Zeilen aus dem Skript aus -- die eine
gegen den Verzeichnisaufbau, den release.sh erzeugt, die andere gegen zwei
Dateien mit und ohne Wagenruecklauf.
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>
Drei Fehler, alle drei gemessen statt vermutet.
1. Freigaben wurden nie eingebunden ("mount error(1): Operation not
permitted"). Ursache war NICHT der Mount-Namensraum, sondern
SystemCallFilter=@system-service: darin fehlen mount/umount2, und
SystemCallErrorNumber=EPERM macht daraus genau diese Meldung. Nachgewiesen
in einem nachgebauten Sandkasten: mit "@mount" gelingt die Einbindung,
ohne nicht. Die Unit von TESM erlaubt jetzt "@system-service @mount"; alles
andere an der Haertung bleibt. Der zuvor eingebaute Umweg ueber nsenter ist
zurueckgenommen -- RestrictNamespaces=yes verbietet setns, er konnte nie
funktionieren.
Eingebunden wird damit im Namensraum des Webprozesses. Deshalb ist die
Aufraeumaufgabe fuer abgelaufene Einbindungen aus dem Ueberwachungsdienst in
den Webprozess gewandert: ein anderer Dienst sieht diese Einbindungen nicht
und haette Datenbankzeilen als geloest markiert, waehrend die Freigabe
eingebunden blieb.
2. apt-Installationen aus der Oberflaeche scheiterten mit "dpkg returned an
error code (1)". Der Sandkasten wird an dpkg und dessen Maintainer-Skripte
weitervererbt; mit ProtectKernelLogs=yes liefert "dmesg" ein EPERM und ein
Postinst-Skript bricht daran ab. apt laeuft jetzt ueber systemd-run, also
von PID 1 gestartet und damit ohne unsere Haertung.
3. Der RPC-Neustart loeste SSH aus. run_reboot entschied den Weg anhand von
device["category"] -- und dieser Schluessel bedeutet je Herkunft etwas
anderes: die Wartungsabfrage legt die Kategorie der *Zugangsdaten* darunter,
inventory.get_device die des *Geraets*. Ueber die Statusuebersicht kam ein
Windows-Geraet mit leerer Geraetekategorie an, wurde fuer Linux gehalten und
ueber SSH auf Port 22 angesprochen, bis das Zeitlimit griff. Der Weg wird
jetzt uebergeben -- entschieden wird er ohnehin schon vorher in
restart.resolve. Ausserdem zeigte die Neustart-Historie alles ausser "ssh"
als "PoE-Port"; RPC hat jetzt einen eigenen Eintrag.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eine Freigabe wurde nie eingebunden -- "mount error(1): Operation not
permitted". Gemessen: mit denselben Optionen und Zugangsdaten gelingt die
Einbindung aus einer normalen Root-Shell sofort. Ursache ist der eigene
Mount-Namensraum des Dienstes (ProtectSystem, ProtectHome, PrivateTmp,
ReadWritePaths); darin laesst sich kein neues Dateisystem einbinden.
Nachgebaut und belegt: derselbe Aufruf scheitert in einem transienten Unit mit
dieser Haertung und gelingt, sobald er ueber nsenter im Namensraum des Wirts
laeuft. Von dort wird der Mount in den Namensraum des Dienstes weitergegeben,
die Anwendung sieht ihn also -- geprueft, inklusive Lesbarkeit fuer den
Dienstbenutzer.
mount-cifs und umount laufen deshalb ueber nsenter. Ohne nsenter im System
bleibt das Verhalten unveraendert. Die Haertung des Dienstes bleibt vollstaendig
erhalten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entfernt: Tests, Architekturdokumentation und die Serverhaelfte des
Lizenzprotokolls. Beides liegt im Entwicklungsrepository alientim/TESM-DEV.
Das Lizenzprotokoll ist geteilt. TESM braucht nur die gemeinsame Haelfte
(tesm-licensing): Lizenzen verifizieren, Status bewerten, Anfragen stellen,
Antworten pruefen. Ausstellen, erneuern, Antworten signieren und
Schluesselerzeugung liegen jetzt in tesm-licensing-server und damit
ausschliesslich beim Lizenzserver -- ein Client soll den Code zum Ausstellen
nicht einmal mitbringen. Nachgeprueft: kein Modul von TESM oder tesm-core
importiert eine der verschobenen Funktionen.
install.sh installiert entsprechend je Anwendung nur die noetigen Pakete und
bricht mit klarer Meldung ab, wenn die verlangte Anwendung nicht im Baum liegt.
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>