# 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 0–3 + 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.
