Skip to content

Per-host-type tooling install matrix (implements the host-setup contract) #483

Description

@ptr727

docs/host-setup.md states the contract (which tools a host must provide and which procedure stops without each one, added in #482). This issue collects the implementation: how each tool actually gets installed, per host type.

The split is deliberate. The contract is identical on every platform, so it belongs in a doc. The install command is not, so it belongs in a matrix that whoever runs that platform can fill in and verify.

The matrix

Fill in a cell only when you have run it on that platform. An unverified install command in a setup guide is worse than a blank, because a blank prompts a question while a wrong command produces a broken host and a false sense that setup succeeded.

Tool Linux (apt) Windows (winget) WSL2 macOS (brew)
git apt install git winget install -e --id Git.Git apt install git (inside the distro)
gh apt install gh (or the GitHub apt repo for a current version) winget install -e --id GitHub.cli apt install gh (inside the distro; Debian ships an old one — see notes)
python3 apt install python3 winget install -e --id Python.Python.3.13 (registers python/py, not python3 — see notes) apt install python3 (inside the distro; do not use the Windows install — see notes)
docker Docker Engine via the official apt repo winget install -e --id Docker.DockerDesktop (Docker Desktop; needs WSL2 backend) Docker Desktop on Windows + Settings → Resources → WSL integration enabled for the distro (installs nothing inside it), or Docker Engine via the official apt repo inside the distro — see notes
uv / uvx curl -LsSf https://astral.sh/uv/install.sh | sh winget install -e --id astral-sh.uv (ships uv + uvx) curl -LsSf https://astral.sh/uv/install.sh | sh (inside the distro)
write-safety kit host-setup/agent-safety/install.sh host-setup\agent-safety\install.ps1 install.sh install.sh

Verified on Linux (this host)

Versions observed on the proxmox Debian host, recorded as a known-good reference rather than a floor:

git 2.47.3
gh 2.46.0
python3 3.13.5
docker 29.6.2
uv 0.11.31

One caveat worth carrying: this gh is 2.46.0, old enough that gh pr checks --json does not exist, so a watcher written against it prints nothing rather than failing loudly. If a platform's package manager ships an old gh, note the version in its cell.

Verified on Windows (this host)

Versions observed on a native Windows 11 host (winget v1.29.280), recorded as a known-good reference rather than a floor. Left is the tool-reported version; right in parentheses is the winget package id and its version:

git    2.55.0.windows.3   (Git.Git 2.55.0.3)
gh     2.96.0             (GitHub.cli 2.96.0)
python 3.13.14            (Python.Python.3.13 3.13.14)
docker 29.6.2             (Docker.DockerDesktop 4.83.0)
uv     0.11.32            (astral-sh.uv 0.11.32)

Unlike the Linux host, this gh is 2.96.0, so gh pr checks --json exists here — the old-gh caveat above is Linux-only. Install commands use -e --id (exact match on the package id) so a name search cannot silently resolve to a different package.

Verified on WSL2 (this host)

Versions observed on Debian 13 (trixie) under WSL 2.7.10.0 / kernel 6.18.33.2 on Windows 11 (10.0.26200.8973), recorded as a known-good reference rather than a floor:

git     2.47.3    (Debian trixie main, 1:2.47.3-0+deb13u1)
gh      2.46.0    (Debian trixie main, 2.46.0-3)
python3 3.13.5    (Debian trixie main, 3.13.5-1)
docker  29.6.2    (Docker Desktop WSL integration — no docker package inside the distro)
uv      0.11.17   (astral installer, ~/.local/bin; the installer currently ships 0.12.1)

This distro's gh is 2.46.0, the same Debian-repo build as the Linux host, so the old-gh caveat carries here too: gh pr checks has --watch, --fail-fast and --interval but no --json, confirmed against this binary's own help.

What was actually run to fill the column, since the rule above is worth honoring precisely:

  • git / gh / python3: not re-installed (this host has no non-interactive sudo), but each was confirmed to be the stock Debian package from trixie/main at the version above via dpkg -S + apt-cache policy, which is exactly what apt install <pkg> yields on this distro. Treat these three as provenance-verified rather than command-verified.
  • docker: two of the four pinned linter images were run end to end from inside the distro — docker run --rm --pull=always -v "$PWD":/repo ... rhysd/actionlint:latest (which also carries shellcheck) and the davidanson/markdownlint-cli2:latest equivalent both pulled and exited 0 against this repo. cspell was not run, though it is the same invocation shape.
  • uv: the installer was run for real, into a throwaway UV_INSTALL_DIR, and produced a working uv/uvx 0.12.1. uvx ruff@0.14.4 --version resolves and runs.
  • write-safety kit: host-setup/agent-safety/install.sh was run against a throwaway CLAUDE_HOME, twice. First run reported hook self-test: PASS and safety block appended; second reported safety block updated, so the idempotency claim holds on WSL2. The live ~/.claude copy passes --selftest and has both agent-safety v markers.
  • the rest of the contract: git -c gpg.format=ssh commit -S --allow-empty produces a Good "git" signature in a throwaway repo, gh auth status is authenticated with a file token store (matching the WSL2 row in docs/devcontainer.md), and python3 spec/validate.py plus the 159 scripts/ unit tests pass with no packages installed, which is the stdlib-only claim in the contract.

Per-platform notes to capture

Beyond the bare install command, the cells that have bitten us before:

  • Windows: native PowerShell is enough to install all five tools via winget, but three cells bite. (1) python3: the winget package registers python, py, and python3.13not python3. On a default box python3 resolves first to the Microsoft Store alias stub (%LOCALAPPDATA%\Microsoft\WindowsApps\python3.exe), which errors Python was not found… (exit 9009) instead of running; use python/py, disable the Store alias under Settings → Apps → Advanced app settings → App execution aliases, or run under WSL2. (2) docker: Docker Desktop requires the WSL2 backend, matching docs/host-setup.md on the devcontainer flow. (3) write-safety kit install.ps1: it is a thin wrapper that runs install.py under Python 3, resolving the interpreter py -3python3python — so it needs Python 3 on PATH, and it sidesteps the python3 Store-alias trap above because py -3 is tried first. Running it needs script execution allowed: a git clone carries no Mark-of-the-Web so it runs under the default RemoteSigned, but a browser-downloaded ZIP is blocked as unsigned until Unblock-File (or invoke it as pwsh -ExecutionPolicy Bypass -File host-setup\agent-safety\install.ps1). The POSIX snippets still do not run as-is in PowerShell.
  • WSL2 (answered below; kept as a note because the choices still bite): five things.
    1. Docker provenance is a real fork, and this host takes the Desktop branch. Evidence it is the integration rather than an in-distro engine: /usr/bin/docker is a symlink into /mnt/wsl/docker-desktop/cli-tools/, docker info reports Docker Desktop / docker-desktop, and no docker* or containerd* package is installed. Socket access still looks native — /var/run/docker.sock is root:docker, so the account needs to be in the docker group exactly as with an in-distro engine, and the default context (not desktop-linux, which is a Windows named pipe) is the one that works from inside the distro. Bind mounts work from the distro's own filesystem with no path translation: a repo under $HOME mounts natively, the container sees the caller's uid/gid, and files it writes come back owned correctly. The engine-inside-the-distro branch is untested here.
    2. Keep the repo on the distro's ext4, not under /mnt/c. Both bind-mount successfully, but /mnt/c is a 9p/drvfs mount that reports every file as 0777. Verified side by side: the same file is 644 1000:1000 from $HOME and 777 1000:1000 from /mnt/c. That is a broken exec bit for git and for any mode-sensitive check, on top of 9p being far slower.
    3. Docker Desktop's credential helper is a Windows .exe (~/.docker/config.json has "credsStore": "desktop.exe", resolved via /usr/bin/docker-credential-desktop.exe). So an uncached docker pull depends on WSL interop being healthy, and the linters use --pull=always. When systemd leaves the WSLInterop binfmt handler unregistered, any .exe gives exec format error and the pull fails on the credential helper while cached images still run. Check with cat /proc/sys/fs/binfmt_misc/WSLInterop (want enabled, interpreter /init); fix with wsl --update, or an /etc/binfmt.d/WSLInterop.conf of :WSLInterop:M::MZ::/init:PF plus systemctl restart systemd-binfmt. Fully quit Docker Desktop before any wsl --update / wsl --shutdown, since a running or paused Desktop blocks the WSL service from stopping.
    4. Install the tools inside the distro; the Windows copies are on $PATH but never win. WSL2 appends the Windows PATH by default (51 entries here), so python3.exe, docker.exe and friends are reachable — but type -a confirms the Linux binaries resolve first for all five tools (docker finds /usr/bin/docker before the interop /Docker/host/bin/docker). The practical consequence is the inverse of the Windows column's trap: the Store-alias python3.exe stub exists at .../WindowsApps/python3.exe yet is shadowed, so python3 just works here. Note there is no bare python on stock Debian — install.sh is fine because it tries python3 first, but a snippet written for python will not run.
    5. The doc's systemd ssh-agent.socket step assumes an opt-in. WSL2 does not run systemd unless /etc/wsl.conf carries a [boot] section with systemd = true; this host does, and ssh-agent.socket is enabled + active. On a distro without that opt-in, docs/host-setup.md's Linux/WSL2 SSH section only offers the ~/.bashrc fallback, which is the branch to take.
  • macOS: the Keychain path for the SSH agent, which docs/host-setup.md already covers for signing but not for tooling.
  • Proxmox: whether the host is a shell for agent work or also runs containers, which decides whether docker is required there at all.

Related

Assignment

Linux, Windows and WSL2 are filled in and verified above. macOS still needs someone on that platform, as does the Proxmox question in the notes. The engine-inside-the-distro variant of the WSL2 docker cell is also still unverified — this host only exercised the Docker Desktop integration branch.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions