diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 695cafa..3565eee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,3 +44,9 @@ jobs: - name: Installer behaviour matrix run: tests/installer-matrix.sh "$RC_BIN" + + # Cheap and offline: builds the bundles and checks the docs inside them + # against the files shipped beside them. The real airgapped build (claw-ui + # fetch + bun) stays in drift.yml. + - name: Bundle/doc consistency + run: tests/bundle-consistency.sh "$RC_BIN" diff --git a/.github/workflows/drift.yml b/.github/workflows/drift.yml index b29f3fd..0390763 100644 --- a/.github/workflows/drift.yml +++ b/.github/workflows/drift.yml @@ -41,6 +41,9 @@ jobs: - name: Installer behaviour matrix run: tests/installer-matrix.sh "$RC_BIN" + - name: Bundle/doc consistency + run: tests/bundle-consistency.sh "$RC_BIN" + - name: Airgapped bundle (build, then install it offline) run: tests/airgapped-bundle.sh "$RC_BIN" diff --git a/release/files/QUICKSTART.md b/release/files/QUICKSTART.md index ccbf75d..c172a3a 100644 --- a/release/files/QUICKSTART.md +++ b/release/files/QUICKSTART.md @@ -6,20 +6,32 @@ This bundle contains everything to **operate a Hypervisor cluster from a prompt* ## 1. Set it up (once) -**One-liner (recommended):** +Use the installer that matches the bundle you have — **they are not interchangeable**. Check with +`ls setup*.sh`: the airgapped bundle ships `setup-airgapped.sh`, the online one does not. + +**Airgapped bundle** (`rantai-copilot-airgapped-*`) — for a host with no internet. This is the only +installer that stages the prebuilt web console shipped inside the tarball. `setup.sh` does not, so +using it here leaves `copilot-web` trying to fetch claw-ui over a network you don't have: ```bash -curl -fsSL https://raw.githubusercontent.com/RantAI-dev/RantAI-Copilot/main/get.sh | bash -rantaiclaw setup # set your LLM provider + key (OpenRouter / Anthropic / MiniMax) +./setup-airgapped.sh +rantaiclaw setup provider # LLM provider + key — offline, needs no network ``` -**Or from this bundle** (if you downloaded the tarball): +**Online bundle** (`rantai-copilot-*`) — for a host that can reach GitHub: ```bash -tar xzf rantai-copilot-*-x86_64-linux.tar.gz -cd rantai-copilot-*-x86_64-linux ./setup.sh +rantaiclaw setup # LLM provider + key (OpenRouter / Anthropic / MiniMax) ``` -Either way: installs `rantaiclaw` to `~/.local/bin` and deploys the skills. Configure your LLM -afterward with `rantaiclaw setup` (both the one-liner and `setup.sh` leave this to you). + +**No tarball yet?** On a connected machine: +```bash +curl -fsSL https://raw.githubusercontent.com/RantAI-dev/RantAI-Copilot/main/get.sh | bash +``` + +Either installer puts `rantaiclaw` in `~/.local/bin` and deploys the skills; both leave the LLM +config to you. **Updating an airgapped host — and rolling back if it goes wrong — is in +`README.md`, under *Install the agent → Airgapped mode*. Read it before you upgrade**: the config +migration is one-way, and those steps are what keep it reversible. > Linux x86_64 only (the binary is static, runs on any modern distro). Other platforms: build > from source — see the repo README. diff --git a/tests/bundle-consistency.sh b/tests/bundle-consistency.sh new file mode 100755 index 0000000..f575a97 --- /dev/null +++ b/tests/bundle-consistency.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# Do the documents we ship agree with the files we ship alongside them? +# +# Every defect this guards against was found the same way: by opening the tarball +# an operator would actually carry, not by reading the repo. A doc that names the +# wrong installer, or lags the repo by a release, is discovered on a host with no +# internet and no way to look anything up — so it gets checked here instead. +# +# Usage: tests/bundle-consistency.sh [path-to-rantaiclaw] +# Falls back to $RC_BIN, then to whatever `rantaiclaw` is on PATH. +# No network and no bun needed: the airgapped bundle is staged the way +# pack-airgapped.sh stages it, minus the claw-ui fetch (tests/airgapped-bundle.sh +# builds the real thing). The staging is pinned to pack-airgapped.sh below so this +# shortcut cannot silently drift away from what actually ships. +set -uo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO="$(cd "$HERE/.." && pwd)" +RC="${1:-${RC_BIN:-$(command -v rantaiclaw 2>/dev/null || true)}}" +[ -x "$RC" ] || { echo "✗ need a rantaiclaw binary: tests/bundle-consistency.sh "; exit 2; } +RC="$(cd "$(dirname "$RC")" && pwd)/$(basename "$RC")" + +W="$(mktemp -d)"; trap 'rm -rf "$W"; rm -rf "$REPO/dist"' EXIT +PASS=0; FAIL=0 +ck() { if eval "$2"; then printf ' PASS %s\n' "$1"; PASS=$((PASS+1)); else printf ' FAIL %s\n' "$1"; FAIL=$((FAIL+1)); fi; } +TAG=v0.0.0-test + +bash "$REPO/release/build-bundle.sh" "$RC" "$TAG" >"$W/build.log" 2>&1 \ + || { echo " FAIL build-bundle.sh — see $W/build.log"; exit 1; } +tar xzf "$REPO/dist/rantai-copilot-$TAG-x86_64-linux.tar.gz" -C "$W" +ONLINE="$W/rantai-copilot-$TAG-x86_64-linux" + +# Airgapped = the same base plus the offline installer (pack-airgapped.sh step 4). +AIR="$W/airgapped"; cp -r "$ONLINE" "$AIR" +install -m755 "$REPO/release/files/setup-airgapped.sh" "$AIR/setup-airgapped.sh" + +echo "== the staging shortcut still matches pack-airgapped.sh ==" +ck "pack-airgapped.sh installs setup-airgapped.sh" \ + "grep -q 'install -m755 \"\$HERE/files/setup-airgapped.sh\" \"\$STAGE/setup-airgapped.sh\"' '$REPO/release/pack-airgapped.sh'" +ck "pack-airgapped.sh builds on the base bundle" \ + "grep -q 'build-bundle.sh' '$REPO/release/pack-airgapped.sh'" + +echo "== shipped docs match the repo (a bundle must never carry a stale copy) ==" +ck "README.md is the repo's" "cmp -s '$ONLINE/README.md' '$REPO/README.md'" +ck "QUICKSTART.md is the repo's" "cmp -s '$ONLINE/QUICKSTART.md' '$REPO/release/files/QUICKSTART.md'" +ck "tutorials/ is the repo's" "diff -r -q '$ONLINE/tutorials' '$REPO/tutorials' >/dev/null" + +echo "== entrypoints are present, executable, and parse ==" +for f in setup.sh web-ui.sh copilot-uninstall; do + ck "online bundle ships $f (executable)" "[ -x '$ONLINE/$f' ]" + ck "online bundle $f parses" "bash -n '$ONLINE/$f' 2>/dev/null" +done +ck "airgapped bundle ships setup-airgapped.sh (executable)" "[ -x '$AIR/setup-airgapped.sh' ]" +ck "airgapped bundle setup-airgapped.sh parses" "bash -n '$AIR/setup-airgapped.sh' 2>/dev/null" +ck "online bundle does NOT ship setup-airgapped.sh" "[ ! -e '$ONLINE/setup-airgapped.sh' ]" +ck "no bundle ships install.sh (git-clone only)" "[ ! -e '$ONLINE/install.sh' ] && [ ! -e '$AIR/install.sh' ]" + +echo "== docs route the operator to an installer that is actually in the box ==" +# The airgapped installer is the only one that stages the prebuilt console, so a +# QUICKSTART that never names it sends an offline operator to `setup.sh` and a +# `copilot-web` that tries to reach the network. +ck "QUICKSTART names setup-airgapped.sh" "grep -q 'setup-airgapped.sh' '$AIR/QUICKSTART.md'" +ck "QUICKSTART names setup.sh" "grep -q 'setup.sh' '$ONLINE/QUICKSTART.md'" +ck "QUICKSTART points at the update/rollback docs" "grep -qi 'README.md' '$ONLINE/QUICKSTART.md'" +# `install.sh` exists only in a git checkout. Naming it in a shipped doc is fine — +# telling a bundle operator to run it is not, so every mention must say where it comes from. +for d in QUICKSTART.md README.md tutorials/hypervisor.md; do + ck "$d qualifies any install.sh mention" \ + "! grep -q 'install\\.sh' '$ONLINE/$d' || grep -qiE 'clone|from source' '$ONLINE/$d'" +done + +echo "== the offline console really is what setup-airgapped.sh stages ==" +# setup.sh must not claim to handle the offline console; if it ever grows that, +# the QUICKSTART split above becomes wrong and should be revisited deliberately. +ck "setup.sh does not stage the prebuilt console" "! grep -q 'web-ui/server.js' '$ONLINE/setup.sh'" +ck "setup-airgapped.sh does stage it" "grep -q 'web-ui/server.js' '$AIR/setup-airgapped.sh'" + +printf '\n%d passed, %d failed\n' "$PASS" "$FAIL" +[ "$FAIL" -eq 0 ] diff --git a/tutorials/hypervisor.md b/tutorials/hypervisor.md index 624a091..e579544 100644 --- a/tutorials/hypervisor.md +++ b/tutorials/hypervisor.md @@ -18,8 +18,10 @@ against the cluster; it never invents data and confirms before it changes anythi - `kubectl` on your machine (the agent runs it locally). - A kubeconfig for the cluster — from your cluster UI (Rancher) → cluster → **Download KubeConfig**. -- RantaiClaw with an LLM provider (`rantaiclaw setup`) and the `hypervisor` skill - deployed (`./install.sh`). +- RantaiClaw with an LLM provider (`rantaiclaw setup`) and the `hypervisor` skill deployed. Which + command does that depends on where you got it: `./setup-airgapped.sh` from an airgapped bundle, + `./setup.sh` from an online bundle, or `./install.sh` from a git clone — the bundles do not ship + `install.sh`. ## 2. Give the agent the kubeconfig diff --git a/web-ui.sh b/web-ui.sh index f823ec4..b332d0d 100755 --- a/web-ui.sh +++ b/web-ui.sh @@ -29,7 +29,22 @@ q() { if [ "$VERBOSE" = 1 ]; then "$@"; else "$@" >>"$LOG" 2>&1; fi; } port_busy() { (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null && { exec 3>&- 3<&-; return 0; }; return 1; } case "${1:-up}" in - stop) rantaiclaw ui stop --dir "$UIDIR" >/dev/null 2>&1 && say "✓ stopped" || say "nothing to stop"; exit 0 ;; + # `rantaiclaw ui stop` exits 0 whether or not anything was running, so its exit + # code cannot tell the two apart — an unconditional "✓ stopped" is what an + # operator reads right before pulling the console's files out from under it. + # Decide from the run marker `ui start` maintains, and report what really happened. + stop) + if [ ! -f "$UIDIR/.run" ]; then + say "nothing to stop (no console running from $UIDIR)" + exit 0 + fi + rantaiclaw ui stop --dir "$UIDIR" >/dev/null 2>&1 || true + if [ -f "$UIDIR/.run" ]; then + say "✗ could not stop the console — try: rantaiclaw ui stop --dir \"$UIDIR\"" + exit 1 + fi + say "✓ stopped" + exit 0 ;; up|"") ;; *) say "usage: $0 [up|stop]"; exit 2 ;; esac