Item A — LAN access (base image):
- New rootfs/usr/local/lib/opencode-devbox/setup-lan-access.sh, invoked
non-fatally from entrypoint-user.sh. On VM-backed hosts (macOS OrbStack /
Docker Desktop, detected via host.docker.internal) it generates a writable
~/.ssh-local/config that uses the host as an SSH jump to reach LAN peers;
no-op on native Linux. Ships the mechanism (generic 'host' jump alias),
not policy (targets stay in the user's bind-mounted ~/.ssh/config).
- New env knobs: DEVBOX_LAN_ACCESS (auto|jump|off), HOST_SSH_USER,
DEVBOX_HOST_ALIAS. dssh/dscp aliases in .bash_aliases (guarded).
Item B — pi-fork (fork) + pi-observational-memory (recall) in pi variants:
- Dockerfile.variant clones both elpapi42 repos to /opt and runs npm install
there at build time (local-path 'pi install' does not npm-install, so deps
must be present to load). New args PI_FORK_REPO/REF, PI_OBSMEM_REPO/REF.
- entrypoint-user.sh registers them at runtime via 'pi install /opt/<pkg>'
(instant, in-place, idempotent; tools bind on next pi start).
- CI resolve-versions resolves each repo's master HEAD to a commit SHA and
passes PI_FORK_REF/PI_OBSMEM_REF — same cache-hit guard as PI_VERSION.
- smoke-test asserts /opt clones + node_modules + settings.json registration;
size thresholds bumped (with-pi 2700->2900, omos-with-pi 3700->3900).
Versions unchanged (opencode 1.15.13, pi 0.78.0 — both still latest).
Docs: README LAN section + env table, .env.example, AGENTS.md, CHANGELOG.
Plan recorded in docs/plan-lan-access-and-pi-extensions.md.
Context7 provides up-to-date library documentation for LLMs via a
remote endpoint — no local binary needed. Always registered since it
has no PATH dependency.
Also switches generated config from .json to .jsonc so we can include
a comment about the optional API key for higher rate limits. The
existing-config check now detects both file extensions.
The mempalace Python package ships a 'mempalace-mcp' console entry
point; 'uv tool install' places it on PATH as a shim whose shebang
points at the isolated venv's Python. Our hand-rolled wrapper at
/usr/local/bin/mempalace-mcp-server was duplicating what uv installs
for free — one less file to maintain.
Fixes the MCP error users saw after the v1.14.28b → v1.14.29 upgrade
path: custom opencode.json files typically had the pre-v1.14.29
command ['python3', '-m', 'mempalace.mcp_server'] which worked with
the old pip install but fails silently after the uv-tool migration
because system python3 cannot import from the venv. Opencode surfaced
this as 'MCP error -32000: connection closed'.
- generate-config.py now emits ['mempalace-mcp'] and keys its detect
on shutil.which('mempalace-mcp').
- Dockerfile drops 'COPY rootfs/usr/local/bin/' and the chmod of the
wrapper. Build shrinks from 30 to 29 stages.
- rootfs/usr/local/bin/ removed entirely.
- Smoke test asserts /usr/local/bin/mempalace-mcp is executable and
prints its symlink target.
- README's MemPalace section shows ['mempalace-mcp'] and explicitly
warns against the old pattern with the observed failure mode.
- CHANGELOG adds a v1.14.29c entry.
Main changes:
- Extract opencode.json generation from entrypoint-user.sh into a
standalone Python script (rootfs/usr/local/lib/opencode-devbox/
generate-config.py). Preserves the never-overwrite-existing-config
guarantee. Cuts entrypoint-user.sh from 176 to 97 lines.
- Install MemPalace via 'uv tool install' into an isolated venv at
/opt/uv-tools/mempalace/ with a /usr/local/bin/mempalace-mcp-server
wrapper, replacing the 'pip install --break-system-packages' escape
hatch. The wrapper is what generate-config.py references in the
auto-generated opencode.json. Also fix 'mempalace init' in
entrypoint-user.sh to use --yes so first-start initialization isn't
interactive (this used to hang or print prompts into the user's
terminal). Gated by INSTALL_MEMPALACE build arg (default true) so
users who don't need AI memory can shave ~300 MB.
- Sentinel-file pattern in entrypoint.sh volume-ownership loop: write
.devbox-owner after a successful chown -R, skip the recursive walk
on subsequent starts when the sentinel matches FINAL_UID:FINAL_GID.
Cuts multi-second startup costs to milliseconds on large volumes
(nvim plugins, palace data). UID changes still trigger a full chown.
- Float all GitHub/Gitea-hosted binary versions: gosu, fzf, git-lfs,
neovim, bat, eza, zoxide, uv, gitea-mcp now default to 'latest' and
resolve the newest upstream release at build time via the /releases/
latest redirect. Go (go.dev JSON feed) and oh-my-opencode-slim (npm
@latest) likewise. Intentional pins still in place: OPENCODE_VERSION,
NODE_VERSION=22, DEBIAN_VERSION=trixie-slim. Each *_VERSION ARG
accepts an explicit value to lock a specific version when needed.
- New scripts/smoke-test.sh verifies binary presence, opencode startup,
entrypoint user drop, generate-config idempotency, bun's presence-
per-variant, and image size against thresholds (2500 MB base, 3000
MB OMOS). Prints resolved component versions as its first step so
CI logs always record what got baked into a given image.
- New .gitea/workflows/validate.yml runs on push to main and PRs:
single-arch amd64 build, smoke test, DOCKER_HUB.md sync check. Tag-
triggered docker-publish.yml now smoke-tests each variant on amd64
before the full multi-arch push.
- scripts/generate-dockerhub-md.py auto-generates DOCKER_HUB.md from
README.md using explicit SECTION_RULES. --check mode fails CI when
the committed file is out of sync. Enforces the 25 kB Docker Hub
limit. Adding a new README section forces an explicit keep/drop/
replace decision.
- Remove dead INSTALL_PYTHON build arg (was a no-op since mempalace
added python3 unconditionally).
If the host bind-mounts ~/.config/devbox-shell/ into the container
(the directory-mount pattern that avoids single-file inode breakage),
the container needs a bridge line in .bashrc or .bash_aliases to
source the mounted file. Previously this bridge had to be re-added
manually after every --force-recreate because it lived in the
container's writable layer.
Baking it into the skel .bash_aliases makes it automatic: every
fresh container sources ~/.config/devbox-shell/bash_aliases if it
exists, with zero manual steps. Hosts that don't use the devbox-shell
pattern are unaffected — the [ -r ... ] test silently skips.
The previous guard used an exported DEVBOX_PS1_SET env var to avoid
double-prefixing on re-source. But env vars survive 'exec bash'
while PS1 does not — a new bash rebuilds PS1 from .bashrc. Result:
the guard saw DEVBOX_PS1_SET=1, skipped the prefix, and the new
shell ran with bare PS1 (no [devbox] marker).
Replace the env-var guard with a substring check on PS1 itself.
If PS1 already contains '[devbox]' we skip, otherwise we prepend.
Correct in all three cases: first shell (PS1 has no marker → add),
exec bash (fresh PS1 has no marker → add), re-source within same
shell (PS1 still has marker → skip, no doubling).
v1.14.19c installed 'history -a; ' at the start of PROMPT_COMMAND
before zoxide's init ran. Zoxide's init uses ';' as its separator
when prepending __zoxide_hook, producing 'history -a;;__zoxide_hook'.
Every interactive prompt then emitted:
bash: PROMPT_COMMAND: syntax error near unexpected token ';;'
History flushing still worked (the 'history -a' half parsed fine),
but the error spam made the shell feel broken.
Fix by moving the history-flush PROMPT_COMMAND assignment AFTER
zoxide's init, and using a newline separator (via ${PROMPT_COMMAND:+...}
parameter expansion) so there's no semicolon involved at all. Each
PROMPT_COMMAND line runs as its own statement, no parsing contention.
Known upstream issue: https://github.com/ajeetdsouza/zoxide/issues/722
Two changes that address a longstanding frustration: bash history is
lost on every container recreate, and the container's ~/.bashrc and
~/.inputrc are stock Debian (no history tuning, no prefix search on
arrow keys, no integrations).
Added a named volume 'devbox-shell-history' mounted at ~/.cache/bash
with HISTFILE pointing there; history now survives 'docker compose up
--force-recreate'. The volume is added to both docker-compose.yml and
docker-compose.shared.yml, and ~/.cache/bash is registered in the
entrypoint ownership-fix loop per the AGENTS.md convention.
Baked rootfs/home/developer/.bash_aliases (sourced automatically by
Debian's default ~/.bashrc) and rootfs/home/developer/.inputrc into
the image. They give new containers: 100k-entry timestamped dedup
history with per-prompt flush, Up/Down arrow prefix history search,
case-insensitive coloured completion, aliases that prefer eza and
bat when present, git shortcuts, interactive rm/mv/cp, zoxide and
fzf (via 'fzf --bash') integration, and a [devbox] prompt marker.
The fzf integration uses 'fzf --bash' because we install fzf from
GitHub releases, not apt — the apt-path key-bindings aren't present.
Users who prefer their host's own shell config can uncomment two
commented bind-mount lines in docker-compose.yml to shadow the
baked defaults.