feat(up): Guix-native container runtime (phase 1) #6

Merged
pti merged 3 commits from feat-guix-container-runtime into main 2026-07-07 13:14:54 +02:00
Owner

Phase 1 of design/guix-container-runtime.md (merged in #5). Adds a Guix-native wraptool up backend beside the devcontainer path.

What

  • --runtime=auto|guix|devcontainer on up. auto = guix when the guix CLI is on PATH and the worktree has manifest.scm, else devcontainer (selectRuntime, hasGuix).
  • bringUpGuixContainer: guix shell --container --network from a harness container.scm (.wraptool/container.scm or container.scm — node/bash/coreutils/nss-certs, no git/creds). Forwards WRAPTOOL_TOKEN/_CWD via --preserve, marks the shell WRAPTOOL_CONTAINER=1.
  • writeMCPConfig: writes .mcp.json on the host into the shared workspace (Go twin of connect.sh) — SSE at the server's loopback listen addr, ?cwd= scoped, bearer header when a token is set.
  • inContainer now also honors WRAPTOOL_CONTAINER so a nested up is refused.

Why this shape

Per the design: internet is mandatory → -N (shared host netns) → harness reaches wraptool directly on loopback (no socat/unix needed in v1). Credential isolation is a mount/user-namespace property, intact regardless of net mode.

Verified

  • Gate cleared: node v22/npm 10 run in a -N guix container, registry reachable, no -F needed.
  • End-to-end on a Guix host: up --runtime=guix starts the server, writes a correct .mcp.json; inside the container the harness gets the SSE event: endpoint handshake, the workspace is at its host path, host $HOME is empty / host repo not visible, git absent.
  • Unit tests: selectRuntime truth table, writeMCPConfig (token/no-token, cwd encoding, backup), manifest-lookup priority. go build/vet/golangci-lint clean (2 unrelated pre-existing validate_test.go failures assume /usr/bin/git, absent on Guix).

Deferred

--scaffold-guix (writes container.scm), strict-net mode (pasta), non-Claude harnesses.

🤖 Generated with Claude Code

Phase 1 of `design/guix-container-runtime.md` (merged in #5). Adds a Guix-native `wraptool up` backend beside the devcontainer path. ## What - `--runtime=auto|guix|devcontainer` on `up`. **auto** = guix when the `guix` CLI is on PATH **and** the worktree has `manifest.scm`, else devcontainer (`selectRuntime`, `hasGuix`). - `bringUpGuixContainer`: `guix shell --container --network` from a harness `container.scm` (`.wraptool/container.scm` or `container.scm` — node/bash/coreutils/nss-certs, **no git/creds**). Forwards `WRAPTOOL_TOKEN`/`_CWD` via `--preserve`, marks the shell `WRAPTOOL_CONTAINER=1`. - `writeMCPConfig`: writes `.mcp.json` on the host into the shared workspace (Go twin of `connect.sh`) — SSE at the server's loopback listen addr, `?cwd=` scoped, bearer header when a token is set. - `inContainer` now also honors `WRAPTOOL_CONTAINER` so a nested `up` is refused. ## Why this shape Per the design: internet is mandatory → `-N` (shared host netns) → harness reaches wraptool directly on loopback (no socat/unix needed in v1). Credential isolation is a mount/user-namespace property, intact regardless of net mode. ## Verified - **Gate cleared**: node v22/npm 10 run in a `-N` guix container, registry reachable, **no `-F`** needed. - **End-to-end** on a Guix host: `up --runtime=guix` starts the server, writes a correct `.mcp.json`; inside the container the harness gets the SSE `event: endpoint` handshake, the workspace is at its host path, **host `$HOME` is empty / host repo not visible**, git absent. - Unit tests: `selectRuntime` truth table, `writeMCPConfig` (token/no-token, cwd encoding, backup), manifest-lookup priority. `go build`/`vet`/`golangci-lint` clean (2 unrelated pre-existing `validate_test.go` failures assume `/usr/bin/git`, absent on Guix). ## Deferred `--scaffold-guix` (writes `container.scm`), strict-net mode (pasta), non-Claude harnesses. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(up): Guix-native container runtime (phase 1)
All checks were successful
Lint / lint (push) Successful in 23s
Lint / lint (pull_request) Successful in 17s
986640bdf5
Add a second `wraptool up` backend using `guix shell --container` alongside the
devcontainer path, per design/guix-container-runtime.md. `--runtime=auto|guix|
devcontainer` (auto = guix when the guix CLI is present and the worktree has
manifest.scm, else devcontainer).

The guix path runs `guix shell --container --network` from a harness-runtime
container.scm (.wraptool/container.scm or container.scm — node/bash/coreutils/
nss-certs, no git/creds), writes .mcp.json on the host into the shared workspace
(writeMCPConfig, the Go twin of connect.sh), forwards WRAPTOOL_TOKEN/_CWD via
--preserve, and marks the shell WRAPTOOL_CONTAINER=1 (also taught to
inContainer so a nested `up` is refused). Under -N the harness reaches wraptool
at the server's loopback listen addr; credential isolation comes from the
mount/user namespace.

Verified end-to-end on a Guix host: `up --runtime=guix` starts the server,
writes a correct .mcp.json, and inside the container the harness reaches host
wraptool (SSE handshake) while host $HOME/repo are not visible and git is
absent. Unit tests cover selectRuntime, writeMCPConfig, and manifest lookup.

Deferred to later phases: --scaffold-guix, strict-net (pasta), non-Claude
harnesses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test: use real binaries in config-validation tests, not /usr/bin/git
Some checks failed
Lint / lint (pull_request) Failing after 1m51s
Lint / lint (push) Successful in 1m54s
4f2f186bc9
Config validation lstat's each tool's binary, so tests that must pass Validate()
hardcoded /usr/bin/git and failed on hosts without it (e.g. Guix, where binaries
live under /gnu/store). Point those configs at an executable that always exists:
os.Executable() (cmd) or a dummy exec written into t.TempDir() (config, webui —
the house style already used in config_test.go). Decode-only tests that never
resolve the path are left as-is. `go test ./...` is now green on Guix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pti force-pushed feat-guix-container-runtime from 4f2f186bc9
Some checks failed
Lint / lint (pull_request) Failing after 1m51s
Lint / lint (push) Successful in 1m54s
to bd0339a982
Some checks failed
Lint / lint (pull_request) Failing after 44s
Lint / lint (push) Successful in 46s
2026-07-07 13:08:45 +02:00
Compare
ci: scope lint push trigger to main to stop double-runs
All checks were successful
Lint / lint (pull_request) Successful in 56s
35b96b3cee
`on: [push, pull_request]` fired two lint jobs for any push to a branch with an
open PR. On the shared native Guix runner they run concurrently and collide on
golangci-lint's host-wide lock ("another instance is running"), failing one.
Scope `push` to main: feature branches lint once via pull_request, main lints
once via push, never both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
pti merged commit e15fdd4767 into main 2026-07-07 13:14:54 +02:00
pti deleted branch feat-guix-container-runtime 2026-07-07 13:14:54 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
pti/wraptool!6
No description provided.