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.
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# link-pi-models.sh — point ~/.pi/agent/models.json at a shared, mounted file.
|
||||
#
|
||||
# pi reads custom providers (any OpenAI-, Anthropic- or Google-compatible
|
||||
# endpoint: a company gateway, vLLM, Ollama, LM Studio …) from
|
||||
# ~/.pi/agent/models.json. In pi-devbox that directory is the devbox-pi-config
|
||||
# named volume, which makes both obvious ways of providing the file wrong:
|
||||
# - baked into the image, it would only seed a FRESH volume, once;
|
||||
# - copied by hand into each volume, it is one more per-device step, and the
|
||||
# copies drift the first time a price or model id changes.
|
||||
# Instead, keep ONE models.json in a host directory, mount that directory
|
||||
# read-only, and let this script link it in at every container start:
|
||||
#
|
||||
# volumes:
|
||||
# - ./pi-shared:/opt/fleet-pi:ro # a directory containing models.json
|
||||
#
|
||||
# Mount the DIRECTORY, not the file. `git pull` (and most editors) replace a
|
||||
# file by rename, and a single-file bind mount keeps the old inode and shows
|
||||
# "No such file" until the next recreate (measured on Docker Desktop,
|
||||
# 2026-10-12). A directory mount shows the new file at once, and pi re-reads
|
||||
# models.json when it starts, so a pulled change reaches the next pi session.
|
||||
#
|
||||
# PI_MODELS_JSON overrides the path (default /opt/fleet-pi/models.json).
|
||||
# PI_MODELS_JSON=off (or empty) disables this script.
|
||||
#
|
||||
# Never destructive. It only ever creates a link where nothing exists:
|
||||
# - no file at the path → nothing (the case for most users)
|
||||
# - models.json is already our link → nothing
|
||||
# - models.json is a real file → left alone; one NOTE line says why
|
||||
# - models.json links somewhere else → left alone; a deliberate choice
|
||||
# A link this script made whose target later disappears (mount removed) is
|
||||
# harmless: pi treats a dangling models.json exactly like a missing one —
|
||||
# no error, no custom models (measured with pi 1.0.0, 2026-10-12).
|
||||
#
|
||||
# Called by entrypoint-user.sh. Safe to run by hand at any time.
|
||||
set -u
|
||||
|
||||
src="${PI_MODELS_JSON-/opt/fleet-pi/models.json}"
|
||||
case "$src" in
|
||||
"" | off | OFF | Off) exit 0 ;;
|
||||
esac
|
||||
[ -f "$src" ] && [ -r "$src" ] || exit 0
|
||||
|
||||
agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
|
||||
dest="$agent_dir/models.json"
|
||||
|
||||
if [ -L "$dest" ]; then
|
||||
# Ours already, or someone else's deliberate link: either way, nothing to do.
|
||||
[ "$(readlink "$dest")" = "$src" ] || \
|
||||
echo "NOTE: $dest links to $(readlink "$dest"), not $src; left alone"
|
||||
exit 0
|
||||
fi
|
||||
if [ -e "$dest" ]; then
|
||||
echo "NOTE: $dest is a real file; not linking $src (move it away to use the shared one)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$agent_dir" 2>/dev/null || true
|
||||
if ln -s "$src" "$dest" 2>/dev/null; then
|
||||
echo "pi models.json: linked to $src"
|
||||
else
|
||||
echo "WARN: could not link $dest -> $src (continuing)"
|
||||
fi
|
||||
exit 0
|
||||
Reference in New Issue
Block a user