From 580c72a59dcf203bc96c9716d60f4e23d0a262d1 Mon Sep 17 00:00:00 2001 From: boban Date: Fri, 19 Jun 2026 22:49:10 +0200 Subject: [PATCH] feat: clusev host CLI wrapper for short stack commands --- docker/clusev/clusev | 48 +++++++++++++++++++++++++++++++ install.sh | 15 ++++++++-- tests/scripts/test-clusev-cli.sh | 49 ++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 docker/clusev/clusev create mode 100644 tests/scripts/test-clusev-cli.sh diff --git a/docker/clusev/clusev b/docker/clusev/clusev new file mode 100644 index 0000000..4079bec --- /dev/null +++ b/docker/clusev/clusev @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Clusev host CLI — short wrappers around the production stack. +# Generated by install.sh (it substitutes __CLUSEV_DIR__ with the install directory) and +# installed to /usr/local/bin/clusev. Operators run `clusev ` from anywhere instead +# of long `docker compose -f docker-compose.prod.yml ...` invocations. The compose filename is +# referenced internally only — it is never printed to the operator. +set -euo pipefail + +CLUSEV_DIR="__CLUSEV_DIR__" +COMPOSE_FILE="${CLUSEV_DIR}/docker-compose.prod.yml" + +compose() { docker compose -f "$COMPOSE_FILE" "$@"; } + +usage() { + cat <<'EOF' +clusev — Fleet-Control Verwaltung (Host) + + sudo clusev update Update holen, Image neu bauen, Migrationen anwenden (root) + clusev reset-admin Admin-Zugang zuruecksetzen (2FA entfernen, Passwort neu setzen) + clusev restart Stack neu starten + clusev logs [dienst] Logs folgen (Strg-C beendet) + clusev ps Dienst-Status (Alias: status) + clusev migrate Datenbank-Migrationen anwenden + clusev artisan <...> beliebiges artisan-Kommando im app-Container + clusev version installierte Version anzeigen + clusev help diese Uebersicht + +Was im Hintergrund laeuft: + update -> update.sh (git pull + Image-Rebuild + migrate) + reset-admin -> docker compose exec app php artisan clusev:reset-admin + migrate -> docker compose exec app php artisan migrate --force +EOF +} + +cmd="${1:-help}"; shift 2>/dev/null || true +case "$cmd" in + update) exec "${CLUSEV_DIR}/update.sh" "$@" ;; + reset-admin) compose exec app php artisan clusev:reset-admin "$@" ;; + restart) compose up -d "$@" ;; + logs) compose logs -f "$@" ;; + ps|status) compose ps "$@" ;; + migrate) compose exec app php artisan migrate --force "$@" ;; + artisan) compose exec app php artisan "$@" ;; + version) grep -oE "'version' => '[^']+'" "${CLUSEV_DIR}/config/clusev.php" \ + | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1 || true ;; + help|-h|--help) usage ;; + *) printf 'Unbekannter Befehl: %s\n\n' "$cmd" >&2; usage; exit 64 ;; +esac diff --git a/install.sh b/install.sh index 0abb681..c47a158 100755 --- a/install.sh +++ b/install.sh @@ -312,8 +312,19 @@ ADMIN_PW="$(printf '%s\n' "$INSTALL_OUT" | sed -n 's/^CLUSEV_ADMIN_PASSWORD=// ADMIN_MAIL="$(printf '%s\n' "$INSTALL_OUT" | sed -n 's/^CLUSEV_ADMIN_EMAIL=//p' | head -n1)" [ -n "$ADMIN_MAIL" ] || ADMIN_MAIL="$ADMIN_EMAIL" -# ── [9/9] MOTD (themed, shown at host login) ───────────────────────── -phase 9/9 "MOTD installieren" +# ── [9/9] Host-CLI + MOTD ──────────────────────────────────────────── +phase 9/9 "Host-CLI + MOTD installieren" + +# clusev host command: bake the install dir into the template, drop it in PATH. Literal +# (not sed) substitution so any path is safe; best-effort with the whole group's stderr muted +# so a read-only /usr/local/bin never fails the installer (no stray "Permission denied"). +cli_template="$(cat docker/clusev/clusev)" +cli_rendered="${cli_template//__CLUSEV_DIR__/$(pwd)}" +if { printf '%s\n' "$cli_rendered" > /usr/local/bin/clusev && chmod 0755 /usr/local/bin/clusev; } 2>/dev/null; then + info "Host-Befehl installiert: clusev (z. B. 'clusev ps', 'sudo clusev update')" +else + info "Host-Befehl uebersprungen (/usr/local/bin nicht beschreibbar)" +fi if [ -n "$DOMAIN" ] && [ "$DOMAIN_OK" = 1 ]; then ACCESS_URL="https://${DOMAIN}" elif [ -n "$DOMAIN" ]; then diff --git a/tests/scripts/test-clusev-cli.sh b/tests/scripts/test-clusev-cli.sh new file mode 100644 index 0000000..b6797fa --- /dev/null +++ b/tests/scripts/test-clusev-cli.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Verifies the clusev host CLI template renders and behaves. No docker needed — only the +# help / version / unknown-command paths are exercised. Run from the repo root: +# bash tests/scripts/test-clusev-cli.sh +set -euo pipefail +cd "$(dirname "$0")/../.." +REPO="$(pwd)" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +fail() { printf 'FAIL: %s\n' "$1" >&2; exit 1; } + +# Render the template the way install.sh does. +sed "s|__CLUSEV_DIR__|${REPO}|g" docker/clusev/clusev > "${TMP}/clusev" +chmod +x "${TMP}/clusev" +CLI="${TMP}/clusev" + +# 1. Valid bash syntax. +bash -n "$CLI" || fail "syntax error in rendered clusev" + +# 2. help lists the key subcommands. +out="$("$CLI" help)" +for needle in "clusev update" "clusev reset-admin" "clusev logs" "clusev ps" "clusev migrate"; do + printf '%s' "$out" | grep -qF "$needle" || fail "help missing: $needle" +done + +# 3. bare invocation and --help also show usage. +"$CLI" | grep -qF "clusev reset-admin" || fail "bare invocation did not show usage" +"$CLI" --help | grep -qF "clusev reset-admin" || fail "--help did not show usage" + +# 4. unknown command exits 64 and warns on stderr. +set +e +"$CLI" definitely-not-a-command >/dev/null 2>"${TMP}/err"; rc=$? +set -e +[ "$rc" -eq 64 ] || fail "unknown command exit = $rc (want 64)" +grep -qF "Unbekannter Befehl" "${TMP}/err" || fail "unknown command did not warn" + +# 5. version reads config/clusev.php (no docker). +want="$(grep -oE "'version' => '[^']+'" config/clusev.php | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)" +got="$("$CLI" version)" +[ "$got" = "$want" ] || fail "version = '$got' (want '$want')" + +# 6. install.sh wires the CLI into PATH. +grep -qF "/usr/local/bin/clusev" install.sh || fail "install.sh does not install /usr/local/bin/clusev" + +# 7. usage text must NOT leak the long compose filename. +printf '%s' "$out" | grep -qF "docker-compose.prod.yml" && fail "usage leaks docker-compose.prod.yml" + +printf 'ok — clusev CLI checks passed\n'