#!/usr/bin/env bash # ─── Mosaic Stack — End-to-End Install Test ──────────────────────────────────── # # Runs a clean-container install test to verify the full first-run flow: # tools/install.sh -> mosaic wizard (non-interactive) # -> mosaic gateway install # -> mosaic gateway verify # # Usage: # bash tools/e2e-install-test.sh # # Requirements: # - Docker (skips gracefully if not available) # - Run from the repository root # # How it works: # 1. Mounts the repository into a node:22-alpine container. # 2. Installs prerequisites (bash, curl, jq, git) inside the container. # 3. Runs `bash tools/install.sh --yes --no-auto-launch` to install the # framework and CLI from the Gitea registry. # 4. Runs `mosaic wizard --non-interactive` to set up SOUL/USER. # 5. Runs `mosaic gateway install` with piped defaults (non-interactive). # 6. Runs `mosaic gateway verify` and checks its exit code. # NOTE: `mosaic gateway verify` is a new command added in the # feat/mosaic-first-run-ux branch. If the installed CLI version # pre-dates this branch (does not have `gateway verify`), the test # marks this step as EXPECTED-SKIP and reports the installed version. # 7. Reports PASS or FAIL with a summary. # # To run manually: # cd /path/to/mosaic-stack # bash tools/e2e-install-test.sh # # ────────────────────────────────────────────────────────────────────────────── set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" IMAGE="node:22-alpine" CONTAINER_NAME="mosaic-e2e-install-$$" # ─── Colour helpers ─────────────────────────────────────────────────────────── if [[ -t 1 ]]; then R=$'\033[0;31m' G=$'\033[0;32m' Y=$'\033[0;33m' BOLD=$'\033[1m' RESET=$'\033[0m' else R="" G="" Y="" BOLD="" RESET="" fi info() { echo "${BOLD}[e2e]${RESET} $*"; } ok() { echo "${G}[PASS]${RESET} $*"; } fail() { echo "${R}[FAIL]${RESET} $*" >&2; } warn() { echo "${Y}[WARN]${RESET} $*"; } # ─── Docker availability check ──────────────────────────────────────────────── if ! command -v docker &>/dev/null; then warn "Docker not found — skipping e2e install test." warn "Install Docker and re-run this script to exercise the full install flow." exit 0 fi if ! docker info &>/dev/null 2>&1; then warn "Docker daemon is not running or not accessible — skipping e2e install test." exit 0 fi info "Docker available — proceeding with e2e install test." info "Repo root: ${REPO_ROOT}" info "Container image: ${IMAGE}" # ─── Inline script that runs INSIDE the container ──────────────────────────── INNER_SCRIPT="$(mktemp /tmp/mosaic-e2e-inner-XXXXXX.sh)" trap 'rm -f "$INNER_SCRIPT"' EXIT cat > "$INNER_SCRIPT" <<'INNER_SCRIPT_EOF' #!/bin/sh # Bootstrap: /bin/sh until bash is installed, then re-exec. set -e echo "=== [inner] Installing system prerequisites ===" apk add --no-cache bash curl jq git 2>/dev/null || \ apt-get install -y -q bash curl jq git 2>/dev/null || true # Re-exec under bash. if [ -z "${BASH_VERSION:-}" ] && command -v bash >/dev/null 2>&1; then exec bash "$0" "$@" fi # ── bash from here ──────────────────────────────────────────────────────────── set -euo pipefail echo "=== [inner] Node.js / npm versions ===" node --version npm --version echo "=== [inner] Setting up npm global prefix ===" export NPM_PREFIX="/root/.npm-global" mkdir -p "$NPM_PREFIX/bin" npm config set prefix "$NPM_PREFIX" 2>/dev/null || true export PATH="$NPM_PREFIX/bin:$PATH" echo "=== [inner] Running install.sh --yes --no-auto-launch ===" # Install both framework and CLI from the Gitea registry. MOSAIC_SKIP_SKILLS_SYNC=1 \ MOSAIC_ASSUME_YES=1 \ bash /repo/tools/install.sh --yes --no-auto-launch INSTALLED_VERSION="$(mosaic --version 2>/dev/null || echo 'unknown')" echo "[inner] mosaic CLI installed: ${INSTALLED_VERSION}" echo "=== [inner] Running mosaic wizard (non-interactive) ===" mosaic wizard \ --non-interactive \ --name "test-agent" \ --user-name "tester" \ --pronouns "they/them" \ --timezone "UTC" || { echo "[WARN] mosaic wizard exited non-zero — continuing" } echo "=== [inner] Running mosaic gateway install ===" # Feed non-interactive answers: # "1" → storage tier: local # "" → port: accept default (14242) # "" → ANTHROPIC_API_KEY: skip # "" → CORS origin: accept default # Then admin bootstrap: name, email, password printf '1\n\n\n\nTest Admin\ntest@example.com\ntestpassword123\n' \ | mosaic gateway install INSTALL_EXIT="$?" if [ "${INSTALL_EXIT}" -ne 0 ]; then echo "[ERR] mosaic gateway install exited ${INSTALL_EXIT}" mosaic gateway status 2>/dev/null || true exit "${INSTALL_EXIT}" fi echo "=== [inner] Running mosaic gateway verify ===" # `gateway verify` was added in feat/mosaic-first-run-ux. # If the installed version pre-dates this, skip gracefully. if ! mosaic gateway --help 2>&1 | grep -q 'verify'; then echo "[SKIP] 'mosaic gateway verify' not available in installed version ${INSTALLED_VERSION}." echo "[SKIP] This command was added in the feat/mosaic-first-run-ux release." echo "[SKIP] Re-run after the new version is published to validate this step." # Treat as pass — the install flow itself worked. exit 0 fi mosaic gateway verify VERIFY_EXIT="$?" echo "=== [inner] verify exit code: ${VERIFY_EXIT} ===" exit "${VERIFY_EXIT}" INNER_SCRIPT_EOF chmod +x "$INNER_SCRIPT" # ─── Pull image ─────────────────────────────────────────────────────────────── info "Pulling ${IMAGE}…" docker pull "${IMAGE}" --quiet # ─── Run container ──────────────────────────────────────────────────────────── info "Starting container ${CONTAINER_NAME}…" EXIT_CODE=0 docker run --rm \ --name "${CONTAINER_NAME}" \ --volume "${REPO_ROOT}:/repo:ro" \ --volume "${INNER_SCRIPT}:/e2e-inner.sh:ro" \ --network host \ "${IMAGE}" \ /bin/sh /e2e-inner.sh \ || EXIT_CODE=$? # ─── Report ─────────────────────────────────────────────────────────────────── echo "" if [[ "$EXIT_CODE" -eq 0 ]]; then ok "End-to-end install test PASSED (exit ${EXIT_CODE})" else fail "End-to-end install test FAILED (exit ${EXIT_CODE})" echo "" echo " Troubleshooting:" echo " - Review the output above for the failing step." echo " - Re-run with bash -x tools/e2e-install-test.sh for verbose trace." echo " - Run mosaic gateway logs inside a manual container for daemon output." exit 1 fi