Files
pi-devbox/scripts/test-link-pi-models.sh
T
Joakim Persson 11e512e497 feat(entrypoint): link ~/.pi/agent/models.json to a mounted shared file
pi reads custom providers from ~/.pi/agent/models.json, and ~/.pi is the
devbox-pi-config named volume. A file baked into the image would only seed a
fresh volume, once. A hand-made link is one more step on every new volume: on
2026-10-12 that was the same `ln -s /opt/fleet-pi/models.json …` once per
device, for one fleet-shared file.

New rootfs/usr/local/lib/pi-devbox/link-pi-models.sh, called from
entrypoint-user.sh after the pi-toolkit and pi-extensions installs. When
/opt/fleet-pi/models.json exists (PI_MODELS_JSON overrides the path, =off
disables it), it links ~/.pi/agent/models.json to it, honouring
PI_CODING_AGENT_DIR. It only ever creates a link where nothing exists: a real
file or a link of the user's own is left alone, with one NOTE line. With
nothing mounted, it does nothing and prints nothing. A link whose target later
vanishes is harmless; measured with pi 1.0.0, a dangling models.json behaves
like a missing one (rc 0, no error, no custom models).

Tests:
- scripts/test-link-pi-models.sh: 13 two-sided cases against a throwaway HOME.
  Wired into lint.yml, so it runs on every push and not only at release.
- Of 11 mutations, 10 are caught. The 11th (ln -s -> ln -sfn on the final
  link) is unreachable behind the guards.
- smoke-test.sh: with nothing mounted no link appears, and the script links a
  mounted file. Both commands were run under dash here.
- recreate-sanity-check.sh: pass, warn or fail on the link, but only when a
  shared file is mounted. Here it passes.

Docs:
- README "Custom model providers (models.json)": mount the DIRECTORY (git pull
  renames, and a single-file mount then shows "No such file" until recreate),
  keep keys in .env as "$NAME".
- .env.example: PI_MODELS_JSON.

Local gates green: lint-shell, check-workflow-shell, check-dockerfile-directives,
check-base-hash, check-doc-drift.
2026-10-12 16:28:28 +02:00

101 lines
5.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# test-link-pi-models.sh — two-sided tests for rootfs/usr/local/lib/pi-devbox/link-pi-models.sh.
#
# Runs the real script against a throwaway HOME / PI_CODING_AGENT_DIR, so it never
# touches a live ~/.pi/agent. Every "must link" case has a "must NOT touch" twin:
# the script's whole contract is that it only ever creates a link where nothing
# exists, so the negative cases are the important half.
#
# bash scripts/test-link-pi-models.sh # CI (lint.yml) and locally
# LINK_PI_MODELS=/path/to/variant.sh bash scripts/test-link-pi-models.sh
set -u
here="$(cd "$(dirname "$0")/.." && pwd)"
SCRIPT="${LINK_PI_MODELS:-$here/rootfs/usr/local/lib/pi-devbox/link-pi-models.sh}"
T="$(mktemp -d)"; trap 'rm -rf "$T"' EXIT
PASS=0; FAIL=0
ok() { printf ' ✅ %s\n' "$1"; PASS=$((PASS+1)); }
bad() { printf ' ❌ %s — %s\n' "$1" "$2"; FAIL=$((FAIL+1)); }
# One fresh world per case: shared dir with models.json, empty HOME, agent dir unset.
world() {
rm -rf "$T/w"; mkdir -p "$T/w/shared" "$T/w/home"
printf '{"providers":{}}\n' > "$T/w/shared/models.json"
SRC="$T/w/shared/models.json"; AG="$T/w/home/.pi/agent"; DEST="$AG/models.json"
}
# run [env assignments...] — run the script with HOME and PI_CODING_AGENT_DIR pointed into the world.
# "$@" goes FIRST so an option like `-u PI_MODELS_JSON` is still parsed as one by env.
run() { OUT="$(env "$@" HOME="$T/w/home" PI_CODING_AGENT_DIR="$AG" bash "$SCRIPT" 2>&1)"; RC=$?; }
echo "── link-pi-models.sh ($SCRIPT)"
world; run PI_MODELS_JSON="$SRC"
if [ "$RC" = 0 ] && [ -L "$DEST" ] && [ "$(readlink "$DEST")" = "$SRC" ] && [[ "$OUT" == *linked* ]]; then
ok "links models.json when the shared file exists and nothing is there (creates the agent dir)"
else bad "link when absent" "rc=$RC link=$(readlink "$DEST" 2>&1) out=[$OUT]"; fi
run PI_MODELS_JSON="$SRC"
if [ "$RC" = 0 ] && [ -z "$OUT" ] && [ "$(readlink "$DEST")" = "$SRC" ]; then
ok "second run is a silent no-op (every container start runs it)"
else bad "idempotent" "rc=$RC out=[$OUT]"; fi
world; mkdir -p "$AG"; printf 'my own\n' > "$DEST"; before="$(sha256sum "$DEST")"; run PI_MODELS_JSON="$SRC"
if [ "$RC" = 0 ] && [ ! -L "$DEST" ] && [ "$(sha256sum "$DEST")" = "$before" ] && [[ "$OUT" == NOTE*"real file"* ]]; then
ok "never replaces a real models.json (content unchanged, one NOTE line)"
else bad "real file kept" "rc=$RC islink=$([ -L "$DEST" ] && echo y) out=[$OUT]"; fi
world; mkdir -p "$AG"; ln -s /somewhere/else.json "$DEST"; run PI_MODELS_JSON="$SRC"
if [ "$RC" = 0 ] && [ "$(readlink "$DEST")" = /somewhere/else.json ] && [[ "$OUT" == NOTE*"left alone"* ]]; then
ok "never re-points a link the user made to another file"
else bad "foreign link kept" "rc=$RC link=$(readlink "$DEST") out=[$OUT]"; fi
world; rm -f "$SRC"; run PI_MODELS_JSON="$SRC"
if [ "$RC" = 0 ] && [ ! -e "$DEST" ] && [ ! -L "$DEST" ] && [ -z "$OUT" ]; then
ok "no shared file → creates nothing and says nothing (the default for most users)"
else bad "absent source" "rc=$RC out=[$OUT]"; fi
world; rm -f "$SRC"; mkdir -p "$SRC"; run PI_MODELS_JSON="$SRC"
if [ "$RC" = 0 ] && [ ! -e "$DEST" ] && [ ! -L "$DEST" ]; then
ok "a directory at the path is not a models.json → nothing"
else bad "directory source" "rc=$RC out=[$OUT]"; fi
for v in off OFF ""; do
world; run PI_MODELS_JSON="$v"
if [ "$RC" = 0 ] && [ ! -e "$DEST" ] && [ ! -L "$DEST" ] && [ -z "$OUT" ]; then
ok "PI_MODELS_JSON='$v' disables it"
else bad "disable '$v'" "rc=$RC out=[$OUT]"; fi
done
world; run PI_MODELS_JSON="$SRC"; rm -f "$SRC"; run PI_MODELS_JSON="$SRC"
if [ "$RC" = 0 ] && [ -L "$DEST" ] && [ "$(readlink "$DEST")" = "$SRC" ] && [ -z "$OUT" ]; then
ok "our link whose target vanished (mount removed) is left as is, silently (pi treats it as no file)"
else bad "dangling own link" "rc=$RC out=[$OUT]"; fi
# pi honours PI_CODING_AGENT_DIR, so the link must land THERE, not in $HOME/.pi/agent.
world; AG="$T/w/elsewhere/agent"; DEST="$AG/models.json"; run PI_MODELS_JSON="$SRC"
if [ "$RC" = 0 ] && [ "$(readlink "$DEST" 2>/dev/null)" = "$SRC" ] && [ ! -e "$T/w/home/.pi/agent/models.json" ]; then
ok "links into PI_CODING_AGENT_DIR when set (and leaves \$HOME/.pi/agent alone)"
else bad "PI_CODING_AGENT_DIR" "rc=$RC link=$(readlink "$DEST" 2>&1) out=[$OUT]"; fi
# 'off' must be a switch, not merely a path that happens not to exist: with a file
# literally named "off" in the working directory it must still disable.
world; printf '{}\n' > "$T/w/off"; OUT="$(cd "$T/w" && env PI_MODELS_JSON=off HOME="$T/w/home" PI_CODING_AGENT_DIR="$AG" bash "$SCRIPT" 2>&1)"; RC=$?
if [ "$RC" = 0 ] && [ ! -e "$DEST" ] && [ ! -L "$DEST" ]; then
ok "PI_MODELS_JSON=off disables it even when a file named 'off' exists in the cwd"
else bad "off is a switch" "rc=$RC link=$(readlink "$DEST" 2>&1) out=[$OUT]"; fi
# The default path is /opt/fleet-pi/models.json. Whether it exists depends on where
# this runs (a fleet devbox: yes; CI: no), so derive the expectation, don't assume it.
world; run -u PI_MODELS_JSON
if [ -f /opt/fleet-pi/models.json ]; then
if [ "$RC" = 0 ] && [ "$(readlink "$DEST" 2>/dev/null)" = /opt/fleet-pi/models.json ]; then
ok "PI_MODELS_JSON unset → default /opt/fleet-pi/models.json (present here) is linked"
else bad "default path (present)" "rc=$RC link=$(readlink "$DEST" 2>&1) out=[$OUT]"; fi
else
if [ "$RC" = 0 ] && [ ! -e "$DEST" ] && [ ! -L "$DEST" ] && [ -z "$OUT" ]; then
ok "PI_MODELS_JSON unset → default /opt/fleet-pi/models.json (absent here) → nothing"
else bad "default path (absent)" "rc=$RC out=[$OUT]"; fi
fi
echo "── $PASS passed, $FAIL failed"
[ "$FAIL" = 0 ]