Commit Graph

109 Commits (7224825db1fafe033d728bf2887660de8d619e10)

Author SHA1 Message Date
nexxo 7224825db1 Version 1.4.3 — das Terminalfenster diagnostiziert sich selbst
tests / pest (push) Failing after 9m30s Details
tests / assets (push) Successful in 31s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 22:34:32 +02:00
nexxo 624ad0bfff Version 1.4.2 — Terminal auf einem Host, aus der Konsole heraus
tests / pest (push) Failing after 14m58s Details
tests / assets (push) Successful in 25s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 19:41:53 +02:00
nexxo f294db5259 Release v1.4.1 — Banner mit Hintergrund, Rechte mit Klartext
tests / pest (push) Failing after 9m3s Details
tests / assets (push) Successful in 24s Details
tests / release (push) Has been skipped Details
2026-08-02 16:47:15 +02:00
nexxo 462c84ee23 Release v1.4.0 — Offene Punkte nutzen die volle Breite
tests / pest (push) Failing after 9m22s Details
tests / assets (push) Successful in 27s Details
tests / release (push) Has been skipped Details
2026-08-02 16:40:26 +02:00
nexxo 8a77544c62 Release v1.3.99 — Offene Punkte nach Dringlichkeit sortiert und neu gestaltet
tests / pest (push) Failing after 9m24s Details
tests / assets (push) Successful in 23s Details
tests / release (push) Has been skipped Details
2026-08-02 16:35:22 +02:00
nexxo 566d5fc47f Release v1.3.98 — Update laeuft aus einer Kopie, neue Seite fuer offene Punkte
tests / pest (push) Failing after 9m8s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
2026-08-02 02:02:39 +02:00
nexxo e8a532e150 Release v1.3.97 — Seitenleiste neu geordnet, Deployment heilt seinen npm-Cache
tests / pest (push) Failing after 9m22s Details
tests / assets (push) Successful in 24s Details
tests / release (push) Has been skipped Details
2026-08-02 01:51:42 +02:00
nexxo 413826f3d7 Release v1.3.96 — die Anfrage ueberlebt einen gescheiterten Mailversand
tests / pest (push) Failing after 9m32s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
2026-08-01 23:51:43 +02:00
nexxo e6ee0716f0 Release v1.3.95 — die Leiter passt jetzt zur Maschine
tests / pest (push) Failing after 9m24s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Has been skipped Details
Die alte Leiter wurde entworfen, bevor jemand ausgerechnet hatte, was ein Server
traegt. Ein Host mit 456 GB und 15 % Reserve vergibt 388 GB — das sind auf
100-GB-Paketen drei Kunden fuer 147 EUR, waehrend 64 GB RAM und zwoelf Kerne
acht bis sechzehn getragen haetten. Es wurde eine Rechenmaschine fuer ein
Speicherprodukt gekauft, und sechs RAM-Plaetze verfielen.

Neu: Start 30 GB fuer 39 EUR, Team 85 fuer 79, Business 175 fuer 139 — jeweils
auf 40, 100 und 200 GB Platte. Neun Startkunden je Server statt drei, 351 EUR
statt 147 bei unveraenderter Miete, und der Punkt, an dem Speicher und RAM
gleichzeitig ausgehen, ist derselbe.

Der Kopfraum waechst mit: max(10 GB, 12 %). Eine 175-GB-Nextcloud mit fuenfzig
Nutzern hat eine groessere Datenbank, mehr Dateiversionen und mehr Papierkorb
als eine mit drei Leuten, und eine volle Platte legt nicht den Upload still,
sondern die Instanz.

Der Zusatzblock geht auf 20 GB fuer 15 EUR und ist bei drei gedeckelt. Bei
12 EUR waere er mit 0,60 EUR/GB billiger gewesen als jeder Aufstieg (0,73 und
0,67) — dann stapeln Kunden, statt umzusteigen, und belegen den knappsten
Rohstoff zum niedrigsten Preis. Ein Block bringt zwei Gigabyte eigenen Kopfraum
mit, damit die Regel auch fuer ein gestapeltes Paket gilt.

Drei Reparaturen mussten der Preisaenderung vorausgehen: die Packungsgroesse
stand in der Konfiguration statt auf der Buchung (der Zuschnitt haette jeden
gekauften 100-GB-Block auf 20 schrumpfen lassen), der Deckel war nirgends
geprueft, und das Aufstocken fragte den Host nicht nach Platz.

Enterprise verlaesst den Laden und wird angefragt: 500 GB finden auf 388 GB
keinen Platz, und der Kunde erfuehre das nach der Zahlung. Damit "eigener
Server" mehr ist als ein Wort, koennen Hosts jetzt einem Kunden reserviert
werden — placeableIn() nahm bisher jeden aktiven Host, und die
Kapazitaetszahlen zaehlten eine exklusiv verkaufte Maschine mit.

Das Testpaket heisst Intern, verschwindet aus dem Verkauf und bleibt
verschenkbar. Dafuer trennen zwei Schalter jetzt zwei Fragen: internal heisst
"nicht im Laden, aber ueber die Konsole vergebbar", sales_enabled = false
heisst "ganz aus".

Codex fand drei Fehler, die keine Durchsicht vorher hatte: der Deckel war nicht
nebenlaeufigkeitssicher, ein angehefteter Host umging die Reservierung, und
dieselbe max(1, ...)-Falle hatte im Bestaetigungsfenster ueberlebt.

2466 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:44:53 +02:00
nexxo 55b749c129 Release v1.3.94 — die Kurven zeigen wieder, wie ausgelastet der Host ist
tests / pest (push) Failing after 9m19s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
Drei Punkte des Besitzers, und der dritte hat den eigentlichen Fehler
freigelegt.

Die Verlaufslinien sahen komisch aus, weil sie auf ihr EIGENES Minimum und
Maximum skalierten. Ein Host mit 0,2 % CPU, der zwischen 0,1 und 0,3 schwankt,
zeichnete damit einen Seismographen über die volle Höhe — und widersprach flach
der Zahl direkt daneben. Dieselbe Regel stand im Chart.js-Entwurf schon
ausformuliert ("eine automatisch skalierte Achse lässt 3 % wie eine Wand
aussehen") und ging beim Umstieg auf die Kacheln verloren.

x-ui.spark bekommt deshalb `min`/`max`. Ohne Angabe bleibt alles wie bisher —
das ist, was jeder bestehende Aufrufer übergibt. Die Prozent-Kacheln geben
0 und 100 mit, die Netz-Kacheln nur die 0, weil MiB/s keine natürliche
Obergrenze haben. Ein untätiger Host zeichnet jetzt vier ruhige Linien statt
zweier wogender und zweier flacher — vorher wirkte CPU (0,2 %) belebter als RAM
(3,6 %), also genau verkehrt herum.

Dazu: Werte außerhalb der Grenzen werden geklemmt statt aus dem Kasten
gezeichnet, die Fläche ist ein Verlauf statt einer harten Kante, und die
Linien sind mit 120×40 statt 80×32 lesbar.

Ich hatte max=100 zwischendurch selbst verworfen, weil es "zu tot" aussah — auf
einem Bild, auf dem die Füllung wegen eines CSS-Fehlers gar nicht gezeichnet
wurde. Ein Vergleich mit einem kaputten Bild. Der Fehler: `.spark path
{ fill: none }` schlägt als CSS-Regel das Präsentationsattribut fill="url(#…)".
`fill: none` gehört an die Linie, nicht an jeden Pfad.

Die Ausstattung ist wieder einzeilig. Sechs Felder, sechs Spalten — und die
Bau-Kennung steht nur noch im Titel: als eigene Zeile zwang sie die ganze Tafel
in eine zweite Reihe, für eine Zeichenkette, die fast niemand liest.

Und die Wartezeit: die Seite stößt beim Öffnen eine Sammlung an, wenn noch
keine Messwerte da sind, statt bis zum nächsten minütlichen Lauf leer zu
bleiben. Ein leerer Kasten liest sich als "kaputt", nicht als "gleich". Der Job
ist ShouldBeUnique, zwei geöffnete Seiten reihen also keine zwei ein.

Codex-Befund dazu (P2): das galt auch für Hosts, die der Sammler ohnehin
überspringt — ein Host mitten in der Übernahme hat keinen Token, und seine
Seite hätte die ganze Flotte abgeklappert und die eigenen Kacheln trotzdem leer
gelassen. Wer gesammelt wird, steht jetzt einmal am Modell (Host::collectable),
gelesen von Job und Seite. Zwei Fassungen dieser Frage waren genau der Grund.

Geprüft: 2307 Tests grün, Pint sauber, Codex ohne Befund. Der Test für den
Maßstab prüft jetzt auch die echten Aufrufstellen und nicht nur das Bauteil —
gegengeprobt durch Entfernen von max=100, dann fällt er. Im Browser beide
Fälle angesehen: ruhiger Host vier flache Linien, ausgelasteter Host lesbare
Form, null Konsolenfehler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 11:56:10 +02:00
nexxo dc35e5310f Release v1.3.93 — die Seite konnte den Host gar nicht fragen
tests / pest (push) Failing after 9m23s Details
tests / assets (push) Successful in 28s Details
tests / release (push) Has been skipped Details
Auf echter Hardware blieb jede Messkachel leer, während der Host sichtbar
online war und vor zwei Minuten geantwortet hatte. Kein Zufall und kein
Aussetzer: ein Entwurfsfehler.

Nur der queue-provisioning-Container hängt im WireGuard-Netz — NET_ADMIN,
/dev/net/tun, das wireguard-Volume. Der app-Container, der die Konsole rendert,
hat überhaupt keine Route zu 10.66.0.x. Ich hatte den Proxmox-Aufruf in
render() gelegt, also in den einen Container, der die Management-Adresse nicht
erreichen kann. Ein Aufruf von dort konnte nie etwas anderes sein als eine
Zeitüberschreitung.

PingHosts schreibt dieselbe Regel seit Langem in seinen Kopf: "runs on the
provisioning queue, which is where the Proxmox credentials are usable." Ich
habe sie gelesen und nicht angewendet.

Verschlimmert hat es mein eigenes catch (Throwable): der Grund wurde
verschluckt, und die Kachel sagte "keine Messwerte" — ununterscheidbar davon,
dass der Host schweigt. Auf dem Testhost fiel nichts auf, weil TEST-NET
ohnehin nie antwortet.

Jetzt zwei Hälften:

- HostLoadSeries::collect() holt und legt ab, aus Jobs\CollectHostLoad auf der
  provisioning-Warteschlange, minütlich — der Takt, in dem Proxmox einen
  frischen Messwert schreibt. Ein Fehlschlag wird protokolliert, mit Host,
  Node und Grund.
- HostLoadSeries::forHost() liest nur aus dem Zwischenspeicher und öffnet nie
  eine Verbindung. Ein Test hält das mit Http::assertNothingSent() fest.

Der Eintrag lebt fünf Minuten bei minütlichem Sammeln: länger als der Takt,
damit ein ausgefallener Lauf keine Seite leert, die eine Sekunde vorher in
Ordnung war — und kurz genug, dass ein stehengebliebener Sammler die Zahlen
mitnimmt, statt eine alte Stunde als aktuell stehenzulassen.

Der Sammler ist ShouldBeUnique (Codex-Befund, P1). Die provisioning-Warteschlange
ist DIESELBE, auf der Kunden-Bereitstellung läuft; ein stiller Host kostet den
vollen HTTP-Zeitablauf, und ohne diese Sperre stauten sich minütlich neue Läufe
hinter dem alten und verzögerten bezahlte Arbeit. Dasselbe Mittel, das
CollectInstanceTraffic nebenan schon benutzt.

Dazu: der Zustands-Punkt war mit 62 px so groß wie der Speicher-Ring nebenan.
Eine gefüllte Scheibe wiegt optisch weit mehr als ein dünner Ring und erschlug
die Kachel — jetzt 32 px.

Und ein Test, der aus Versehen recht behielt: die Kachel-Prüfung verglich mit
"50", was auch die 500 GB in der Instanzenliste darunter trifft. Sie prüft
jetzt Zahlen, die sonst nirgends auf der Seite vorkommen.

Noch offen, nicht hier angefasst: VmTemplateCheck fragt die Proxmox-API
ebenfalls aus dem app-Container heraus, von der Bereitschaftsseite aus. Selber
Fehler, Bestand, eigener Punkt.

Geprüft: 2299 Tests grün, Pint sauber, Codex ohne Befund. Im Browser mit
eingespielten Messwerten: sechs gefüllte Kacheln, Zustands-Scheibe in
Proportion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 11:23:00 +02:00
nexxo 589489a9fc Release v1.3.92 — sechs Kacheln statt dreier ungleicher Kästen
tests / pest (push) Failing after 10m15s Details
tests / assets (push) Successful in 28s Details
tests / release (push) Has been skipped Details
Nach der Vorlage des Besitzers. Zustand, Speicher und die gemeinsame Kurve
standen nebeneinander: zwei davon halb leer, die dritte überfüllt, und die
gemeinsame Kurve brauchte eine Legende, um zu sagen, welche Linie welche ist.

Eine Reihe je Kachel löst alle drei Beschwerden auf einmal. Die Beschriftung
der Kachel benennt die Reihe, also braucht es keine Legende mehr. Die Höhen
sind durch das Raster gleich statt zufällig. Und der leere Platz ist mit
Messwerten gefüllt, die es ohnehin schon gab: Proxmox' Aufzeichnung liefert
Netzdurchsatz in beide Richtungen mit, ungefragt.

Sechs Kacheln: CPU-Auslastung, RAM-Auslastung, Speicher (als Ring), Eingehend,
Ausgehend, Zustand. Gebaut mit x-ui.metric, x-ui.spark und x-ui.ring — die
gab es alle schon, und der Kopfkommentar von x-ui.metric sagt selbst "exactly
as the approved template draws it". Nichts daneben neu gebaut.

Die öffentliche IP steht jetzt auf der Seite. Sie stand vorher NUR klein unter
der Überschrift — die Adresse, unter der der Host wirklich erreichbar ist, war
in der Detailseite nirgends ein Feld. Sie führt jetzt die Ausstattungs-Tafel
an, und die Reserve-Eingabe ist mit dorthin gezogen: die Kacheln zeigen, was
gemessen wurde, die Tafel, was eingestellt ist. Ein Eingabefeld zwischen
Messwerten sähe aus, als ließe sich eine Messung ändern.

Alle vier Verlaufslinien tragen denselben Ton. Die Regel steht im Bauteil
selbst — "muted where the figure is observed, accent where it can be acted on"
—, und hier ist keine Zahl anzufassen. Vier verschiedene Töne nebeneinander
behaupten einen Unterschied, den es nicht gibt.

Zwei Funde aus der Prüfung
--------------------------

- x-ui.spark warf fehlende Messwerte per array_filter heraus und verband die
  Nachbarn. Zwei Fehler auf einmal: die Linie behauptete eine Messung, die es
  nicht gab, und alles danach rutschte nach links — eine Stunde mit zwei Lücken
  zeichnete sich als achtundfünfzig Minuten. Die x-Lage kommt jetzt aus dem
  Platz in der URSPRÜNGLICHEN Reihe, und zusammenhängende Messwerte werden als
  eigene Züge gezeichnet. Eine saubere Reihe ergibt genau einen Zug und
  dasselbe Bild wie vorher, was alle bisherigen Aufrufer liefern.

- Der Zwischenspeicher überlebt einen Deploy. Ein Eintrag aus v1.3.91 kennt
  netin/netout nicht, und die Host-Seite wäre 55 Sekunden lang an einem
  fehlenden Schlüssel gestorben — genau in der Minute, in der jemand nachsieht,
  ob das Update durch ist. Der Schlüssel heißt jetzt host-load:v2:<id> und
  wandert mit der Form mit.

Und einer, den kein Prüfer gemeldet hat: beim Zerlegen in Züge stand im
Flächenpfad ein `L` unmittelbar vor einem `M`. Gültig gelesen, nicht
gezeichnet — die Füllung verschwand still. Aufgefallen ist es beim Ansehen der
Seite, nicht durch eine Meldung; jetzt prüft ein Test, dass jeder Flächenpfad
mit M anfängt, mit Z endet und keinen Befehl direkt hinter einem anderen trägt.

x-ui.chart behält seinen update-on-Weg, obwohl diese Seite ihn nicht mehr
benutzt: er ist eine geprüfte Fähigkeit des gemeinsamen Bauteils, und der
darunterliegende Fix (Instanz aus dem reaktiven Alpine-Objekt) gilt für jeden
Chart.

Geprüft: 2291 Tests grün, Pint sauber, Codex ohne Befund. Im Browser mit
eingespielten Messwerten: sechs Kacheln, Lücke als echte Aussparung in Linie
UND Fläche, Leerzustand zeigt "—" statt einer Null, null Konsolenfehler über
einen vollen Poll-Zyklus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 11:02:55 +02:00
nexxo 665a0c4e08 Release v1.3.91 — die Host-Seite zeigt Last statt Ausstattung
tests / pest (push) Failing after 9m29s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Die Karte "Rechenleistung" zeigte keine Leistung. 12 Kerne und 63 GB sind die
Ausstattung des Blechs und ändern sich nie — sie standen aber im selben
Kartenraster wie "Zustand" und "Speicher", die beide leben. Wer die Seite
öffnete, um zu sehen, wie es dem Host geht, las dort eine Zahl, die das nie
sagen konnte.

An ihrer Stelle steht jetzt die Last: CPU und RAM als Stundenkurve, beide in
Prozent auf EINER Achse, dazu die aktuellen Werte als beschriftete Zahlen.

Die Geschichte kommt aus Proxmox' eigener Aufzeichnung
(/nodes/{node}/rrddata), nicht aus einem eigenen Sampler. Ein Sampler hieße
neue Tabelle, minütlicher Job, Aufräum-Job und ~1440 Zeilen je Host und Tag —
um weniger genau nachzubauen, was ohnehin auf der Platte liegt. Die RRD ist ab
der ersten Sekunde gefüllt, auch für die Stunde vor dem ersten Hinsehen, und
kann nicht von dem abweichen, was Proxmox' eigene Oberfläche zeigt.

Eine Lücke bleibt eine Lücke: ein Punkt ohne Messwert wird null, nie 0, und
spanGaps steht auf false. Dieselbe Regel wie in instance_metrics. Antwortet der
Host gar nicht, sagt die Tafel das in einem Satz, statt eine ruhige Stunde zu
zeichnen — auf dem Testhost live bestätigt.

Der Fehler, den das ans Licht gebracht hat
------------------------------------------

x-ui.chart steht überall unter wire:ignore, sonst zerstört Livewire das Canvas.
Ein Poll erreicht den Chart also nie. Dafür bekam das Bauteil ein optionales
update-on: es hört auf ein Fenster-Ereignis und tauscht die Daten IM
bestehenden Chart.js-Objekt.

Das lief nicht. Die Zahlen neben der Kurve wanderten, die Kurve nicht, und
chart.update() starb still im Legenden-Plugin:

    TypeError: Cannot set properties of undefined (setting 'fullSize')

Grund: die Chart.js-Instanz lag als Eigenschaft im Alpine-Objekt und wurde
damit reaktiv umhüllt. Chart.js' Plugin-Innenleben überlebt das Proxy nicht.
Gemessen statt geschlossen: dieselbe Instanz wirft über das Proxy und läuft
über Alpine.raw(). Sie liegt jetzt in der Closure.

Aufgefallen ist es nie, weil bis zum ersten Live-Chart kein einziger Chart in
diesem Repo je update() gerufen hat — konstruieren und Erstzeichnen gehen durch
die Hülle noch. tests/Feature/ChartLiveUpdateTest.php hält die Regel fest,
damit der nächste Live-Chart nicht denselben Nachmittag kostet.

Der Rest
--------

- Version lesbar: "Proxmox VE 9.2.6" statt pve-manager/9.2.6/7f8d…, mitten in
  der Bau-Kennung abgeschnitten. Die Kennung steht klein darunter. Eine
  unerwartete Form wird unverändert durchgereicht statt verschluckt.
- Vier Kleinkarten (Mgmt-IP, Node, Version, Instanzen) sind eine
  Ausstattungs-Tafel geworden. Die Instanzen-Anzahl steht in der Überschrift
  der Liste, die sie ohnehin zeigt.
- Der Übernahme-Fortschritt klappt zu, sobald sie durch ist. Fünfzehn
  abgehakte Schritte sind auf einem laufenden Host kein Dauerinhalt —
  aufklappbar über <details>, ohne JavaScript.
- PlanVersion::requiredTemplateVmids() ersetzt die dritte Kopie derselben
  Fensterlogik.
- BuildVmTemplate sagt nicht mehr "this takes 10–20 minutes". Das war eine
  Schätzung vor dem ersten Lauf; gemessen waren es unter zwei. Eine Konsole,
  die falsch vorhersagt, erzieht dazu, sie zu ignorieren.

Geprüft: 2281 Tests grün, Pint sauber, Codex ohne Befund. Die Farbwahl gegen
den Validator gerechnet (ΔE 28,3 protan / 39,2 normal; der Akzent liegt unter
3:1 gegen die Fläche, deshalb tragen beide Reihen sichtbare Beschriftung). Im
Browser: null Konsolenfehler über einen vollen Poll-Zyklus, und die Kurve
wandert auf dem echten Poll ohne Neuladen — mit eingespielten Messwerten
belegt, weil die Testhosts in TEST-NET liegen und nie antworten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:24:21 +02:00
nexxo 5aedef2b35 Release v1.3.90 — nach dem Paket fragen, nicht nach dem Pfad
tests / pest (push) Failing after 9m12s Details
tests / assets (push) Successful in 23s Details
tests / release (push) Has been skipped Details
Der zweite Bau auf echter Hardware brach ab mit "qemu-guest-agent ist NICHT im
Abbild". Es war im Abbild. Die Prüfung suchte /usr/bin/qemu-ga, Debian legt den
Gastagenten nach /usr/sbin/qemu-ga.

Dass das Paket eingebaut war, stand im selben Protokoll: customise_cloud_image
ruft --install und danach --run-command 'systemctl enable qemu-guest-agent'. Ein
fehlgeschlagenes --run-command lässt virt-customize abbrechen, und systemctl
kann keine Unit aktivieren, die es nicht gibt. Der Lauf ging durch — also war
das Paket da.

Das ist zum zweiten Mal derselbe Fehler in dieser Datei: eine Eigenschaft über
einen NAMEN behauptet statt über die Sache. Erst die Partitionsnummer statt der
Lage auf der Platte (v1.3.89), jetzt ein Pfad statt der Verpackung. Ein Pfad ist
eine Vermutung darüber, wie ein Paket seine Dateien verteilt.

Gefragt wird jetzt /var/lib/dpkg/info/<paket>.list — die Aussage des Pakets über
sich selbst, und genau die Frage, die hier gestellt gehört: hat apt es still
übergangen? Ein einziges Verzeichnis-Listing für alle vier Pakete, weil jeder
virt-ls-Aufruf eine eigene Appliance startet.

Geprüft werden damit qemu-guest-agent (Falle 3), cloud-guest-utils für growpart
(Falle 4), docker-compose-plugin und docker-ce. Der Vergleich ist grep -qx: ein
vorhandenes docker-ce-cli.list darf nicht als docker-ce durchgehen —
durchgespielt, zusammen mit einem vollständigen und einem lückenhaften Listing.

Falle 1 bleibt eine Inhaltsprüfung, weil sie eine ist: user: www-data steht in
einer Datei, die wir selbst hineinkopieren, nicht in einem Paket.

Und die Meldung nennt jetzt die fehlenden Pakete beim Namen statt nur die Falle.

Bestätigt aus demselben Lauf: Falle 2 besteht seit v1.3.89 ("Wurzel auf
/dev/sda1 und nichts dahinter"), und der Umbau selbst — Docker, Compose,
Gastagent — lief in 38 Sekunden durch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:32:18 +02:00
nexxo 88dadf759a Release v1.3.89 — Falle 2 lehnte das einzige Abbild ab, für das sie gedacht war
tests / pest (push) Failing after 9m49s Details
tests / assets (push) Successful in 25s Details
tests / release (push) Has been skipped Details
Der erste echte Vorlagenbau brach ab mit:

  Die Root-Partition (/dev/sda1) ist nicht die letzte auf der Platte
  (/dev/sda15).

Das Abbild war in Ordnung, die Prüfung nicht. Sie verglich Partitions-NAMEN und
hielt für die letzte, was `sort -V` nach hinten sortiert. Debians Cloud-Abbild
legt die BIOS-Boot- und die EFI-Partition aber an den ANFANG der Platte und
nummeriert sie als 14 und 15; die Wurzel ist Nummer 1 und liegt physisch
dahinter. Gemessen: sda15 beginnt bei 4 MB, sda1 bei 128 MB.

Das ist kein Zufall dieses Abbilds, sondern genau die Eigenschaft, die Falle 2
verlangt — nur so kann growpart die Wurzel über den freien Rest ausdehnen. Die
Prüfung verweigerte also ein Abbild, weil es ihre Bedingung erfüllte.

Jetzt wird nach dem ANFANG auf der Platte gefragt statt nach der Nummer, über
`guestfish part-list`, das Offsets in Bytes liefert. "Nichts liegt dahinter" ist
eine Aussage über die Platte, nicht über die Benennung.

Die Auswertung steht in zwei eigenen Funktionen (last_partition_number,
partition_start), damit sie sich ohne Proxmox und ohne libguestfs gegen
aufgezeichnete part-list-Ausgaben durchspielen lässt — gemacht, in beide
Richtungen: Debians Anordnung besteht, eine Platte mit etwas hinter der Wurzel
fällt durch. Genau diese Gegenprobe fehlte, weshalb der Fehler bis auf echte
Hardware durchkam.

Und die Fehlermeldung trägt jetzt die Byte-Offsets beider Partitionen mit.
Scheitert das je wieder, steht die Platte im Protokoll statt einer Vermutung.

Nebenbei bestätigt: der VPN-Aussetzer vor dem Bau hat zweimal
"could not ask Proxmox about the VM templates" gemeldet und wiederholt, statt
einen Neubau auszulösen — der Codex-P1-Fix aus v1.3.88 hat am ersten Tag
verhindert, dass ein Netzausfall qm destroy --purge auslöst.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 09:21:54 +02:00
nexxo db125781ed Release v1.3.88 — die Vorlage baut sich selbst
tests / pest (push) Failing after 9m17s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Has been skipped Details
Der letzte Handgriff in der Host-Übernahme fällt weg. VerifyVmTemplate meldete
bisher nur, dass eine Vorlage fehlt, weil niemand entschieden hatte, was in die
goldene Vorlage gehört. Entschieden ist es längst und steht in
deploy/bootstrap/lib/template.sh — der neue Schritt BuildVmTemplate lädt genau
diese Datei auf den Host und führt sie dort aus, statt ihre Prüfungen ein
zweites Mal in PHP zu haben.

Er läuft abgekoppelt und wird abgefragt: Abbild laden und drei
virt-customize-Läufe brauchen zehn bis zwanzig Minuten, ein einzelner
SSH-Aufruf liefe gegen den Befehlszeitablauf von 2000 s. "Läuft noch" heißt
dabei, dass der Prozess lebt (kill -0 gegen die hinterlegte PID) — in der
Statusdatei steht "running" auch dann noch, wenn niemand mehr da ist, der sie
ändert.

Fünf Fehler, die dabei aufgefallen sind und Geld gekostet hätten:

- qm importdisk hängte die Platte unter ${storage}:vm-9000-disk-0 ein. Der Name
  gilt nur bei Block-Ablagen; auf einer Verzeichnis-Ablage heißt sie
  local:9000/vm-9000-disk-0.qcow2 — also genau auf dem per Debian aufgesetzten
  Proxmox, um das es hier geht. Jetzt qm set --import-from, und Proxmox
  benennt selbst.
- growpart war nie installiert. GrowGuestFilesystem ruft es auf, und es lief
  bisher, weil Debians Cloud-Abbild es zufällig mitbringt. Fiele es heraus,
  läge jedes gekaufte Kontingent über einem Dateisystem, das nie gewachsen ist.
  Jetzt ausdrücklich eingebaut und als vierte Falle nachgewiesen.
- local nimmt ab Werk keine Platten an. Ohne das stirbt nicht nur der Bau,
  RegisterCapacity meldet danach Kapazität 0: ein Host, der fertig aussieht und
  nie einen Kunden tragen kann. ensure_image_storage greift nur ein, wenn keine
  Ablage Platten annimmt, hängt images an die vorhandene Liste an statt sie zu
  ersetzen, und schreibt über pvesm set statt in die pmxcfs-Datei.
- Ein abgebrochener Download blieb unter dem Zielnamen liegen und wäre beim
  nächsten Lauf ungeprüft weiterbenutzt worden. Jetzt .part, umbenannt erst
  nach geprüfter Summe.
- VerifyVmTemplate und VmTemplateCheck fragten nur, ob VMID 9000 existiert. Ein
  abgebrochener Bau hinterlässt eine gewöhnliche VM mit dieser Nummer, und
  beide sagten dazu "passt" — der Fehler kam beim ersten bezahlten Klon zurück.
  Jetzt template: 1.

isTemplate() stellt zwei Anfragen, weil die falsche Antwort hier etwas
zerstört: false heißt "Vorlage fehlt", und der Bau fängt mit qm destroy --purge
an. Proxmox beantwortet die Konfiguration einer nicht vorhandenen VM mit 500 —
demselben Code wie einen Knoten in Not. Die VM-Liste klärt deshalb die
Abwesenheit, alles darunter wirft und landet im Wiederholungs-Zweig.

Aufgeben beendet erst die Prozessgruppe, dann räumt es auf, und gebaut wird nur
die Fehlliste: create_proxmox_template räumt eine VMID weg, bevor es sie
anlegt, also hätte "alles Verlangte" eine gesunde zweite Vorlage auf dem Weg
zerstört.

Geprüft: 2267 Tests grün, Pint sauber, sh -n über alle drei Shell-Dateien, die
storage.cfg-Auswertung gegen eine echte Beispieldatei durchgespielt, und jeder
Befehl, den der Schritt absetzt, geht durch sh -n — keine andere Prüfung führt
diese Shell je aus. Drei Codex-Runden (R15), alle Befunde behoben.

Nicht geprüft: nichts davon lief je gegen echte Hardware. Die erste Übernahme
auf einem Proxmox-Host ist die Abnahme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 05:01:44 +02:00
nexxo 97ac6c04e8 Release v1.3.87 — VM.Monitor gibt es in Proxmox 9 nicht mehr
tests / pest (push) Failing after 8m56s Details
tests / assets (push) Successful in 24s Details
tests / release (push) Has been skipped Details
Die Übernahme des ersten PVE-9-Hosts blieb in CreateAutomationToken stehen:
`pveum role modify` lehnt die GANZE Privilegienliste ab, sobald ein einziger
Name darin unbekannt ist — "invalid privilege 'VM.Monitor'". Die Liste stammt
aus der PVE-8-Zeit.

Ersatzlos gestrichen, nicht ersetzt: VM.Monitor gab Zugriff auf den
QEMU-Monitor, und den benutzt CluPilot nirgends. Geprüft, nicht vermutet — die
einzigen Treffer auf "monitor" im Provisioning betreffen Uptime Kuma. Auf PVE 8
fehlt damit ein Recht, das dort ohnehin niemand gebraucht hat; eine Liste passt
weiterhin auf beide Fassungen.

Dazu ein Test, der die angeforderten Namen gegen die gültigen hält — abgelesen
aus der Administrator-Rolle eines echten PVE 9. Die Liste ist die Verabredung
zwischen CluPilot und jedem Host, den es je übernimmt; sie hier zu prüfen ist
billiger als der Abbruch auf einer Maschine, die schon halb eingerichtet ist.

2245 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 03:42:37 +02:00
nexxo 4f04d9d29d Release v1.3.86 — der Grund von pveum, nicht nur die Feststellung
tests / pest (push) Failing after 8m49s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
„could not converge the Proxmox automation role privileges", fünfmal
hintereinander. Die Meldung sagte, WAS scheiterte, und verschwieg das Einzige,
was weiterhilft: was `pveum` dazu geschrieben hat. Der Betreiber musste sich
auf den Host melden und den Befehl von Hand nachstellen, um eine Zeile zu
lesen, die daneben schon dastand.

Die Fehlerausgabe von `pveum role modify` steht jetzt in der Meldung — erste
Zeile, gekürzt: `pveum` stellt den Grund voran und breitet danach seine
Aufrufhilfe aus, und die wäre ein Bildschirm Text in einer Ereigniszeile.

2244 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 03:38:02 +02:00
nexxo b9fb401823 Release v1.3.85 — Handshake-Prüfung hing an einem Programm, das es nicht gibt
tests / pest (push) Failing after 8m54s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Has been skipped Details
Die Übernahme brach in "WireGuard einrichten" ab: fünf Wiederholungen,
"WireGuard handshake not up yet". Geprüft wurde mit `ping -c1 -W2 <hub-ip>`.
Das fragte drei Dinge auf einmal und nannte nur eines: ob der Handshake steht,
ob ICMP durchkommt — und ob es `ping` auf der Maschine überhaupt GIBT.

Auf Hetzners `Debian-trixie-latest-amd64-base` gibt es das nicht. `iputils-ping`
ist im Image nicht dabei, und PrepareBaseSystem installiert
`curl gnupg ifupdown2 chrony`. Der Schritt scheiterte damit über einem Tunnel,
der stehen konnte. Auf dem alten Proxmox-Image war ping dabei, deshalb lief es
dort durch — dieselbe Pipeline, anderes Grundsystem.

Gefragt wird jetzt WireGuard selbst: `date +%s; wg show wg0 latest-handshakes`.
Die Uhr des HOSTS kommt in derselben Antwort mit, weil `latest-handshakes` eine
absolute Zeit ausgibt und ein Vergleich gegen UNSERE Uhr eine Zeitverschiebung
zwischen zwei Maschinen als Tunnelzustand läse. `date` ist in coreutils und
überall da.

Codex, zwei Runden:

- P1: `> 0` hiesse "hat jemals". WireGuard behält den Zeitstempel unbegrenzt,
  also meldete ein Wiederholungslauf über einem toten Tunnel "steht", und die
  Schritte danach wählten die Tunneladresse für SSH. Jetzt muss der Handshake
  frisch sein (180 s) und vom KONFIGURIERTEN Hub kommen — ein fremder Peer auf
  wg0 ist kein Beweis dafür, dass wir erreichbar sind.
- P1: Der neue Host-Versatz (.100) liess die Vergabe in einem Subnetz kleiner
  als /26 "erschöpft" melden, obwohl unten alles frei war. Der Versatz ist eine
  Bevorzugung, keine Bedingung: zweiter Durchgang von vorn.

Ausserdem, wie gewünscht: Hosts bekommen ihre Tunneladresse ab .100
(CLUPILOT_WG_HOST_OFFSET), Personen zählen weiter von unten. Fortlaufend
vergeben landete der erste Host zwischen zwei Notebooks, und wer eine Adresse
in einem Protokoll las, konnte nicht sagen, ob dahinter ein Mensch oder eine
Maschine steht.

2243 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 03:00:19 +02:00
nexxo 2f15d1e7a4 Release v1.3.84 — VPN-Zugänge nach Personen und Hosts getrennt
tests / pest (push) Failing after 9m6s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Personen und Hosts standen gemischt untereinander, nach Verbindungszustand
sortiert — ein Host-Zugang konnte also zwischen zwei Mitarbeitern stehen.
Beide sind Peers im selben Netz und haben dieselben Messwerte, aber es sind
zwei verschiedene Fragen: "wer von uns ist im Netz" und "welche Maschinen
hängen dran". Wer die eine stellt, liest die Antworten der anderen als
Rauschen.

Jetzt zwei Gruppen mit Überschrift und Anzahl. Eine Gruppe ohne Einträge wird
gar nicht erst gezeichnet — auf einer frischen Installation stünde sonst
"Hosts" über einer Lücke, bevor je einer angelegt wurde.

Getrennt wird nach der geladenen host-Beziehung, nicht nach `kind`: die
Plakette in der Zeile fragt dasselbe, und wer einen "Host-Zugang" liest, soll
ihn auch unter den Hosts finden. Ein adoptierter Peer (kind=system) mit
host_id gehört zu den Hosts, obwohl seine Art etwas anderes sagt.

Die Zeile selbst ist nach resources/views/components/admin/vpn-peer-row.blade.php
gewandert. Sie zweimal hinzuschreiben hätte geheissen, sie ab dem nächsten
Knopf an zwei Stellen zu pflegen — und die zweite fällt erst auf, wenn jemand
einen Host-Zugang sucht und ihn anders aussehen sieht als seinen eigenen.

Die Plakette bleibt trotz der Überschrift daneben: eine Zeile wandert beim
Suchen aus ihrer Überschrift heraus, und dann steht sie ohne sie da.

Codex: keine Befunde. 2237 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 02:34:15 +02:00
nexxo e0c0bb2c29 Release v1.3.83 — Fortschritt zeigte Übersetzungsschlüssel
tests / pest (push) Failing after 8m48s Details
tests / assets (push) Successful in 23s Details
tests / release (push) Has been skipped Details
Unter jedem Schritt stand `dashboard.step_state.done` statt eines Wortes —
im Adminbereich UND im Kundenportal, wo derselbe Stepper jemanden durch die
Einrichtung seiner Cloud begleitet.

Die Schlüssel gab es in keiner der beiden Sprachdateien. Die Paritätsprüfung
aus R16 konnte das nicht finden: sie vergleicht Deutsch mit Englisch, und die
beiden waren sich einig. Der Aufruf ist zusätzlich zusammengesetzt
(`__('dashboard.step_state.'.$state)`), also findet ihn auch keine Suche nach
festen Zeichenketten. Der neue Test rendert deshalb das Bauteil und prüft alle
vier Zustände in beiden Sprachen.

Dazu ein Test, der heute Nacht hochgegangen ist: PortalInvoicesTest verbot die
Zeichenkette '01.08.2026' — das Datum aus einer alten Attrappe. Heute ist der
1. August 2026, und die echten Rechnungen des Tests tragen das
Ausstellungsdatum von heute. Eine Zusicherung, die ein Datum verbietet,
verbietet auch den Tag, an dem es echt wird. Was die Attrappe ausmachte, war
ihr Satz, und den prüft der Nachbartest über 'Nächste Abbuchung' — stabil,
weil er nirgends sonst entsteht.

2235 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 02:08:28 +02:00
nexxo ac81bc5b98 Release v1.3.82 — Übernahme-Befehlszeile neu ausstellen
tests / pest (push) Failing after 8m50s Details
tests / assets (push) Successful in 23s Details
tests / release (push) Has been skipped Details
Die Zeile gab es genau einmal: auf der Seite direkt nach dem Anlegen. Wer sie
dort nicht kopierte — oder wem sie wegbrach, wie beim WireGuard-Fehler bis
1.3.80 — hatte danach einen Host in der Liste, für den es keinen Weg zu einer
Befehlszeile mehr gab. Ein zweites Anlegen scheitert an der eindeutigen IP,
also blieb nur Entfernen und von vorn.

HostTakeoverCommand behauptet in seinem Kopfkommentar, die Zeile werde "an
zwei Stellen gezeigt (beim Anlegen und beim Neuausstellen eines Codes)". Das
Zweite gab es nie — ein Kommentar, der eine Absicht beschreibt und wie eine
Beschreibung des Gebauten klingt.

Neu: ReissueTakeover als Modal auf der Host-Detailseite. Bestätigen zuerst
(R23), denn ein bisher ausgegebener Code wird dabei wertlos; danach steht die
ganze Zeile mit Kopieren-Knopf darin, nicht nur der Code.

Codex, zwei Runden, beide Male dieselbe Sorte Fehler von mir — die Ansicht
versteckt den Knopf, die Methode prüft nichts:

- P1: `issue()` erzwingt jetzt selbst, welcher Zustand zulässig ist. Ein
  Modal, das offen blieb, während der Host aktiv wurde, rief die Methode
  trotzdem; eine Livewire-Methode ist ohnehin von aussen aufrufbar.
- P2: `issue()` prüft die Tunnel-Einstellungen erneut. Sonst wird ein gültiger
  Code entwertet und dafür eine Zeile ausgegeben, der die WireGuard-Angaben
  fehlen — genau die kaputte Zeile, vor der die Warnung daneben steht.
- P1 der zweiten Runde: `disabled` gehörte nicht in die Liste der zulässigen
  Zustände. Es sieht aus wie "noch nicht fertig" und ist das Gegenteil —
  toggleMaintenance() schaltet einen LAUFENDEN Host so still. Der Knopf hätte
  eine Produktionsmaschine zur Neuinstallation angeboten.

Die Bedingung steht deshalb einmal am Bauteil (ReissueTakeover::eligible) und
wird von Ansicht und Methode gefragt: zwei Fassungen liefen auseinander,
sobald jemand eine ändert.

2231 Tests grün.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 01:00:53 +02:00
nexxo 8ab50f5650 Release v1.3.81 — Host anlegen scheiterte am WireGuard-Peer
tests / pest (push) Failing after 9m3s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Has been skipped Details
Das Anlegen eines Hosts endete in der Konsole mit einem 500, bevor der
Betreiber den Einmal-Code je zu sehen bekam. Ursache war nicht das Anlegen,
sondern der Tunnel-Peer: HostEnrolment::issueWithKeys() rief `wg set wg0`
selbst auf — im Web-Request, also im `app`-Container. Der hat weder NET_ADMIN
noch /dev/net/tun; wg0 lebt im Provisioning-Container. Die Antwort war
"Unable to modify interface: Operation not permitted".

Der Peer geht jetzt als ApplyHostVpnPeer auf die Provisioning-Queue — genau
dorthin, wo ApplyVpnPeer es für die VPN-Zugänge längst richtig macht, mit
derselben `wireguard:hub`-Sperre und demselben Grundsatz: der Sollzustand
kommt beim Ausführen aus der Zeile, nicht aus dem beim Einreihen
festgehaltenen Wert. Der abgelöste Schlüssel reist als Wert mit, weil in der
Zeile zu diesem Zeitpunkt schon der neue steht.

Aufgefallen ist es nie, weil die Testsuite den Hub gegen FakeWireguardHub
tauscht — der bestehende Test blieb grün, während der echte Weg seit jeher
fehlschlug. Die zwei neuen Tests prüfen deshalb den WEG statt des Ergebnisses:
in der Anfrage bleibt der Hub unberührt, und der Auftrag liegt auf der
Provisioning-Queue.

Codex: 0 Fehler, 0 Sicherheitsbefunde. Dazu sein P2 — /.claude/ stand weder
im Index noch in .gitignore, ein `git add -A` hätte 153 MB als verschachteltes
Repo eingebettet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 00:43:10 +02:00
nexxo f1133edcbd Release v1.3.80
tests / pest (push) Failing after 9m8s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
2026-07-31 23:02:46 +02:00
nexxo 88382069b8 Release v1.3.79
tests / pest (push) Failing after 8m59s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
2026-07-31 20:58:34 +02:00
nexxo 1e490e03ee Release v1.3.78
tests / pest (push) Failing after 8m59s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Has been skipped Details
2026-07-31 19:52:20 +02:00
nexxo 7880ac7cf5 Release v1.3.77
tests / pest (push) Failing after 8m33s Details
tests / assets (push) Successful in 25s Details
tests / release (push) Has been skipped Details
The DNS check now requires a real zones array before it says anything about the
account. A 200 carrying an HTML page — a portal, a filter, a proxy — was being
read as "no zones at all", which is what the live server was seeing while the
token had read and write and the zone existed with fifteen records.

An intercepted response is now reported as one, with its status and the first
characters of what actually came back.

2053 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 15:42:46 +02:00
nexxo 64045e0a04 Release v1.3.76
tests / pest (push) Failing after 8m37s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Has been skipped Details
The DNS check no longer reports an unclassified HTTP failure as "this account
holds no zones". Anything other than a successful zone list is now named as
what it is, with its status code, and says explicitly that it tells you nothing
about the zones.

2050 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 15:37:01 +02:00
nexxo f9620f29a3 Release v1.3.75
tests / pest (push) Failing after 8m55s Details
tests / assets (push) Successful in 29s Details
tests / release (push) Has been skipped Details
The readiness page no longer shows a green badge over a red measurement. A
check that was actually run decides the badge; the passive "something is
configured" answer only fills in where nothing was measured.

zone_not_found now names the zone it looked for and the zones the token can
actually see, so it stops reading like "the token is wrong" — a wrong token
never gets that far.

Also: two traffic tests failed on the 29th, 30th and 31st of long months,
because they built "last month" with subMonth() and Carbon resolves that
calendrically. The production code was never affected.

2045 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 15:26:50 +02:00
nexxo d3f2533565 Release v1.3.74
tests / pest (push) Failing after 8m19s Details
tests / assets (push) Successful in 22s Details
tests / release (push) Has been skipped Details
IP addresses no longer appear in the certificate overview. 127.0.0.1 comes from
ADMIN_HOSTS, where a bare IP is deliberately allowed as the way back into the
console — but no authority issues certificates for one, so the row was
permanently red and unfixable.

The row already stored is removed on the next sync. A vanished config entry that
holds a live certificate becomes a manual entry instead of disappearing.

2043 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 01:02:07 +02:00
nexxo d013124c12 Release v1.3.73
tests / pest (push) Failing after 8m19s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
The hostnames page fills itself from the installation instead of starting
blank. Every name this installation answers on — from SITE_HOSTS, APP_HOST,
FILES_HOST and ADMIN_HOSTS — appears with its certificate and the days it has
left, without anybody typing it in.

Four counts at the top: total, valid, expiring soon, without a certificate. A
daily run at 04:17 keeps them current, because the question is not whether a
certificate is valid today but whether renewal is running.

2040 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 00:53:41 +02:00
nexxo 1e3199b72e Release v1.3.72
tests / pest (push) Failing after 8m45s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
install-agent.sh now creates /etc/caddy/clupilot-proxy-hosts.conf and adds the
import line itself, validating afterwards and taking the line back out if Caddy
rejects it. The previous release told the operator to add that line by hand
while nothing created the file — and an import of a missing file makes the whole
proxy configuration invalid, which would have taken the portal down with it.

A hostname already written by hand in the Caddyfile is skipped when the managed
file is rendered. Two blocks for one name make Caddy reject everything.

Run once per server:

  sudo bash /opt/clupilot/deploy/install-agent.sh

2037 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 23:35:38 +02:00
nexxo dac84f024a Release v1.3.71
tests / pest (push) Failing after 8m16s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
The hostnames page is now in the navigation under Betrieb — it had a route and
no entry, which meant it existed only for whoever knew the URL.

And files.… no longer redirects to app.… when called without a path. It answers
404, like every other address in this installation that has nothing to offer:
the redirect told anyone who tried the name where the portal lives and that both
share a machine.

2037 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 23:21:39 +02:00
nexxo 3c5bded19c Release v1.3.70
tests / pest (push) Failing after 8m57s Details
tests / assets (push) Successful in 19s Details
tests / release (push) Has been skipped Details
Hostnames and certificates from the console: add a name, see whether it has a
certificate and for how long, and apply the list to the reverse proxy without
logging into the server.

Two numbers are skipped and both were my mistake, so they are named here rather
than left as gaps somebody has to work out later. v1.3.68 was cut while the
working directory was still inside the feature worktree, so it landed on the
branch instead of on main; this release carries it. v1.3.69 was pushed pointing
at v1.3.67's commit. Deleted rather than moved, both times: a published tag that
changes what it points at is worse than a number nobody uses.

This release needs one thing done once per server, because the root-owned helper
gained a verb:

  sudo bash deploy/install-agent.sh

and one line in /etc/caddy/Caddyfile:

  import /etc/caddy/clupilot-proxy-hosts.conf

The installer creates that file before anything imports it — an import of a
missing file makes the whole proxy configuration invalid, and that takes the
public site with it.

2035 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 22:57:47 +02:00
nexxo 87264ef1f7 Merge console-managed hostnames, and put the release line back on main
One conflict, in VERSION, and it is worth naming rather than resolving quietly.

v1.3.68 was cut on the FEATURE branch, not on main: the working directory was
still inside .worktrees/host-bootstrap when the merge-and-tag ran, so the merge
was a no-op against itself, the version commit landed on the branch, and the tag
went with it. main stayed on 1.3.67 while a tag claimed otherwise.

This merge brings that commit onto main, so v1.3.68 becomes reachable from the
line it always claimed to be on, and the version continues at 1.3.69 rather than
pretending the number was never used.

The project's own memory records this exact failure — check which branch you are
on before every commit — from the time two releases landed on feat/granted-plans
while live stayed where it was. It was a parallel session then and a stale
working directory now; the rule is the same one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 22:56:49 +02:00
nexxo a3b43c5175 Release v1.3.68
The reference Caddyfile now carries a block for FILES_HOST. Setting the variable
alone was never enough: the proxy has to terminate TLS for that name and forward
it, and without a site block Caddy never even asks for a certificate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 22:34:15 +02:00
nexxo fced87d23a Release v1.3.67
tests / pest (push) Failing after 8m48s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Add host rebuilt as a page with one job: a sticky rail of six short steps, the
two provider prerequisites in a panel above the form, and after saving the
command line as the page itself.

Downloads now pass PublicSiteGate. While the site is hidden, a server in a
rescue system would have received the 503 placeholder and piped it into tar — an
unpack error on a machine only reachable through the provider's console.

The number skips 1.3.66. That tag was pushed pointing at v1.3.65's commit,
because the merge behind it had not actually happened, and a server pinning it
would have installed the older tree. It was deleted rather than moved: a
published tag that changes what it points at is worse than a number nobody uses.

2026 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 21:53:59 +02:00
nexxo fc9cc20610 Release v1.3.65
tests / pest (push) Failing after 8m51s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
A hostname of its own for downloads. Public: the terms, the DPA and the TOM,
anonymous and indexable, under versioned filenames so an address in a signed
contract keeps meaning what it meant. Private: the bootstrap archive, gated by
the one-time enrolment code that is already in the copied line.

Set FILES_HOST to switch it on. Left empty, the archive stays on the portal
host and nothing changes.

2025 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 21:37:48 +02:00
nexxo 1e22d66639 Release v1.3.64
tests / pest (push) Failing after 8m53s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Two features reach a tag at once, because the host-takeover branch was cut from
the operating-mode branch and carries all of it.

The operating mode: a test and a live setting for the whole installation, every
credential with a test slot beside its live one, a stored key that says which
mode it belongs to, and a Stripe catalogue that refuses to act on objects from
the other account.

The host takeover: the bootstrap script that turns a machine in a rescue system
into a finished Proxmox host, the archive that serves it, the one-time enrolment
code, and the six-step guide in the console under Hosts and Add host. None of
the script's steps has been accepted on real hardware yet — every hardware step
in its plan is still unticked, and the console work is what this release makes
visible.

Also in here, found while writing the takeover: host names were being built from
the CUSTOMER zone. RegisterHostDns says clupilot.com in its own docblock, and on
this installation a host was actually called fsn-01.node.clupilot.cloud. There
is a platform_zone now and the step uses it.

2017 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 21:22:00 +02:00
nexxo 69a9322a4e Release v1.3.63
tests / pest (push) Failing after 8m36s Details
tests / assets (push) Successful in 19s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:59:16 +02:00
nexxo 4b0eebeccc Release v1.3.62
tests / pest (push) Failing after 8m35s Details
tests / assets (push) Successful in 23s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:38:57 +02:00
nexxo f25a0030f0 Release v1.3.61
tests / pest (push) Failing after 8m45s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:30:55 +02:00
nexxo 976637afb2 Release v1.3.60
tests / pest (push) Failing after 8m51s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:19:39 +02:00
nexxo 3b0c6b19b9 Release v1.3.59
tests / pest (push) Failing after 8m48s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 17:12:30 +02:00
nexxo 56daaef7de Release v1.3.58
tests / pest (push) Failing after 8m40s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:56:58 +02:00
nexxo 48581c3978 Release v1.3.57
tests / pest (push) Failing after 8m31s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:41:32 +02:00
nexxo 74d925bc66 Release v1.3.56
tests / pest (push) Failing after 8m40s Details
tests / assets (push) Successful in 20s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:25:10 +02:00
nexxo 7da2939c55 Release v1.3.55
tests / pest (push) Failing after 8m19s Details
tests / assets (push) Successful in 25s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:05:56 +02:00
nexxo d4b70b7477 Release v1.3.54
tests / pest (push) Failing after 8m32s Details
tests / assets (push) Successful in 18s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 16:00:09 +02:00
nexxo de3ecacc13 Release v1.3.53
tests / pest (push) Failing after 8m6s Details
tests / assets (push) Successful in 21s Details
tests / release (push) Has been skipped Details
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 15:44:45 +02:00