Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff6e17b732 | |||
| c6f9d1148b | |||
| 56e6a782e3 | |||
| 49d3e113ee | |||
| f1e879ca6c | |||
| 9c31c641d6 | |||
| d9dc85d825 | |||
| 0b78ab4a94 | |||
| 440218fc4c | |||
| a56a5846a5 |
+11
-2
@@ -37,8 +37,11 @@ SSH_KEY_PATH=~/.ssh
|
|||||||
# directly-attached LAN peers by default. On native Linux Docker the LAN is
|
# directly-attached LAN peers by default. On native Linux Docker the LAN is
|
||||||
# reachable directly and nothing is needed. The entrypoint detects this and,
|
# reachable directly and nothing is needed. The entrypoint detects this and,
|
||||||
# on VM-backed hosts, generates ~/.ssh-local/config so the host can be used
|
# on VM-backed hosts, generates ~/.ssh-local/config so the host can be used
|
||||||
# as an SSH jump (use the `dssh` alias, or add `ProxyJump host` to targets
|
# as an SSH jump (use the `dssh` alias). Reach the host itself with
|
||||||
# in your bind-mounted ~/.ssh/config).
|
# `dssh host`. To reach named LAN peers, put `ProxyJump host` overrides in a
|
||||||
|
# host-owned ~/.config/devbox-shell/ssh-lan.conf (bind-mounted in) rather than
|
||||||
|
# editing your ~/.ssh/config — see ssh-lan.conf.example. Public-IP hosts (and
|
||||||
|
# anything reached via a public jump host) connect directly, no jump needed.
|
||||||
#
|
#
|
||||||
# DEVBOX_LAN_ACCESS: auto (default) | jump | off
|
# DEVBOX_LAN_ACCESS: auto (default) | jump | off
|
||||||
# auto = set up the jump only on VM-backed hosts; no-op on native Linux.
|
# auto = set up the jump only on VM-backed hosts; no-op on native Linux.
|
||||||
@@ -54,6 +57,12 @@ SSH_KEY_PATH=~/.ssh
|
|||||||
#
|
#
|
||||||
# DEVBOX_HOST_ALIAS: host hostname to reach (default host.docker.internal).
|
# DEVBOX_HOST_ALIAS: host hostname to reach (default host.docker.internal).
|
||||||
# DEVBOX_HOST_ALIAS=host.docker.internal
|
# DEVBOX_HOST_ALIAS=host.docker.internal
|
||||||
|
#
|
||||||
|
# DEVBOX_LAN_AUTOJUMP_PRIVATE: 1 = ProxyJump ANY RFC1918 (private) IP through
|
||||||
|
# the host, so bare `dssh user@<ip>` works on whatever LAN the (roaming) host
|
||||||
|
# is currently joined to, without naming peers. Matches the typed address, not
|
||||||
|
# the resolved HostName, so named hosts with their own ProxyJump are unaffected.
|
||||||
|
# DEVBOX_LAN_AUTOJUMP_PRIVATE=0
|
||||||
|
|
||||||
# ── Skillset (agent skills and instructions) ─────────────────────────
|
# ── Skillset (agent skills and instructions) ─────────────────────────
|
||||||
# If you have a skillset repo, the entrypoint auto-deploys skills and
|
# If you have a skillset repo, the entrypoint auto-deploys skills and
|
||||||
|
|||||||
@@ -386,6 +386,7 @@ jobs:
|
|||||||
PI_OBSMEM_REF=${{ needs.resolve-versions.outputs.obsmem_ref }}
|
PI_OBSMEM_REF=${{ needs.resolve-versions.outputs.obsmem_ref }}
|
||||||
- env:
|
- env:
|
||||||
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
|
STRICT_REGISTRATION: "1"
|
||||||
run: bash scripts/smoke-test.sh opencode-devbox:smoke-with-pi --variant with-pi
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-with-pi --variant with-pi
|
||||||
|
|
||||||
smoke-omos-with-pi:
|
smoke-omos-with-pi:
|
||||||
@@ -435,6 +436,7 @@ jobs:
|
|||||||
- env:
|
- env:
|
||||||
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
EXPECTED_OMOS_VERSION: ${{ needs.resolve-versions.outputs.omos_version }}
|
EXPECTED_OMOS_VERSION: ${{ needs.resolve-versions.outputs.omos_version }}
|
||||||
|
STRICT_REGISTRATION: "1"
|
||||||
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos-with-pi --variant omos-with-pi
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-omos-with-pi --variant omos-with-pi
|
||||||
|
|
||||||
smoke-pi-only:
|
smoke-pi-only:
|
||||||
@@ -482,6 +484,7 @@ jobs:
|
|||||||
PI_OBSMEM_REF=${{ needs.resolve-versions.outputs.obsmem_ref }}
|
PI_OBSMEM_REF=${{ needs.resolve-versions.outputs.obsmem_ref }}
|
||||||
- env:
|
- env:
|
||||||
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
EXPECTED_PI_VERSION: ${{ needs.resolve-versions.outputs.pi_version }}
|
||||||
|
STRICT_REGISTRATION: "1"
|
||||||
run: bash scripts/smoke-test.sh opencode-devbox:smoke-pi-only --variant pi-only
|
run: bash scripts/smoke-test.sh opencode-devbox:smoke-pi-only --variant pi-only
|
||||||
|
|
||||||
# ── Phase 4: multi-arch publish per variant ────────────────────────
|
# ── Phase 4: multi-arch publish per variant ────────────────────────
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ name: Validate
|
|||||||
# release tags are the gate that fully validates base-image changes.
|
# release tags are the gate that fully validates base-image changes.
|
||||||
# The base-change-warning job below surfaces a runtime warning when this
|
# The base-change-warning job below surfaces a runtime warning when this
|
||||||
# blind-spot applies.
|
# blind-spot applies.
|
||||||
|
#
|
||||||
|
# Because of this, the fork/recall *registration* smoke checks (which depend on
|
||||||
|
# the base entrypoint running `pi install /opt/<pkg>`) are warn-only here:
|
||||||
|
# smoke-test.sh leaves STRICT_REGISTRATION unset on this path, so a base-latest
|
||||||
|
# that lags the entrypoint in the current commit can't red the run with a false
|
||||||
|
# negative. The release smoke jobs build the base fresh and set
|
||||||
|
# STRICT_REGISTRATION=1 to enforce those checks. The build-time /opt +
|
||||||
|
# node_modules checks stay hard in both paths.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -4,13 +4,22 @@
|
|||||||
|
|
||||||
Docker image packaging [opencode](https://opencode.ai) into a production-ready dev container. Image variants are published to Docker Hub via Gitea Actions CI. Not a library or application — this is infrastructure (Dockerfiles, entrypoint scripts, docker-compose, documentation).
|
Docker image packaging [opencode](https://opencode.ai) into a production-ready dev container. Image variants are published to Docker Hub via Gitea Actions CI. Not a library or application — this is infrastructure (Dockerfiles, entrypoint scripts, docker-compose, documentation).
|
||||||
|
|
||||||
|
> **pi is deprecated here (since v1.17.2), removed in v2.0.0.** The
|
||||||
|
> `INSTALL_PI` build arg, the `with-pi` / `omos-with-pi` / `pi-only`
|
||||||
|
> variants, the `base-pi-only` published tag, and all `~/.pi`-related
|
||||||
|
> wiring are slated for removal. pi now ships from its own repo
|
||||||
|
> (`joakimp/pi-devbox`). Do not add new pi functionality here. Full
|
||||||
|
> removal plan + the `NPM_CONFIG_PREFIX` relocation: see
|
||||||
|
> `docs/CLEANUP-v2.0.0.md`. The pi-related descriptions below remain
|
||||||
|
> accurate only until the v2.0.0 removal lands.
|
||||||
|
|
||||||
## File roles
|
## File roles
|
||||||
|
|
||||||
- `Dockerfile.base` — variant-independent layers (apt, locales, AWS CLI, Node.js, mempalace, gitea-mcp, user setup, chromadb prewarm, ENVs, entrypoints). Published as `joakimp/opencode-devbox:base-<sha12>`. Rebuilt only when its content hash changes.
|
- `Dockerfile.base` — variant-independent layers (apt, locales, AWS CLI, Node.js, mempalace, gitea-mcp, user setup, chromadb prewarm, ENVs, entrypoints). Published as `joakimp/opencode-devbox:base-<sha12>`. Rebuilt only when its content hash changes.
|
||||||
- `Dockerfile.variant` — `FROM`s the base and adds only opencode/omos/pi installs gated by build args: `INSTALL_OPENCODE` (default true), `INSTALL_OMOS`, `INSTALL_PI`, and `INSTALL_MEMPALACE`. All GitHub-sourced binaries are pinned with version ARGs. When `INSTALL_PI=true` it also clones `pi-fork` + `pi-observational-memory` (from `github.com/elpapi42`, refs `PI_FORK_REF`/`PI_OBSMEM_REF`) to `/opt` and runs `npm install` there at build time so the `fork`/`recall` extensions can load (a local-path `pi install` does not npm-install). The `pi-only` variant sets `INSTALL_OPENCODE=false`, `INSTALL_PI=true` — pi without opencode, the single source of truth for the separate `pi-devbox` image. It is built and smoke-tested here, but **published into the `joakimp/pi-devbox` repo** as the internal building-block tag `base-pi-only[-vX.Y.Z]` (NOT under `opencode-devbox`), so an opencode-devbox tag never ships without opencode.
|
- `Dockerfile.variant` — `FROM`s the base and adds only opencode/omos/pi installs gated by build args: `INSTALL_OPENCODE` (default true), `INSTALL_OMOS`, `INSTALL_PI`, and `INSTALL_MEMPALACE`. All GitHub-sourced binaries are pinned with version ARGs. When `INSTALL_PI=true` it also clones `pi-fork` + `pi-observational-memory` (from `github.com/elpapi42`, refs `PI_FORK_REF`/`PI_OBSMEM_REF`) to `/opt` and runs `npm install` there at build time so the `fork`/`recall` extensions can load (a local-path `pi install` does not npm-install). The `pi-only` variant sets `INSTALL_OPENCODE=false`, `INSTALL_PI=true` — pi without opencode, the single source of truth for the separate `pi-devbox` image. It is built and smoke-tested here, but **published into the `joakimp/pi-devbox` repo** as the internal building-block tag `base-pi-only[-vX.Y.Z]` (NOT under `opencode-devbox`), so an opencode-devbox tag never ships without opencode.
|
||||||
- `entrypoint.sh` — runs as root: UID/GID adjustment, SSH permissions, volume ownership fixes (skipped via `.devbox-owner` sentinel when ownership is already correct). Then drops to developer via gosu. Volume ownership loop covers `~/.pi/` when `INSTALL_PI=true`.
|
- `entrypoint.sh` — runs as root: UID/GID adjustment, SSH permissions, volume ownership fixes (skipped via `.devbox-owner` sentinel when ownership is already correct). Then drops to developer via gosu. Volume ownership loop covers `~/.pi/` when `INSTALL_PI=true`.
|
||||||
- `entrypoint-user.sh` — runs as developer: git config, opencode.jsonc generation (delegated to `generate-config.py`), LAN-access setup (delegated to `setup-lan-access.sh`), pi-toolkit + pi-extensions deploy (when pi installed), pi settings.json bootstrap, mempalace pi-bridge symlink, runtime `pi install /opt/{pi-fork,pi-observational-memory}` registration (idempotent), skillset auto-deploy from mounted skillset repo, OMOS setup.
|
- `entrypoint-user.sh` — runs as developer: git config, opencode.jsonc generation (delegated to `generate-config.py`), LAN-access setup (delegated to `setup-lan-access.sh`), pi-toolkit + pi-extensions deploy (when pi installed), pi settings.json bootstrap, mempalace pi-bridge symlink, runtime `pi install /opt/{pi-fork,pi-observational-memory}` registration (idempotent), skillset auto-deploy from mounted skillset repo, OMOS setup.
|
||||||
- `rootfs/usr/local/lib/opencode-devbox/setup-lan-access.sh` — host-OS-agnostic LAN reachability helper. Detects VM-backed hosts (macOS OrbStack / Docker Desktop, via `host.docker.internal` resolution) and generates a writable `~/.ssh-local/config` using the host as an SSH jump; no-op on native Linux. Controlled by `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` / `DEVBOX_HOST_ALIAS`. Ships the mechanism only (generic `host` jump alias); user targets stay in their bind-mounted `~/.ssh/config`. Non-fatal. Counted in the base hash, so editing it advances `base-latest`.
|
- `rootfs/usr/local/lib/opencode-devbox/setup-lan-access.sh` — host-OS-agnostic LAN reachability helper. Detects VM-backed hosts (macOS OrbStack / Docker Desktop, via `host.docker.internal` resolution) and generates a writable `~/.ssh-local/config` using the host as an SSH jump; no-op on native Linux. Controlled by `DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` / `DEVBOX_HOST_ALIAS` / `DEVBOX_LAN_AUTOJUMP_PRIVATE`. Ships the mechanism only (generic `host` jump alias); user targets stay host-side — named-peer `ProxyJump host` overrides go in a bind-mounted `~/.config/devbox-shell/ssh-lan.conf` (Included before `~/.ssh/config`), never baked into the image. **Scoping invariant:** every `Include` in the generated config MUST be preceded by a bare `Host *` reset — an `Include` is scoped to the enclosing `Host`/`Match` block, so without the reset the included config only applies when targeting `host`/`mac` and named peers fall back to SSH defaults. The top `Host *` block also overrides `UserKnownHostsFile` and `ControlPath` into the writable `~/.ssh-local` sidecar (first-value-wins), because the bind-mounted `~/.ssh` is read-only — otherwise multiplexed hosts (`ControlPath ~/.ssh/cm/...`) fail to create their master socket. Non-fatal. Counted in the base hash, so editing it advances `base-latest`.
|
||||||
- `rootfs/usr/local/lib/opencode-devbox/generate-config.py` — generates `~/.config/opencode/opencode.jsonc` from env vars. Never overwrites an existing config (checks both `.json` and `.jsonc`). Auto-registers MCP servers for detected tools (mempalace via `mempalace-mcp`, gitea-mcp, context7 remote endpoint).
|
- `rootfs/usr/local/lib/opencode-devbox/generate-config.py` — generates `~/.config/opencode/opencode.jsonc` from env vars. Never overwrites an existing config (checks both `.json` and `.jsonc`). Auto-registers MCP servers for detected tools (mempalace via `mempalace-mcp`, gitea-mcp, context7 remote endpoint).
|
||||||
- `scripts/smoke-test.sh` — post-build image verification. Asserts binary presence, opencode startup, entrypoint correctness, config generation idempotency, and image size thresholds. Used by both CI workflows.
|
- `scripts/smoke-test.sh` — post-build image verification. Asserts binary presence, opencode startup, entrypoint correctness, config generation idempotency, and image size thresholds. Used by both CI workflows.
|
||||||
- `scripts/generate-dockerhub-md.py` — generates `DOCKER_HUB.md` from a hand-maintained `HUB_TEMPLATE` constant. `--check` fails if the committed file is out of sync (enforced by the `validate` workflow).
|
- `scripts/generate-dockerhub-md.py` — generates `DOCKER_HUB.md` from a hand-maintained `HUB_TEMPLATE` constant. `--check` fails if the committed file is out of sync (enforced by the `validate` workflow).
|
||||||
@@ -104,6 +113,7 @@ cd /tmp && npm pack @earendil-works/pi-coding-agent@0.75.5 && tar -xzf earendil-
|
|||||||
- **`actions/upload-artifact` and `actions/download-artifact` must stay at @v3 on Gitea.** v4+ uses a GitHub-Enterprise-specific Artifact API; runs fail with `GHESNotSupportedError`. If you need artifacts for a new reason (build logs, SBOMs, etc.), pin @v3 explicitly.
|
- **`actions/upload-artifact` and `actions/download-artifact` must stay at @v3 on Gitea.** v4+ uses a GitHub-Enterprise-specific Artifact API; runs fail with `GHESNotSupportedError`. If you need artifacts for a new reason (build logs, SBOMs, etc.), pin @v3 explicitly.
|
||||||
- **Step scripts run under `/bin/sh` (dash), not bash.** Avoid bash-isms like `${VAR//a/b}` parameter-pattern substitution; use POSIX alternatives (`tr`, `sed`) or declare `shell: bash` on the step.
|
- **Step scripts run under `/bin/sh` (dash), not bash.** Avoid bash-isms like `${VAR//a/b}` parameter-pattern substitution; use POSIX alternatives (`tr`, `sed`) or declare `shell: bash` on the step.
|
||||||
- **`BUILDKIT_PROGRESS=plain`** is set at workflow level on `docker-publish-split.yml` so arm64-under-QEMU builds log each layer line-by-line. The default collapsed progress UI hides which step is stalled, which made diagnosing earlier hangs expensive.
|
- **`BUILDKIT_PROGRESS=plain`** is set at workflow level on `docker-publish-split.yml` so arm64-under-QEMU builds log each layer line-by-line. The default collapsed progress UI hides which step is stalled, which made diagnosing earlier hangs expensive.
|
||||||
|
- **`STRICT_REGISTRATION` gates the fork/recall *registration* smoke assertions.** `smoke-test.sh`'s two pi-extension registration checks (that `pi-fork`/`pi-observational-memory` registered in `~/.pi/agent/settings.json`) depend on the *base* entrypoint running `pi install /opt/<pkg>`. `validate.yml` builds variants from the **published** `base-latest`, which lags the in-repo entrypoint until a release rebuilds the base — so those checks would false-negative there. They are therefore warn-only unless `STRICT_REGISTRATION=1`: `validate.yml` leaves it unset (warn), and `docker-publish-split.yml` (which builds the base fresh in the same run) sets `STRICT_REGISTRATION: "1"` on the three pi-bearing smoke jobs to enforce them. Build-time `/opt` + `node_modules` checks stay hard in both paths. If you touch the registration checks or the base-freshness model, keep this flag wiring in lockstep across both workflows.
|
||||||
|
|
||||||
## Testing changes
|
## Testing changes
|
||||||
|
|
||||||
|
|||||||
+243
-1
@@ -8,7 +8,249 @@ Tags follow `v{opencode_version}[letter]` — bare tag for the first build on a
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
_(no changes since v1.15.13c)_
|
## v1.17.2 — 2026-06-10
|
||||||
|
|
||||||
|
First container build on **opencode-ai `1.17.2`** (from `1.16.2`). This
|
||||||
|
release also **deprecates all pi support** ahead of its removal in v2.0.0,
|
||||||
|
and hardens the mempalace install.
|
||||||
|
|
||||||
|
### Bumped: opencode-ai 1.16.2 → 1.17.2
|
||||||
|
|
||||||
|
`OPENCODE_VERSION` ARG in `Dockerfile.variant`. Bare `v1.17.2` tag per the
|
||||||
|
`v{opencode_version}` scheme.
|
||||||
|
|
||||||
|
### Deprecated: pi support (removed in v2.0.0)
|
||||||
|
|
||||||
|
pi has been decoupled into its own self-contained image,
|
||||||
|
[`joakimp/pi-devbox`](https://gitea.jordbo.se/joakimp/pi-devbox) (v1.0.0+,
|
||||||
|
which no longer FROMs `base-pi-only`). The pi paths in opencode-devbox are
|
||||||
|
now dead weight and are **deprecated as of v1.17.2, scheduled for removal in
|
||||||
|
v2.0.0**:
|
||||||
|
|
||||||
|
- The `INSTALL_PI` build arg and all `PI_*` args.
|
||||||
|
- The `with-pi`, `omos-with-pi`, and `pi-only` build variants.
|
||||||
|
- The `base-pi-only[-vX.Y.Z]` tag published (to the `joakimp/pi-devbox`
|
||||||
|
repo) from this repo's CI.
|
||||||
|
- All `~/.pi`-related entrypoint wiring (pi-toolkit / pi-extensions deploy,
|
||||||
|
settings.json bootstrap, mempalace pi-bridge symlink, pi-fork / pi-obsmem
|
||||||
|
registration).
|
||||||
|
|
||||||
|
What this release does:
|
||||||
|
|
||||||
|
- Building with `INSTALL_PI=true` now prints a **build-time deprecation
|
||||||
|
warning** to stderr.
|
||||||
|
- README, DOCKER_HUB.md, and AGENTS.md mark the pi variants deprecated and
|
||||||
|
point to `joakimp/pi-devbox`.
|
||||||
|
- The full removal plan is documented in `docs/CLEANUP-v2.0.0.md`.
|
||||||
|
|
||||||
|
**Migration:** pull `joakimp/pi-devbox:latest` directly instead of any
|
||||||
|
`*-with-pi` / `pi-only` opencode-devbox tag. opencode-only users are
|
||||||
|
unaffected by this release.
|
||||||
|
|
||||||
|
#### ⚠ Heads-up for v2.0.0: global npm prefix relocation
|
||||||
|
|
||||||
|
Today `NPM_CONFIG_PREFIX` points at the pi-specific `~/.pi/npm-global`
|
||||||
|
(backed by the `devbox-pi-config` named volume), so `npm install -g` as the
|
||||||
|
developer user persists across recreates. **v2.0.0 will move the prefix to a
|
||||||
|
neutral opencode path** (e.g. `~/.config/opencode/npm-global`). Consequences
|
||||||
|
for existing opencode users at the v2.0.0 upgrade:
|
||||||
|
|
||||||
|
- Previously global-installed npm tools remain on disk in the old volume but
|
||||||
|
**drop off `PATH`** until migrated.
|
||||||
|
- The new prefix path is not currently a named volume, so persistence needs a
|
||||||
|
compose/volume update.
|
||||||
|
|
||||||
|
v2.0.0 will ship a **one-time migration shim** (copies old prefix contents to
|
||||||
|
the new path on first run) and an updated volume mapping. This notice is the
|
||||||
|
one-release-cycle advance warning.
|
||||||
|
|
||||||
|
### Hardened: mempalace install pinned + diary_write schema workaround
|
||||||
|
|
||||||
|
- `MEMPALACE_VERSION` is now an explicit ARG (default `3.4.0`) in
|
||||||
|
`Dockerfile.base`; the install uses `mempalace==${MEMPALACE_VERSION}`
|
||||||
|
instead of an unpinned `uv tool install mempalace`. An unpinned install is
|
||||||
|
what silently swept in the broken `diary_write` schema. Mirrors pi-devbox.
|
||||||
|
- Added an idempotent, self-deactivating post-install patch that strips the
|
||||||
|
**top-level `anyOf`** from `mempalace_diary_write`'s `input_schema`.
|
||||||
|
Mempalace 3.3.x/3.4.0 advertise `anyOf:[{required:[entry]},{required:[content]}]`
|
||||||
|
at the schema root, which the Anthropic tools API rejects outright
|
||||||
|
(`input_schema does not support oneOf, allOf, or anyOf at the top level`),
|
||||||
|
breaking pi/Claude tool registration at session start. The handler still
|
||||||
|
accepts `content` server-side. Upstream: MemPalace/mempalace#1728, PR #1735.
|
||||||
|
Remove once a fixed mempalace release is pinned.
|
||||||
|
|
||||||
|
### Fixed: smoke-test pi-extensions readiness race (test-only, no image change)
|
||||||
|
|
||||||
|
`scripts/smoke-test.sh`'s entrypoint-deploy wait loop gated only on
|
||||||
|
`keybindings.json` (written by pi-toolkit, which runs *before* pi-extensions),
|
||||||
|
so the `~/.pi/agent/extensions/*.ts ≥ 4` assertion could sample mid-deploy and
|
||||||
|
see fewer than 4 files under parallel build load. Observed on v1.16.2 run 370:
|
||||||
|
`smoke-with-pi` saw `<4` while `smoke-omos-with-pi` / `smoke-pi-only` (identical
|
||||||
|
pi-extensions `357fcc6`) both saw 8, skipping `build-variant-with-pi`. The wait
|
||||||
|
loop now blocks until the *last*-deployed artifact (the `mempalace.ts` bridge
|
||||||
|
symlink) exists **and** the extension count has settled ≥ 4 (up to 45s).
|
||||||
|
|
||||||
|
## v1.16.2 — 2026-06-08
|
||||||
|
|
||||||
|
First container build on the `opencode-ai@1.16.x` minor release (rolls up
|
||||||
|
`1.16.0` → `1.16.1` → `1.16.2`, all published 2026-06-05). Also picks up
|
||||||
|
**pi `0.78.1` → `0.79.0`** (resolved fresh by CI's `resolve-versions` job) in
|
||||||
|
the `with-pi`, `omos-with-pi`, and `pi-only` variants.
|
||||||
|
|
||||||
|
### Bumped: opencode-ai 1.15.13 → 1.16.2
|
||||||
|
|
||||||
|
`OPENCODE_VERSION` ARG in `Dockerfile.variant`. Highlights from the upstream
|
||||||
|
release (full notes: <https://github.com/anomalyco/opencode/releases>):
|
||||||
|
|
||||||
|
- **1.16.0** — ~38% faster startup (@StarpTech); managed workspace cloning that
|
||||||
|
keeps dirty/untracked files; move sessions between workspaces/directories;
|
||||||
|
proper OpenAI-via-Bedrock support; skill discovery + file-based agent loading;
|
||||||
|
`run --replay` for interactive session replay. Plus TUI/desktop polish and
|
||||||
|
numerous bugfixes (shell cancellation races, Windows path normalization, ACP
|
||||||
|
cancel/abort).
|
||||||
|
- **1.16.1** — internal/no user-visible notes (empty release body).
|
||||||
|
- **1.16.2** — reasoning summaries only run on supporting providers (avoids
|
||||||
|
GPT-5 request failures); edit operations refuse loose matches that could
|
||||||
|
overwrite the wrong code; Bedrock hang-before-first-token fix; diff-viewer
|
||||||
|
hunk navigation; subagents can be backgrounded; Snowflake Cortex provider.
|
||||||
|
|
||||||
|
### Picks up: pi 0.78.1 → 0.79.0
|
||||||
|
|
||||||
|
Resolved at build time for the pi-bearing variants. Headlines: project-trust
|
||||||
|
prompting for project-local settings/resources/instructions/packages (with
|
||||||
|
`--approve`/`--no-approve` and a `project_trust` extension event), cache-hit
|
||||||
|
rate in the interactive footer, richer SDK/RPC extension surfaces, plus a
|
||||||
|
stack of TUI and provider fixes. Full notes ship in the npm tarball's
|
||||||
|
`CHANGELOG.md`.
|
||||||
|
|
||||||
|
### Smoke size thresholds bumped +150 MB (preemptive)
|
||||||
|
|
||||||
|
Ahead of the combined minor opencode + pi bump, all opencode-bearing variant
|
||||||
|
thresholds in `scripts/smoke-test.sh` were raised: `base` 2600 → 2750,
|
||||||
|
`omos` 3300 → 3450, `with-pi` 2900 → 3050, `omos-with-pi` 3900 → 4050; and
|
||||||
|
`pi-only` 2750 → 2850 (+100, pi-only carries only the pi bump). Both `base`
|
||||||
|
(last 2506 MB) and `omos` (last 3206 MB) were on ~94 MB headroom, and a minor
|
||||||
|
opencode bump has tripped these ceilings before (v1.15.0 omos, v1.15.4
|
||||||
|
omos-with-pi), causing a partial publish + letter-suffix recovery cycle.
|
||||||
|
Restoring ~250 MB headroom avoids that. CI's smoke size print records actual
|
||||||
|
landed sizes — tighten later if they come in well under.
|
||||||
|
|
||||||
|
## v1.15.13e — 2026-06-04
|
||||||
|
|
||||||
|
Letter-suffix rebuild on opencode `1.15.13` (version unchanged). Picks up
|
||||||
|
**pi `0.78.1`** (resolved fresh by CI's `resolve-versions` job) plus the LAN-jump
|
||||||
|
key-persistence work, an entrypoint ownership fix for the new `devbox-ssh-local`
|
||||||
|
volume, a CI smoke false-negative fix, and documentation. Touches `entrypoint.sh`
|
||||||
|
and `setup-lan-access.sh` (both in the base hash), so `base-latest` /
|
||||||
|
`base-pi-only` advance and the fixes propagate to `pi-devbox`.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
The push-to-main `validate.yml` builds variants FROM the published `base-latest`
|
||||||
|
image, which lags the entrypoint in the current commit until a release tag
|
||||||
|
rebuilds the base. The fork/recall *registration* smoke checks depend on the
|
||||||
|
base entrypoint running `pi install /opt/<pkg>`, so a stale `base-latest` reded
|
||||||
|
those runs with a false negative even when the variant layer was correct.
|
||||||
|
`smoke-test.sh` now gates the two registration assertions behind
|
||||||
|
`STRICT_REGISTRATION` (warn-only when unset). `validate.yml` leaves it unset;
|
||||||
|
the release pipeline (`docker-publish-split.yml`), which builds the base fresh
|
||||||
|
in the same run, sets `STRICT_REGISTRATION=1` on the pi-bearing smoke jobs to
|
||||||
|
enforce them. The build-time `/opt` + `node_modules` checks stay hard in both
|
||||||
|
paths.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
### Fixed: chown the `devbox-ssh-local` volume so the jump key can be generated
|
||||||
|
|
||||||
|
The previous change persisted `~/.ssh-local` via a named volume, but the
|
||||||
|
entrypoint's volume-ownership loop was never updated to include it. Docker
|
||||||
|
creates named volumes as `root:root`, so on a fresh volume `~/.ssh-local`
|
||||||
|
stayed root-owned while `setup-lan-access.sh` runs as `developer` — both its
|
||||||
|
`mkdir cm` and `ssh-keygen` failed silently (`|| true` / `|| exit 0`), leaving
|
||||||
|
**no jump key and no config**, breaking LAN access on the first recreate after
|
||||||
|
the persistence change. `entrypoint.sh` now chowns `~/.ssh-local` to the
|
||||||
|
developer user alongside the other named-volume mount points.
|
||||||
|
|
||||||
|
### Docs: document the optional `~/.config/devbox-shell` mount in the compose template
|
||||||
|
|
||||||
|
`docker-compose.yml` now carries a commented-out `~/.config/devbox-shell` bind
|
||||||
|
mount with an explanatory note. It's the recommended home for host-owned shell
|
||||||
|
config: the image's `~/.bash_aliases` sources `~/.config/devbox-shell/bash_aliases`
|
||||||
|
if present, and `setup-lan-access.sh` reads `~/.config/devbox-shell/ssh-lan.conf`
|
||||||
|
for named-peer `ProxyJump host` overrides. A directory mount is preferred over
|
||||||
|
the single-file `~/.bash_aliases` mount because it survives editors' atomic-save.
|
||||||
|
Template comment only; no behavior change.
|
||||||
|
|
||||||
|
## v1.15.13d — 2026-06-04
|
||||||
|
|
||||||
|
LAN-access fixes + ergonomics. Letter-suffix rebuild on opencode `1.15.13`
|
||||||
|
(version unchanged). Touches `setup-lan-access.sh`, which is in the base hash,
|
||||||
|
so `base-latest` / `base-pi-only` advance and the fix propagates to `pi-devbox`.
|
||||||
|
|
||||||
|
### Fixed: LAN-access `Include` was scoped to the `host`/`mac` block (named peers ignored)
|
||||||
|
|
||||||
|
The generated `~/.ssh-local/config` placed `Include ~/.ssh/config` *inside* the
|
||||||
|
`Host host mac` block. Because SSH scopes an `Include` to the enclosing
|
||||||
|
`Host`/`Match` block, the user's `~/.ssh/config` was only consulted when
|
||||||
|
targeting `host`/`mac` — so `dssh pve` / `dssh <peer>` by name silently fell
|
||||||
|
back to SSH defaults (wrong user, unresolved hostname) and never applied the
|
||||||
|
peer's settings or any `ProxyJump`. Fixed by emitting a bare `Host *` scope
|
||||||
|
reset before every `Include`.
|
||||||
|
|
||||||
|
### Fixed: read-only `~/.ssh/cm` ControlPath broke multiplexed hosts
|
||||||
|
|
||||||
|
The bind-mounted `~/.ssh/config` commonly sets `ControlPath ~/.ssh/cm/...`
|
||||||
|
(CGNAT flow-cap multiplexing), but `~/.ssh` is read-only in the container, so
|
||||||
|
every `ControlMaster`-enabled host (e.g. `pmx-jh`, `proxmox*`, `synlig`) failed
|
||||||
|
with `cannot bind to path … Read-only file system`. The generated config now
|
||||||
|
sets `ControlPath ~/.ssh-local/cm/%r@%h:%p` in the top `Host *` block
|
||||||
|
(first-value-wins) so master sockets land in the writable sidecar.
|
||||||
|
|
||||||
|
### Added: host-owned `ssh-lan.conf` for named-peer jump overrides
|
||||||
|
|
||||||
|
When the host bind-mounts `~/.config/devbox-shell/ssh-lan.conf`, the generated
|
||||||
|
config now Includes it *before* `~/.ssh/config`. Put `ProxyJump host` overrides
|
||||||
|
there (first-value-wins inherits HostName/User/IdentityFile from `~/.ssh/config`)
|
||||||
|
instead of editing the shared `~/.ssh/config` — which would break the host's own
|
||||||
|
direct access to those peers and is read-only from the container anyway. New
|
||||||
|
[`ssh-lan.conf.example`](ssh-lan.conf.example).
|
||||||
|
|
||||||
|
### Added: `DEVBOX_LAN_AUTOJUMP_PRIVATE=1` opt-in RFC1918 auto-jump
|
||||||
|
|
||||||
|
Emits a catch-all that ProxyJumps any private (RFC1918) IP through the host, so
|
||||||
|
bare `dssh user@<ip>` reaches whatever LAN the (roaming) host is currently on,
|
||||||
|
without naming peers. Matches the typed address (not the resolved HostName), so
|
||||||
|
named hosts carrying their own ProxyJump are unaffected; public IPs stay direct.
|
||||||
|
|
||||||
|
All three land in `rootfs/usr/local/lib/opencode-devbox/setup-lan-access.sh`,
|
||||||
|
which is counted in the base hash → advances `base-latest` and propagates to
|
||||||
|
`pi-devbox` (built `FROM` the base).
|
||||||
|
|
||||||
## v1.15.13c — 2026-06-03
|
## v1.15.13c — 2026-06-03
|
||||||
|
|
||||||
|
|||||||
+7
-6
@@ -10,15 +10,16 @@ Designed for teams who want a reproducible coding-agent setup that runs the same
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `latest` / `vX.Y.Z` | Base image — opencode, Node.js, AWS CLI, dev tools |
|
| `latest` / `vX.Y.Z` | Base image — opencode, Node.js, AWS CLI, dev tools |
|
||||||
| `latest-omos` / `vX.Y.Z-omos` | Base + [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration and Bun |
|
| `latest-omos` / `vX.Y.Z-omos` | Base + [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration and Bun |
|
||||||
| `latest-with-pi` / `vX.Y.Z-with-pi` | Base + [pi](https://github.com/earendil-works/pi) as alternative/complementary harness (shares the mempalace install with opencode) |
|
| `latest-with-pi` / `vX.Y.Z-with-pi` | **DEPRECATED (removed in v2.0.0)** — Base + [pi](https://github.com/earendil-works/pi). Use [`joakimp/pi-devbox`](https://hub.docker.com/r/joakimp/pi-devbox) instead |
|
||||||
| `latest-omos-with-pi` / `vX.Y.Z-omos-with-pi` | OMOS + pi together |
|
| `latest-omos-with-pi` / `vX.Y.Z-omos-with-pi` | **DEPRECATED (removed in v2.0.0)** — OMOS + pi together |
|
||||||
|
|
||||||
All variants support `linux/amd64` and `linux/arm64`.
|
All variants support `linux/amd64` and `linux/arm64`.
|
||||||
|
|
||||||
> A fifth, pi-without-opencode build is produced from the same `Dockerfile.variant`
|
> **Looking for pi?** The `*-with-pi` / `pi-only` builds and the `base-pi-only`
|
||||||
> (`INSTALL_OPENCODE=false`) but is **not** published under this repo — it ships as
|
> tag are **deprecated since v1.17.2 and will be removed in v2.0.0**. pi now
|
||||||
> the separate [`joakimp/pi-devbox`](https://hub.docker.com/r/joakimp/pi-devbox)
|
> ships as its own self-contained image:
|
||||||
> image so an "opencode-devbox" tag never lacks opencode.
|
> [`joakimp/pi-devbox`](https://hub.docker.com/r/joakimp/pi-devbox). Pull that
|
||||||
|
> directly instead of any pi-bearing opencode-devbox tag.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
|
|||||||
+37
-1
@@ -259,14 +259,50 @@ RUN ARCH=$(case "${TARGETARCH}" in amd64) echo "x86_64" ;; arm64) echo "aarch64"
|
|||||||
# Always installed in the base (variant-independent). Set
|
# Always installed in the base (variant-independent). Set
|
||||||
# INSTALL_MEMPALACE=false at base-build time to shave ~300 MB.
|
# INSTALL_MEMPALACE=false at base-build time to shave ~300 MB.
|
||||||
ARG INSTALL_MEMPALACE=true
|
ARG INSTALL_MEMPALACE=true
|
||||||
|
# Pin mempalace explicitly (mirrors pi-devbox). An unpinned
|
||||||
|
# `uv tool install mempalace` is what silently swept in the broken
|
||||||
|
# diary_write top-level-anyOf schema (3.3.x/3.4.0) that breaks the
|
||||||
|
# Anthropic tools API; pinning makes every bump a deliberate, reviewable
|
||||||
|
# diff. Bump this in lockstep with pi-devbox's MEMPALACE_VERSION.
|
||||||
|
ARG MEMPALACE_VERSION=3.4.0
|
||||||
ENV UV_TOOL_DIR=/opt/uv-tools
|
ENV UV_TOOL_DIR=/opt/uv-tools
|
||||||
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
ENV UV_TOOL_BIN_DIR=/usr/local/bin
|
||||||
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
||||||
mkdir -p /opt/uv-tools && \
|
mkdir -p /opt/uv-tools && \
|
||||||
uv tool install --no-cache mempalace && \
|
uv tool install --no-cache "mempalace==${MEMPALACE_VERSION}" && \
|
||||||
/opt/uv-tools/mempalace/bin/python -c "import mempalace; print('mempalace', mempalace.__version__ if hasattr(mempalace, '__version__') else 'installed')" ; \
|
/opt/uv-tools/mempalace/bin/python -c "import mempalace; print('mempalace', mempalace.__version__ if hasattr(mempalace, '__version__') else 'installed')" ; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── workaround: strip top-level anyOf from mempalace_diary_write schema ──
|
||||||
|
# Mempalace 3.3.x/3.4.0 advertise diary_write's input_schema with a
|
||||||
|
# top-level `anyOf: [{required:[entry]}, {required:[content]}]` to express
|
||||||
|
# "either entry or content must be supplied". Anthropic's tools API rejects
|
||||||
|
# top-level anyOf/oneOf/allOf, so pi/Claude fail at session start with
|
||||||
|
# `tools.<n>.custom.input_schema: input_schema does not support oneOf,
|
||||||
|
# allOf, or anyOf at the top level`.
|
||||||
|
#
|
||||||
|
# Patch the advertised schema to require ["agent_name", "entry"] and remove
|
||||||
|
# the anyOf block. The handler keeps accepting `content` server-side as a
|
||||||
|
# kwarg alias so existing callers still work.
|
||||||
|
#
|
||||||
|
# Idempotent and self-deactivating: once upstream releases the fix the
|
||||||
|
# regex no longer matches and this RUN is a silent no-op.
|
||||||
|
# Upstream tracking:
|
||||||
|
# https://github.com/MemPalace/mempalace/issues/1728
|
||||||
|
# https://github.com/MemPalace/mempalace/pull/1735
|
||||||
|
# TODO: remove this RUN once a mempalace release containing PR #1735 is on
|
||||||
|
# PyPI and installed by the line above.
|
||||||
|
RUN if [ "${INSTALL_MEMPALACE}" = "true" ]; then \
|
||||||
|
MP_FILE="$(find /opt/uv-tools/mempalace -path '*/mempalace/mcp_server.py' | head -n1)" && \
|
||||||
|
if [ -z "$MP_FILE" ]; then echo "mempalace mcp_server.py not found" >&2; exit 1; fi && \
|
||||||
|
perl -0777 -i -pe 's/(?:[ \t]*\#[^\n]*\n)*[ \t]*"required":\s*\[\s*"agent_name"\s*\]\s*,\s*\n[ \t]*"anyOf":\s*\[\s*\n[ \t]*\{\s*"required":\s*\[\s*"entry"\s*\]\s*\}\s*,\s*\n[ \t]*\{\s*"required":\s*\[\s*"content"\s*\]\s*\}\s*,?\s*\n[ \t]*\]\s*,\s*\n/ "required": ["agent_name", "entry"],\n/s' "$MP_FILE" && \
|
||||||
|
if grep -q '"required": \["agent_name", "entry"\]' "$MP_FILE"; then \
|
||||||
|
echo "mempalace diary_write anyOf workaround: applied (or already clean)"; \
|
||||||
|
else \
|
||||||
|
echo "WARN: mempalace diary_write anyOf workaround did not match expected schema — upstream may have changed shape" >&2; \
|
||||||
|
fi ; \
|
||||||
|
fi
|
||||||
|
|
||||||
# ── mempalace-toolkit — bash wrappers for session/docs mining ────────
|
# ── mempalace-toolkit — bash wrappers for session/docs mining ────────
|
||||||
ARG INSTALL_MEMPALACE_TOOLKIT=true
|
ARG INSTALL_MEMPALACE_TOOLKIT=true
|
||||||
ARG MEMPALACE_TOOLKIT_REF=main
|
ARG MEMPALACE_TOOLKIT_REF=main
|
||||||
|
|||||||
+20
-8
@@ -10,14 +10,19 @@
|
|||||||
# ───────────────── ──────────────── ──────────── ──────────
|
# ───────────────── ──────────────── ──────────── ──────────
|
||||||
# base true false false
|
# base true false false
|
||||||
# omos true true false
|
# omos true true false
|
||||||
# with-pi true false true
|
# with-pi *DEPR* true false true
|
||||||
# omos-with-pi true true true
|
# omos-with-pi*DEPR* true true true
|
||||||
# pi-only false false true
|
# pi-only *DEPR* false false true
|
||||||
#
|
#
|
||||||
# The `pi-only` variant is the single source of truth for the pi-devbox
|
# DEPRECATION (since v1.17.2): the three pi-bearing variants (with-pi,
|
||||||
# image (pi + companions, no opencode). It exists so pi-devbox can FROM it
|
# omos-with-pi, pi-only) and the INSTALL_PI build path are DEPRECATED and
|
||||||
# without inheriting opencode, while the pi install logic stays defined
|
# will be REMOVED in v2.0.0. pi now ships from its own self-contained image:
|
||||||
# here in one place.
|
# joakimp/pi-devbox:latest (https://gitea.jordbo.se/joakimp/pi-devbox).
|
||||||
|
# See docs/CLEANUP-v2.0.0.md for the removal plan.
|
||||||
|
#
|
||||||
|
# Until v2.0.0 the `pi-only` variant remains the source of truth for the
|
||||||
|
# legacy pi build (pi + companions, no opencode); pi-devbox v1.0.0+ no
|
||||||
|
# longer FROMs it.
|
||||||
#
|
#
|
||||||
# Pass `--build-arg BASE_IMAGE=<repo>:base-<hash>` to select the base.
|
# Pass `--build-arg BASE_IMAGE=<repo>:base-<hash>` to select the base.
|
||||||
# The CI workflow computes the base hash from Dockerfile.base + rootfs/
|
# The CI workflow computes the base hash from Dockerfile.base + rootfs/
|
||||||
@@ -42,7 +47,7 @@ ARG USER_NAME=developer
|
|||||||
# edit, so the cache-hit class of bug that bit pi-devbox v0.74.0..
|
# edit, so the cache-hit class of bug that bit pi-devbox v0.74.0..
|
||||||
# v0.75.5 cannot apply here.
|
# v0.75.5 cannot apply here.
|
||||||
ARG INSTALL_OPENCODE=true
|
ARG INSTALL_OPENCODE=true
|
||||||
ARG OPENCODE_VERSION=1.15.13
|
ARG OPENCODE_VERSION=1.17.2
|
||||||
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
RUN if [ "${INSTALL_OPENCODE}" = "true" ]; then \
|
||||||
NPM_CONFIG_PREFIX=/usr npm install -g opencode-ai@${OPENCODE_VERSION} && \
|
NPM_CONFIG_PREFIX=/usr npm install -g opencode-ai@${OPENCODE_VERSION} && \
|
||||||
opencode --version ; \
|
opencode --version ; \
|
||||||
@@ -81,6 +86,13 @@ ARG PI_OBSMEM_REPO=https://github.com/elpapi42/pi-observational-memory.git
|
|||||||
ARG PI_OBSMEM_REF=master
|
ARG PI_OBSMEM_REF=master
|
||||||
RUN if [ "${INSTALL_PI}" = "true" ]; then \
|
RUN if [ "${INSTALL_PI}" = "true" ]; then \
|
||||||
set -e && \
|
set -e && \
|
||||||
|
printf '%s\n' \
|
||||||
|
"===========================================================" \
|
||||||
|
"DEPRECATION WARNING: INSTALL_PI is deprecated in opencode-devbox" \
|
||||||
|
"(since v1.17.2) and will be REMOVED in v2.0.0. Use the dedicated" \
|
||||||
|
"image joakimp/pi-devbox:latest instead." \
|
||||||
|
"See https://gitea.jordbo.se/joakimp/pi-devbox" \
|
||||||
|
"===========================================================" >&2 && \
|
||||||
git_clone_retry() { \
|
git_clone_retry() { \
|
||||||
url="$1"; ref="$2"; dest="$3"; \
|
url="$1"; ref="$2"; dest="$3"; \
|
||||||
for i in 1 2 3 4 5; do \
|
for i in 1 2 3 4 5; do \
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ docker compose exec -u developer devbox aws --version
|
|||||||
| `DEVBOX_LAN_ACCESS` | LAN-access mode: `auto` (jump only on VM-backed hosts), `jump` (always), `off` | `auto` |
|
| `DEVBOX_LAN_ACCESS` | LAN-access mode: `auto` (jump only on VM-backed hosts), `jump` (always), `off` | `auto` |
|
||||||
| `HOST_SSH_USER` | Username to SSH into the host as (required for the LAN jump) | — |
|
| `HOST_SSH_USER` | Username to SSH into the host as (required for the LAN jump) | — |
|
||||||
| `DEVBOX_HOST_ALIAS` | Hostname used to reach the container host | `host.docker.internal` |
|
| `DEVBOX_HOST_ALIAS` | Hostname used to reach the container host | `host.docker.internal` |
|
||||||
|
| `DEVBOX_LAN_AUTOJUMP_PRIVATE` | `1` = ProxyJump *any* RFC1918 (private) IP through the host, so bare `dssh user@<ip>` works on whatever LAN the host is currently on | `0` |
|
||||||
| `USER_UID` | Override container user UID | Auto-detect from `/workspace` |
|
| `USER_UID` | Override container user UID | Auto-detect from `/workspace` |
|
||||||
| `USER_GID` | Override container user GID | Auto-detect from `/workspace` |
|
| `USER_GID` | Override container user GID | Auto-detect from `/workspace` |
|
||||||
| `LANG` | System locale | `en_US.UTF-8` |
|
| `LANG` | System locale | `en_US.UTF-8` |
|
||||||
@@ -154,26 +155,53 @@ 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 with `dssh host`, and reach LAN peers by adding `ProxyJump host` to their entries in your bind-mounted `~/.ssh/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`:
|
||||||
|
|
||||||
```sshconfig
|
```sshconfig
|
||||||
# in your host ~/.ssh/config (mounted read-only into the container)
|
# ~/.config/devbox-shell/ssh-lan.conf — on the host, bind-mounted in
|
||||||
Host my-nas
|
# Only ProxyJump goes here; HostName/User/IdentityFile are inherited
|
||||||
HostName 192.168.1.50
|
# (first-value-wins) from the matching block in your ~/.ssh/config.
|
||||||
User admin
|
Host my-nas pve pbs
|
||||||
ProxyJump host
|
ProxyJump host
|
||||||
```
|
```
|
||||||
|
|
||||||
Then `dssh my-nas` routes container → host → LAN peer. (`dssh`/`dscp` wrap `ssh -F ~/.ssh-local/config`; the host config is pulled in via `Include`.)
|
Now `dssh my-nas` routes container → host → LAN peer, pulling HostName/User/key from your existing `~/.ssh/config`. See [`ssh-lan.conf.example`](ssh-lan.conf.example).
|
||||||
|
|
||||||
> This ships the **mechanism** only — your specific target hosts live in your own `~/.ssh/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"]`).
|
**Roaming / unnamed peers.** Because the jump always targets `host` (= the host on whatever LAN it's currently joined to), you can reach the *current* LAN from anywhere. To make bare `dssh user@<private-ip>` jump automatically without naming peers, set `DEVBOX_LAN_AUTOJUMP_PRIVATE=1` — it ProxyJumps any RFC1918 address through the host. It matches the address you *type* (not the resolved HostName), so named hosts that already carry their own ProxyJump are unaffected.
|
||||||
|
|
||||||
|
**Public IPs go direct.** The container has normal internet egress, so a host with a public IP (or one reached via a *public* jump host) connects straight out — the local `host` jump is not involved. e.g. a `Host bastion` whose `HostName` is public, and everything that `ProxyJump bastion`, works from the container by name with no extra setup.
|
||||||
|
|
||||||
|
> 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
|
||||||
|
|
||||||
@@ -392,7 +420,8 @@ docker compose build --build-arg NVIM_VERSION=0.12.1 # pin to a specific versi
|
|||||||
| `INSTALL_MEMPALACE` | `true` | [MemPalace](https://github.com/MemPalace/mempalace) local AI memory system (~300 MB — disable to shrink image if you don't need MCP memory) |
|
| `INSTALL_MEMPALACE` | `true` | [MemPalace](https://github.com/MemPalace/mempalace) local AI memory system (~300 MB — disable to shrink image if you don't need MCP memory) |
|
||||||
| `INSTALL_MEMPALACE_TOOLKIT` | `true` | [mempalace-toolkit](https://gitea.jordbo.se/joakimp/mempalace-toolkit) bash wrappers (`mempalace-session`, `mempalace-docs`). Cloned at build time from `MEMPALACE_TOOLKIT_REF` (default `main`). Requires `INSTALL_MEMPALACE=true`. |
|
| `INSTALL_MEMPALACE_TOOLKIT` | `true` | [mempalace-toolkit](https://gitea.jordbo.se/joakimp/mempalace-toolkit) bash wrappers (`mempalace-session`, `mempalace-docs`). Cloned at build time from `MEMPALACE_TOOLKIT_REF` (default `main`). Requires `INSTALL_MEMPALACE=true`. |
|
||||||
| `INSTALL_OMOS` | `false` | [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration (installs Bun and plugin) |
|
| `INSTALL_OMOS` | `false` | [oh-my-opencode-slim](https://github.com/alvinunreal/oh-my-opencode-slim) multi-agent orchestration (installs Bun and plugin) |
|
||||||
| `INSTALL_OPENCODE` | `true` | Install opencode. Set `false` to build a pi-only image (still includes Bun if `INSTALL_OMOS=true`; for a fully stripped pi-only image see the `pi-devbox` repo). |
|
| `INSTALL_PI` | `false` | **DEPRECATED (removed in v2.0.0)** — install pi alongside opencode. Use [`joakimp/pi-devbox`](https://gitea.jordbo.se/joakimp/pi-devbox) instead. |
|
||||||
|
| `INSTALL_OPENCODE` | `true` | Install opencode. Set `false` to build a pi-only image (still includes Bun if `INSTALL_OMOS=true`; for a fully stripped pi-only image see the `pi-devbox` repo). **Note: the pi-only path is deprecated and removed in v2.0.0.** |
|
||||||
| `INSTALL_PI` | `false` | Install [pi](https://github.com/earendil-works/pi) as alternative/complementary harness. Both clones [pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit) (~5 MB) and [pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions) (~1 MB) into `/opt/`; entrypoint deploys them on container start. ~150 MB total image growth. |
|
| `INSTALL_PI` | `false` | Install [pi](https://github.com/earendil-works/pi) as alternative/complementary harness. Both clones [pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit) (~5 MB) and [pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions) (~1 MB) into `/opt/`; entrypoint deploys them on container start. ~150 MB total image growth. |
|
||||||
| `PI_VERSION` | `latest` | npm version of `@earendil-works/pi-coding-agent`. Floats by default (image rebuild = pi update). |
|
| `PI_VERSION` | `latest` | npm version of `@earendil-works/pi-coding-agent`. Floats by default (image rebuild = pi update). |
|
||||||
| `PI_TOOLKIT_REF`, `PI_EXTENSIONS_REF` | `main` | Git refs for the toolkit/extensions clones. Pin to a tag/commit for reproducibility. |
|
| `PI_TOOLKIT_REF`, `PI_EXTENSIONS_REF` | `main` | Git refs for the toolkit/extensions clones. Pin to a tag/commit for reproducibility. |
|
||||||
@@ -459,6 +488,19 @@ All six agents should respond if your provider authentication is working.
|
|||||||
|
|
||||||
## pi (alternative/complementary harness)
|
## pi (alternative/complementary harness)
|
||||||
|
|
||||||
|
> **⚠ DEPRECATED since v1.17.2 — removed in v2.0.0.** pi support in
|
||||||
|
> opencode-devbox (the `INSTALL_PI` build arg, the `*-with-pi` /
|
||||||
|
> `omos-with-pi` / `pi-only` variants, and the `base-pi-only` tag) is
|
||||||
|
> deprecated. pi now ships as its own self-contained image:
|
||||||
|
> **[`joakimp/pi-devbox`](https://gitea.jordbo.se/joakimp/pi-devbox)** — pull
|
||||||
|
> that directly. The section below documents the legacy path until removal.
|
||||||
|
>
|
||||||
|
> *Migration note for v2.0.0:* the global npm prefix
|
||||||
|
> (`NPM_CONFIG_PREFIX`) will move off the pi-specific `~/.pi/npm-global`
|
||||||
|
> path to a neutral opencode path. Globally `npm install -g`'d tools may
|
||||||
|
> need their volume/PATH refreshed; the v2.0.0 release notes will carry a
|
||||||
|
> one-time migration shim and details.
|
||||||
|
|
||||||
[pi](https://github.com/earendil-works/pi) is a lightweight TUI coding-agent that can run alongside opencode in the same container. Both harnesses share the mempalace install and palace data — wing/diary entries created by one are visible to the other.
|
[pi](https://github.com/earendil-works/pi) is a lightweight TUI coding-agent that can run alongside opencode in the same container. Both harnesses share the mempalace install and palace data — wing/diary entries created by one are visible to the other.
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -95,6 +100,14 @@ services:
|
|||||||
# - ~/.bash_aliases:/home/developer/.bash_aliases:ro
|
# - ~/.bash_aliases:/home/developer/.bash_aliases:ro
|
||||||
# - ~/.inputrc:/home/developer/.inputrc:ro
|
# - ~/.inputrc:/home/developer/.inputrc:ro
|
||||||
|
|
||||||
|
# Optional: host-owned shell config + LAN jump overrides (recommended
|
||||||
|
# over the single-file ~/.bash_aliases mount above — it's a directory,
|
||||||
|
# so it survives editors' atomic-save). The image's ~/.bash_aliases
|
||||||
|
# sources ~/.config/devbox-shell/bash_aliases if present, and
|
||||||
|
# setup-lan-access.sh reads ~/.config/devbox-shell/ssh-lan.conf for
|
||||||
|
# named-peer `ProxyJump host` overrides (see ssh-lan.conf.example).
|
||||||
|
# - ~/.config/devbox-shell:/home/developer/.config/devbox-shell:ro
|
||||||
|
|
||||||
# Optional: persist uv data (Python installs, tool installs)
|
# Optional: persist uv data (Python installs, tool installs)
|
||||||
# Without this, 'uv python install' must be re-run after container removal.
|
# Without this, 'uv python install' must be re-run after container removal.
|
||||||
- devbox-uv:/home/developer/.local/share/uv
|
- devbox-uv:/home/developer/.local/share/uv
|
||||||
@@ -126,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:
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
# PR-5: Retire pi from opencode-devbox
|
||||||
|
|
||||||
|
After pi-devbox has shipped v1.0.0 as a fully independent image (with
|
||||||
|
its own base + variant Dockerfiles, CI, smoke tests, and docs), the
|
||||||
|
pi-related paths in opencode-devbox become dead weight. This PR
|
||||||
|
removes them.
|
||||||
|
|
||||||
|
## Pre-conditions before merging
|
||||||
|
|
||||||
|
This PR should land **only after** all of the following are stable:
|
||||||
|
|
||||||
|
1. `pi-devbox v1.0.0` published, smoke tests passing, in active use
|
||||||
|
for at least one release cycle.
|
||||||
|
2. Anyone consuming `joakimp/pi-devbox:base-pi-only` directly (e.g.
|
||||||
|
forks pinned to it) has been notified and migrated.
|
||||||
|
3. The deprecation warning (PR-1 of this work — see below) has been
|
||||||
|
live for at least one release cycle so consumers have visible
|
||||||
|
notice.
|
||||||
|
|
||||||
|
## Files / sections to remove from opencode-devbox
|
||||||
|
|
||||||
|
### `Dockerfile.variant`
|
||||||
|
|
||||||
|
Remove these blocks entirely:
|
||||||
|
|
||||||
|
- The `INSTALL_PI` / `PI_VERSION` / `PI_TOOLKIT_REF` /
|
||||||
|
`PI_EXTENSIONS_REF` / `PI_FORK_REPO` / `PI_FORK_REF` /
|
||||||
|
`PI_OBSMEM_REPO` / `PI_OBSMEM_REF` build-args.
|
||||||
|
- The `RUN if [ "${INSTALL_PI}" = "true" ]; then ...` block (entire
|
||||||
|
block — git_clone_retry, git_fetch_ref, npm install
|
||||||
|
pi-coding-agent, the four /opt/pi-* clones, the npm installs in
|
||||||
|
/opt/pi-fork and /opt/pi-observational-memory, and the four
|
||||||
|
rev-parse echoes).
|
||||||
|
- All comments referencing pi-only as "the single source of truth for
|
||||||
|
the pi-devbox image" (the variant matrix table, the explanatory
|
||||||
|
paragraph, and the "rationale" comments at the top of the file
|
||||||
|
about pi-only existing for pi-devbox to FROM).
|
||||||
|
|
||||||
|
Update the variant matrix table at the top of `Dockerfile.variant`:
|
||||||
|
|
||||||
|
```
|
||||||
|
variant INSTALL_OPENCODE INSTALL_OMOS
|
||||||
|
───────────────── ──────────────── ────────────
|
||||||
|
base true false
|
||||||
|
omos true true
|
||||||
|
```
|
||||||
|
|
||||||
|
(only two variants now; pi-only and the with-pi/omos-with-pi axis are
|
||||||
|
gone).
|
||||||
|
|
||||||
|
### `entrypoint-user.sh`
|
||||||
|
|
||||||
|
Remove:
|
||||||
|
|
||||||
|
- The pi-toolkit and pi-extensions install hooks (the section that
|
||||||
|
runs `(cd /opt/pi-toolkit && ./install.sh --yes)` etc.).
|
||||||
|
- The `~/.pi/agent/settings.json` seeding from
|
||||||
|
`/opt/pi-toolkit/settings.example.json`.
|
||||||
|
- Any other pi-conditional blocks (search for `INSTALL_PI`, `pi-toolkit`,
|
||||||
|
`pi-extensions`, `~/.pi/`).
|
||||||
|
|
||||||
|
Verify that the AWS Bedrock auth bootstrap (the pi-toolkit AWS env
|
||||||
|
loader) is not relied on by opencode users. If it is, lift it out of
|
||||||
|
the pi-toolkit dependency (it's small and self-contained).
|
||||||
|
|
||||||
|
### `Dockerfile.base`
|
||||||
|
|
||||||
|
Remove:
|
||||||
|
|
||||||
|
- The `mkdir -p /home/${USER_NAME}/.pi/agent/extensions` line in the
|
||||||
|
standard-directories block. Replace with the equivalent opencode-
|
||||||
|
specific paths if any aren't already present (`~/.config/opencode`
|
||||||
|
is already there).
|
||||||
|
- `NPM_CONFIG_PREFIX=/home/${USER_NAME}/.pi/npm-global` — change to
|
||||||
|
`/home/${USER_NAME}/.config/opencode/npm-global` or a more neutral
|
||||||
|
path. Update the corresponding `PATH` env var.
|
||||||
|
|
||||||
|
Also update the long base-image header comment to remove the
|
||||||
|
"variants for pi-devbox" rationale.
|
||||||
|
|
||||||
|
### CI (`.gitea/workflows/docker-publish-split.yml` or equivalent)
|
||||||
|
|
||||||
|
Remove:
|
||||||
|
|
||||||
|
- The `pi-only` variant build job.
|
||||||
|
- The `with-pi` and `omos-with-pi` variant build jobs (they're
|
||||||
|
redundant with the standalone pi-devbox now).
|
||||||
|
- The `base-pi-only` tag publish step (which pushes to
|
||||||
|
`joakimp/pi-devbox:base-pi-only` from this repo).
|
||||||
|
- The `resolve-pi-version` job step (no longer needed).
|
||||||
|
- Smoke-test invocations with `--variant pi-only`, `--variant with-pi`,
|
||||||
|
`--variant omos-with-pi`.
|
||||||
|
|
||||||
|
Remaining variants in CI: `base`, `omos`. The "with-pi" axis is
|
||||||
|
fully retired.
|
||||||
|
|
||||||
|
### `scripts/smoke-test.sh`
|
||||||
|
|
||||||
|
Remove:
|
||||||
|
|
||||||
|
- The `--variant pi-only`, `--variant with-pi`, `--variant
|
||||||
|
omos-with-pi` branches.
|
||||||
|
- pi-related assertions: `pi --version`, the
|
||||||
|
`~/.pi/agent/extensions/*.ts ≥ 4` check, the mempalace.ts bridge
|
||||||
|
gate (mempalace itself stays, but its bridge into pi is no longer
|
||||||
|
this image's concern).
|
||||||
|
|
||||||
|
Remaining variant axis in smoke tests: `base`, `omos`.
|
||||||
|
|
||||||
|
### `README.md` (and `AGENTS.md`, `DOCKER_HUB.md`)
|
||||||
|
|
||||||
|
- Remove the pi-only variant from the "Image variants" table.
|
||||||
|
- Remove the with-pi / omos-with-pi variants if they were documented.
|
||||||
|
- Remove all sections about pi-toolkit, pi-extensions, pi-fork,
|
||||||
|
pi-observational-memory, ~/.pi paths, and pi-related env vars.
|
||||||
|
- Remove the "this image also produces base-pi-only for pi-devbox"
|
||||||
|
notes.
|
||||||
|
- Add a single-paragraph **"Looking for pi?"** section pointing to
|
||||||
|
`joakimp/pi-devbox`.
|
||||||
|
|
||||||
|
### `Dockerfile` references in `pi-devbox` repo (cleanup of cross-repo coupling)
|
||||||
|
|
||||||
|
This isn't a change to opencode-devbox, but it's part of the same
|
||||||
|
deprecation:
|
||||||
|
|
||||||
|
- Once pi-devbox v1.0.0 is the single source of truth, remove
|
||||||
|
pi-devbox/Dockerfile (the 5-line shim with the long
|
||||||
|
`joakimp/pi-devbox:base-pi-only` rationale comment). It's replaced
|
||||||
|
by `Dockerfile.base` + `Dockerfile.variant` produced by PR-1 of
|
||||||
|
this work.
|
||||||
|
|
||||||
|
## Two-step deprecation path (recommended)
|
||||||
|
|
||||||
|
Rather than a single big-bang removal, use a deprecation cycle:
|
||||||
|
|
||||||
|
### Step 1 — pre-PR (lands at the same time as pi-devbox v1.0.0)
|
||||||
|
|
||||||
|
Add a deprecation warning to opencode-devbox:
|
||||||
|
|
||||||
|
1. **Build-time message** — when `INSTALL_PI=true`,
|
||||||
|
`INSTALL_PI_DEPRECATED=warn` is the default; the variant build
|
||||||
|
prints to stderr:
|
||||||
|
```
|
||||||
|
===========================================================
|
||||||
|
DEPRECATION WARNING: INSTALL_PI is deprecated in opencode-devbox
|
||||||
|
and will be removed in v2.0.0. Use joakimp/pi-devbox:latest
|
||||||
|
instead. See https://gitea.jordbo.se/joakimp/pi-devbox
|
||||||
|
===========================================================
|
||||||
|
```
|
||||||
|
2. **CHANGELOG** entry on opencode-devbox: "INSTALL_PI build-arg path
|
||||||
|
deprecated; will be removed in v2.0.0."
|
||||||
|
3. **README and DOCKER_HUB** updates: mark `pi-only`, `with-pi`,
|
||||||
|
`omos-with-pi` variants as deprecated, point to pi-devbox.
|
||||||
|
4. The `base-pi-only` tag continues to be published but with a
|
||||||
|
notice in the description: "Internal artifact for pi-devbox.
|
||||||
|
Deprecated; pull joakimp/pi-devbox:latest directly."
|
||||||
|
|
||||||
|
### Step 2 — removal PR (this document)
|
||||||
|
|
||||||
|
Lands one release cycle (or one calendar month, whichever is later)
|
||||||
|
after step 1. Removes everything listed in the per-file sections
|
||||||
|
above. Tagged as opencode-devbox v2.0.0 (the major bump signals the
|
||||||
|
breaking change).
|
||||||
|
|
||||||
|
## Risk assessment
|
||||||
|
|
||||||
|
### What could go wrong
|
||||||
|
|
||||||
|
- **Someone is consuming `base-pi-only` directly** without going
|
||||||
|
through pi-devbox. The deprecation warning + one-cycle delay should
|
||||||
|
surface this.
|
||||||
|
- **Mempalace bridge in pi-extensions** — this stays in pi-devbox; no
|
||||||
|
impact on opencode-devbox.
|
||||||
|
- **Shared base assumptions** — opencode-devbox's
|
||||||
|
`~/.pi/npm-global` NPM_CONFIG_PREFIX was a pi-specific design. In
|
||||||
|
the cleanup we move it to a neutral path. Existing opencode-devbox
|
||||||
|
users get a one-time migration: their `npm install -g` packages
|
||||||
|
installed at the old path stop being on PATH. Document this in the
|
||||||
|
v2.0.0 changelog and add a one-liner that copies the old prefix
|
||||||
|
contents to the new one if the old one exists.
|
||||||
|
|
||||||
|
### What's safe
|
||||||
|
|
||||||
|
- The base apt set, the Go-binary installs, MemPalace, the SSH
|
||||||
|
ControlMaster setup, the entrypoint UID/GID dance — all of these
|
||||||
|
stay. They're not pi-specific.
|
||||||
|
- The `omos` variant — fully unaffected.
|
||||||
|
- Existing opencode-only users — no change to their workflow.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
After PR-5 lands, the following should be true:
|
||||||
|
|
||||||
|
- `grep -ri "INSTALL_PI\|pi-toolkit\|pi-extensions\|pi-fork\|pi-observational-memory\|base-pi-only" .` in opencode-devbox returns no matches.
|
||||||
|
- `docker history joakimp/opencode-devbox:latest` shows no pi-related layers.
|
||||||
|
- The opencode-devbox CI matrix builds only `base` and `omos` variants.
|
||||||
|
- pi-devbox CI is unaffected (it's a different repo).
|
||||||
|
- Both repos build cleanly in their own CI without referencing the other.
|
||||||
|
|
||||||
|
## Estimated effort
|
||||||
|
|
||||||
|
- Step 1 (deprecation warnings): ~2 hours.
|
||||||
|
- Step 2 (removal): ~4 hours including local testing of opencode-only
|
||||||
|
build paths.
|
||||||
|
- One release cycle of monitoring between them.
|
||||||
|
|
||||||
|
Total: ~1 working day of focused effort, spread over a calendar month.
|
||||||
|
|
||||||
|
## Order in the broader plan
|
||||||
|
|
||||||
|
1. PR-1 on pi-devbox — copy base + variant Dockerfiles, strip
|
||||||
|
opencode/omos paths, tag v1.0.0.
|
||||||
|
2. PR-2 on pi-devbox — add pandoc, graphviz, imagemagick, tldr, yq.
|
||||||
|
3. PR-3 on pi-devbox — add `:latest-studio` variant.
|
||||||
|
4. (Optional) PR-4 on pi-devbox — add `:latest-studio-tex` variant.
|
||||||
|
5. PR-pre on opencode-devbox — deprecation warnings (step 1 above).
|
||||||
|
6. **PR-5 on opencode-devbox — actual removal (this document, step 2).**
|
||||||
|
|
||||||
|
PRs 1–4 are independent and can land in any order on pi-devbox. PR-pre
|
||||||
|
should land alongside or shortly after pi-devbox v1.0.0 (PR-1) so
|
||||||
|
consumers know to migrate. PR-5 lands one release cycle after PR-pre.
|
||||||
@@ -88,6 +88,7 @@ for dir in \
|
|||||||
/home/"$USER_NAME"/.config/opencode \
|
/home/"$USER_NAME"/.config/opencode \
|
||||||
/home/"$USER_NAME"/.config/nvim \
|
/home/"$USER_NAME"/.config/nvim \
|
||||||
/home/"$USER_NAME"/.pi \
|
/home/"$USER_NAME"/.pi \
|
||||||
|
/home/"$USER_NAME"/.ssh-local \
|
||||||
/home/"$USER_NAME"/.agents/skills; do
|
/home/"$USER_NAME"/.agents/skills; do
|
||||||
[ -d "$dir" ] || continue
|
[ -d "$dir" ] || continue
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,30 @@
|
|||||||
# jump to authenticate. If unset we still generate the config but print
|
# jump to authenticate. If unset we still generate the config but print
|
||||||
# a hint with the public key to authorize on the host.
|
# a hint with the public key to authorize on the host.
|
||||||
# DEVBOX_HOST_ALIAS — host hostname to reach (default host.docker.internal).
|
# 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
|
# Idempotent: re-renders the config every run (cheap); never regenerates the
|
||||||
# key. Always non-fatal — never blocks container startup.
|
# key. Always non-fatal — never blocks container startup.
|
||||||
@@ -73,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 ────────────────────────────────────────
|
||||||
@@ -84,9 +114,51 @@ if [ -n "${HOST_SSH_USER:-}" ]; then
|
|||||||
USER_LINE=" User ${HOST_SSH_USER}"
|
USER_LINE=" User ${HOST_SSH_USER}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
INCLUDE_LINE=""
|
# 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
|
if [ -r "${HOME}/.ssh/config" ]; then
|
||||||
INCLUDE_LINE="Include ~/.ssh/config"
|
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
|
fi
|
||||||
|
|
||||||
cat > "$CONFIG" <<EOF
|
cat > "$CONFIG" <<EOF
|
||||||
@@ -95,9 +167,15 @@ cat > "$CONFIG" <<EOF
|
|||||||
# (or the dssh / dscp aliases). See the script header for the full rationale.
|
# (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.
|
# ~/.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 *
|
Host *
|
||||||
UserKnownHostsFile ~/.ssh-local/known_hosts
|
UserKnownHostsFile ~/.ssh-local/known_hosts
|
||||||
StrictHostKeyChecking accept-new
|
StrictHostKeyChecking accept-new
|
||||||
|
ControlPath ~/.ssh-local/cm/%r@%h:%p
|
||||||
|
|
||||||
# The container host (OrbStack / Docker Desktop). 'host' and 'mac' are aliases.
|
# The container host (OrbStack / Docker Desktop). 'host' and 'mac' are aliases.
|
||||||
Host host mac
|
Host host mac
|
||||||
@@ -109,25 +187,39 @@ ${USER_LINE}
|
|||||||
ControlPath ~/.ssh-local/cm/%r@%h:%p
|
ControlPath ~/.ssh-local/cm/%r@%h:%p
|
||||||
ControlPersist 4h
|
ControlPersist 4h
|
||||||
ServerAliveInterval 30
|
ServerAliveInterval 30
|
||||||
|
${LAN_CONF_BLOCK}
|
||||||
# Your own target hosts: add 'ProxyJump host' to their entries in your
|
${AUTOJUMP_BLOCK}
|
||||||
# bind-mounted ~/.ssh/config, pulled in below.
|
${INCLUDE_BLOCK}
|
||||||
${INCLUDE_LINE}
|
|
||||||
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
|
||||||
|
|||||||
+55
-11
@@ -30,6 +30,19 @@ fi
|
|||||||
FAILED=0
|
FAILED=0
|
||||||
pass() { echo " ✓ $1"; }
|
pass() { echo " ✓ $1"; }
|
||||||
fail() { echo " ✗ $1" >&2; FAILED=$((FAILED + 1)); }
|
fail() { echo " ✗ $1" >&2; FAILED=$((FAILED + 1)); }
|
||||||
|
warn() { echo " ⚠ $1" >&2; }
|
||||||
|
|
||||||
|
# Registration assertions (fork/recall installed by the BASE image's
|
||||||
|
# entrypoint-user.sh via `pi install /opt/<pkg>`) depend on the base, not the
|
||||||
|
# variant layer built here. validate.yml builds variants FROM the published
|
||||||
|
# base-latest, which can lag the entrypoint in the current commit (the base
|
||||||
|
# only rebuilds on a release tag), so a stale base-latest would red the
|
||||||
|
# push-to-main run with a false negative. These checks are therefore warn-only
|
||||||
|
# by default; the release pipeline (docker-publish-split.yml) builds the base
|
||||||
|
# fresh in the same run and sets STRICT_REGISTRATION=1 to enforce them hard.
|
||||||
|
# The build-time /opt + node_modules checks below stay hard in every path —
|
||||||
|
# those are produced by the variant layer and must always be correct.
|
||||||
|
STRICT_REGISTRATION="${STRICT_REGISTRATION:-0}"
|
||||||
|
|
||||||
run() {
|
run() {
|
||||||
# Run a command inside the image and capture its output.
|
# Run a command inside the image and capture its output.
|
||||||
@@ -187,10 +200,21 @@ if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v pi" >/dev/null 2>&
|
|||||||
trap 'docker rm -f "$CID" >/dev/null 2>&1 || true' EXIT
|
trap 'docker rm -f "$CID" >/dev/null 2>&1 || true' EXIT
|
||||||
|
|
||||||
# Wait for entrypoint-user.sh to finish deploying pi-toolkit + extensions.
|
# Wait for entrypoint-user.sh to finish deploying pi-toolkit + extensions.
|
||||||
# Marker: keybindings.json symlink lands once pi-toolkit/install.sh has run.
|
# The deploy order is: pi-toolkit (writes keybindings.json) -> pi-extensions
|
||||||
# Up to 30s — omos-with-pi has more setup work than base+pi.
|
# (symlinks its *.ts) -> settings.json -> mempalace.ts bridge (LAST). Gating
|
||||||
for _ in $(seq 1 30); do
|
# only on keybindings.json races: it lands when pi-toolkit finishes, before
|
||||||
if docker exec "$CID" test -L /home/developer/.pi/agent/keybindings.json 2>/dev/null; then
|
# pi-extensions has symlinked its *.ts, so the "*.ts >= 4" check below could
|
||||||
|
# sample mid-deploy under parallel build load (observed v1.16.2 run 370:
|
||||||
|
# smoke-with-pi saw <4 .ts while omos-with-pi/pi-only saw 8). Wait for the
|
||||||
|
# LAST-deployed artifact (the mempalace.ts bridge symlink) AND a settled
|
||||||
|
# extension count so the deploy is fully complete before any assertion runs.
|
||||||
|
# Up to 45s — pi-bearing variants have more setup work under load.
|
||||||
|
for _ in $(seq 1 45); do
|
||||||
|
if docker exec "$CID" sh -c \
|
||||||
|
'test -L $HOME/.pi/agent/keybindings.json && \
|
||||||
|
test -L $HOME/.pi/agent/extensions/mempalace.ts && \
|
||||||
|
[ "$(ls -1 $HOME/.pi/agent/extensions/*.ts 2>/dev/null | wc -l)" -ge 4 ]' \
|
||||||
|
2>/dev/null; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
@@ -206,6 +230,19 @@ if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v pi" >/dev/null 2>&
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Like exec_test but warn-only unless STRICT_REGISTRATION=1 (see note at top).
|
||||||
|
exec_test_reg() {
|
||||||
|
local label="$1"; shift
|
||||||
|
local out
|
||||||
|
if out=$(docker exec -u developer "$CID" sh -c "$*" 2>&1); then
|
||||||
|
pass "$label ($(echo "$out" | head -1))"
|
||||||
|
elif [ "$STRICT_REGISTRATION" = "1" ]; then
|
||||||
|
fail "$label: $out"
|
||||||
|
else
|
||||||
|
warn "$label (warn-only — stale base-latest? set STRICT_REGISTRATION=1 to enforce): $out"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
exec_test "~/.pi/agent/keybindings.json (pi-toolkit)" \
|
exec_test "~/.pi/agent/keybindings.json (pi-toolkit)" \
|
||||||
'test -L $HOME/.pi/agent/keybindings.json && echo ok'
|
'test -L $HOME/.pi/agent/keybindings.json && echo ok'
|
||||||
exec_test "~/.pi/agent/extensions/*.ts ≥ 4 (pi-extensions)" \
|
exec_test "~/.pi/agent/extensions/*.ts ≥ 4 (pi-extensions)" \
|
||||||
@@ -225,9 +262,9 @@ if docker run --rm --entrypoint="" "$IMAGE" sh -c "command -v pi" >/dev/null 2>&
|
|||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
exec_test "pi-fork registered in settings.json (fork tool)" \
|
exec_test_reg "pi-fork registered in settings.json (fork tool)" \
|
||||||
'grep -q pi-fork $HOME/.pi/agent/settings.json && echo ok'
|
'grep -q pi-fork $HOME/.pi/agent/settings.json && echo ok'
|
||||||
exec_test "pi-observational-memory registered in settings.json (recall tool)" \
|
exec_test_reg "pi-observational-memory registered in settings.json (recall tool)" \
|
||||||
'grep -q pi-observational-memory $HOME/.pi/agent/settings.json && echo ok'
|
'grep -q pi-observational-memory $HOME/.pi/agent/settings.json && echo ok'
|
||||||
|
|
||||||
docker rm -f "$CID" >/dev/null 2>&1 || true
|
docker rm -f "$CID" >/dev/null 2>&1 || true
|
||||||
@@ -366,13 +403,20 @@ echo " Uncompressed size: ${SIZE_MB} MB"
|
|||||||
# deliberately zero-headroom 2500 ceiling and skipping promote-base-latest.
|
# deliberately zero-headroom 2500 ceiling and skipping promote-base-latest.
|
||||||
# omos variant to ~3.1 GB. Functional smoke checks all pass; this is a
|
# omos variant to ~3.1 GB. Functional smoke checks all pass; this is a
|
||||||
# guardrail, not a performance limit.
|
# guardrail, not a performance limit.
|
||||||
THRESHOLD=2600
|
# v1.16.2: all thresholds bumped +150 MB preemptively ahead of the combined
|
||||||
[ "$VARIANT" = "omos" ] && THRESHOLD=3300
|
# opencode 1.15.13->1.16.2 (minor) + pi 0.78.1->0.79.0 (minor) bump. Both
|
||||||
[ "$VARIANT" = "with-pi" ] && THRESHOLD=2900
|
# base (2506/2600) and omos (3206/3300) were sitting on ~94 MB headroom and
|
||||||
[ "$VARIANT" = "omos-with-pi" ] && THRESHOLD=3900
|
# a minor opencode bump has tripped them before (v1.15.0 omos, v1.15.4
|
||||||
|
# omos-with-pi). Restoring ~250 MB headroom avoids a partial-publish +
|
||||||
|
# letter-suffix recovery cycle. CI's smoke size print + resolved-versions
|
||||||
|
# table records the actual landed sizes; tighten later if they come in low.
|
||||||
|
THRESHOLD=2750
|
||||||
|
[ "$VARIANT" = "omos" ] && THRESHOLD=3450
|
||||||
|
[ "$VARIANT" = "with-pi" ] && THRESHOLD=3050
|
||||||
|
[ "$VARIANT" = "omos-with-pi" ] && THRESHOLD=4050
|
||||||
# pi-only = with-pi minus opencode (its platform binary is ~145 MB), so it
|
# pi-only = with-pi minus opencode (its platform binary is ~145 MB), so it
|
||||||
# lands a bit under base. Threshold 2750 leaves the same headroom pattern.
|
# lands a bit under base. Threshold 2750 leaves the same headroom pattern.
|
||||||
[ "$VARIANT" = "pi-only" ] && THRESHOLD=2750
|
[ "$VARIANT" = "pi-only" ] && THRESHOLD=2850
|
||||||
if [ "$SIZE_MB" -gt "$THRESHOLD" ]; then
|
if [ "$SIZE_MB" -gt "$THRESHOLD" ]; then
|
||||||
fail "image size ${SIZE_MB} MB exceeds threshold ${THRESHOLD} MB for variant=$VARIANT"
|
fail "image size ${SIZE_MB} MB exceeds threshold ${THRESHOLD} MB for variant=$VARIANT"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# ssh-lan.conf.example — host-owned LAN-peer jump overrides for opencode-devbox
|
||||||
|
# ============================================================================
|
||||||
|
# WHAT THIS IS
|
||||||
|
# On a VM-backed host (macOS OrbStack / Docker Desktop) the container can't
|
||||||
|
# reach the host's LAN directly; it tunnels through the host via the `host`
|
||||||
|
# SSH jump that the entrypoint sets up (see the README "Reaching your LAN"
|
||||||
|
# section). To reach your LAN peers *by name*, they need `ProxyJump host`.
|
||||||
|
#
|
||||||
|
# WHY NOT JUST EDIT ~/.ssh/config?
|
||||||
|
# The host itself reaches those peers DIRECTLY — adding `ProxyJump host`
|
||||||
|
# there would break the host's own access (and ~/.ssh is mounted read-only
|
||||||
|
# into the container anyway). So container-only jump overrides live HERE.
|
||||||
|
#
|
||||||
|
# HOW IT'S WIRED
|
||||||
|
# If this file exists at ~/.config/devbox-shell/ssh-lan.conf on the host
|
||||||
|
# (the same bind-mounted devbox-shell bridge dir used for shared aliases),
|
||||||
|
# the generated ~/.ssh-local/config Includes it BEFORE your ~/.ssh/config.
|
||||||
|
# SSH's first-value-wins rule means ProxyJump is taken from here, while
|
||||||
|
# HostName / User / IdentityFile are inherited from the matching block in
|
||||||
|
# your ~/.ssh/config. So you only list the names + the jump — nothing else.
|
||||||
|
#
|
||||||
|
# SETUP
|
||||||
|
# 1. Copy to your host: cp ssh-lan.conf.example ~/.config/devbox-shell/ssh-lan.conf
|
||||||
|
# 2. Bind-mount ~/.config/devbox-shell into the container (most setups
|
||||||
|
# already do this for shared shell aliases).
|
||||||
|
# 3. List the host aliases (as named in your ~/.ssh/config) that should be
|
||||||
|
# reached through the host jump.
|
||||||
|
# 4. Restart the container, then: dssh <name>
|
||||||
|
#
|
||||||
|
# NOTE: these are facts about ONE host's LAN. A roaming laptop sees different
|
||||||
|
# networks — keep this per-host, never in the image. For ad-hoc private IPs on
|
||||||
|
# whatever LAN you're currently on, prefer DEVBOX_LAN_AUTOJUMP_PRIVATE=1
|
||||||
|
# instead of naming every peer.
|
||||||
|
|
||||||
|
# Example — names must match Host blocks already defined in your ~/.ssh/config:
|
||||||
|
Host pve pve-2 pbs-vm my-nas
|
||||||
|
ProxyJump host
|
||||||
|
|
||||||
|
# You can also give a peer its own settings here if it isn't in ~/.ssh/config
|
||||||
|
# at all (then specify everything, not just ProxyJump):
|
||||||
|
# Host lab-box
|
||||||
|
# HostName 192.168.1.77
|
||||||
|
# User admin
|
||||||
|
# IdentityFile ~/.ssh/id_ed25519
|
||||||
|
# ProxyJump host
|
||||||
Reference in New Issue
Block a user