clusev/docs/superpowers/specs/2026-06-15-installer-overha...

6.0 KiB
Raw Blame History

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 clusev with a random password, in the docker group, 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 03 + 9 added, sentinel rendering → clusev, closing banner reworked.
  • docker/motd/00-clusev (NEW) — the themed MOTD template (with a __CLUSEV_URL__ placeholder).
  • .env writes — 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).
  • clusev is in the docker group (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-clusev clean (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.

Out of scope

  • #3 professional README (documents this), #2 de-Claude history rewrite (last). Non-apt Docker auto-install (RHEL/dnf). Unattended/cloud-init variants.