Skip to content

Pin and verify the llama-swap installation - #25

Merged
rammsguns merged 4 commits into
mainfrom
llama-swap-pinning
Aug 12, 2026
Merged

Pin and verify the llama-swap installation#25
rammsguns merged 4 commits into
mainfrom
llama-swap-pinning

Conversation

@rammsguns

Copy link
Copy Markdown
Owner

Closes #22.

40-serve.sh installs executable code into /usr/local/bin as root. It now
installs a named version whose SHA-256 matches, or it installs nothing. New
library lib/swap.sh; the install block in 40-serve.sh rewritten around it.

Acceptance criteria

Criterion Where
Documented default LLAMA_SWAP_VERSION, overridable SWAP_VERSION in lib/swap.sh, pinned to v249; LLAMA_SWAP_VERSION=v248 ./40-serve.sh
Exact asset for linux/amd64; ambiguity rejected swap_select_asset — exact name first, pattern only as a fallback that refuses on more than one match
SHA-256 verified; fail closed when unavailable or mismatched swap_verify — pin first, upstream checksums.txt second, refuse third
No @latest fallback; source build uses the same tag install_llama_swap_from_source builds github.com/mostlygeek/llama-swap@$SWAP_VERSION
Atomic install; a working binary is not replaced until verification succeeds swap_install_binary — stage beside the destination, then rename
Version and digest recorded and printed ~/llm-rig/etc/llama-swap.installed
Idempotent; a version change is explicit "already installed" vs "v248 installed; this llm-rig revision pins v249"
Mocked fixture tests for all six listed cases tests/cases/swap_install_test.sh, 36 tests
Upgrade procedure and rollback path documented README § The llama-swap binary is pinned and verified

Two decisions worth challenging

A mismatch stops the run and does not fall back to a source build. The
issue asked for a pinned source fallback, and it exists — but only for an
acquisition failure. A digest that does not match is not "could not fetch
it": the bytes are not what this version should be, and quietly obtaining the
same version by another route buries the one signal worth looking at. If you
would rather a mismatch fall through to go install, that is a one-line
change, and I would want it argued rather than assumed.

The pin beats upstream. A release asset can be deleted and re-uploaded
under the same tag, with checksums.txt re-uploaded alongside it — so upstream
agreeing with itself proves nothing about whether the bytes changed since this
repo was tested against them. Upstream is used only for versions not pinned
here, which is the path an explicit LLAMA_SWAP_VERSION override takes.

Three bugs the tests caught, all in code written for this issue

  1. The suite was reading the real /usr/local/bin/llama-swap. The library
    set SWAP_BIN from LLAMA_SWAP_BIN only, so the value the tests exported
    was overwritten and swap_installed_version reported this machine's actual
    v248. A test suite that can see — let alone write — the real install target
    is not a test suite. Fixed, with a guard test asserting the target is inside
    the sandbox.
  2. Staging used install(1), which is a mocked command, so the staging step
    silently did nothing under test and every install assertion was passing for
    the wrong reason. cp + chmod is equivalent and not shadowed.
  3. swap_verify returned the verifying authority in a variable, and every
    caller reads it through a command substitution where the assignment does not
    survive. Returned in band now.

Two changes to the shared test rig

  • The curl mock corrupted binary bodies. A file-backed route was slurped
    into a variable, which strips trailing newlines and cannot carry NUL bytes —
    so a tarball fixture arrived damaged, which looks exactly like a checksum
    failure in whatever is under test. File-backed routes are now copied.
  • A go mock lets the source fallback be exercised end to end — right tag,
    failed build, unverified provenance — with no toolchain and no network.

Tests

36 new, 413 across 13 suites, ordinary and network-isolated both green.

Not done

No real installation was performed. Verifying the pin against the live release
would mean downloading and installing a binary as root on this machine, which
is not something a test run should do. The pinned digest for v249 was taken
from upstream's published checksums.txt and is recorded in the test fixture
too, so the parsing is exercised against the real file.

🤖 Generated with Claude Code

rammsguns and others added 3 commits August 12, 2026 00:25
Closes #22.

40-serve.sh installed executable code into /usr/local/bin as root from
`releases/latest` -- a moving target -- with no checksum and no
signature, falling back to `go install ...@latest` when that failed. Two
runs of the same llm-rig revision could install two different binaries,
and a corrupted or substituted download was installed without anything
noticing.

It now installs a NAMED version whose SHA-256 matches, or it installs
nothing.

THE RULES, in lib/swap.sh

A digest pinned in llm-rig is the authority. Upstream's checksums.txt is
consulted only for versions this repo has not pinned, because a release
asset can be deleted and re-uploaded under the same tag with its
checksums file re-uploaded alongside it.

No checksum from either source means refuse. "Could not check" is not
"checked", and a network failure is not a reason to install something
unverified as root.

A mismatch stops the run outright and does NOT fall back to a source
build. A digest that does not match is not "could not fetch it": the
bytes are not what this version is supposed to be, and quietly acquiring
the same version by another route buries exactly the signal worth
looking at.

Exactly one asset may match. The old code ran a broad grep over asset
URLs and took `head -1` of a list that includes linux_arm64. Selection is
now by exact expected name, with the pattern kept only as a fallback that
refuses when it matches more than one -- and it matches on the asset NAME
rather than the URL, which only worked upstream because the name happens
to appear in it.

The install is atomic: staged next to the destination and renamed over
it, so there is no window in which /usr/local/bin/llama-swap is
half-written. The replaced binary is kept as .previous, which makes
rollback a mv with no network. Nothing is touched until verification has
passed.

The source fallback builds the same tag. `@latest` meant the machine
whose download failed silently ran a different version from every other
machine -- and only that machine.

What was installed, its digest, and how it was verified are recorded in
etc/llama-swap.installed and printed. A source build records "digest
recorded, not verified", because Go builds are not bit-identical across
toolchains and the digest is a record of what was produced rather than
evidence it matches anything.

Re-running the same version does nothing and says so. A version change is
stated explicitly.

TESTS

36 new. Three bugs they caught, all in code written for this issue:

The library ignored an already-set SWAP_BIN, so the suite was reading
this machine's real /usr/local/bin/llama-swap (v248) instead of its
sandbox. A test suite that can see -- let alone write -- the real install
target is not a test suite. There is now a guard test asserting the
target is inside the sandbox.

Staging used install(1), which is one of the mocked commands, so the
staging step silently did nothing under test. cp + chmod is equivalent
and not shadowed.

swap_verify returned the authority that checked the digest in a variable,
and every caller reads it through a command substitution, where the
assignment does not survive the subshell. It is returned in band now.

Two supporting changes to the test rig. The curl mock kept a file-backed
route body in a variable, so any binary fixture came back corrupted --
which looks exactly like a checksum failure in whatever is under test; it
copies the file now. And a `go` mock lets the source fallback be
exercised end to end without a toolchain and without the network.

413 tests across 13 suites, ordinary and network-isolated both green.

README documents the upgrade procedure (bump the version and its digest
in the same commit) and both rollback paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same SC2034 class as 04b7f67 on the other branch: lib/swap.sh defines
SWAP_VERSION, SWAP_REPO, SWAP_API and SWAP_DL for 40-serve.sh to read,
and SWAP_VERIFIED_BY / SWAP_VERIFIED_DIGEST for a direct caller of
swap_verify. ShellCheck at --severity=warning cannot see those uses.

Directives at the assignment, naming them documented return channels,
matching the convention in the rest of lib/.

No behaviour change; the fixture suites were already green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A comment line that BEGINS with the word shellcheck is parsed as a
directive, so

  # These four are read by 40-serve.sh rather than in this file, which is why
  # shellcheck cannot see a use for them.

failed with SC1073/SC1072 ("expected '=' after directive key") -- and,
worse, SC1094 on every file that sources this one, because a file with an
unparseable directive is skipped entirely. The suites stayed green; the
lint did not.

Reworded, with a note saying why the wording matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rammsguns
rammsguns merged commit 9bcf936 into main Aug 12, 2026
1 check passed
@rammsguns
rammsguns deleted the llama-swap-pinning branch August 12, 2026 22:02
rammsguns added a commit that referenced this pull request Aug 13, 2026
One conflict, in README.md, and it was an insertion collision rather than a
disagreement: this branch adds "Rating the models you serve" immediately
before "## Configuration", and #25 adds "The llama-swap binary is pinned and
verified" in the same place. Neither edits the other's text.

Both sections are kept, ratings first. The ratings section continues the
catalog thread the surrounding prose is already on -- it opens by answering
"a quarter of the score is a neutral placeholder", which is the sentence two
sections above it -- and the llama-swap section is about 40-serve.sh, so it
reads as the last thing before Configuration rather than an interruption.
Nothing was dropped from either side.

Everything else merged clean, including the two places most likely to have
collided: catalog_ratings() in lib/catalog.sh, where #27 added two Laguna
rows carrying unknown ratings while this branch changes how ratings are
produced, and tests/cases/catalog_test.sh.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[P1] Pin and verify the llama-swap installation

1 participant