From b9b4b333e1f4e2b5cafc68fb9ee29aaefb871103 Mon Sep 17 00:00:00 2001 From: boban Date: Fri, 19 Jun 2026 16:32:25 +0200 Subject: [PATCH] feat(motd): richer host login banner + README git prerequisite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Validated install.sh end-to-end on a fresh Debian 13 VM (Docker apt install, clusev user, image build, stack up, migrate, admin, MOTD) and a fully idempotent re-run. Two follow-ups from that test: - MOTD: replace the bare two-liner with an orange CLUSEV wordmark, the version, a LIVE stack status ("X/Y Dienste aktiv", green/red via `docker compose ps` at each login), the dashboard URL, a login hint (where the one-time credentials come from — no secret), and management commands. install.sh substitutes __CLUSEV_URL__, __CLUSEV_VERSION__ and __CLUSEV_COMPOSE__; the status lookup degrades to "unbekannt" and never fails a login. - README: minimal Debian/Ubuntu images ship without `git`, so the documented `git clone` failed before reaching the installer. The install block now installs git first and the requirements note it. (curl is missing too but the installer self-installs it.) Admin password strategy confirmed correct as-is: clusev:install generates a random 20-char password with must_change_password=true (forced rotation on first login) — kept over a guessable default like "clusev", which would be a default-credential vuln and would nullify the brute-force hardening. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 12 +++++++++++- README.md | 3 ++- docker/motd/00-clusev | 45 ++++++++++++++++++++++++++++++++++++------- install.sh | 14 ++++++++++++-- 4 files changed, 63 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95deebb..f307739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,17 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui ## [Unreleased] -_Keine offenen Änderungen — der nächste Stand wird hier gesammelt und als `vX.Y.Z` getaggt._ +### Geändert +- **Host-MOTD deutlich aufgewertet.** Statt zwei Zeilen jetzt eine orange CLUSEV-Wortmarke, die + Version, ein **Live-Stack-Status** („X/Y Dienste aktiv", grün/rot — bei jedem Login frisch aus + `docker compose ps`), die Dashboard-URL, eine Login-Hinweiszeile (woher die Zugangsdaten kommen — + ohne Secret) und die Verwaltungs-Befehle. `install.sh` setzt URL, Version und den Compose-Pfad ein; + fällt bei fehlendem Docker robust auf „Status unbekannt" zurück (bricht nie einen Login ab). + +### Behoben +- **README-Voraussetzungen:** `git` ist auf minimalen Debian/Ubuntu-Images nicht vorinstalliert, der + `git clone`-Schritt scheiterte dort vor dem Installer. Der Install-Block installiert `git` jetzt + explizit, und „Voraussetzungen" nennt es (ein Full-VM-Installtest hat das aufgedeckt). ## [0.9.4] - 2026-06-17 diff --git a/README.md b/README.md index 12af9a0..da22e90 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,12 @@ and phpseclib — all running in Docker. The interface is in German (English ava - A **Debian or Ubuntu** server (a small VM is enough) with **root** access. - A public IP. Optionally a domain whose DNS points at the server (for automatic HTTPS). -- Nothing else — the installer sets up Docker for you. +- Only **git** to fetch the repo (one line below) — the installer sets up Docker and everything else. ## Install ```bash +sudo apt-get update && sudo apt-get install -y git # minimal images often lack git git clone https://git.bave.dev/boban/clusev.git cd clusev sudo ./install.sh diff --git a/docker/motd/00-clusev b/docker/motd/00-clusev index 433107d..9b95fba 100644 --- a/docker/motd/00-clusev +++ b/docker/motd/00-clusev @@ -1,15 +1,46 @@ #!/usr/bin/env bash # Clusev themed MOTD — printed at host login (Debian/Ubuntu run-parts MOTD). -# Installed by install.sh; __CLUSEV_URL__ is substituted with the live access URL. -# Signal-orange brand, hairline rule, no emoji. Idempotent: install.sh only ever -# overwrites this single 00-clusev file, never other MOTD parts. +# Installed by install.sh, which substitutes __CLUSEV_URL__, __CLUSEV_VERSION__ and +# __CLUSEV_COMPOSE__ (absolute path to docker-compose.prod.yml). Signal-orange brand, +# live stack status, no emoji (status = colour + dot). Idempotent: install.sh only ever +# overwrites this single 00-clusev file, never other MOTD parts. Never fails a login +# (every dynamic lookup degrades to "unbekannt"). O=$'\033[38;5;208m' # signal-orange (brand) +G=$'\033[38;5;42m' # online green +E=$'\033[38;5;203m' # offline red D=$'\033[2m' # dim B=$'\033[1m' # bold R=$'\033[0m' # reset + +URL="__CLUSEV_URL__" +VERSION="__CLUSEV_VERSION__" +COMPOSE="__CLUSEV_COMPOSE__" + +# Live stack status — "X/Y Dienste aktiv" (green when all up, red otherwise). Best-effort: +# any failure (no docker, daemon down, file moved) falls back to a dim "Status unbekannt". +stack_status() { + command -v docker >/dev/null 2>&1 || { printf '%s● Status unbekannt%s' "$D" "$R"; return; } + [ -f "$COMPOSE" ] || { printf '%s● Status unbekannt%s' "$D" "$R"; return; } + local total running + total=$(docker compose -f "$COMPOSE" config --services 2>/dev/null | grep -c .) + running=$(docker compose -f "$COMPOSE" ps --status running -q 2>/dev/null | grep -c .) + if [ "${total:-0}" -gt 0 ] && [ "${running:-0}" = "$total" ]; then + printf '%s● %s/%s Dienste aktiv%s' "$G" "$running" "$total" "$R" + elif [ "${total:-0}" -gt 0 ]; then + printf '%s● %s/%s aktiv — Stack pruefen%s' "$E" "$running" "$total" "$R" + else + printf '%s● Status unbekannt%s' "$D" "$R" + fi +} + printf '%s\n' "" -printf ' %s%sClus%sev%s %s· Fleet Control%s\n' "$B" "$O" "$R$B" "$R" "$D" "$R" -printf ' %s--------------------------------------%s\n' "$D" "$R" -printf ' Dashboard: %s%s%s\n' "$O" "__CLUSEV_URL__" "$R" -printf ' %sverwaltet vom Benutzer clusev · docker compose -f docker-compose.prod.yml ps%s\n' "$D" "$R" +printf ' %s%s█▀▀ █ █ █ █▀▀ █▀▀ █ █%s\n' "$B" "$O" "$R" +printf ' %s%s█ █ █ █ ▀▀█ █▀▀ █ █%s\n' "$B" "$O" "$R" +printf ' %s%s▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀ ▀ %s %sFleet Control · v%s%s\n' "$B" "$O" "$R" "$D" "$VERSION" "$R" +printf ' %s──────────────────────────────────────────────────────────%s\n' "$D" "$R" +printf ' %sDashboard%s %s%s%s\n' "$D" "$R" "$O" "$URL" "$R" +printf ' %sStack%s %s\n' "$D" "$R" "$(stack_status)" +printf ' %sLogin%s %sAdmin-Zugang stand einmalig im Install-Banner · zuruecksetzen: clusev:reset-admin%s\n' "$D" "$R" "$D" "$R" +printf ' %sVerwalten%s %sdocker compose -f docker-compose.prod.yml ps | logs -f | restart%s\n' "$D" "$R" "$D" "$R" +printf ' %s──────────────────────────────────────────────────────────%s\n' "$D" "$R" printf '%s\n' "" diff --git a/install.sh b/install.sh index ef06a66..9872840 100755 --- a/install.sh +++ b/install.sh @@ -293,12 +293,22 @@ elif [ -n "$DOMAIN" ]; then else if [ "$HTTP_PORT" = "80" ]; then ACCESS_URL="http://${SERVER_IP}"; else ACCESS_URL="http://${SERVER_IP}:${HTTP_PORT}"; fi fi +# Version (for the MOTD header) and the absolute compose path (the MOTD shows a LIVE +# stack status via `docker compose ps` and runs from any cwd at login, so it needs the +# full path). cwd is the repo root (cd at the top of this script). +MOTD_VERSION="$(grep -oE "'version' => '[^']+'" config/clusev.php | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)" +MOTD_COMPOSE="$(pwd)/docker-compose.prod.yml" +motd_render() { + sed -e "s|__CLUSEV_URL__|${ACCESS_URL}|" \ + -e "s|__CLUSEV_VERSION__|${MOTD_VERSION}|" \ + -e "s|__CLUSEV_COMPOSE__|${MOTD_COMPOSE}|" docker/motd/00-clusev +} if [ -d /etc/update-motd.d ]; then - sed "s|__CLUSEV_URL__|${ACCESS_URL}|" docker/motd/00-clusev > /etc/update-motd.d/00-clusev + motd_render > /etc/update-motd.d/00-clusev chmod +x /etc/update-motd.d/00-clusev info "Dynamisches MOTD installiert (/etc/update-motd.d/00-clusev)" else - sed "s|__CLUSEV_URL__|${ACCESS_URL}|" docker/motd/00-clusev | bash > /etc/motd 2>/dev/null || true + motd_render | bash > /etc/motd 2>/dev/null || true info "Statisches MOTD geschrieben (/etc/motd)" fi