From f3411eb1fd1bb5a13c562db2466ad0b4a9ce25ad Mon Sep 17 00:00:00 2001 From: Sulthan Nauval Abdillah Date: Tue, 4 Aug 2026 08:46:32 +0000 Subject: [PATCH 1/3] fix(web-ui): report what `copilot-web stop` actually did MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `rantaiclaw ui stop` returns Ok(()) on every path, including "Nothing to stop" (src/webui.rs:1104-1109), so the exit code cannot distinguish "stopped it" from "there was nothing to stop" — the `&&` branch always won and the `||` branch was unreachable - `>/dev/null` also discarded rantaiclaw's own honest message, so nothing survived - decide from the `.run` marker `ui start` maintains: report "nothing to stop" when no console is tracked, "✓ stopped" only once the marker is gone, and fail loudly (exit 1) when it is still there - this is the pre-upgrade step the README tells an operator to run before pulling the console's files out from under it; an unconditional ✓ is exactly the wrong thing to read at that moment --- web-ui.sh | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) 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 From ada29f3a96f69d5bb90e7aa4cea086c0cf4ce361 Mon Sep 17 00:00:00 2001 From: Sulthan Nauval Abdillah Date: Tue, 4 Aug 2026 08:46:32 +0000 Subject: [PATCH 2/3] docs: route bundle operators to the installer their bundle actually has MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - QUICKSTART.md ships inside the airgapped bundle but documented only the online one-liner and `./setup.sh`, never `./setup-airgapped.sh` — and `setup.sh` is the one installer that does NOT stage the prebuilt console, so an offline operator following it ends up with `copilot-web` fetching claw-ui over a network that isn't there - split the two routes explicitly, say how to tell the bundles apart, and point at the README's airgapped update/rollback steps before an upgrade rather than after - tutorials/hypervisor.md told bundle users to run `./install.sh`, which build-bundle.sh never ships; name the right entrypoint per install mode --- release/files/QUICKSTART.md | 28 ++++++++++++++++++++-------- tutorials/hypervisor.md | 6 ++++-- 2 files changed, 24 insertions(+), 10 deletions(-) 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/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 From e97592e97b67b8f3c049db4ca7d0fba23e288ade Mon Sep 17 00:00:00 2001 From: Sulthan Nauval Abdillah Date: Tue, 4 Aug 2026 08:46:49 +0000 Subject: [PATCH 3/3] test(ci): check shipped docs against the files shipped beside them - three of the last four defects here were the same shape: a document that ships inside a tarball naming something the tarball does not contain, or lagging the repo by a release. All three were found by opening the artifact by hand - tests/bundle-consistency.sh builds the bundles and asserts the invariants: shipped README/QUICKSTART/tutorials are byte-identical to the repo's, every entrypoint is present + executable + parses, the airgapped bundle carries setup-airgapped.sh and the online one does not, neither ships install.sh, and every doc mention of install.sh says where it comes from - the airgapped bundle is staged the way pack-airgapped.sh stages it, minus the claw-ui fetch, so the check needs no bun and no network; two assertions pin that shortcut to pack-airgapped.sh so it cannot drift silently. The real build stays in tests/airgapped-bundle.sh - wired into ci.yml (every PR) and drift.yml (weekly, against the newest rantaiclaw) --- .github/workflows/ci.yml | 6 +++ .github/workflows/drift.yml | 3 ++ tests/bundle-consistency.sh | 79 +++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100755 tests/bundle-consistency.sh 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/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 ]