6.0 KiB
Installer overhaul + themed MOTD — Design
Date: 2026-06-15 · Branch: feat/v1-foundation · Status: approved · Beta-prep #4 (+ #5 MOTD)
Make install.sh a true one-command bootstrap on a fresh Debian/Ubuntu host: install Docker, create
a dedicated clusev user, check the domain's DNS, bring the stack up, and end with a clean banner
(dedicated-user creds + admin creds + dashboard URL only). Also install a product-themed MOTD shown at
every host login. Builds on the existing 7-phase idempotent installer (secrets, stack, migrate, admin,
sentinel) — extend, don't rewrite.
Operator-approved decisions
- Docker auto-install: Debian/Ubuntu (apt, official Docker repo) only; other OS → clear message + manual link.
- Dedicated user: login user
clusevwith a random password, in thedockergroup, owns the install dir. - Run mode:
sudo ./install.sh(root required for Docker install + user creation). - Domain: DNS-check → if it resolves to this server, proceed with TLS; else warn + offer "take the domain anyway (HTTP until DNS points here)" or "continue on the IP".
Flow (extends the current phases)
Phase 0 — root + OS. Require root ([ "$(id -u)" = 0 ] else die "Bitte mit sudo ausführen: sudo ./install.sh").
Read /etc/os-release; set OS_FAMILY from ID/ID_LIKE (debian|ubuntu → apt). Non-apt + Docker missing → die with a manual-install hint.
Phase 1 — preflight + Docker. If docker + docker compose present → skip. Else on apt:
install via the official repo — install -m0755 -d /etc/apt/keyrings, fetch
https://download.docker.com/linux/$ID/gpg → keyring, add the deb [signed-by=…] repo, apt-get update,
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin,
systemctl enable --now docker. Verify docker compose version after. Keep openssl check.
Phase 2 — dedicated clusev user. If id clusev fails → useradd -m -s /bin/bash clusev, set a
random password (CLUSEV_USER_PW=$(openssl rand -base64 18), chpasswd), usermod -aG docker clusev.
Always: chown -R clusev:clusev the install dir. Set HOST_UID/HOST_GID in .env to clusev's
uid/gid (so containers run as clusev). Idempotent: existing user kept, password NOT regenerated (only
shown if freshly created — track USER_CREATED=1).
Phase 3 — inputs + domain DNS check. Keep the interactive domain/email prompts (+ env + bare-IP
fallback). When a domain is given: resolve it (getent ahosts "$domain" | awk '{print $1}' | sort -u)
and get the server's public IP (curl -fsS https://api.ipify.org with a hostname -I fallback). If the
domain's A/AAAA set contains the server IP → DOMAIN_OK=1, proceed with TLS. Else warn with the
resolved-vs-server IPs and offer (interactive): [1] take the domain anyway — "Cert kommt automatisch,
sobald DNS hierher zeigt; bis dahin nur HTTP", [2] continue on the IP (clear the domain). Non-interactive
default: take the domain anyway (on-demand TLS will issue once DNS is correct) — but print the warning.
Phases 4-8 — unchanged in spirit: .env/secrets (idempotent), session hardening, build image, start
stack, restart-sentinel units (render User=clusev + the real path now), wait for DB, migrate + caches,
first admin (one-time random password). The sentinel chown/unit-user becomes clusev.
Phase 9 — MOTD (beta-prep #5). Install a themed dynamic MOTD: write an executable
/etc/update-motd.d/00-clusev (Debian/Ubuntu run-parts MOTD) that prints the Clusev wordmark in the
product's signal-orange ANSI (256-color \033[38;5;208m), a hairline, and the live access line
(https://<domain> or http://<server-ip>), plus "verwaltet vom Benutzer clusev · docker compose -f
docker-compose.prod.yml ps". Ship the template under docker/motd/00-clusev and install-copy it
(chmod +x), substituting the URL. Idempotent (overwrite our file only; never touch other MOTD parts).
On non-Debian (no update-motd.d) write /etc/motd static as a fallback.
Closing banner. Print ONLY (signal-orange, no phase noise): "Installation erfolgreich." then a boxed
summary — Dashboard URL/IP · Admin-Login (email + the one-time admin password) · (if freshly created)
the clusev host user + its one-time password · "Diese Passwörter werden nur jetzt angezeigt." No other
output after. Secrets appear ONLY in this banner (never logged/echoed elsewhere).
Files
install.sh— phases 0–3 + 9 added, sentinel rendering →clusev, closing banner reworked.docker/motd/00-clusev(NEW) — the themed MOTD template (with a__CLUSEV_URL__placeholder)..envwrites —HOST_UID/HOST_GID= clusev's; existing keys unchanged.- (README documents all of this — beta-prep #3, separate.)
Security
- Docker installed from the official GPG-verified apt repo (no
curl|bash get.docker.com). clusevis in thedockergroup (docker = root-equivalent) — this IS the dedicated admin user, by design; documented.- Random passwords via
openssl rand, shown once in the closing banner, never logged. - Root is required up-front and clearly stated; the script stays idempotent +
set -euo pipefail. - DNS check uses the resolved A/AAAA vs the server IP; a mismatch never silently enables a wrong-host cert (on-demand TLS already gates issuance to the configured domain).
Testing
shellcheck install.sh+docker/motd/00-clusevclean (no errors).- The MOTD script renders the banner with a substituted URL (run it locally, check ANSI + the URL line).
- Idempotency: re-running on an already-installed host must not regenerate secrets/passwords or recreate the user (dry-reason: the closing banner shows the freshly-created creds only on first run).
- A full root install can only be validated on a fresh Debian/Ubuntu VM (out of CI) — the operator runs it there; the build verifies via shellcheck + a structured read-through + the MOTD render.