Root cause of the recurring "pi/bw not found in jail" bug: the npm-global-on-PATH fix was solved canonically in the clawdie-iso image (/etc/profile.d/clawdie.sh, all login shells), but the agent jail is a separate environment that never reused it — a fresh Bastille jail doesn't inherit the image's profile.d, and the bootstrap set no PATH. PR #120 band-aided it with a hardcoded append to one user's ~/.profile (sh-only, drifts from NPM_PREFIX). Replace that band-aid with the same mechanism the image uses, scoped to the jail: - write one managed /etc/profile.d/clawdie-npm.sh derived from NPM_PREFIX - source it from /etc/profile (covers all sh/bash login shells, system-wide), idempotently - delete the per-user ~/.profile append from #120 Now the PATH content lives in a single file tied to NPM_PREFIX, so it can't miss shells or drift from the prefix. Follow-up (not here): hoist the snippet into one shared file installed by both clawdie-iso and the jail bootstrap, so a future new environment can't re-grow this. Verified: sh -n clean; smoke test — snippet expands NPM_PREFIX (keeps $PATH literal), /etc/profile sources it, append is idempotent, sourced shell resolves the npm-global bin onto PATH. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
4 KiB
Bash
Executable file
103 lines
4 KiB
Bash
Executable file
#!/bin/sh
|
|
# Agent jail bootstrap — install the minimum runtime into a fresh Bastille jail,
|
|
# pinned to the EXACT package versions the host already has. The jail reaches the
|
|
# host's pkg cache (no internet needed), so installing the host's exact versions
|
|
# guarantees host/jail parity and works offline.
|
|
#
|
|
# Usage: sudo agent-jail-bootstrap.sh <jail_name>
|
|
#
|
|
# Run order: bastille create <jail> -> this script -> vault provision -> register.
|
|
set -eu
|
|
|
|
JAIL_NAME="${1:-}"
|
|
PKG_CACHE_DIR="${PKG_CACHE_DIR:-/var/cache/pkg}"
|
|
|
|
# The jail name becomes a path component, so reject anything that could escape
|
|
# /usr/local/bastille/jails/<name>/root (empty, traversal, odd characters).
|
|
case "${JAIL_NAME}" in
|
|
'')
|
|
echo "usage: $0 <jail_name>" >&2
|
|
exit 2
|
|
;;
|
|
*[!A-Za-z0-9_-]*)
|
|
echo "error: invalid jail name '${JAIL_NAME}' (allowed: A-Z a-z 0-9 _ -)" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
JAIL_ROOT="/usr/local/bastille/jails/${JAIL_NAME}/root"
|
|
|
|
if [ ! -d "${JAIL_ROOT}" ]; then
|
|
echo "error: jail root not found: ${JAIL_ROOT} — create the jail first" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "=== Bootstrap ${JAIL_NAME} ==="
|
|
|
|
# Runtime packages. Each is pinned to the host's installed version (the host's
|
|
# cache supplies it), so the jail matches the host exactly. If the host is
|
|
# missing one, fail loudly rather than pulling a different version into the jail.
|
|
PKGS="python312 node24 npm-node24 bash curl"
|
|
|
|
for p in ${PKGS}; do
|
|
ver="$(pkg query '%v' "${p}" 2>/dev/null || true)"
|
|
if [ -z "${ver}" ]; then
|
|
echo "error: host has no '${p}' installed — install it on the host first" >&2
|
|
echo " (versions are pinned to the host; the cache has nothing to serve otherwise)" >&2
|
|
exit 1
|
|
fi
|
|
if ! ls "${PKG_CACHE_DIR}/${p}-${ver}"*.pkg >/dev/null 2>&1; then
|
|
echo "error: host pkg cache is missing ${p}-${ver}" >&2
|
|
echo " prime it first: pkg fetch -y ${p}-${ver}" >&2
|
|
echo " (offline/exact-version bootstrap depends on ${PKG_CACHE_DIR})" >&2
|
|
exit 1
|
|
fi
|
|
echo " ${p}-${ver}"
|
|
pkg -c "${JAIL_ROOT}" install -y "${p}-${ver}"
|
|
done
|
|
|
|
# Copy colibri binaries from the host (same FreeBSD base, so shared libs match).
|
|
for bin in colibri colibri-daemon colibri-probe colibri-mcp colibri-test-agent colibri-host-status colibri-runtime-inventory; do
|
|
src="/usr/local/bin/${bin}"
|
|
if [ ! -x "${src}" ]; then
|
|
echo "error: missing host binary ${src} — build/stage it before bootstrap" >&2
|
|
exit 1
|
|
fi
|
|
cp "${src}" "${JAIL_ROOT}/usr/local/bin/${bin}"
|
|
chmod 755 "${JAIL_ROOT}/usr/local/bin/${bin}"
|
|
done
|
|
|
|
# Copy npm global agents from the host (jails have no internet).
|
|
NPM_PREFIX="/home/clawdie/.npm-global"
|
|
mkdir -p "${JAIL_ROOT}${NPM_PREFIX}/bin" "${JAIL_ROOT}${NPM_PREFIX}/lib/node_modules"
|
|
|
|
if [ ! -d "${NPM_PREFIX}/lib/node_modules/@earendil-works" ]; then
|
|
echo "error: missing ${NPM_PREFIX}/lib/node_modules/@earendil-works on host" >&2
|
|
exit 1
|
|
fi
|
|
cp -a "${NPM_PREFIX}/lib/node_modules/@earendil-works" "${JAIL_ROOT}${NPM_PREFIX}/lib/node_modules/"
|
|
|
|
if [ ! -e "${NPM_PREFIX}/bin/pi" ]; then
|
|
echo "error: missing ${NPM_PREFIX}/bin/pi on host" >&2
|
|
exit 1
|
|
fi
|
|
cp -a "${NPM_PREFIX}/bin/pi" "${JAIL_ROOT}${NPM_PREFIX}/bin/pi"
|
|
|
|
# Put the npm-global bin on PATH for every login shell. Canonical mechanism
|
|
# (same shape as the clawdie-iso image): the PATH content lives in one managed
|
|
# /etc/profile.d snippet derived from NPM_PREFIX, and /etc/profile sources it —
|
|
# not a per-user, single-shell ~/.profile line that drifts from the prefix.
|
|
install -d -m 0755 "${JAIL_ROOT}/etc/profile.d"
|
|
cat > "${JAIL_ROOT}/etc/profile.d/clawdie-npm.sh" <<EOF
|
|
# Managed by agent-jail-bootstrap.sh — clawdie npm-global bin on PATH.
|
|
PATH="${NPM_PREFIX}/bin:\$PATH"
|
|
export PATH
|
|
EOF
|
|
chmod 0644 "${JAIL_ROOT}/etc/profile.d/clawdie-npm.sh"
|
|
|
|
if ! grep -q '/etc/profile.d/clawdie-npm.sh' "${JAIL_ROOT}/etc/profile" 2>/dev/null; then
|
|
printf '\n[ -r /etc/profile.d/clawdie-npm.sh ] && . /etc/profile.d/clawdie-npm.sh\n' \
|
|
>> "${JAIL_ROOT}/etc/profile"
|
|
fi
|
|
|
|
echo "Done — ${JAIL_NAME} ready for vault provision."
|