LAN jump key: persist via named volume + one-line authorize hint
Validate / docs-check (push) Successful in 7s
Validate / base-change-warning (push) Successful in 9s
Validate / validate-omos (push) Successful in 4m26s
Validate / validate-with-pi (push) Successful in 4m30s
Validate / validate-pi-only (push) Successful in 3m33s
Validate / validate-omos-with-pi (push) Successful in 8m44s
Validate / validate-base (push) Successful in 9m8s

Persist ~/.ssh-local (devbox-ssh-local named volume) so the generated
LAN-jump key survives 'docker compose up --force-recreate'. Authorize
it on the host once per machine instead of after every container update.

setup-lan-access.sh now prints a copy-paste
'echo <pubkey> >> ~/.ssh/authorized_keys' line whenever it generates a
new key (not only when HOST_SSH_USER is unset), and stays silent once
the key is persisted. README + CHANGELOG updated.
This commit is contained in:
pi
2026-06-04 14:33:58 +02:00
parent 440218fc4c
commit 0b78ab4a94
4 changed files with 54 additions and 7 deletions
+16 -1
View File
@@ -8,7 +8,22 @@ Tags follow `v{opencode_version}[letter]` — bare tag for the first build on a
## Unreleased ## Unreleased
_(no changes since v1.15.13d)_ ### Added: persist the LAN-jump key + one-line authorize hint (authorize once per machine)
The jump keypair (`~/.ssh-local/devbox_jump_ed25519`) was stored on the
container's ephemeral overlay, so `docker compose up --force-recreate` (every
image update) regenerated it — forcing you to re-authorize the new key on the
host each time. The compose files now persist `~/.ssh-local` via a named volume
(`devbox-ssh-local`), matching the pattern already used for `.pi`, shell
history, etc. The key is generated **once** and reused across updates, so you
authorize it on the host **once per machine**.
`setup-lan-access.sh` now also prints a ready-to-paste authorize line whenever
it generates a **new** key (not just when `HOST_SSH_USER` is unset), e.g.
`echo 'ssh-ed25519 …' >> ~/.ssh/authorized_keys` — no helper file to locate, no
workspace path to guess. It stays silent once the key is persisted.
_(no other changes since v1.15.13d)_
## v1.15.13d — 2026-06-04 ## v1.15.13d — 2026-06-04
+8 -3
View File
@@ -155,15 +155,20 @@ The devbox works the same way whether the host is **native Linux Docker** or a *
- **Native Linux Docker:** the host NATs container egress onto its LAN, so other devices on your LAN are reachable directly. Nothing to configure. - **Native Linux Docker:** the host NATs container egress onto its LAN, so other devices on your LAN are reachable directly. Nothing to configure.
- **VM-backed (macOS / Docker Desktop):** the container runs in a Linux VM behind the host's network stack. The host's *directly-attached* LAN peers are **not** bridged into the container by default — only the host itself and *routed* subnets are reachable. - **VM-backed (macOS / Docker Desktop):** the container runs in a Linux VM behind the host's network stack. The host's *directly-attached* LAN peers are **not** bridged into the container by default — only the host itself and *routed* subnets are reachable.
On every start the entrypoint detects which case applies. On VM-backed hosts it generates a writable `~/.ssh-local/config` that uses the **host as an SSH jump** to reach LAN peers; on native Linux it does nothing. On every start the entrypoint detects which case applies. On VM-backed hosts it generates a writable `~/.ssh-local/config` that uses the **host as an SSH jump** to reach LAN peers; on native Linux it does nothing. The jump keypair lives in `~/.ssh-local`, which is persisted by the `devbox-ssh-local` named volume — so it's generated **once** and reused across container updates.
**To enable it on a VM-backed host:** **To enable it on a VM-backed host (one-time setup per machine):**
1. Set `HOST_SSH_USER=<your host username>` in `.env`. 1. Set `HOST_SSH_USER=<your host username>` in `.env`.
2. Start the container once. The entrypoint prints a public key — append it to your host's `~/.ssh/authorized_keys`. 2. Start the container once. When it generates the jump key it prints a ready-to-paste line — run it **on the host** to authorize the key:
```bash
echo 'ssh-ed25519 AAAA…devbox-jump@…' >> ~/.ssh/authorized_keys
```
3. Ensure the host's SSH server is on (on macOS: System Settings → General → Sharing → Remote Login). 3. Ensure the host's SSH server is on (on macOS: System Settings → General → Sharing → Remote Login).
4. Reach the host itself with `dssh host`. (`dssh`/`dscp` wrap `ssh -F ~/.ssh-local/config`.) 4. Reach the host itself with `dssh host`. (`dssh`/`dscp` wrap `ssh -F ~/.ssh-local/config`.)
Because the key is persisted, you do this **once per machine** — not after every `docker compose up --force-recreate`. You'll only see the authorize line again if you reset the `devbox-ssh-local` volume.
That alone gets you `container → host`. To reach **named LAN peers** by name, give them a `ProxyJump host` override. Don't add it to the shared `~/.ssh/config` entries — the host itself reaches those peers *directly*, and a jump-through-`host` would break the host's own access (and that file is mounted read-only anyway). Instead, drop the overrides in a **host-owned** file that the container Includes ahead of your `~/.ssh/config`: That alone gets you `container → host`. To reach **named LAN peers** by name, give them a `ProxyJump host` override. Don't add it to the shared `~/.ssh/config` entries — the host itself reaches those peers *directly*, and a jump-through-`host` would break the host's own access (and that file is mounted read-only anyway). Instead, drop the overrides in a **host-owned** file that the container Includes ahead of your `~/.ssh/config`:
```sshconfig ```sshconfig
+6
View File
@@ -59,6 +59,11 @@ services:
# allowing both native and containerized opencode on the same machine. # allowing both native and containerized opencode on the same machine.
- devbox-opencode-config:/home/developer/.config/opencode - devbox-opencode-config:/home/developer/.config/opencode
- devbox-pi-config:/home/developer/.pi - devbox-pi-config:/home/developer/.pi
# Persist the generated LAN-jump keypair (~/.ssh-local) across recreates.
# setup-lan-access.sh generates this key once and reuses it; persisting
# it means you authorize it on the host ONCE rather than re-authorizing
# after every `docker compose up --force-recreate`.
- devbox-ssh-local:/home/developer/.ssh-local
# NOTE: Do NOT bind-mount ~/.agents/skills/ from the host. The # NOTE: Do NOT bind-mount ~/.agents/skills/ from the host. The
# container manages its own skills directory independently — the # container manages its own skills directory independently — the
@@ -134,6 +139,7 @@ services:
volumes: volumes:
devbox-opencode-config: devbox-opencode-config:
devbox-pi-config: devbox-pi-config:
devbox-ssh-local:
devbox-data: devbox-data:
devbox-state: devbox-state:
devbox-shell-history: devbox-shell-history:
@@ -97,9 +97,15 @@ mkdir -p "${SSH_LOCAL}/cm" 2>/dev/null || true
chmod 700 "${SSH_LOCAL}" "${SSH_LOCAL}/cm" 2>/dev/null || true chmod 700 "${SSH_LOCAL}" "${SSH_LOCAL}/cm" 2>/dev/null || true
# ── Jump key (generated once; preserved across restarts) ────────────── # ── Jump key (generated once; preserved across restarts) ──────────────
# Persisted via a named volume on ~/.ssh-local (see compose), so a fresh key
# is generated only on the very first start (or if the volume is wiped). When
# we DO generate one it must be (re-)authorized on the host, so we flag it and
# print a copy-paste authorize line below.
KEY_JUST_GENERATED=0
if [ ! -f "$KEY" ]; then if [ ! -f "$KEY" ]; then
ssh-keygen -t ed25519 -N '' -C "devbox-jump@${HOSTNAME:-container}" -f "$KEY" >/dev/null 2>&1 || exit 0 ssh-keygen -t ed25519 -N '' -C "devbox-jump@${HOSTNAME:-container}" -f "$KEY" >/dev/null 2>&1 || exit 0
chmod 600 "$KEY" 2>/dev/null || true chmod 600 "$KEY" 2>/dev/null || true
KEY_JUST_GENERATED=1
fi fi
# ── Render the writable config ──────────────────────────────────────── # ── Render the writable config ────────────────────────────────────────
@@ -187,18 +193,33 @@ ${INCLUDE_BLOCK}
EOF EOF
chmod 600 "$CONFIG" 2>/dev/null || true chmod 600 "$CONFIG" 2>/dev/null || true
# ── One-time hint when we can't authenticate yet ────────────────────── # ── Authorize hints ───────────────────────────────────────────────────
# Print the copy-paste authorize line whenever we either (a) can't yet
# authenticate (HOST_SSH_USER unset) or (b) just generated a NEW key that the
# host won't recognize. With ~/.ssh-local persisted via a named volume, case
# (b) fires only on first-ever start (or after the volume is reset) — so this
# is normally a one-time, one-line step per machine, with no file to locate.
PUBKEY_TEXT="$(cat "${KEY}.pub" 2>/dev/null)"
if [ -z "${HOST_SSH_USER:-}" ]; then if [ -z "${HOST_SSH_USER:-}" ]; then
cat <<EOF cat <<EOF
[devbox] LAN-access jump config generated at ~/.ssh-local/config, but [devbox] LAN-access jump config generated at ~/.ssh-local/config, but
HOST_SSH_USER is unset so it can't authenticate to the host yet. HOST_SSH_USER is unset so it can't authenticate to the host yet.
To enable container -> host -> LAN-peer access: To enable container -> host -> LAN-peer access:
1. Set HOST_SSH_USER=<your host username> in the container env. 1. Set HOST_SSH_USER=<your host username> in the container env.
2. Authorize this key on the host (append to ~/.ssh/authorized_keys): 2. Authorize this key on the host (run ON THE HOST, once):
$(cat "${KEY}.pub" 2>/dev/null) echo '${PUBKEY_TEXT}' >> ~/.ssh/authorized_keys
3. Ensure the host's SSH server (Remote Login) is enabled. 3. Ensure the host's SSH server (Remote Login) is enabled.
Then: dssh host (or add 'ProxyJump host' to targets in ~/.ssh/config) Then: dssh host (or add 'ProxyJump host' to targets in ~/.ssh/config)
EOF EOF
elif [ "$KEY_JUST_GENERATED" = "1" ]; then
cat <<EOF
[devbox] Generated a NEW LAN-jump key. Authorize it on the host (${HOST_SSH_USER}@host),
then 'dssh host' and your LAN peers will work. Run this ONCE, ON THE HOST:
echo '${PUBKEY_TEXT}' >> ~/.ssh/authorized_keys
(Ensure the host's SSH server / Remote Login is enabled.)
This key is persisted in the ~/.ssh-local volume, so you won't need to
repeat this on container updates — only if that volume is reset.
EOF
fi fi
exit 0 exit 0