127 lines
5.9 KiB
Bash
Executable File
127 lines
5.9 KiB
Bash
Executable File
# Shared by the I1 spike scripts (row 86, #1580). Sourced, never run.
|
|
#
|
|
# Rules it enforces for every spike (docs/plans/2026-10-12_i1-containerized-install.md):
|
|
# - every docker call goes through D, which names the rootless endpoint;
|
|
# - the first step refuses unless that endpoint reports name=rootless;
|
|
# - everything a spike creates carries the label mosaic.i1spike=<spike>;
|
|
# - on exit, pass or fail, the spike's containers, networks, built images and
|
|
# scratch directory are removed, and the script checks that nothing is left.
|
|
#
|
|
# Exit codes: 0 the observed behavior matched the assertions, 1 it did not,
|
|
# 2 a rule refused the run (wrong daemon, leftovers). Output is also saved to
|
|
# out/<spike>.out.
|
|
|
|
EP=unix:///run/user/1000/docker.sock
|
|
# node:24-bookworm-slim, linux/amd64, pulled 2026-10-12T19:09Z. The worker's base.
|
|
NODE_IMG=node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
|
HERE=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
REPO=$(cd "$HERE/../../../.." && pwd)
|
|
|
|
D() { docker --host "$EP" "$@"; }
|
|
|
|
FAILS=0
|
|
say() { printf '%s\n' "$*"; }
|
|
# check DESCRIPTION CONDITION-EXIT-STATUS: records one assertion.
|
|
check() {
|
|
if [ "$2" -eq 0 ]; then say "ok $1"; else say "FAIL $1"; FAILS=$((FAILS + 1)); fi
|
|
}
|
|
|
|
# spike_begin NAME: pin the endpoint, set up output, scratch and cleanup.
|
|
spike_begin() {
|
|
SPIKE=$1
|
|
mkdir -p "$HERE/out"
|
|
exec > >(tee "$HERE/out/$SPIKE.out") 2>&1
|
|
say "# $SPIKE $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
local sec
|
|
if ! sec=$(D info --format '{{json .SecurityOptions}}' 2>&1); then
|
|
say "refused: $EP does not answer: $sec"; trap - EXIT; exit 2
|
|
fi
|
|
case $sec in
|
|
*'"name=rootless"'*) say "endpoint $EP: name=rootless" ;;
|
|
*) say "refused: $EP does not report name=rootless: $sec"; trap - EXIT; exit 2 ;;
|
|
esac
|
|
LABEL="mosaic.i1spike=$SPIKE"
|
|
# The scratch parent is mktemp's 0700 directory; spikes mount only subdirectories.
|
|
SCRATCH=$(mktemp -d "${TMPDIR:-/tmp}/i1spike-$SPIKE.XXXXXX")
|
|
say "scratch $SCRATCH"
|
|
trap spike_end EXIT
|
|
}
|
|
|
|
# Labels and the --rm flag a spike adds to every container it starts.
|
|
RUNFLAGS() { printf '%s\n' --label "$LABEL"; }
|
|
|
|
spike_end() {
|
|
local rc=$?
|
|
trap - EXIT
|
|
set +e
|
|
local ids
|
|
ids=$(D ps -a -q --filter "label=$LABEL")
|
|
[ -n "$ids" ] && D rm -f $ids >/dev/null 2>&1
|
|
ids=$(D network ls -q --filter "label=$LABEL")
|
|
[ -n "$ids" ] && D network rm $ids >/dev/null 2>&1
|
|
ids=$(D image ls -q --filter "label=$LABEL")
|
|
[ -n "$ids" ] && D image rm -f $ids >/dev/null 2>&1
|
|
# Rootless uid 0 in a container is the host user; other container uids map into
|
|
# the subuid range and may have left files this user cannot remove. Remove them
|
|
# from inside a throwaway container as container root (host jwoltje).
|
|
rm -rf "$SCRATCH" 2>/dev/null
|
|
if [ -e "$SCRATCH" ]; then
|
|
D run --rm --label "$LABEL" -v "$SCRATCH:/s" "$NODE_IMG" sh -c 'rm -rf /s/* /s/.[!.]* 2>/dev/null' >/dev/null 2>&1
|
|
rm -rf "$SCRATCH" 2>/dev/null
|
|
fi
|
|
local left=0
|
|
[ -n "$(D ps -a -q --filter "label=$LABEL")" ] && left=1
|
|
[ -n "$(D network ls -q --filter "label=$LABEL")" ] && left=1
|
|
[ -n "$(D image ls -q --filter "label=$LABEL")" ] && left=1
|
|
[ -e "$SCRATCH" ] && left=1
|
|
if [ "$left" -eq 0 ]; then
|
|
say "cleanup: no container, network, image or scratch directory labelled $SPIKE is left"
|
|
else
|
|
say "cleanup: LEFTOVERS for $SPIKE"; rc=2
|
|
fi
|
|
if [ "$rc" -eq 0 ] && [ "$FAILS" -ne 0 ]; then rc=1; fi
|
|
if [ "$rc" -eq 0 ]; then say "result: pass"; else say "result: exit $rc ($FAILS failed assertion(s))"; fi
|
|
exit "$rc"
|
|
}
|
|
|
|
# Pull the pinned base image if the rootless daemon does not hold it. The image
|
|
# is shared by the spikes and removed by s0-clean-images.sh, not per spike.
|
|
need_node_image() {
|
|
D image inspect "$NODE_IMG" >/dev/null 2>&1 || D pull -q "$NODE_IMG" >/dev/null
|
|
}
|
|
|
|
# net_up ALLOWED-HOSTS: an internal network INT, an ordinary bridge EXT and the
|
|
# allowlisting CONNECT proxy PROXY (tools/connect-proxy.mjs on the pinned node
|
|
# image, port 443 only) on both, reachable from INT as http://proxy:3128. Sets
|
|
# FAKE, a random API-key-shaped string that no provider has issued. With no key
|
|
# at all, pi says "No API key found" (no network) and Claude Code says "Not logged
|
|
# in" (after startup connections), so neither shows a model-request answer from the provider;
|
|
# a made-up key makes the provider answer "invalid key", which is the
|
|
# authentication failure S6 wants. It is not a credential.
|
|
net_up() {
|
|
mkdir -p "$SCRATCH/tools"; cp "$HERE/tools/connect-proxy.mjs" "$SCRATCH/tools/"
|
|
INT="i1${SPIKE%%-*}-int-$$"; EXT="i1${SPIKE%%-*}-ext-$$"; PROXY="i1${SPIKE%%-*}-proxy-$$"
|
|
D network create --label "$LABEL" --internal "$INT" >/dev/null
|
|
D network create --label "$LABEL" "$EXT" >/dev/null
|
|
D run -d --name "$PROXY" --label "$LABEL" --network "$EXT" --user 0:0 \
|
|
--read-only --cap-drop ALL --security-opt no-new-privileges \
|
|
-e ALLOW="$1" -e PORTS=443 -v "$SCRATCH/tools:/t:ro" "$NODE_IMG" node /t/connect-proxy.mjs >/dev/null
|
|
D network connect --alias proxy "$INT" "$PROXY"
|
|
for _ in $(seq 50); do D logs "$PROXY" 2>&1 | grep -q listening && break; sleep 0.1; done
|
|
FAKE=$(node -e 'process.stdout.write("sk-ant-api03-FAKE" + require("crypto").randomBytes(20).toString("hex"))')
|
|
}
|
|
# plines: how many lines the proxy has logged so far; proxy_new N: the lines after N.
|
|
plines() { D logs "$PROXY" 2>&1 | wc -l; }
|
|
proxy_new() { sleep 0.3; D logs "$PROXY" 2>&1 | tail -n +"$(($1 + 1))"; }
|
|
|
|
# Build the S6/S7 image (tools/Dockerfile.clis) from an otherwise empty context
|
|
# in the spike's scratch directory, labelled so spike_end removes it. Sets CLI_IMG.
|
|
build_cli_image() {
|
|
mkdir -p "$SCRATCH/ctx"
|
|
cp "$HERE/tools/Dockerfile.clis" "$SCRATCH/ctx/Dockerfile"
|
|
CLI_IMG="mosaic-i1spike-clis:$SPIKE"
|
|
say "building $CLI_IMG from $NODE_IMG (npm install -g inside the image, exact versions)"
|
|
D build -q --label "$LABEL" --build-arg "NODE_IMG=$NODE_IMG" -t "$CLI_IMG" "$SCRATCH/ctx" >/dev/null || return 1
|
|
say "built $CLI_IMG: $(D image inspect "$CLI_IMG" --format '{{.Id}} {{.Size}} bytes')"
|
|
}
|