2026-09-27 10:59:40 +02:00
IMAGE_NAME := opencode-sandbox
IMAGE_TAG := local
IMAGE := $( IMAGE_NAME) :$( IMAGE_TAG)
NETWORK_NAME := opencode-sandbox-net
WORKDIR_NAME := $( notdir $( patsubst %/,%,$( CURDIR) ) )
# OpenCode follows the XDG Base Directory spec with three distinct dirs:
# config (settings/plugins), data (auth.json, opencode.db, snapshots), and
# state (session/model history, locks) -- data and state are NOT the same
# directory, despite the similar names.
CONFIG_DIR := $( HOME) /.config/opencode
DATA_DIR := $( HOME) /.local/share/opencode
STATE_DIR := $( HOME) /.local/state/opencode
# No :Z/:z SELinux suffixes: on macOS the host side (APFS) has no SELinux
# concept, so the labeling flag would be a meaningless no-op here.
#
# Known virtiofs caveat (default podman-machine file share on macOS/applehv):
# inotify file-change events don't propagate into the container. Not a
# blocker for this sandbox, just a rough edge if a tool relies on watching
# /workspace for live changes.
RUN_FLAGS := \
--rm -it \
--userns= keep-id \
--network $( NETWORK_NAME) \
--network-alias $( WORKDIR_NAME) \
-v $( CURDIR) :/workspace \
-v $( CONFIG_DIR) :/home/dev/.config/opencode \
-v $( DATA_DIR) :/home/dev/.local/share/opencode \
-v $( STATE_DIR) :/home/dev/.local/state/opencode \
-w /workspace
2026-09-27 20:49:11 +02:00
.PHONY : build opencode shell install
2026-09-27 10:59:40 +02:00
# Only relevant on macOS, where podman requires a running VM ("podman
# machine"). Handles "machine exists but stopped" -> start it. Does NOT
# handle "no machine exists at all" -- that needs a one-time manual
# `podman machine init`, left as a documented prerequisite since it's a
# heavier, resource-allocating operation.
#
# Written as a single shell line deliberately: macOS ships GNU Make 3.81
# (frozen pre-GPLv3), which predates `.ONESHELL:` (added in 3.82). Without
# it, Make runs a separate shell per newline in the *expanded* recipe text,
# which would split an if/then/fi block from a multi-line variable across
# unrelated shell invocations and break it. A single logical line sidesteps
# the issue entirely and works on any Make version.
d e f i n e E N S U R E _ M A C H I N E
[ "$$(uname -s)" ! = "Darwin" ] | | p o d m a n m a c h i n e l i s t - - f o r m a t '{{.Running}}' 2 > / d e v / n u l l | g r e p - q '^true$$' | | { e c h o "==> No running podman machine found; starting default machine..." ; p o d m a n m a c h i n e s t a r t ; }
e n d e f
# Idempotent: creates the shared cross-container network once, reused on
# every subsequent invocation. Any other Podman dev container joined to this
# same network name can resolve and reach this sandbox by its workdir alias.
d e f i n e E N S U R E _ N E T W O R K
p o d m a n n e t w o r k e x i s t s $( NETWORK_NAME ) | | p o d m a n n e t w o r k c r e a t e $( NETWORK_NAME )
e n d e f
build :
@$( ENSURE_MACHINE)
podman build -t $( IMAGE) -f Containerfile .
opencode :
@$( ENSURE_MACHINE)
@$( ENSURE_NETWORK)
podman run $( RUN_FLAGS) $( IMAGE) opencode
shell :
@$( ENSURE_MACHINE)
@$( ENSURE_NETWORK)
podman run $( RUN_FLAGS) $( IMAGE) bash
2026-09-27 20:49:11 +02:00
# Builds the image, then installs a global `opencode-sandbox` shortcut on
# the host (see scripts/install-shortcut.sh for what "installs" means -- a
# shim in ~/.local/bin plus a PATH edit for the user's login shell). Kept out
# of the recipe itself: multi-line shell-with-conditionals in a Make recipe
# means doubling every $ as $$ and fighting Make's per-line shell splitting
# (see the ENSURE_MACHINE comment above) -- not worth it for something this
# size.
install : build
sh scripts/install-shortcut.sh " $( CURDIR) "