Skip to content

M7-29 Setup wizard install.sh

No Date Ruling Reason
D-M7-29-0 2026-10-21 The ticket was built on an integration base (stacked on M1-73) and ported onto main with only its own commits; the English docs of main win, and text this ticket adds is English. The first commit of the build only ported spec text, the gate release-pins and ADR 0060 item 6 from main onto the old base; main has all of it, so the port drops that commit. Names that differ from the brief (D-W-100): the release build of the product is tools/ci/product_release.py behind make release-build; the check of requirement 19 is the gate make install-options; the script tests are make install-test. Two docstrings with a backslash are raw strings because main's ruff configuration requires it (rule D301); the job product-signing runs on ubuntu-24.04 like the other release jobs of main. The tickets are built in parallel on integration bases; main takes each ticket as one pull request; merged names win (D-W-100).
D-M7-29-1 2026-10-20 Test framework of the script (Design note 4): POSIX shell tests (infra/compose/test/install/run.sh) with stub executables for docker, curl, cosign and sudo on PATH, run in debian:trixie-slim pinned by digest with the repository mounted read-only and no network (make install-test, part of make test), so the shell is dash as on most Linux hosts; INSTALL_TEST_NATIVE=1 runs the same tests with the host's /bin/sh (macOS). The terminal case runs the script under a pseudo terminal from util-linux script, so the prompts really read /dev/tty. No bats: it would add a second pinned image and a test language for one script. The brief allows POSIX shell tests in a container; stubs are the documented options as the only interface (16 3.1 rule 5).
D-M7-29-2 2026-10-20 Image existence check (Design note 2): docker manifest inspect <registry>/<image>:<tag> for every image compose.yaml names with ${BOTHY_VERSION}, read from the commit; the build job of release.yml logs in to ghcr.io before make release-build for bothy. No crane pin: the Docker CLI is on every runner and on the build host, and the tests replace it by a stub on PATH. One tool fewer to pin; the same login the server jobs use.
D-M7-29-3 2026-10-20 Trial of the product (Design note 3): RELEASE_TRIAL_REGISTRY=127.0.0.1:<port> replaces ghcr.io/<owner> and RELEASE_TRIAL_BASE_URL=http://127.0.0.1:<port>/ the download address; both are refused when GITHUB_ACTIONS is set and for any host but 127.0.0.1 (tests in test_release_build_bothy.py), and the image check then uses --insecure against the plain HTTP registry. make release-trial COMPONENT=bothy runs the server trial, then the product build from a throwaway repository with the bundle paths of HEAD and BOTHY_VERSION pinned to the trial server version (the committed template carries the placeholder), served by a static HTTP server on 127.0.0.1, and checks download, checksum, registry and version of the bundle, the signed digests and a docker pull of each image. It does not run a full installation: the M1 server still rejects BOTHY_FILE__SCAN__CLAMD_ADDRESS of the installation compose.yaml (see infra/compose/test/compose.smoke.yaml), which M7-14 reconciles together with its local end-to-end run. D-W-127: the same code in trial and release; a real installation would fail for a reason outside this ticket and prove nothing about it.
D-M7-29-4 2026-10-20 WSL 2 (16 3.1 rule 3): not verified, the build host has no Windows. The script uses only POSIX sh and the Docker CLI, ShellCheck checks it as sh, and the tests run under dash (Linux) and the macOS /bin/sh. Brief, out of scope: verify as the build host allows and report it.
D-M7-29-5 2026-10-20 Bundle content (18 section 2 row bothy, updated): besides infra/compose without infra/compose/test, the bundle contains infra/otel and infra/observability, because compose.yaml mounts ../otel and ../observability; without them the collector of every installation would start without its configuration. The source of install.sh stays out of the bundle: the release ships it rendered next to the bundle, and a second copy with unset built-in values would mislead. The archive is reproducible (order, modes and times of the commit, gzip header without a time); test_release_bundle_excludes_smoke_overlay of M1-73 now builds from the fixture repository of requirement 22 instead of this repository, whose template carries the placeholder that make release-build refuses. A bundle that cannot start its collector is no installation; the merged test is kept, only its input changes as the brief demands.
D-M7-29-6 2026-10-20 Layout and options of install.sh beyond the brief: the bundle unpacks with its own layout, so .env and secrets/ are in <dir>/infra/compose; the marker <dir>/.bothy-install names an earlier installation (step 1 and rule 1). TS_AUTHKEY is never an option, because the arguments of a process are visible to other users of the host; without a terminal it comes from the environment variable TS_AUTHKEY. Exit code 2 for an invalid option, 1 for a failed step. The signature check (step 2) uses the identity of the release workflow of the repository in the download address (refs/tags/v); with another address (trial) or without cosign the script names the command. 16 3.1 names no option for the hidden prompt; the relative mounts of compose.yaml need the layout.
D-M7-29-7 2026-10-20 make release-prep COMPONENT=bothy also sets the product version in every documented command https://github.com/<owner>/bothy/releases/download/v<version>/install.sh of README.md and docs/operations/install-compose.md, so the gate install-options stays green after a release commit. SERVER_VERSION given on the make command line becomes --server-version; the Makefile variable of the same name for the image targets is not passed. Requirement 19 holds the documented version equal to VERSION; without the update every release commit would fail its own gate.
D-M7-29-8 2026-10-20 ShellCheck 0.11.0 is pinned in tools/bin/tools.lock.json (refreshed with make tools-lock); its releases are .tar.xz only, so fetch_tools.py reads tar archives with any compression tarfile knows. make lint runs it over infra/compose/*.sh and the scripts of infra/compose/test/install/, each with the dialect of its shebang. GPL-3.0 is acceptable for a build tool that is not shipped, as for golangci-lint. Requirement 13 and the scope line "ShellCheck pinned (tools-lock)".
D-M7-29-9 2026-10-20 release.yml: the job product-signing (environment release, needs build) writes SBOMs of bundle and install.sh, signs both and SHA256SUMS in the mode of the repository visibility and verifies them with tools/ci/release_chain.py, and publish takes that set by artifact id and skips its generic SBOM and checksum steps for bothy. release_chain.py sign writes images.txt only when it signs images. Brief scope: sign both like every artifact of M7-10; the same commands as the server, no second signing path.