c1154f1fa6
Publish Docker Image / resolve-versions (push) Successful in 5s
Publish Docker Image / base-decide (push) Successful in 12s
Publish Docker Image / build-base (push) Successful in 45m47s
Publish Docker Image / smoke (push) Successful in 8m18s
Publish Docker Image / build-variant (push) Successful in 22m41s
Publish Docker Image / update-description (push) Failing after 9s
Publish Docker Image / promote-base-latest (push) Successful in 14s
Self-contained build chain — own Dockerfile.base + Dockerfile.variant
+ entrypoint scripts + rootfs + CI pipeline. Previously v0.79.0 and
earlier were thin re-brands of opencode-devbox's pi-only variant
(joakimp/pi-devbox:base-pi-only built by opencode-devbox CI).
Architectural changes:
- Replace 5-line Dockerfile shim with full base+variant pair.
- Adapt CI workflow from opencode-devbox/docker-publish-split.yml,
simplified to a single variant. Includes content-addressed base hash,
PI_VERSION concrete-resolution to defeat registry-buildcache footgun,
crane-based base-latest promotion, and the c6f9d11 smoke-test gate.
- pi-devbox releases no longer require rebuilding opencode-devbox first.
Base image additions:
- pandoc, graphviz, imagemagick, yq — broadly useful, ~260 MB total.
- tldr (tealdeer) — Rust port replaces Node tldr global, saves 135 MB.
- /etc/tmux.conf with base-index 0 + pane-base-index 0 — required for
the planned :latest-studio variant; pi-studio hard-codes :0.0 target.
Smoke test:
- New checks for pandoc, graphviz, imagemagick, yq, tldr, tmux config,
/tmp/sshcm directory.
- Image-size measurement now sums docker history layers (the prior
inspect --format='{{.Size}}' returned only the variant-unique layer
with the new base/variant split, understating by 2+ GB).
- Threshold 2850 → 3500 MB to absorb base additions + arch margin.
Image size:
- Local arm64 build: 3.20 GB. ~390 MB up from prior pi-only equivalent.
- Will tighten threshold once amd64 actuals settle in CI.
Pre-1.0 history preserved at tag pre-v1.0.0-decouple-backup.
Future work:
- v1.1.0: :latest-studio variant (adds pi-studio).
- v1.2.0: :latest-studio-tex variant (adds texlive-xetex for PDF).
- opencode-devbox v2.0.0 will retire INSTALL_PI / pi-only paths.
226 lines
10 KiB
Bash
Executable File
226 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# setup-lan-access.sh — generic, host-OS-agnostic LAN reachability helper.
|
|
#
|
|
# THE PROBLEM
|
|
# On macOS (OrbStack / Docker Desktop) and Docker Desktop on Windows, the
|
|
# container runs inside a Linux VM behind the host's network stack. The
|
|
# host's *directly-attached* LAN peers (e.g. other boxes on 192.168.1.0/24)
|
|
# are NOT bridged into the container by default — only the host itself and
|
|
# *routed* subnets are reachable. On native Linux Docker the default bridge
|
|
# already NATs container egress onto the host's LAN, so LAN peers are usually
|
|
# reachable directly and no workaround is needed.
|
|
#
|
|
# THE APPROACH ("detect, and on a VM-backed host use the host as a jump")
|
|
# The one thing reachable from a container on every OS is the host itself
|
|
# (host.docker.internal). So on VM-backed hosts we generate a writable SSH
|
|
# config that reaches the host and lets the user ProxyJump onward to LAN
|
|
# peers the host can reach. On native Linux we do nothing.
|
|
#
|
|
# We ship the MECHANISM (a generic `host` jump alias + writable config),
|
|
# never the POLICY: the user's specific target hosts live in their own
|
|
# bind-mounted ~/.ssh/config (add `ProxyJump host` to those entries) — which
|
|
# is pulled in via the `Include ~/.ssh/config` line below.
|
|
#
|
|
# WHY A WRITABLE SIDECAR (~/.ssh-local)
|
|
# The devbox typically bind-mounts the host's ~/.ssh READ-ONLY (so agents
|
|
# can read keys for git but can't tamper with config/known_hosts/authorized_
|
|
# keys). That means we cannot edit ~/.ssh/config or write ~/.ssh/known_hosts.
|
|
# So everything generated here lives under the writable ~/.ssh-local, used
|
|
# via `ssh -F ~/.ssh-local/config` (the `dssh`/`dscp` aliases wrap that).
|
|
#
|
|
# CONTROLS (env)
|
|
# DEVBOX_LAN_ACCESS = auto (default) | jump | off
|
|
# auto → set up the jump config only on VM-backed hosts; no-op on Linux.
|
|
# jump → always set up (e.g. native Linux with extra_hosts host-gateway).
|
|
# off → do nothing.
|
|
# HOST_SSH_USER — the username to SSH into the host as. REQUIRED for the
|
|
# jump to authenticate. If unset we still generate the config but print
|
|
# a hint with the public key to authorize on the host.
|
|
# DEVBOX_HOST_ALIAS — host hostname to reach (default host.docker.internal).
|
|
# DEVBOX_LAN_AUTOJUMP_PRIVATE = 0 (default) | 1
|
|
# 1 → also emit a catch-all that ProxyJumps *any* RFC1918 (private) IP
|
|
# through the host. Lets bare `dssh user@<private-IP>` work on whatever
|
|
# LAN the (roaming) host is currently joined to, without naming peers.
|
|
# Matches by the address you TYPE, not the resolved HostName, so it never
|
|
# overrides named hosts that already carry their own ProxyJump.
|
|
#
|
|
# HOST-OWNED PEER POLICY (portable; keeps this image generic)
|
|
# Named LAN peers are facts about a *specific* host's network, not about the
|
|
# image — a roaming laptop sees different LANs. So we never bake peer names
|
|
# here. Instead, if the host bind-mounts ~/.config/devbox-shell/ssh-lan.conf
|
|
# (the same devbox-shell bridge dir used for shared aliases), we Include it
|
|
# *before* ~/.ssh/config. That file holds the host's own jump overrides, e.g.
|
|
# Host pve pve-2 pbs-vm
|
|
# ProxyJump host
|
|
# First-value-wins means ProxyJump is taken from there while HostName/User/
|
|
# IdentityFile are inherited from the matching block in ~/.ssh/config.
|
|
#
|
|
# SCOPING NOTE (important)
|
|
# `Include` is scoped to the enclosing Host/Match block. So every Include
|
|
# below is preceded by a bare `Host *` to reset the active context to
|
|
# match-all — otherwise the included config would only apply when targeting
|
|
# `host`/`mac` and named peers like `pve` would silently fall back to ssh
|
|
# defaults.
|
|
#
|
|
# Idempotent: re-renders the config every run (cheap); never regenerates the
|
|
# key. Always non-fatal — never blocks container startup.
|
|
|
|
set -uo pipefail
|
|
|
|
MODE="${DEVBOX_LAN_ACCESS:-auto}"
|
|
[ "$MODE" = "off" ] && exit 0
|
|
|
|
HOST_ALIAS_HOSTNAME="${DEVBOX_HOST_ALIAS:-host.docker.internal}"
|
|
SSH_LOCAL="${HOME}/.ssh-local"
|
|
CONFIG="${SSH_LOCAL}/config"
|
|
KEY="${SSH_LOCAL}/devbox_jump_ed25519"
|
|
|
|
# ── Detection: is this a VM-backed host (macOS / Docker Desktop)? ──────
|
|
# host.docker.internal resolves on OrbStack and Docker Desktop (mac/win) but
|
|
# NOT on native Linux Docker (unless the user added extra_hosts: host-gateway,
|
|
# in which case the jump is still harmless / usable, and they can force it
|
|
# with DEVBOX_LAN_ACCESS=jump).
|
|
is_vm_backed() {
|
|
getent hosts "$HOST_ALIAS_HOSTNAME" >/dev/null 2>&1
|
|
}
|
|
|
|
if [ "$MODE" = "auto" ] && ! is_vm_backed; then
|
|
# Native Linux host: LAN peers are reachable directly. Nothing to do.
|
|
exit 0
|
|
fi
|
|
|
|
# From here: MODE=jump, or MODE=auto on a VM-backed host.
|
|
|
|
command -v ssh-keygen >/dev/null 2>&1 || exit 0
|
|
|
|
mkdir -p "${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) ──────────────
|
|
# 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
|
|
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
|
|
KEY_JUST_GENERATED=1
|
|
fi
|
|
|
|
# ── Render the writable config ────────────────────────────────────────
|
|
USER_LINE=""
|
|
if [ -n "${HOST_SSH_USER:-}" ]; then
|
|
USER_LINE=" User ${HOST_SSH_USER}"
|
|
fi
|
|
|
|
# Optional host-owned named-peer jump overrides (portable: lives on the host,
|
|
# not in the image). Included BEFORE ~/.ssh/config so its ProxyJump wins.
|
|
SSH_LAN_CONF="${HOME}/.config/devbox-shell/ssh-lan.conf"
|
|
LAN_CONF_BLOCK=""
|
|
if [ -r "$SSH_LAN_CONF" ]; then
|
|
LAN_CONF_BLOCK=$(cat <<'EOF'
|
|
|
|
# Host-owned named-peer jump overrides (bind-mounted; edit on the host).
|
|
# Scope reset to match-all so the Include applies to every target host.
|
|
Host *
|
|
Include ~/.config/devbox-shell/ssh-lan.conf
|
|
EOF
|
|
)
|
|
fi
|
|
|
|
# Optional opt-in RFC1918 catch-all: ProxyJump every private IP through the
|
|
# host. Matches the typed address, never the resolved HostName, so named hosts
|
|
# with their own ProxyJump are unaffected. Network-agnostic → roaming-safe.
|
|
AUTOJUMP_BLOCK=""
|
|
if [ "${DEVBOX_LAN_AUTOJUMP_PRIVATE:-0}" = "1" ]; then
|
|
AUTOJUMP_BLOCK=$(cat <<'EOF'
|
|
|
|
# RFC1918 auto-jump (DEVBOX_LAN_AUTOJUMP_PRIVATE=1): reach any private IP on
|
|
# the host's CURRENT LAN via bare `dssh user@<ip>`. Public IPs are unmatched
|
|
# and go direct via the container's NAT egress. NOTE: also matches the
|
|
# container's own bridge subnet and any private IP the host can't actually
|
|
# reach — for non-LAN private hosts behind a different jump, use their named
|
|
# entry (which matches first by name and keeps its own ProxyJump).
|
|
Host 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.*
|
|
ProxyJump host
|
|
EOF
|
|
)
|
|
fi
|
|
|
|
INCLUDE_BLOCK=""
|
|
if [ -r "${HOME}/.ssh/config" ]; then
|
|
INCLUDE_BLOCK=$(cat <<'EOF'
|
|
|
|
# Your own target hosts. Scope reset to match-all so this Include applies to
|
|
# every target (an Include is otherwise scoped to the enclosing Host block).
|
|
# Add 'ProxyJump host' to LAN entries here (or in ssh-lan.conf above).
|
|
Host *
|
|
Include ~/.ssh/config
|
|
EOF
|
|
)
|
|
fi
|
|
|
|
cat > "$CONFIG" <<EOF
|
|
# AUTO-GENERATED by setup-lan-access.sh on every container start. Do not edit
|
|
# by hand — edits are overwritten. Used via: ssh -F ~/.ssh-local/config <host>
|
|
# (or the dssh / dscp aliases). See the script header for the full rationale.
|
|
|
|
# ~/.ssh is typically mounted read-only, so keep our own known_hosts here.
|
|
# Also redirect ControlPath into the writable sidecar: the bind-mounted
|
|
# ~/.ssh/config commonly sets 'ControlPath ~/.ssh/cm/...' for CGNAT multiplexing,
|
|
# but ~/.ssh is read-only here so the master socket can't be created and those
|
|
# hosts fail to connect. First-value-wins: setting it here (before the Include)
|
|
# overrides the read-only path for every host. Harmless when ControlMaster is off.
|
|
Host *
|
|
UserKnownHostsFile ~/.ssh-local/known_hosts
|
|
StrictHostKeyChecking accept-new
|
|
ControlPath ~/.ssh-local/cm/%r@%h:%p
|
|
|
|
# The container host (OrbStack / Docker Desktop). 'host' and 'mac' are aliases.
|
|
Host host mac
|
|
HostName ${HOST_ALIAS_HOSTNAME}
|
|
${USER_LINE}
|
|
IdentityFile ~/.ssh-local/devbox_jump_ed25519
|
|
IdentitiesOnly yes
|
|
ControlMaster auto
|
|
ControlPath ~/.ssh-local/cm/%r@%h:%p
|
|
ControlPersist 4h
|
|
ServerAliveInterval 30
|
|
${LAN_CONF_BLOCK}
|
|
${AUTOJUMP_BLOCK}
|
|
${INCLUDE_BLOCK}
|
|
EOF
|
|
chmod 600 "$CONFIG" 2>/dev/null || true
|
|
|
|
# ── 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
|
|
cat <<EOF
|
|
[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.
|
|
To enable container -> host -> LAN-peer access:
|
|
1. Set HOST_SSH_USER=<your host username> in the container env.
|
|
2. Authorize this key on the host (run ON THE HOST, once):
|
|
echo '${PUBKEY_TEXT}' >> ~/.ssh/authorized_keys
|
|
3. Ensure the host's SSH server (Remote Login) is enabled.
|
|
Then: dssh host (or add 'ProxyJump host' to targets in ~/.ssh/config)
|
|
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
|
|
|
|
exit 0
|