diff --git a/deploy/update.sh b/deploy/update.sh index d1ae34f..cabe638 100755 --- a/deploy/update.sh +++ b/deploy/update.sh @@ -440,7 +440,11 @@ after="$(git rev-parse HEAD)" # The image is only rebuilt when its definition changed — minutes versus seconds. if ! git diff --quiet "$base" "$after" -- docker/ 2>/dev/null; then phase image "Rebuilding the image" - docker compose build --quiet app + # `terminal` mitbauen, nicht nur `app`: `docker compose up -d` weiter unten + # baut nur Images, die es noch GAR NICHT gibt. Beim ersten Ausrollen fällt + # das nicht auf, danach nie wieder — eine Änderung an docker/terminal/ + # sähe ausgeliefert aus, und es liefe das alte Image. + docker compose build --quiet app terminal # Recreate now, not at the end: everything below runs INSIDE this container, # and an update that changes the PHP runtime would otherwise install and # migrate under the old one. @@ -526,6 +530,23 @@ docker compose up -d # keep running the code from before the update. docker compose restart queue queue-provisioning scheduler reverb +# Und die Terminal-Brücke gleich hinterher, aus demselben Grund wie die beiden +# VPN-Dienste unten: sie lebt im Netz-Namensraum des Provisioning-Containers +# (`network_mode: "service:queue-provisioning"`), und ein Prozess bleibt in dem +# Namensraum, in dem er gestartet ist. Nach der Zeile darüber lauscht sie in +# einem, den es nicht mehr gibt. +# +# Nichts meldet dabei einen Fehler — im Gegenteil: `docker compose ps` sagt +# weiter „healthy", weil die Lebendprüfung über die Loopback-Adresse INNERHALB +# des verwaisten Namensraums läuft. Nach außen antwortet nginx mit 502, und der +# Betreiber liest „Keine Verbindung — läuft der Terminal-Dienst?", während der +# Dienst behauptet, es gehe ihm gut. +# +# Ohne Profil-Abfrage, anders als unten: `terminal` hängt an keinem Profil und +# läuft immer. Auf wg0 wartet sie nicht — sie bindet 8082 im Namensraum, nicht +# die Tunneladresse; die braucht sie erst, wenn jemand ein Ticket einlöst. +docker compose restart terminal + # AFTER the hub, always. Both VPN services live in the provisioning container's # network namespace, and a process holds the namespace it started in — so once # the hub is restarted they are listening inside one that no longer exists. diff --git a/docker-compose.yml b/docker-compose.yml index 0b9deb0..7447609 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -210,9 +210,11 @@ services: restart: unless-stopped network_mode: "service:queue-provisioning" environment: - # Datenbank 1 — dieselbe, auf der `Redis::connection('cache')` das Ticket - # ablegt (REDIS_CACHE_DB, Vorgabe 1). - REDIS_URL: "redis://redis:6379/1" + # Dieselbe Datenbank, auf der `Redis::connection('cache')` das Ticket + # ablegt. Der Wert kam aus derselben .env-Stelle wie bei PHP, statt fest + # auf 1 zu stehen: sonst legt die eine Seite ab, wo die andere nicht sucht, + # sobald jemand REDIS_CACHE_DB anfasst — und das Ticket wäre einfach nie da. + REDIS_URL: "redis://redis:6379/${REDIS_CACHE_DB:-1}" # phpredis stellt jedem Schluessel dieser Verbindung # `database.redis.options.prefix` voran. Der ist aus APP_NAME abgeleitet # (Str::slug(APP_NAME).'-database-') und fuer PHP unsichtbar, fuer jeden diff --git a/lang/de/hosts.php b/lang/de/hosts.php index 1ef7e09..770a723 100644 --- a/lang/de/hosts.php +++ b/lang/de/hosts.php @@ -158,9 +158,14 @@ return [ 'title' => 'Ticket nicht mehr gültig', 'note' => 'Ein Ticket gilt dreißig Sekunden und genau einmal. Dieses war abgelaufen oder schon eingelöst — ein neuer Anlauf stellt ein frisches aus.', ], + // Absichtlich ohne Festlegung: die Brücke schickt 4502 für JEDE + // gescheiterte Anmeldung — Maschine still, Fingerabdruck abweichend, + // Schlüssel abgewiesen, Zeitüberschreitung. „Antwortet nicht" wäre + // im häufigsten Fall schlicht falsch: nach einem frisch aufgesetzten + // Host antwortet sie sehr wohl und lehnt ab. 'unreachable' => [ - 'title' => ':host antwortet nicht', - 'note' => 'Die Anmeldung über den Tunnel ist gescheitert: entweder ist die Maschine nicht erreichbar, oder ihr Fingerabdruck weicht von dem hinterlegten ab. Was davon, steht im Protokoll des Terminal-Dienstes — nicht in diesem Fenster.', + 'title' => 'Keine Sitzung auf :host', + 'note' => 'Die Brücke kam nicht bis zur Kommandozeile: die Maschine antwortet nicht, ihr Fingerabdruck weicht vom hinterlegten ab, oder sie hat den Flottenschlüssel abgewiesen — Letzteres ist das Wahrscheinlichste, wenn der Host neu aufgesetzt wurde. Was davon, steht im Protokoll des Terminal-Dienstes; dieses Fenster erfährt es absichtlich nicht.', ], 'retry' => 'Neu verbinden', ], diff --git a/lang/en/hosts.php b/lang/en/hosts.php index 540c384..6ccd0fb 100644 --- a/lang/en/hosts.php +++ b/lang/en/hosts.php @@ -159,9 +159,13 @@ return [ 'title' => 'Ticket no longer valid', 'note' => 'A ticket lasts thirty seconds and works exactly once. This one had expired or was already redeemed — another attempt issues a fresh one.', ], + // Deliberately non-committal: the bridge sends 4502 for EVERY failed + // sign-in — machine silent, fingerprint mismatch, key refused, + // timeout. "Not answering" would be plain wrong in the likeliest + // case: after a host is rebuilt it answers and refuses. 'unreachable' => [ - 'title' => ':host is not answering', - 'note' => 'Signing in through the tunnel failed: either the machine is unreachable, or its fingerprint differs from the one on file. Which of the two is in the terminal service log, not in this window.', + 'title' => 'No session on :host', + 'note' => 'The bridge never reached a command line: the machine is not answering, its fingerprint differs from the one on file, or it refused the fleet key — the last is the likeliest once a host has been rebuilt. Which of them is in the terminal service log; this window is deliberately not told.', ], 'retry' => 'Reconnect', ], diff --git a/resources/views/livewire/admin/host-terminal.blade.php b/resources/views/livewire/admin/host-terminal.blade.php index e8c6ed2..816e7f6 100644 --- a/resources/views/livewire/admin/host-terminal.blade.php +++ b/resources/views/livewire/admin/host-terminal.blade.php @@ -102,11 +102,13 @@ terminal.js an; ein `onclick` im Markup wäre Verhalten an der Stelle, an der sonst nur Gestalt steht. - `hidden` sitzt am Wrapper, nicht am Knopf: `x-ui.button` bringt - `inline-flex` mit, und beide Klassen haben dieselbe Spezifität — - wer gewinnt, entschiede dann die Reihenfolge im Stylesheet. Dieselbe - Falle wie bei `size-4`/`size-5` in R18, und sie ist hier mit einem - zusätzlichen Element billiger umgangen als nachgewiesen. --}} + `hidden` sitzt am Wrapper, nicht am Knopf. Nicht, weil es dort nicht + wirkte — Tailwind gibt `.hidden` als letzte der Display-Klassen aus + und schlägt `inline-flex` damit —, sondern weil die Klassen des + Knopfes aus einem geteilten Bauteil kommen und an dieser Stelle + niemandem gehören. Die Bühne selbst trägt `hidden` und `flex` + durchaus gemeinsam: das ist ein Element, das hier steht und hier + bleibt. --}}
$problem !== 'unknown'])> {{ __('hosts.terminal.stage.retry') }} diff --git a/resources/views/livewire/admin/hosts.blade.php b/resources/views/livewire/admin/hosts.blade.php index 4f7e02e..2044bf0 100644 --- a/resources/views/livewire/admin/hosts.blade.php +++ b/resources/views/livewire/admin/hosts.blade.php @@ -82,10 +82,16 @@ {{ __('hosts.col.capacity') }} {{ __('hosts.col.status') }} {{-- Leer mit Absicht: die Spalte trägt nur den - Terminal-Knopf, und der ist nicht auf jeder - Zeile da (Berechtigung, Betriebsbereitschaft). - Eine Beschriftung, die unabhängig davon immer - dastünde, wäre eine falsche Zusage. --}} + Terminal-Knopf, und der ist nicht für jeden da + (`hosts.manage`). Eine Beschriftung, die + unabhängig davon immer dastünde, wäre eine + falsche Zusage. + + Hier stand einmal „Berechtigung, + Betriebsbereitschaft" — Letzteres entscheidet + seit dem Entsperren des Knopfes nichts mehr, + und zwanzig Zeilen tiefer begründet der + Kommentar am Knopf ausführlich das Gegenteil. --}}