feat(motd): richer host login banner + README git prerequisite
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) <noreply@anthropic.com>
feat/v1-foundation
parent
21c087a11f
commit
b9b4b333e1
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -11,7 +11,17 @@ getaggte Releases (Kanal `stable`, optional `beta`) — niemals Entwicklungs-Bui
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [0.9.4] - 2026-06-17
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 **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).
|
- 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
|
## Install
|
||||||
|
|
||||||
```bash
|
```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
|
git clone https://git.bave.dev/boban/clusev.git
|
||||||
cd clusev
|
cd clusev
|
||||||
sudo ./install.sh
|
sudo ./install.sh
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,46 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Clusev themed MOTD — printed at host login (Debian/Ubuntu run-parts MOTD).
|
# 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.
|
# Installed by install.sh, which substitutes __CLUSEV_URL__, __CLUSEV_VERSION__ and
|
||||||
# Signal-orange brand, hairline rule, no emoji. Idempotent: install.sh only ever
|
# __CLUSEV_COMPOSE__ (absolute path to docker-compose.prod.yml). Signal-orange brand,
|
||||||
# overwrites this single 00-clusev file, never other MOTD parts.
|
# 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)
|
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
|
D=$'\033[2m' # dim
|
||||||
B=$'\033[1m' # bold
|
B=$'\033[1m' # bold
|
||||||
R=$'\033[0m' # reset
|
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\n' ""
|
||||||
printf ' %s%sClus%sev%s %s· Fleet Control%s\n' "$B" "$O" "$R$B" "$R" "$D" "$R"
|
printf ' %s%s█▀▀ █ █ █ █▀▀ █▀▀ █ █%s\n' "$B" "$O" "$R"
|
||||||
printf ' %s--------------------------------------%s\n' "$D" "$R"
|
printf ' %s%s█ █ █ █ ▀▀█ █▀▀ █ █%s\n' "$B" "$O" "$R"
|
||||||
printf ' Dashboard: %s%s%s\n' "$O" "__CLUSEV_URL__" "$R"
|
printf ' %s%s▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀ ▀▀▀ ▀ %s %sFleet Control · v%s%s\n' "$B" "$O" "$R" "$D" "$VERSION" "$R"
|
||||||
printf ' %sverwaltet vom Benutzer clusev · docker compose -f docker-compose.prod.yml ps%s\n' "$D" "$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' ""
|
printf '%s\n' ""
|
||||||
|
|
|
||||||
14
install.sh
14
install.sh
|
|
@ -293,12 +293,22 @@ elif [ -n "$DOMAIN" ]; then
|
||||||
else
|
else
|
||||||
if [ "$HTTP_PORT" = "80" ]; then ACCESS_URL="http://${SERVER_IP}"; else ACCESS_URL="http://${SERVER_IP}:${HTTP_PORT}"; fi
|
if [ "$HTTP_PORT" = "80" ]; then ACCESS_URL="http://${SERVER_IP}"; else ACCESS_URL="http://${SERVER_IP}:${HTTP_PORT}"; fi
|
||||||
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
|
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
|
chmod +x /etc/update-motd.d/00-clusev
|
||||||
info "Dynamisches MOTD installiert (/etc/update-motd.d/00-clusev)"
|
info "Dynamisches MOTD installiert (/etc/update-motd.d/00-clusev)"
|
||||||
else
|
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)"
|
info "Statisches MOTD geschrieben (/etc/motd)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue