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]
|
||||
|
||||
_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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' ""
|
||||
|
|
|
|||
14
install.sh
14
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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue