diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca27ddd43..a56929fe6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,9 +77,6 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Test shell installers - run: bash scripts/install.test.sh - - name: Setup Go uses: actions/setup-go@v5 with: @@ -94,3 +91,20 @@ jobs: - name: Test run: cd server && go test ./... + + installer: + # Stub-driven shell tests for scripts/install.sh. Kept off the heavy + # backend job so installer regressions surface independently, and + # exercised on macOS too because the installer targets macOS/Homebrew + # and `tar` / `sed` / `mktemp` differ between BSD and GNU userlands. + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Test shell installers + run: bash scripts/install.test.sh diff --git a/scripts/install.sh b/scripts/install.sh index 59122b2ca..4a82ad541 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -63,25 +63,32 @@ detect_os() { # --------------------------------------------------------------------------- # CLI Installation # --------------------------------------------------------------------------- +_dump_brew_log() { + local log="$1" + if [ -s "$log" ]; then + warn "Homebrew output (last 80 lines):" + tail -n 80 "$log" | sed 's/^/ /' >&2 + fi +} + install_cli_brew() { info "Installing Multica CLI via Homebrew..." - if ! brew tap multica-ai/tap 2>/dev/null; then + local brew_log + brew_log=$(mktemp) + if ! brew tap multica-ai/tap >"$brew_log" 2>&1; then warn "Failed to add Homebrew tap. Falling back to GitHub Releases binary install." + _dump_brew_log "$brew_log" + rm -f "$brew_log" return 1 fi # brew install exits non-zero if already installed on older Homebrew versions - local brew_log - brew_log=$(mktemp) if ! brew install "$BREW_PACKAGE" >"$brew_log" 2>&1; then if brew list "$BREW_PACKAGE" >/dev/null 2>&1; then rm -f "$brew_log" ok "Multica CLI already installed via Homebrew" else warn "Failed to install multica via Homebrew. Falling back to GitHub Releases binary install." - if [ -s "$brew_log" ]; then - warn "Homebrew output (last 80 lines):" - tail -n 80 "$brew_log" | sed 's/^/ /' >&2 - fi + _dump_brew_log "$brew_log" rm -f "$brew_log" return 1 fi @@ -456,6 +463,15 @@ main() { echo " --with-server Install CLI + provision a self-host server (Docker)" echo " --stop Stop a self-hosted installation" echo "" + echo "Environment variables:" + echo " MULTICA_INSTALL_DIR Self-host server install directory" + echo " (default: \$HOME/.multica/server)" + echo " MULTICA_BIN_DIR Target directory for the CLI binary when" + echo " installing from GitHub Releases" + echo " (default: /usr/local/bin, then \$HOME/.local/bin)" + echo " MULTICA_SELFHOST_REF Git ref to check out for self-host assets" + echo " (default: latest release tag, falling back to main)" + echo "" echo "After installation, run 'multica setup' to configure your environment." exit 0 ;; diff --git a/scripts/install.test.sh b/scripts/install.test.sh index 9f4632fd1..439b036ca 100644 --- a/scripts/install.test.sh +++ b/scripts/install.test.sh @@ -3,36 +3,16 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -test_brew_failure_falls_back_to_release_binary() { - local tmp - tmp="$(mktemp -d)" - trap 'rm -rf "$tmp"' RETURN - +# Build a self-contained sandbox with stub `curl` and a tarball that the +# release-binary fallback path will download. Each test supplies its own +# `brew` stub to model a specific Homebrew failure mode. +_setup_sandbox() { + local tmp="$1" local stub_bin="$tmp/stub-bin" local install_bin="$tmp/install-bin" local payload_dir="$tmp/payload" mkdir -p "$stub_bin" "$install_bin" "$payload_dir" - cat >"$stub_bin/brew" <<'STUB' -#!/usr/bin/env bash -case "${1:-}" in - tap) - exit 0 - ;; - install) - echo "simulated brew install failure" >&2 - exit 42 - ;; - list) - exit 1 - ;; - *) - exit 0 - ;; -esac -STUB - chmod +x "$stub_bin/brew" - cat >"$payload_dir/multica" <<'STUB' #!/usr/bin/env bash echo "multica v0.3.2 (commit: test)" @@ -67,26 +47,89 @@ fi cp "$MULTICA_TEST_ARCHIVE" "$out" STUB chmod +x "$stub_bin/curl" +} +_run_installer() { + local tmp="$1" local out="$tmp/install.out" local err="$tmp/install.err" - if ! PATH="$stub_bin:$install_bin:/usr/bin:/bin" \ - MULTICA_BIN_DIR="$install_bin" \ + if ! PATH="$tmp/stub-bin:$tmp/install-bin:/usr/bin:/bin" \ + MULTICA_BIN_DIR="$tmp/install-bin" \ MULTICA_TEST_ARCHIVE="$tmp/multica.tar.gz" \ bash "$ROOT_DIR/scripts/install.sh" >"$out" 2>"$err"; then - echo "expected install.sh to fall back after brew install failure" >&2 + echo "install.sh exited non-zero" >&2 cat "$out" >&2 || true cat "$err" >&2 || true return 1 fi - if [[ ! -x "$install_bin/multica" ]]; then - echo "expected fallback binary at $install_bin/multica" >&2 + if [[ ! -x "$tmp/install-bin/multica" ]]; then + echo "expected fallback binary at $tmp/install-bin/multica" >&2 cat "$out" >&2 || true cat "$err" >&2 || true return 1 fi + + if ! grep -q "Homebrew output (last 80 lines):" "$err"; then + echo "expected diagnostic tail in stderr" >&2 + cat "$err" >&2 || true + return 1 + fi } -test_brew_failure_falls_back_to_release_binary +test_brew_install_failure_falls_back_to_release_binary() { + local tmp + tmp="$(mktemp -d)" + trap 'rm -rf "$tmp"' RETURN + + _setup_sandbox "$tmp" + cat >"$tmp/stub-bin/brew" <<'STUB' +#!/usr/bin/env bash +case "${1:-}" in + tap) + exit 0 + ;; + install) + echo "simulated brew install failure" >&2 + exit 42 + ;; + list) + exit 1 + ;; + *) + exit 0 + ;; +esac +STUB + chmod +x "$tmp/stub-bin/brew" + + _run_installer "$tmp" +} + +test_brew_tap_failure_falls_back_to_release_binary() { + local tmp + tmp="$(mktemp -d)" + trap 'rm -rf "$tmp"' RETURN + + _setup_sandbox "$tmp" + cat >"$tmp/stub-bin/brew" <<'STUB' +#!/usr/bin/env bash +case "${1:-}" in + tap) + echo "simulated brew tap failure" >&2 + exit 17 + ;; + *) + echo "brew $* should not be reached after tap failure" >&2 + exit 99 + ;; +esac +STUB + chmod +x "$tmp/stub-bin/brew" + + _run_installer "$tmp" +} + +test_brew_install_failure_falls_back_to_release_binary +test_brew_tap_failure_falls_back_to_release_binary echo "install.sh tests passed"