forked from heavy-duty/box
chore: finish the debrand — env vars, install dir, docs are 'box', not 'claudebox'
The 0.4.0 rename left surface leftovers the user hit through the
CLAUDEBOX_* env vars. Sweep them, drawing a clean line:
box = everything the user touches — env vars (BOX_REPO/REF/HOME/
BIN), installer messages (box-install:), the install tree
(~/.local/share/box, with the installer sweeping the old
~/.local/share/claudebox on upgrade), tool prose, and the
docs (docs/box-{design,recipe}.md).
claudebox = the GitHub repo name (URLs, the claudebox-<ref> tarball
dir, issue refs), the legacy user.claudebox=1 tag, the
old-stack cleanup code (claudenet/claude-dev/claude-isolate/
claudebox-firewall), and the .claudebox/ runbook convention
— a deliberate v1 hold, since renaming it breaks consuming
repos.
Renamed the two doc files and their links; updated drill.sh/doctor.sh
paths and BOX_REPO/BOX_REF; fixed the claude template's in-box briefing
to say 'box'. RUNS.md left as-is (append-only history). No behavior
change beyond the install-dir move, which the installer migrates.
This commit is contained in:
parent
912e0621ca
commit
4eb6b35a7b
9 changed files with 75 additions and 66 deletions
18
README.md
18
README.md
|
|
@ -18,9 +18,9 @@ live in a shared profile no template can touch, so `blank` is a box with
|
|||
nobody home — not a box with the safety off.
|
||||
|
||||
**The tool knows nothing about your projects.** You just `git clone` inside a
|
||||
box. A repo can ship an optional [`.claudebox/`](docs/claudebox-recipe.md)
|
||||
box. A repo can ship an optional [`.claudebox/`](docs/box-recipe.md)
|
||||
runbook that Claude Code reads and acts on — there is no `install` step and no
|
||||
host-run setup. See [docs/claudebox-design.md](docs/claudebox-design.md) for the
|
||||
host-run setup. See [docs/box-design.md](docs/box-design.md) for the
|
||||
design rationale.
|
||||
|
||||
> **0.4.0 is a clean cut**: the CLI is `box` (no `claudebox` shim), the host
|
||||
|
|
@ -37,14 +37,14 @@ design rationale.
|
|||
curl -fsSL https://raw.githubusercontent.com/heavy-duty/claudebox/main/install.sh | bash
|
||||
```
|
||||
|
||||
Installs the tree to `~/.local/share/claudebox` and links `box` onto your
|
||||
Installs the tree to `~/.local/share/box` and links `box` onto your
|
||||
`PATH`. Re-run any time to upgrade — upgrading from a pre-0.4.0 install also
|
||||
retires the old `claudebox` symlink. (No `git clone` needed.)
|
||||
|
||||
## One-time host setup (Ubuntu 24.04 / Debian 13)
|
||||
|
||||
```sh
|
||||
~/.local/share/claudebox/host/setup-host.sh # run twice if it adds you to incus-admin (re-login between)
|
||||
~/.local/share/box/host/setup-host.sh # run twice if it adds you to incus-admin (re-login between)
|
||||
```
|
||||
|
||||
Idempotent. Installs Incus and creates the isolation stack: the `boxnet` NAT
|
||||
|
|
@ -172,7 +172,7 @@ The box is resolved and tag-checked; the rest is passed to `incus` verbatim, and
|
|||
the command is echoed before it runs. If it can move the box off the isolation
|
||||
stack (profile, network, device, `security.*`), box warns and proceeds —
|
||||
the trust boundary is then yours to keep. See
|
||||
[docs/claudebox-design.md](docs/claudebox-design.md) for the rule and why the
|
||||
[docs/box-design.md](docs/box-design.md) for the rule and why the
|
||||
command surface is a table.
|
||||
|
||||
## Isolation
|
||||
|
|
@ -232,14 +232,14 @@ inherit.
|
|||
A repo that wants to be easy to stand up in a box ships an optional `.claudebox/`
|
||||
folder — a runbook Claude reads and follows (install deps, start services,
|
||||
template env, seed data, smoke-test). It is agent-facing documentation, not a
|
||||
host-executed script. See [docs/claudebox-recipe.md](docs/claudebox-recipe.md).
|
||||
host-executed script. See [docs/box-recipe.md](docs/box-recipe.md).
|
||||
|
||||
## Uninstall
|
||||
|
||||
```sh
|
||||
~/.local/share/claudebox/host/teardown-host.sh # boxes, network, ACL, profile, firewall
|
||||
~/.local/share/claudebox/host/teardown-host.sh --purge-incus # ...and Incus itself
|
||||
rm -rf ~/.local/share/claudebox ~/.local/bin/box # the CLI
|
||||
~/.local/share/box/host/teardown-host.sh # boxes, network, ACL, profile, firewall
|
||||
~/.local/share/box/host/teardown-host.sh --purge-incus # ...and Incus itself
|
||||
rm -rf ~/.local/share/box ~/.local/bin/box # the CLI
|
||||
```
|
||||
|
||||
## Non-goals
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# claudebox design
|
||||
# box design
|
||||
|
||||
`claudebox` is a CLI that mints and manages **trust-less, network-isolated VMs
|
||||
`box` is a CLI that mints and manages **trust-less, network-isolated VMs
|
||||
with Claude Code installed**. It is infrastructure, not a project provisioner.
|
||||
|
||||
See issue #3 for the full reframe and rationale. This doc captures the durable
|
||||
|
|
@ -16,7 +16,7 @@ design decisions.
|
|||
|
||||
## Boxes are strictly creds-free
|
||||
|
||||
`claudebox new --name <n>` launches a blank box: everything installed, **no**
|
||||
`box new --name <n>` launches a blank box: everything installed, **no**
|
||||
git credentials and **no** Claude credentials. The operator authenticates
|
||||
interactively *inside* the box:
|
||||
|
||||
|
|
@ -33,18 +33,18 @@ multi-user problem: nothing shared, nothing committed.
|
|||
Re-authing every fresh box would be toil, so authenticated state is reused via
|
||||
snapshots, not a secrets store:
|
||||
|
||||
- `claudebox snapshot <n> [label]` — checkpoint after login + clone.
|
||||
- `claudebox new --name <n2> --from <src>[/<snapshot>]` — clone an existing box
|
||||
- `box snapshot <n> [label]` — checkpoint after login + clone.
|
||||
- `box new --name <n2> --from <src>[/<snapshot>]` — clone an existing box
|
||||
or snapshot (authed state and all). Isolation is preserved: the clone keeps
|
||||
the `claude-dev` profile + `claudenet` + ACL.
|
||||
- `claudebox restore <n> <snapshot>` — roll a box back to a checkpoint.
|
||||
- `box restore <n> <snapshot>` — roll a box back to a checkpoint.
|
||||
|
||||
Log in once → snapshot → spin up authed boxes from it.
|
||||
|
||||
## The box announces itself to the agent
|
||||
|
||||
cloud-init installs a global `~/.claude/CLAUDE.md` in every box telling Claude it
|
||||
is running in a claudebox (trust-less, ephemeral, creds-free) and to treat a
|
||||
is running in a box (trust-less, ephemeral, creds-free) and to treat a
|
||||
repo's `.claudebox/` folder as its bootstrap runbook. No "tell it" step, no host
|
||||
execution.
|
||||
|
||||
|
|
@ -54,13 +54,13 @@ Not host-executed shell. A repo that wants to be easy to stand up in a sandbox
|
|||
ships a runbook (prose + optional scripts the agent may run). A repo that does
|
||||
not, you set up by hand. The tool enforces no contract; there is no `install`.
|
||||
|
||||
## What claudebox owns, and what it doesn't
|
||||
## What box owns, and what it doesn't
|
||||
|
||||
Boxes are ordinary Incus instances, tagged `user.claudebox=1`. That makes every
|
||||
Incus verb a candidate feature request — `rename`, `info`, `file push`, on
|
||||
forever — and wrapping them one at a time grows a worse `incus`. The rule:
|
||||
|
||||
> **claudebox owns a command when it must enforce an invariant Incus cannot see:**
|
||||
> **box owns a command when it must enforce an invariant Incus cannot see:**
|
||||
> the `user.claudebox=1` boundary (never touch an instance we didn't mint), the
|
||||
> isolation stack (`claude-dev` profile + `claudenet` + ACL), or the creds-free
|
||||
> snapshot→clone workflow. Everything else is Incus's job.
|
||||
|
|
@ -76,17 +76,17 @@ The rule cuts both ways, and that's the point:
|
|||
|
||||
Two mechanisms keep this honest.
|
||||
|
||||
**The command table** (`CMDS` in `bin/claudebox`) is the single source of truth
|
||||
**The command table** (`CMDS` in `bin/box`) is the single source of truth
|
||||
for what exists, its synopsis, its help line, its preconditions and what runs.
|
||||
Dispatch and help are both rendered from it, so the help cannot describe a
|
||||
command that doesn't exist — the failure that produced #8. A thin verb is one
|
||||
row; a verb that can't be expressed as a row and enforces no invariant of ours
|
||||
doesn't belong in the tool.
|
||||
|
||||
**The escape hatch** — `claudebox incus <box> -- <args...>` — resolves and
|
||||
**The escape hatch** — `box incus <box> -- <args...>` — resolves and
|
||||
tag-checks the box, then hands the rest to Incus verbatim. It means "no" to a
|
||||
proxy request is not "you can't do that", and it keeps the one rail that matters:
|
||||
you cannot aim it at an instance claudebox didn't mint. If the command can move
|
||||
you cannot aim it at an instance box didn't mint. If the command can move
|
||||
the box off the isolation stack (profile, network, device, `security.*`), it
|
||||
warns and proceeds — from there the trust boundary is yours to keep.
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ That last clause is the one that was assumed and turned out to be false, so it
|
|||
is spelled out here with the mechanism, and `drill/` tests it on every run.
|
||||
|
||||
- **Box → host, LAN, RFC1918, CGNAT, link-local:** the `claude-isolate` ACL.
|
||||
- **Box → box: an nftables *bridge-family* rule** (`host/claudebox-firewall.sh`).
|
||||
- **Box → box: an nftables *bridge-family* rule** (`host/box-firewall.sh`).
|
||||
It cannot be an ACL rule. Two boxes on one bridge share an L2 segment, so
|
||||
their frames are *switched* between bridge ports and never traverse the
|
||||
netfilter path an L3 ACL lives on — the ACL looked airtight (it drops
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# The `.claudebox/` convention
|
||||
|
||||
`claudebox` mints trust-less, creds-free, isolated VMs with Claude Code already
|
||||
installed (`claudebox new/shell/snapshot/restore/exec/down/start/rm/status`). The
|
||||
`box` mints trust-less, creds-free, isolated VMs with Claude Code already
|
||||
installed (`box new/shell/snapshot/restore/exec/down/start/rm/status`). The
|
||||
tool knows **nothing** about your project. There is no `install` step and no
|
||||
host-run setup script.
|
||||
|
||||
|
|
@ -23,12 +23,12 @@ shell that the host executes.
|
|||
## How it's consumed
|
||||
|
||||
Every box ships a global `~/.claude/CLAUDE.md` telling Claude it is inside a
|
||||
claudebox and to treat a repo's `.claudebox/` folder as its bootstrap runbook.
|
||||
box and to treat a repo's `.claudebox/` folder as its bootstrap runbook.
|
||||
So the whole flow is:
|
||||
|
||||
```
|
||||
claudebox new # get a box
|
||||
claudebox shell # get in
|
||||
box new # get a box
|
||||
box shell # get in
|
||||
git clone <repo> && cd <repo>
|
||||
claude # Claude reads .claudebox/ and brings the project up
|
||||
```
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# The drill
|
||||
|
||||
An end-to-end rehearsal of claudebox against a **real** Incus: install the CLI,
|
||||
An end-to-end rehearsal of box against a **real** Incus: install the CLI,
|
||||
set up the host, mint boxes, drive the whole surface, check that the isolation
|
||||
actually holds — and run the full
|
||||
[#15 audit](https://github.com/heavy-duty/claudebox/issues/15), including a live
|
||||
|
|
@ -32,7 +32,7 @@ The drill mutates the host in phase D; an aborted run can leave a network that
|
|||
mints boxes with **no DNS**.
|
||||
|
||||
**Iterating on the drill?** Read [RUNS.md](RUNS.md) first — it is the run log:
|
||||
what the audit has answered so far, the bugs the drill has found in claudebox,
|
||||
what the audit has answered so far, the bugs the drill has found in box,
|
||||
the traps this script has already fallen into (every one cost a run), how to
|
||||
diagnose a stall, and how to run a single probe by hand instead of paying for a
|
||||
whole run.
|
||||
|
|
@ -46,7 +46,7 @@ wrong. The drill runs the real thing.
|
|||
|
||||
## What it checks
|
||||
|
||||
**A. Incus semantics.** The assumptions claudebox is built on, probed directly:
|
||||
**A. Incus semantics.** The assumptions box is built on, probed directly:
|
||||
that `incus config get <inst> user.claudebox` returns `1` (this is on the path of
|
||||
*every* box command — if it lies, everything fails closed); that the
|
||||
`user.claudebox=1` list filter selects our instances and excludes an untagged
|
||||
|
|
@ -62,7 +62,7 @@ and its isolation warning, the `rm` confirmation guard, and the CLI contract
|
|||
(typo'd command, typo'd flag, `list <box>`).
|
||||
|
||||
**The boundary** gets its own treatment: the drill launches an instance
|
||||
claudebox did *not* mint, aims `down`, `rm` and the escape hatch at it, and
|
||||
box did *not* mint, aims `down`, `rm` and the escape hatch at it, and
|
||||
requires all three to refuse — and the instance to still be standing afterwards.
|
||||
|
||||
**C. Isolation baseline (#15 section A).** From inside a real box: public egress
|
||||
|
|
@ -88,6 +88,6 @@ a **design veto** for #16, caught before the code is written.
|
|||
design. The drill confirms Claude Code is installed and runnable; authenticating
|
||||
is yours.
|
||||
|
||||
If the host has no `/dev/kvm`, claudebox falls back to container mode. The drill
|
||||
If the host has no `/dev/kvm`, box falls back to container mode. The drill
|
||||
still runs, but it says loudly that **the VM trust boundary was not validated**
|
||||
rather than passing quietly on a weaker one.
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ if incus network show boxnet >/dev/null 2>&1; then
|
|||
ok "dns.mode = none — a box cannot enumerate its siblings by name"
|
||||
else
|
||||
no "dns.mode = ${dns:-<unset>} — a box can RESOLVE its siblings' names and addresses"
|
||||
inf "fix: re-run ~/.local/share/claudebox/host/setup-host.sh"
|
||||
inf "fix: re-run ~/.local/share/box/host/setup-host.sh"
|
||||
[ "$FIX" = 1 ] && { incus network set boxnet dns.mode=none && inf "set: dns.mode=none"; }
|
||||
fi
|
||||
inf "ipv4.address = $(incus network get boxnet ipv4.address 2>/dev/null)"
|
||||
|
|
@ -110,7 +110,7 @@ if [ -n "$PROFILES" ]; then
|
|||
no "$p: security.port_isolation is NOT set — BOXES CAN REACH EACH OTHER"
|
||||
inf "an L3 ACL cannot do this: two boxes on one bridge are on the same L2"
|
||||
inf "segment, so their frames are switched, never routed past the ACL."
|
||||
inf "fix: re-run ~/.local/share/claudebox/host/setup-host.sh"
|
||||
inf "fix: re-run ~/.local/share/box/host/setup-host.sh"
|
||||
fi
|
||||
for k in security.mac_filtering security.ipv4_filtering; do
|
||||
v="$(incus profile device get "$p" eth0 "$k" 2>/dev/null)"
|
||||
|
|
@ -192,7 +192,7 @@ else
|
|||
no "the host resolves via a CGNAT/Tailscale resolver ($hostns), and boxes INHERIT it — see issue #33"
|
||||
inf "· box DNS breaks whenever the tailnet's resolver does (this is what kills cold mints)"
|
||||
inf "· and tailnet names RESOLVE from inside a box, though its ACL blocks connecting to them"
|
||||
inf "fix: re-run ~/.local/share/claudebox/host/setup-host.sh (it pins the resolver)"
|
||||
inf "fix: re-run ~/.local/share/box/host/setup-host.sh (it pins the resolver)"
|
||||
inf " or quick-test the pin alone: bash drill/doctor.sh --pin-dns"
|
||||
else
|
||||
inf "boxes inherit the host's resolver (unpinned — setup-host.sh pins this now; re-run it)"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
# drill.sh — end-to-end drill for box (the claudebox repo), against a real Incus.
|
||||
# drill.sh — end-to-end drill for box, against a real Incus.
|
||||
#
|
||||
# ⚠ DESTRUCTIVE, AND MEANT TO BE. Run it on a THROWAWAY host you can format.
|
||||
# It installs Incus, rewrites the host's firewall rules, installs a systemd
|
||||
|
|
@ -32,8 +32,8 @@
|
|||
# false FAILs on the first live run. The pipeline verdict must be grep's alone.
|
||||
set -u
|
||||
|
||||
REPO="${CLAUDEBOX_REPO:-heavy-duty/claudebox}"
|
||||
REF="${CLAUDEBOX_REF:-main}"
|
||||
REPO="${BOX_REPO:-heavy-duty/claudebox}"
|
||||
REF="${BOX_REF:-main}"
|
||||
YES=0; KEEP=0
|
||||
SELF="$(readlink -f "$0")"
|
||||
|
||||
|
|
@ -170,7 +170,7 @@ EOF
|
|||
fi
|
||||
|
||||
phase "Installing box ($REPO@$REF)"
|
||||
CLAUDEBOX_REPO="$REPO" CLAUDEBOX_REF="$REF" \
|
||||
BOX_REPO="$REPO" BOX_REF="$REF" \
|
||||
bash -c "$(curl -fsSL "https://raw.githubusercontent.com/$REPO/$REF/install.sh")" \
|
||||
|| { echo "install failed"; exit 1; }
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
|
@ -206,10 +206,10 @@ EOF
|
|||
fi
|
||||
|
||||
inf "running setup-host.sh (first pass: may only add you to incus-admin)…"
|
||||
~/.local/share/claudebox/host/setup-host.sh || true
|
||||
~/.local/share/box/host/setup-host.sh || true
|
||||
# The group we were just added to isn't in this shell's credentials yet.
|
||||
inf "re-entering inside the incus-admin group…"
|
||||
exec sg incus-admin -c "IN_GROUP=1 CLAUDEBOX_REPO='$REPO' CLAUDEBOX_REF='$REF' KEEP=$KEEP bash '$SELF' --in-group"
|
||||
exec sg incus-admin -c "IN_GROUP=1 BOX_REPO='$REPO' BOX_REF='$REF' KEEP=$KEEP bash '$SELF' --in-group"
|
||||
fi
|
||||
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
|
@ -259,7 +259,7 @@ left="$(incus list --format csv --columns n 2>/dev/null | tr '\n' ' ')"
|
|||
[ -n "$left" ] && inf "instances still on this host (not ours, left alone): $left"
|
||||
|
||||
inf "running setup-host.sh (in-group pass: network, ACL, profile, firewall)…"
|
||||
if ! timeout -k 10 300 ~/.local/share/claudebox/host/setup-host.sh; then
|
||||
if ! timeout -k 10 300 ~/.local/share/box/host/setup-host.sh; then
|
||||
echo "drill: setup-host.sh failed or timed out (>5 min)." >&2
|
||||
echo " it should take seconds on a host that already has incus. usual causes:" >&2
|
||||
echo " · instances still attached to boxnet while its ACLs are reconfigured" >&2
|
||||
|
|
@ -367,7 +367,7 @@ phase "B. The box surface"
|
|||
# ===========================================================================
|
||||
# Compare against the installed tree's VERSION file, not a hardcoded number —
|
||||
# a pinned literal here would fail the drill on every release.
|
||||
expected="$(cat "$HOME/.local/share/claudebox/VERSION" 2>/dev/null || echo '?')"
|
||||
expected="$(cat "$HOME/.local/share/box/VERSION" 2>/dev/null || echo '?')"
|
||||
v="$(box --version 2>&1)"
|
||||
case "$v" in *"$expected"*) ok "box --version → $v" ;; *) no "version mismatch: CLI says '$v', VERSION file says '$expected'" ;; esac
|
||||
|
||||
|
|
@ -396,7 +396,7 @@ box new --name tpl --template nosuch 2>&1 | grep -q 'no such template' \
|
|||
# The one rule that keeps templates honest: no key can name a network. Plant a
|
||||
# bad template in the installed tree (the drill owns this host), expect the
|
||||
# parser to reject it BY NAME, remove it.
|
||||
badt="$HOME/.local/share/claudebox/templates/cbdrill-bad"
|
||||
badt="$HOME/.local/share/box/templates/cbdrill-bad"
|
||||
mkdir -p "$badt" && printf 'BOX_IMAGE="x"\nBOX_USER="y"\nBOX_NETWORK="lan"\n' >"$badt/box.env" && : >"$badt/user-data.yaml"
|
||||
box new --name tpl --template cbdrill-bad 2>&1 | grep -q "unknown key 'BOX_NETWORK'" \
|
||||
&& ok "a template cannot name a network — BOX_NETWORK rejected by name" \
|
||||
|
|
@ -762,7 +762,7 @@ phase "M. Migration — the pre-0.4.0 → box transition (host/migrate-host.sh)"
|
|||
# tag on the OLD network — exactly what a pre-0.4.0 host carries. Then prove
|
||||
# migrate-host.sh moves it onto the new stack with its identity intact, and
|
||||
# retires the legacy stack only once it is empty.
|
||||
MIG="$HOME/.local/share/claudebox/host/migrate-host.sh"
|
||||
MIG="$HOME/.local/share/box/host/migrate-host.sh"
|
||||
if [ ! -f "$MIG" ]; then
|
||||
no "migrate-host.sh not installed — cannot drill the transition"
|
||||
else
|
||||
|
|
@ -853,5 +853,5 @@ fi
|
|||
echo
|
||||
inf "this host still has Incus, boxnet, the ACL, the profile and the firewall rules"
|
||||
inf "(plus, unless re-run: dns.mode=none and NIC filtering from the D phase)."
|
||||
inf "to undo: ~/.local/share/claudebox/host/teardown-host.sh [--purge-incus]"
|
||||
inf "to undo: ~/.local/share/box/host/teardown-host.sh [--purge-incus]"
|
||||
[ "$fail" -eq 0 ]
|
||||
|
|
|
|||
|
|
@ -72,4 +72,4 @@ if $purge; then
|
|||
fi
|
||||
fi
|
||||
|
||||
echo "Teardown complete. (Your ~/.local/bin/box symlink and ~/.local/share/claudebox remain — remove by hand if wanted.)"
|
||||
echo "Teardown complete. (Your ~/.local/bin/box symlink and ~/.local/share/box remain — remove by hand if wanted.)"
|
||||
|
|
|
|||
39
install.sh
39
install.sh
|
|
@ -1,19 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# claudebox installer — intended for: curl -fsSL .../install.sh | bash
|
||||
# box installer — intended for: curl -fsSL .../install.sh | bash
|
||||
#
|
||||
# Downloads the claudebox repo tarball, installs the whole tree under
|
||||
# $DEST, and puts a `box` symlink on PATH via $BINDIR.
|
||||
# Downloads the box source tarball from its GitHub repo (heavy-duty/claudebox —
|
||||
# the repo keeps the old name; only the tool is 'box'), installs the whole tree
|
||||
# under $DEST, and puts a `box` symlink on PATH via $BINDIR.
|
||||
|
||||
REPO="${CLAUDEBOX_REPO:-heavy-duty/claudebox}"
|
||||
REF="${CLAUDEBOX_REF:-main}"
|
||||
DEST="${CLAUDEBOX_HOME:-$HOME/.local/share/claudebox}"
|
||||
BINDIR="${CLAUDEBOX_BIN:-$HOME/.local/bin}"
|
||||
REPO="${BOX_REPO:-heavy-duty/claudebox}"
|
||||
REF="${BOX_REF:-main}"
|
||||
DEST="${BOX_HOME:-$HOME/.local/share/box}"
|
||||
BINDIR="${BOX_BIN:-$HOME/.local/bin}"
|
||||
|
||||
log() { printf 'claudebox-install: %s\n' "$*"; }
|
||||
warn() { printf 'claudebox-install: WARNING: %s\n' "$*" >&2; }
|
||||
die() { printf 'claudebox-install: ERROR: %s\n' "$*" >&2; exit 1; }
|
||||
log() { printf 'box-install: %s\n' "$*"; }
|
||||
warn() { printf 'box-install: WARNING: %s\n' "$*" >&2; }
|
||||
die() { printf 'box-install: ERROR: %s\n' "$*" >&2; exit 1; }
|
||||
|
||||
# --- prerequisites ---------------------------------------------------------
|
||||
command -v curl >/dev/null 2>&1 || die "curl is required but was not found. Please install curl and re-run."
|
||||
|
|
@ -26,18 +27,19 @@ trap cleanup EXIT
|
|||
|
||||
URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz"
|
||||
|
||||
log "installing box (the claudebox repo) ($REPO@$REF)"
|
||||
log "installing box from $REPO@$REF"
|
||||
log "downloading $URL"
|
||||
curl -fsSL "$URL" -o "$TMPDIR/claudebox.tar.gz" \
|
||||
curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz" \
|
||||
|| die "failed to download $URL"
|
||||
|
||||
log "extracting archive"
|
||||
tar -xzf "$TMPDIR/claudebox.tar.gz" -C "$TMPDIR" \
|
||||
tar -xzf "$TMPDIR/box.tar.gz" -C "$TMPDIR" \
|
||||
|| die "failed to extract archive"
|
||||
|
||||
# GitHub archives extract to a single top-level dir like claudebox-<ref>/
|
||||
# GitHub names the archive's top dir after the REPO, which is still 'claudebox':
|
||||
# it extracts to claudebox-<ref>/. That is repo-derived, not a stray brand.
|
||||
EXTRACTED="$(find "$TMPDIR" -maxdepth 1 -type d -name 'claudebox-*' | head -n1)"
|
||||
[ -n "$EXTRACTED" ] || die "could not find extracted claudebox-* directory in archive"
|
||||
[ -n "$EXTRACTED" ] || die "could not find the extracted source directory in archive"
|
||||
[ -f "$EXTRACTED/bin/box" ] || die "archive does not contain bin/box — is $REPO@$REF correct?"
|
||||
|
||||
# --- atomically replace $DEST ---------------------------------------------
|
||||
|
|
@ -59,6 +61,13 @@ if [ -L "$BINDIR/claudebox" ]; then
|
|||
rm -f "$BINDIR/claudebox"
|
||||
log "removed the old claudebox symlink — the command is 'box' now (your existing boxes keep working)"
|
||||
fi
|
||||
# 0.5.0 moved the install tree from ~/.local/share/claudebox to ~/.local/share/box.
|
||||
# Sweep the old tree so an upgrade does not leave a stale copy behind.
|
||||
OLD_DEST="$HOME/.local/share/claudebox"
|
||||
if [ -d "$OLD_DEST" ] && [ "$OLD_DEST" != "$DEST" ]; then
|
||||
rm -rf "$OLD_DEST"
|
||||
log "removed the old install tree at $OLD_DEST (it now lives at $DEST)"
|
||||
fi
|
||||
|
||||
# --- PATH check ------------------------------------------------------------
|
||||
case ":$PATH:" in
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ write_files:
|
|||
permissions: '0644'
|
||||
defer: true
|
||||
content: |
|
||||
# You are running inside a claudebox
|
||||
# You are running inside a box (template: claude)
|
||||
|
||||
A claudebox is a trust-less, network-isolated, ephemeral VM created by the
|
||||
`box` CLI (template: claude). Keep this context in mind:
|
||||
A box is a trust-less, network-isolated, ephemeral VM created by the
|
||||
`box` CLI. Keep this context in mind:
|
||||
|
||||
- **Creds-free by default.** The box starts with no Claude and no git
|
||||
credentials. If you need to authenticate Claude, the operator runs `/login`
|
||||
|
|
@ -49,7 +49,7 @@ runcmd:
|
|||
- curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
||||
- apt-get install -y nodejs
|
||||
- sudo -u claude bash -lc 'curl -fsSL https://claude.ai/install.sh | bash'
|
||||
# 'claudebox exec box -- claude …' runs through a NON-interactive shell, which
|
||||
# 'box exec <box> -- claude …' runs through a NON-interactive shell, which
|
||||
# reads no .bashrc/.zshrc — a PATH export there is invisible to it. The
|
||||
# symlink makes claude reachable from every shell, interactive or not.
|
||||
- ln -sf /home/claude/.local/bin/claude /usr/local/bin/claude
|
||||
|
|
|
|||
Loading…
Reference in a new issue