docs: per-host ControlPath under ~/.ssh breaks pi --ssh (read-only mount)
The bind-mounted ~/.ssh/config is read before the baked Host * default and SSH uses the first ControlPath it sees. A per-host block pointing ControlPath under ~/.ssh/ (CGNAT-multiplexing pattern) wins but fails in-container because ~/.ssh is read-only, silently breaking pi --ssh <host> (falls back to local tools). Documented the host-side fix: drop the override or repoint at the writable /tmp/sshcm/. README + CHANGELOG only, no image change.
This commit is contained in:
@@ -8,6 +8,19 @@ Tags follow `v{opencode_version}[letter]` — bare tag for the first build on a
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Docs: per-host `ControlPath` overrides break `pi --ssh` (read-only `~/.ssh`)
|
||||||
|
|
||||||
|
Documented a gotcha in the README "Reaching your LAN" section: the bind-mounted
|
||||||
|
`~/.ssh/config` is read before the baked `Host *` default, and SSH uses the
|
||||||
|
first `ControlPath` it sees. A per-host block that sets `ControlPath` under
|
||||||
|
`~/.ssh/` (a common CGNAT-multiplexing pattern, e.g. `~/.ssh/cm/%r@%h:%p`) wins
|
||||||
|
but then fails inside the container because `~/.ssh` is mounted read-only — the
|
||||||
|
master socket can't bind. This silently breaks `pi --ssh <host>`: the SSH layer
|
||||||
|
fails and pi falls back to running its tools locally in the container. Fix is
|
||||||
|
host-side — drop the per-host `ControlPath` or repoint it at the writable
|
||||||
|
`/tmp/sshcm/%r@%h:%p` (works on both host and container, preserves multiplexing).
|
||||||
|
No image change; documentation only.
|
||||||
|
|
||||||
### Fixed: validate.yml false-negative on fork/recall registration checks
|
### Fixed: validate.yml false-negative on fork/recall registration checks
|
||||||
|
|
||||||
The push-to-main `validate.yml` builds variants FROM the published `base-latest`
|
The push-to-main `validate.yml` builds variants FROM the published `base-latest`
|
||||||
|
|||||||
@@ -187,6 +187,22 @@ Now `dssh my-nas` routes container → host → LAN peer, pulling HostName/User/
|
|||||||
|
|
||||||
> This ships the **mechanism** only — your specific target hosts are facts about *your* network (and a laptop roams between several), so they live in your own host-side config, never baked into the image. Set `DEVBOX_LAN_ACCESS=off` to disable, or `=jump` to force it (e.g. native Linux with `extra_hosts: ["host.docker.internal:host-gateway"]`).
|
> This ships the **mechanism** only — your specific target hosts are facts about *your* network (and a laptop roams between several), so they live in your own host-side config, never baked into the image. Set `DEVBOX_LAN_ACCESS=off` to disable, or `=jump` to force it (e.g. native Linux with `extra_hosts: ["host.docker.internal:host-gateway"]`).
|
||||||
|
|
||||||
|
#### Gotcha: per-host `ControlPath` and `pi --ssh`
|
||||||
|
|
||||||
|
The base image bakes a `Host *` default (`/etc/ssh/ssh_config.d/00-devbox-controlmaster.conf`) that points `ControlPath` at the writable, per-container `/tmp/sshcm/` (created mode-700 on every start by `entrypoint-user.sh`). Multiplexing therefore works out of the box. **But your bind-mounted `~/.ssh/config` is read first, and SSH uses the first value it sees** — so any per-host block that sets its own `ControlPath` under `~/.ssh/` (a common CGNAT-multiplexing pattern, e.g. `ControlPath ~/.ssh/cm/%r@%h:%p`) **wins, and then fails inside the container** because `~/.ssh` is mounted **read-only** — the master socket can't bind (`cannot bind … Read-only file system`).
|
||||||
|
|
||||||
|
This bites `pi --ssh <host>` especially: the SSH layer fails to establish the master and pi silently falls back to running its `read`/`write`/`edit`/`bash` tools **locally in the container** instead of on the remote (watch for the missing `SSH ⚡` in the status bar — and `hostname` returning the container ID).
|
||||||
|
|
||||||
|
**Fix (host-side, one line):** in your host's `~/.ssh/config`, either drop the per-host `ControlPath` (to inherit the writable baked default) or point it at a path that's writable inside the container too:
|
||||||
|
|
||||||
|
```sshconfig
|
||||||
|
Host my-remote
|
||||||
|
# was: ControlPath ~/.ssh/cm/%r@%h:%p ← read-only in the container
|
||||||
|
ControlPath /tmp/sshcm/%r@%h:%p # writable on both host and container
|
||||||
|
```
|
||||||
|
|
||||||
|
`/tmp/sshcm/` is also writable on the host (macOS/Linux), so native (non-container) `ssh`/`pi --ssh` from the host keeps working and CGNAT multiplexing is preserved (`ControlMaster`/`ControlPersist` unchanged — only the socket *directory* moves). Note SSH does not create the `ControlPath` parent dir; the container makes `/tmp/sshcm` every start, but on the host run `mkdir -p /tmp/sshcm` once if it doesn't already exist.
|
||||||
|
|
||||||
### Custom opencode config
|
### Custom opencode config
|
||||||
|
|
||||||
Opencode configuration is persisted automatically via the named volume `devbox-opencode-config`. This volume is mounted at `/home/developer/.config/opencode` by default — no host directory setup required. All changes to `opencode.jsonc`, skills, and (on the OMOS variant) `oh-my-opencode-slim.json` survive container recreation.
|
Opencode configuration is persisted automatically via the named volume `devbox-opencode-config`. This volume is mounted at `/home/developer/.config/opencode` by default — no host directory setup required. All changes to `opencode.jsonc`, skills, and (on the OMOS variant) `oh-my-opencode-slim.json` survive container recreation.
|
||||||
|
|||||||
Reference in New Issue
Block a user