ward: userspace tailscale SOCKS5 sidecar carry mode for Ollama-tower reach on Docker Desktop #333

Closed
opened 2026-06-25 10:34:46 +00:00 by coilysiren · 2 comments
Owner

Goal

Let a ward carry reach the Ollama tower (kai-tower-3026:11434) over the tailnet on Docker Desktop, where --host-net cannot (the LinuxKit VM is not a tailnet node — see ward#332). This is the concrete implementation of #332's "promote the sidecar path," using the proven tooling-tailscale userspace SOCKS5 pattern.

Design (grounded in the existing pattern)

Mirror agentic-os/.agents/skills/tooling-tailscale/references/containerized-setup.md — do NOT invent a new shape:

  • Userspace tailscale sidecar next to the carry: TS_USERSPACE=true, TS_SOCKS5_SERVER=0.0.0.0:1055, SOCKS5 bound to loopback. No /dev/net/tun, no NET_ADMIN, no host route — this is required because Docker-on-Mac runs in a VM.
  • Auth key: reuse /coilysiren/mac-proxy/ts-authkey (reusable + ephemeral, tag:proxy, already minted by terraform/tailscale). Fetch at launch via ward ops aws ssm get-parameter ... --with-decryption, inject as TS_AUTHKEY without writing to disk. This implies the ~/.aws mount, same implication --host-net already carries — reuse that wiring.
  • Route: dial the tower by tailnet IP resolved at runtime from /coilysiren/kai-tower-3026/tailnet-ip (the param open-webui already consumes), on :11434, through the SOCKS5 proxy at 127.0.0.1:1055. Skip MagicDNS through the proxy (sharp-edges doctrine: dial by IP, avoid resolver confusion).
  • Flag surface: add the carry mode across the same four surfaces hostNetFlag() wires (work/headless/default, task, sandbox/explore, ask), threaded through upPlan. Coordinate the flag name/semantics with #332's host-net rework (it may live as the Docker Desktop branch of --host-net rather than a separate flag — pick whichever #332 settles on).

Dependency (ACL grant) — already in flight

The grant tag:proxy -> tag:kai-tower-3026:11434 is being added in infrastructure#400. It must be applied (operator-run ward terraform-tailscale action=apply) before this functions end to end. The tag:proxy key already exists; the ACL hop is the missing piece.

Scope of THIS run

  • Implement the sidecar wiring, the flag, SSM key injection, and the SOCKS5 routing.
  • Unit tests for the argv + plan change, in the shape of cmd/ward/container_hostnet_test.go.
  • Docs: extend docs/agent-host-net.md (or a sibling doc) with the sidecar path and a worked tower example.
  • Do NOT live-validate against the tower. The ACL grant may not be applied yet, and key minting / tailscale admin is human-gated. State plainly in the commit/issue that end-to-end validation awaits infra#400's apply. Do not mint keys, do not run terraform, do not fetch tailscale admin creds.

Coupling — read before editing

ward#332 is in flight reworking host-net detect/warn/docs and docs/agent-host-net.md. Integrate its main changes before finalizing and resolve doc/code conflicts in favor of #332's detect/warn layer (this issue builds on top of it, it does not replace it).

Front-load before you plan (read in full first)

  • docs/agent.md, docs/agent-preflight.md, .agents/skills/tooling-ward-agent/SKILL.md — dispatch + preflight.
  • docs/container.md, docs/agent-host-net.md — the container network model and the host-net flag this extends.
  • cmd/ward/container_compute.go, cmd/ward/agent.go, cmd/ward/container.go, cmd/ward/container_hostnet_test.gohostNetFlag(), upPlan, dockerArgvHead, the test shape.
  • agentic-os/.agents/skills/tooling-tailscale/ SKILL.md + references/containerized-setup.md + references/sharp-edges.md — the SOCKS5 pattern, the userspace-not-TUN rationale, dial-by-IP.

Filed from a read-only explore carry diagnosing the kai-tower access blocker; dispatched per explore capture-and-dispatch doctrine. Depends on infrastructure#400 for the ACL grant.

## Goal Let a ward carry reach the Ollama tower (`kai-tower-3026:11434`) over the tailnet on **Docker Desktop**, where `--host-net` cannot (the LinuxKit VM is not a tailnet node — see [ward#332](https://forgejo.coilysiren.me/coilyco-flight-deck/ward/issues/332)). This is the concrete implementation of #332's "promote the sidecar path," using the proven `tooling-tailscale` userspace SOCKS5 pattern. ## Design (grounded in the existing pattern) Mirror `agentic-os/.agents/skills/tooling-tailscale/references/containerized-setup.md` — do NOT invent a new shape: - **Userspace tailscale sidecar** next to the carry: `TS_USERSPACE=true`, `TS_SOCKS5_SERVER=0.0.0.0:1055`, SOCKS5 bound to loopback. No `/dev/net/tun`, no `NET_ADMIN`, no host route — this is required because Docker-on-Mac runs in a VM. - **Auth key:** reuse `/coilysiren/mac-proxy/ts-authkey` (reusable + ephemeral, `tag:proxy`, already minted by `terraform/tailscale`). Fetch at launch via `ward ops aws ssm get-parameter ... --with-decryption`, inject as `TS_AUTHKEY` without writing to disk. This **implies the `~/.aws` mount**, same implication `--host-net` already carries — reuse that wiring. - **Route:** dial the tower by **tailnet IP** resolved at runtime from `/coilysiren/kai-tower-3026/tailnet-ip` (the param open-webui already consumes), on `:11434`, through the SOCKS5 proxy at `127.0.0.1:1055`. Skip MagicDNS through the proxy (sharp-edges doctrine: dial by IP, avoid resolver confusion). - **Flag surface:** add the carry mode across the same four surfaces `hostNetFlag()` wires (work/headless/default, task, sandbox/explore, ask), threaded through `upPlan`. Coordinate the flag name/semantics with #332's host-net rework (it may live as the Docker Desktop branch of `--host-net` rather than a separate flag — pick whichever #332 settles on). ## Dependency (ACL grant) — already in flight The grant `tag:proxy -> tag:kai-tower-3026:11434` is being added in [infrastructure#400](https://forgejo.coilysiren.me/coilyco-flight-deck/infrastructure/issues/400). It must be applied (operator-run `ward terraform-tailscale action=apply`) before this functions end to end. The `tag:proxy` key already exists; the ACL hop is the missing piece. ## Scope of THIS run - Implement the sidecar wiring, the flag, SSM key injection, and the SOCKS5 routing. - Unit tests for the argv + plan change, in the shape of `cmd/ward/container_hostnet_test.go`. - Docs: extend `docs/agent-host-net.md` (or a sibling doc) with the sidecar path and a worked tower example. - **Do NOT live-validate against the tower.** The ACL grant may not be applied yet, and key minting / tailscale admin is human-gated. State plainly in the commit/issue that end-to-end validation awaits infra#400's apply. Do not mint keys, do not run terraform, do not fetch tailscale admin creds. ## Coupling — read before editing ward#332 is **in flight** reworking host-net detect/warn/docs and `docs/agent-host-net.md`. Integrate its `main` changes before finalizing and resolve doc/code conflicts in favor of #332's detect/warn layer (this issue builds on top of it, it does not replace it). ## Front-load before you plan (read in full first) - `docs/agent.md`, `docs/agent-preflight.md`, `.agents/skills/tooling-ward-agent/SKILL.md` — dispatch + preflight. - `docs/container.md`, `docs/agent-host-net.md` — the container network model and the host-net flag this extends. - `cmd/ward/container_compute.go`, `cmd/ward/agent.go`, `cmd/ward/container.go`, `cmd/ward/container_hostnet_test.go` — `hostNetFlag()`, `upPlan`, `dockerArgvHead`, the test shape. - `agentic-os/.agents/skills/tooling-tailscale/` SKILL.md + `references/containerized-setup.md` + `references/sharp-edges.md` — the SOCKS5 pattern, the userspace-not-TUN rationale, dial-by-IP. _Filed from a read-only explore carry diagnosing the kai-tower access blocker; dispatched per explore capture-and-dispatch doctrine. Depends on infrastructure#400 for the ACL grant._
Author
Owner

🔒 Reserved by ward agent --driver claude — container ward-ward-issue-333-claude-f0ca9924 on host docker-desktop is carrying this issue (reserved 2026-06-25T10:34:50Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `ward-ward-issue-333-claude-f0ca9924` on host `docker-desktop` is carrying this issue (reserved 2026-06-25T10:34:50Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

Retro from the carry: the feature itself was the easy part — --ts-sidecar slotted in right alongside #330's --host-net plumbing (flag → upPlandockerArgvHead), and the only real design call was joining the carry to the sidecar's netns with --network=container:<carry>-ts so loopback :1055 reaches the SOCKS5. What actually fought back was everything around the code: the repo's ≤2-line comment-block discipline made me rewrite every comment I'd written, the 4000-char/80-line doc caps are brutal (FEATURES.md was parked exactly 1 char under the cap, so each edit was a chars-budget puzzle), and trufflehog wasn't on this container's PATH so I had to fetch the binary and prefix it onto the commit.

The #332 merge was the spicy bit — its doc literally called the sidecar "scope only, not shipped," which #333 directly contradicts, so resolving it meant reconciling prose, not just markers. I kept #332's detect/warn layer intact and reframed the sidecar section as shipped.

Confidence: high on the argv/plan layer (well unit-tested), deliberately unproven end-to-end — the ACL grant (infra#400) isn't applied, so I did not live-test against the tower, mint keys, or run terraform. Rough edges worth a follow-up: the detached-carry sidecar GC is lazy (only swept on the next launch, after the carry ages out of the keep-10 window, so a sidecar can linger), and the in-container entrypoint doesn't yet consume the injected WARD_TOWER_OLLAMA_B64 / WARD_TS_SOCKS5 — that harness-side wiring + a real validation pass should land once infra#400 is applied.

Retro from the carry: the feature itself was the easy part — `--ts-sidecar` slotted in right alongside `#330`'s `--host-net` plumbing (flag → `upPlan` → `dockerArgvHead`), and the only real design call was joining the carry to the sidecar's netns with `--network=container:<carry>-ts` so loopback `:1055` reaches the SOCKS5. What actually fought back was everything *around* the code: the repo's ≤2-line comment-block discipline made me rewrite every comment I'd written, the 4000-char/80-line doc caps are brutal (FEATURES.md was parked exactly 1 char under the cap, so each edit was a chars-budget puzzle), and trufflehog wasn't on this container's PATH so I had to fetch the binary and prefix it onto the commit. The `#332` merge was the spicy bit — its doc literally called the sidecar "scope only, not shipped," which `#333` directly contradicts, so resolving it meant reconciling prose, not just markers. I kept `#332`'s detect/warn layer intact and reframed the sidecar section as shipped. Confidence: high on the argv/plan layer (well unit-tested), deliberately unproven end-to-end — the ACL grant (infra#400) isn't applied, so I did not live-test against the tower, mint keys, or run terraform. Rough edges worth a follow-up: the detached-carry sidecar GC is lazy (only swept on the next launch, after the carry ages out of the keep-10 window, so a sidecar can linger), and the in-container entrypoint doesn't yet consume the injected `WARD_TOWER_OLLAMA_B64` / `WARD_TS_SOCKS5` — that harness-side wiring + a real validation pass should land once infra#400 is applied.
Sign in to join this conversation.
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
coilyco-flight-deck/ward#333
No description provided.