Opencode improvements
This commit is contained in:
@@ -20,3 +20,5 @@ Do not remove existing instructions unless they are outdated or wrong.
|
||||
`bash tests/run_tests.sh` (you can also run them separately)
|
||||
- When running tests from inside sloptrap, inherited `CODEX_HOME=/codex` plus `SLOPTRAP_PREFER_CODEX_HOME=1` can leak into host-style child launches; ignore that preference when `HOME` has been redirected elsewhere and the runtime hints still point into the inherited `/codex` tree.
|
||||
- Capability-enabled runs are Podman-only. `packet-capture` uses a dedicated helper container/pod, and host-network capture must prompt for an explicit acknowledgement on every runtime launch.
|
||||
- `agent=opencode` should download the latest Linux CLI release artifact from GitHub into the build context and verify its digest; it should not depend on a host-installed `opencode` binary.
|
||||
- For isolated networking, sloptrap exposes the host inside the container as `sloptrap.host`; opencode localhost URLs should be rewritten to that alias, and Podman `slirp4netns` runs need `allow_host_loopback=true` for host-local servers.
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Dockerfile.sloptrap
|
||||
ARG BASE_IMAGE=debian:trixie-slim
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 ripgrep xxd file procps util-linux"
|
||||
ARG EXTRA_PACKAGES=""
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG CODEX_UID=1337
|
||||
ARG CODEX_GID=1337
|
||||
RUN groupadd --gid ${CODEX_GID} sloptrap \
|
||||
&& useradd --create-home --home-dir /home/sloptrap \
|
||||
--gid sloptrap --uid ${CODEX_UID} --shell /bin/bash sloptrap
|
||||
|
||||
ARG CODEX_BIN=codex
|
||||
ARG CODEX_BIN_PATH=/usr/local/bin/codex
|
||||
COPY ${CODEX_BIN} ${CODEX_BIN_PATH}
|
||||
RUN chmod 0755 ${CODEX_BIN_PATH} \
|
||||
&& chown -R sloptrap:sloptrap /home/sloptrap
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
ENV SHELL=/bin/bash HOME=/home/sloptrap
|
||||
ENTRYPOINT ["${CODEX_BIN_PATH}"]
|
||||
@@ -63,8 +63,9 @@ Supported keys when the manifest is present:
|
||||
| `name` | project directory name | Must match `^[A-Za-z0-9_.-]+$`. Used for image/container naming. |
|
||||
| `packages_extra` | *empty* | Additional Debian packages installed during `docker/podman build`. Tokens must be alphanumeric plus `+.-`. |
|
||||
| `agent` | `codex` | AI backend: `codex` (OpenAI Codex CLI) or `opencode` (Anomaly opencode CLI). |
|
||||
| `opencode_server` | `http://localhost:11434` | OpenAI-compatible server URL (opencode only). Supports llama.cpp, Ollama, vLLM, etc. |
|
||||
| `opencode_model` | `llama3` | Model name on the server (opencode only). |
|
||||
| `opencode_server` | `http://localhost:8080` | OpenAI-compatible server URL (opencode only). Supports llama.cpp, Ollama, vLLM, etc. |
|
||||
| `opencode_model` | `bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0` | Model name on the server (opencode only). |
|
||||
| `opencode_context` | `256K` | Context window for the opencode model. Accepts an integer optionally suffixed with `K`, `M`, or `G`. |
|
||||
| `allow_host_network` | `false` | `true` opts into `--network host`; keep `false` unless the project absolutely requires direct access to host-local services. |
|
||||
|
||||
Values containing `$`, `` ` ``, or newlines are rejected to prevent command injection. Setting illegal keys or malformed values aborts the run before containers start.
|
||||
@@ -73,7 +74,7 @@ Values containing `$`, `` ` ``, or newlines are rejected to prevent command inje
|
||||
|
||||
**Codex** (default): Uses OpenAI Codex CLI with state stored in `~/.codex/`. Supports login mode for credential sharing.
|
||||
|
||||
**opencode**: Uses Anomaly opencode CLI with state stored in `~/.opencode/`. Connects to any OpenAI-compatible inference server (llama.cpp, Ollama, vLLM, etc.). No authentication required for self-hosted models; API keys supported via manifest if needed.
|
||||
**opencode**: Uses Anomaly opencode CLI with state stored in `~/.opencode/`. sloptrap downloads the latest Linux CLI release artifact from Anomaly during image builds, verifies its digest from the GitHub release metadata, and copies it into the container image. Connects to any OpenAI-compatible inference server (llama.cpp, Ollama, vLLM, etc.). When `opencode_server` points at `localhost` under isolated networking, sloptrap rewrites it to `http://sloptrap.host:...` so host-local model servers remain reachable from inside the container. No authentication required for self-hosted models; API keys supported via manifest if needed.
|
||||
|
||||
### `.sloptrapignore`
|
||||
|
||||
@@ -142,7 +143,7 @@ The launcher executes targets sequentially, so `./sloptrap repo build run` perfo
|
||||
- **Codex**: project directory at `/workspace`; `${HOME}/.codex/sloptrap/state/<project-hash>` at `/codex`; auth at `/codex/auth.json`.
|
||||
- **opencode**: project directory at `/workspace`; `${HOME}/.opencode/sloptrap/state/<project-hash>` at `/codex/state/opencode`; state at `/codex/state`.
|
||||
- Ignore filter: `.sloptrapignore` entries are overlaid with tmpfs directories or empty bind mounts so data remains unavailable to the agent.
|
||||
- Network: isolated networking is used by default; `allow_host_network=true` opts into `--network host`.
|
||||
- Network: isolated networking is used by default; `allow_host_network=true` opts into `--network host`. For isolated runs, sloptrap injects `sloptrap.host` as a container-side hostname for the host gateway. On Podman `slirp4netns`, opencode runs also enable host loopback access so host-local servers bound to `localhost` remain reachable.
|
||||
- Process context: standard runs drop capabilities, set `no-new-privileges`, use a read-only root filesystem, and keep scratch paths (`/tmp`, `/run`, `/run/lock`) on tmpfs.
|
||||
- Agent configuration:
|
||||
- **Codex**: runtime flags fixed to `--sandbox danger-full-access --ask-for-approval never`. Supports login mode for credential sharing.
|
||||
|
||||
512
sloptrap
512
sloptrap
@@ -242,7 +242,11 @@ ALLOW_HOST_NETWORK=false
|
||||
BACKEND="codex"
|
||||
OPENCODE_SERVER=""
|
||||
OPENCODE_MODEL=""
|
||||
OPENCODE_CONTEXT=""
|
||||
OPENCODE_STATE_HOME_HOST=""
|
||||
OPENCODE_CONFIG_HOST=""
|
||||
OPENCODE_CONFIG_CONT=""
|
||||
SLOPTRAP_HOST_ALIAS=""
|
||||
|
||||
declare -a SLOPTRAP_TEMP_PATHS=()
|
||||
|
||||
@@ -302,24 +306,35 @@ create_temp_dir() {
|
||||
|
||||
write_embedded_dockerfile() {
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
cat <<'EOF'
|
||||
local opencode_pkg
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64)
|
||||
opencode_pkg="opencode-desktop-linux-amd64.deb"
|
||||
;;
|
||||
arm64|aarch64)
|
||||
opencode_pkg="opencode-desktop-linux-arm64.deb"
|
||||
;;
|
||||
*)
|
||||
error "unsupported architecture for opencode"
|
||||
;;
|
||||
esac
|
||||
|
||||
local dockerfile_content
|
||||
dockerfile_content=$(cat <<'DOCKERFILE_EOF'
|
||||
# Dockerfile.sloptrap
|
||||
ARG BASE_IMAGE=debian:trixie-slim
|
||||
FROM ${BASE_IMAGE}
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 ripgrep xxd file procps util-linux"
|
||||
ARG EXTRA_PACKAGES=""
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
ARG BASE_PACKAGES="curl bash ca-certificates libstdc++6 wget ripgrep xxd file procps util-linux binutils"
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends apt-utils ${BASE_PACKAGES} ${EXTRA_PACKAGES} && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ARG CODEX_UID=1337
|
||||
ARG CODEX_GID=1337
|
||||
RUN groupadd --gid ${CODEX_GID} sloptrap \
|
||||
&& useradd --create-home --home-dir /home/sloptrap \
|
||||
--gid sloptrap --uid ${CODEX_UID} --shell /bin/bash sloptrap
|
||||
--gid sloptrap --uid ${CODEX_UID} --shell /bin/bash sloptrap
|
||||
|
||||
ARG OPENCODE_BIN=opencode
|
||||
ARG OPENCODE_CONF=config/config.toml
|
||||
@@ -330,8 +345,12 @@ RUN chmod 0755 /usr/local/bin/opencode \
|
||||
WORKDIR /workspace
|
||||
|
||||
ENV SHELL=/bin/bash HOME=/home/sloptrap
|
||||
ENTRYPOINT ["/usr/local/bin/opencode"]
|
||||
EOF
|
||||
ENTRYPOINT ["opencode"]
|
||||
DOCKERFILE_EOF
|
||||
)
|
||||
# Replace placeholder with actual package name
|
||||
dockerfile_content=$(printf '%s' "$dockerfile_content" | sed "s|PLACEHOLDER_OPENCODE_PKG|${opencode_pkg}|g")
|
||||
printf '%s' "$dockerfile_content"
|
||||
else
|
||||
cat <<'EOF'
|
||||
# Dockerfile.sloptrap
|
||||
@@ -725,6 +744,24 @@ validate_package_list() {
|
||||
done
|
||||
}
|
||||
|
||||
# Common package name aliases for user convenience
|
||||
expand_package_alias() {
|
||||
local pkg=$1
|
||||
case "$pkg" in
|
||||
rg) printf 'ripgrep' ;;
|
||||
git) printf 'git' ;;
|
||||
curl) printf 'curl' ;;
|
||||
bash) printf 'bash' ;;
|
||||
ca-certificates) printf 'ca-certificates' ;;
|
||||
libstdc++6) printf 'libstdc++6' ;;
|
||||
xxd) printf 'xxd' ;;
|
||||
file) printf 'file' ;;
|
||||
procps) printf 'procps' ;;
|
||||
util-linux) printf 'util-linux' ;;
|
||||
*) printf '%s' "$pkg" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
detect_container_engine() {
|
||||
local override=${SLOPTRAP_CONTAINER_ENGINE-}
|
||||
if [[ -n $override ]]; then
|
||||
@@ -815,6 +852,26 @@ normalize_wizard_allow_host_network() {
|
||||
esac
|
||||
}
|
||||
|
||||
parse_opencode_context() {
|
||||
local raw=$1
|
||||
local upper=${raw^^}
|
||||
local number suffix multiplier=1
|
||||
[[ -n $upper ]] || error "$MANIFEST_PATH: opencode_context must not be empty"
|
||||
if [[ $upper =~ ^([0-9]+)([KMG]?)$ ]]; then
|
||||
number=${BASH_REMATCH[1]}
|
||||
suffix=${BASH_REMATCH[2]}
|
||||
else
|
||||
error "$MANIFEST_PATH: opencode_context must be an integer optionally suffixed with K, M, or G (got '$raw')"
|
||||
fi
|
||||
case "$suffix" in
|
||||
"") multiplier=1 ;;
|
||||
K) multiplier=1024 ;;
|
||||
M) multiplier=$((1024 * 1024)) ;;
|
||||
G) multiplier=$((1024 * 1024 * 1024)) ;;
|
||||
esac
|
||||
printf '%s' $(( number * multiplier ))
|
||||
}
|
||||
|
||||
run_wizard() {
|
||||
local manifest_path=$1
|
||||
if [[ -L $manifest_path ]]; then
|
||||
@@ -832,13 +889,15 @@ run_wizard() {
|
||||
local default_allow_host_network
|
||||
local default_opencode_server
|
||||
local default_opencode_model
|
||||
local default_opencode_context
|
||||
|
||||
default_name=$(manifest_default_value "name" "$(basename "$CODE_DIR")")
|
||||
default_packages_extra=$(manifest_default_value "packages_extra" "")
|
||||
default_agent=$(manifest_default_value "agent" "codex")
|
||||
default_allow_host_network=$(manifest_default_value "allow_host_network" "false")
|
||||
default_opencode_server=$(manifest_default_value "opencode_server" "http://localhost:11434")
|
||||
default_opencode_model=$(manifest_default_value "opencode_model" "llama3")
|
||||
default_opencode_server=$(manifest_default_value "opencode_server" "http://localhost:8080")
|
||||
default_opencode_model=$(manifest_default_value "opencode_model" "bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0")
|
||||
default_opencode_context=$(manifest_default_value "opencode_context" "256K")
|
||||
|
||||
local action="Creating"
|
||||
if [[ -f $manifest_path ]]; then
|
||||
@@ -886,22 +945,32 @@ run_wizard() {
|
||||
# If opencode, prompt for server and model
|
||||
if [[ "$default_agent" == "opencode" ]]; then
|
||||
while true; do
|
||||
info_line "opencode_server: OpenAI-compatible server URL (e.g., http://localhost:11434).\n"
|
||||
info_line "opencode_server: OpenAI-compatible server URL (e.g., http://localhost:8080).\n"
|
||||
value=$(prompt_manifest_value "opencode_server" "$default_opencode_server")
|
||||
value=$(trim "$value")
|
||||
[[ -n $value ]] || value="http://localhost:11434"
|
||||
[[ -n $value ]] || value="http://localhost:8080"
|
||||
default_opencode_server=$value
|
||||
break
|
||||
done
|
||||
|
||||
while true; do
|
||||
info_line "opencode_model: Model name on the server (e.g., llama3).\n"
|
||||
info_line "opencode_model: Model name on the server (e.g., bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0).\n"
|
||||
value=$(prompt_manifest_value "opencode_model" "$default_opencode_model")
|
||||
value=$(trim "$value")
|
||||
[[ -n $value ]] || value="llama3"
|
||||
[[ -n $value ]] || value="bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0"
|
||||
default_opencode_model=$value
|
||||
break
|
||||
done
|
||||
|
||||
while true; do
|
||||
info_line "opencode_context: Context window for the model (e.g., 256K).\n"
|
||||
value=$(prompt_manifest_value "opencode_context" "$default_opencode_context")
|
||||
value=$(trim "$value")
|
||||
[[ -n $value ]] || value="256K"
|
||||
parse_opencode_context "$value" >/dev/null
|
||||
default_opencode_context=$value
|
||||
break
|
||||
done
|
||||
fi
|
||||
|
||||
while true; do
|
||||
@@ -924,6 +993,7 @@ EOF
|
||||
cat >> "$manifest_path" <<EOF
|
||||
opencode_server=$default_opencode_server
|
||||
opencode_model=$default_opencode_model
|
||||
opencode_context=$default_opencode_context
|
||||
EOF
|
||||
fi
|
||||
info_line "Wrote %s\n" "$manifest_path"
|
||||
@@ -948,7 +1018,10 @@ print_config() {
|
||||
info_line "backend=%s\n" "opencode"
|
||||
info_line "opencode_server=%s\n" "$OPENCODE_SERVER"
|
||||
info_line "opencode_model=%s\n" "$OPENCODE_MODEL"
|
||||
info_line "opencode_context=%s\n" "$OPENCODE_CONTEXT"
|
||||
info_line "opencode_state_home=%s\n" "$OPENCODE_STATE_HOME_HOST"
|
||||
info_line "opencode_config=%s\n" "$OPENCODE_CONFIG_HOST"
|
||||
info_line "runtime_flags=\n"
|
||||
else
|
||||
info_line "backend=%s\n" "codex"
|
||||
info_line "codex_home=%s\n" "$CODEX_STATE_HOME_HOST"
|
||||
@@ -959,9 +1032,10 @@ print_config() {
|
||||
info_line "codex_home_bootstrap=%s\n" "$CODEX_HOME_BOOTSTRAP"
|
||||
info_line "codex_archive=%s\n" "$SLOPTRAP_CODEX_ARCHIVE"
|
||||
info_line "codex_url=%s\n" "$SLOPTRAP_CODEX_URL"
|
||||
info_line "runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
|
||||
fi
|
||||
info_line "host_alias=%s\n" "${SLOPTRAP_HOST_ALIAS:-}"
|
||||
info_line "needs_login=%s\n" "$NEED_LOGIN"
|
||||
info_line "runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
|
||||
info_line "ignore_stub_base=%s\n" "$IGNORE_STUB_BASE"
|
||||
if [[ ${#SLOPTRAP_IGNORE_ENTRIES[@]} -gt 0 ]]; then
|
||||
local ignore_paths
|
||||
@@ -990,10 +1064,16 @@ print_manifest_summary() {
|
||||
comment_line " backend=%s\n" "opencode"
|
||||
comment_line " opencode_server=%s\n" "$OPENCODE_SERVER"
|
||||
comment_line " opencode_model=%s\n" "$OPENCODE_MODEL"
|
||||
comment_line " opencode_context=%s\n" "$OPENCODE_CONTEXT"
|
||||
comment_line " opencode_config=%s\n" "$OPENCODE_CONFIG_CONT"
|
||||
comment_line " runtime_flags=\n"
|
||||
else
|
||||
comment_line " backend=%s\n" "codex"
|
||||
comment_line " runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
|
||||
fi
|
||||
if [[ -n $SLOPTRAP_HOST_ALIAS ]]; then
|
||||
comment_line " host_alias=%s\n" "$SLOPTRAP_HOST_ALIAS"
|
||||
fi
|
||||
comment_line " runtime_flags=%s\n" "$CODEX_ARGS_DISPLAY"
|
||||
comment_line " allow_host_network=%s\n" "$ALLOW_HOST_NETWORK"
|
||||
}
|
||||
|
||||
@@ -1019,6 +1099,10 @@ Current resolved sloptrap state:
|
||||
- network_mode=$network_mode (host when host networking is enabled; otherwise isolated)
|
||||
EOF
|
||||
)
|
||||
if [[ -n $SLOPTRAP_HOST_ALIAS ]]; then
|
||||
prompt+=$'\n'
|
||||
prompt+="- host_alias=$SLOPTRAP_HOST_ALIAS (container-side address for services running on the host)\n"
|
||||
fi
|
||||
printf '%s' "$prompt"
|
||||
}
|
||||
|
||||
@@ -1063,7 +1147,7 @@ get_env_default() {
|
||||
|
||||
validate_codex_archive_name() {
|
||||
local name=$1
|
||||
[[ $name =~ ^codex-[A-Za-z0-9_.-]+$ ]] || error "invalid Codex archive name '$name'"
|
||||
[[ $name =~ ^(codex|opencode)-[A-Za-z0-9_.-]+$ ]] || error "invalid binary name '$name'"
|
||||
}
|
||||
|
||||
detect_codex_archive_name() {
|
||||
@@ -1186,6 +1270,106 @@ ensure_opencode_storage_paths() {
|
||||
ensure_codex_directory "$state_root" "sloptrap Opencode namespace"
|
||||
ensure_codex_directory "$state_bucket" "sloptrap Opencode state root"
|
||||
ensure_codex_directory "$CODEX_STATE_HOME_HOST" "project Opencode state"
|
||||
ensure_codex_directory "$OPENCODE_STATE_HOME_HOST" "project Opencode runtime state"
|
||||
}
|
||||
|
||||
ensure_opencode_config() {
|
||||
local provider_id="llama.cpp"
|
||||
local provider_name="llama-server (local)"
|
||||
local base_url="$OPENCODE_SERVER"
|
||||
local context_limit
|
||||
context_limit=$(parse_opencode_context "$OPENCODE_CONTEXT")
|
||||
local model_key="${OPENCODE_MODEL} - ${context_limit}"
|
||||
local model_ref="${provider_id}/${model_key}"
|
||||
local config_dir
|
||||
case "$base_url" in
|
||||
*/v1|*/v1/|*/v1\?*|*/v1\#*)
|
||||
;;
|
||||
*)
|
||||
base_url="${base_url%/}/v1"
|
||||
;;
|
||||
esac
|
||||
config_dir=$(dirname "$OPENCODE_CONFIG_HOST")
|
||||
if $DRY_RUN; then
|
||||
print_command mkdir -p "$config_dir"
|
||||
print_command env OPENCODE_CONFIG="$OPENCODE_CONFIG_CONT" opencode
|
||||
return 0
|
||||
fi
|
||||
ensure_codex_directory "$config_dir" "project Opencode config"
|
||||
if ! jq -n \
|
||||
--arg schema "https://opencode.ai/config.json" \
|
||||
--arg provider_id "$provider_id" \
|
||||
--arg provider_name "$provider_name" \
|
||||
--arg base_url "$base_url" \
|
||||
--arg model_id "$OPENCODE_MODEL" \
|
||||
--arg model_key "$model_key" \
|
||||
--arg model_ref "$model_ref" \
|
||||
--argjson context_limit "$context_limit" \
|
||||
'{
|
||||
"$schema": $schema,
|
||||
enabled_providers: [$provider_id],
|
||||
share: "disabled",
|
||||
autoupdate: false,
|
||||
provider: {
|
||||
($provider_id): {
|
||||
npm: "@ai-sdk/openai-compatible",
|
||||
name: $provider_name,
|
||||
options: {
|
||||
baseURL: $base_url,
|
||||
timeout: 1800000,
|
||||
chunkTimeout: 180000
|
||||
},
|
||||
models: {
|
||||
($model_key): {
|
||||
name: $model_id,
|
||||
limit: {
|
||||
context: $context_limit,
|
||||
output: 32768
|
||||
},
|
||||
cost: {
|
||||
input: 0.0221,
|
||||
output: 0.1684,
|
||||
cache_read: 0.0001,
|
||||
cache_write: 0.0001
|
||||
},
|
||||
tool_call: true,
|
||||
reasoning: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
model: $model_ref,
|
||||
compaction: {
|
||||
auto: true,
|
||||
prune: true,
|
||||
reserved: 16000
|
||||
},
|
||||
watcher: {
|
||||
ignore: [
|
||||
".git/**",
|
||||
"node_modules/**",
|
||||
"dist/**",
|
||||
".run/**"
|
||||
]
|
||||
},
|
||||
permission: {
|
||||
read: "allow",
|
||||
edit: "allow",
|
||||
glob: "allow",
|
||||
grep: "allow",
|
||||
list: "allow",
|
||||
bash: "allow",
|
||||
task: "allow",
|
||||
question: "allow",
|
||||
webfetch: "allow",
|
||||
websearch: "allow",
|
||||
codesearch: "allow",
|
||||
external_directory: "deny",
|
||||
doom_loop: "ask"
|
||||
}
|
||||
}' >"$OPENCODE_CONFIG_HOST"; then
|
||||
error "failed to write opencode config '$OPENCODE_CONFIG_HOST'"
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_latest_codex_digest() {
|
||||
@@ -1208,42 +1392,43 @@ fetch_latest_codex_digest() {
|
||||
printf '%s' "$digest_line"
|
||||
}
|
||||
|
||||
detect_opencode_archive_name() {
|
||||
local os arch opencode_os opencode_arch
|
||||
os=$(uname -s 2>/dev/null || true)
|
||||
arch=$(uname -m 2>/dev/null || true)
|
||||
[[ -n $os ]] || error "failed to detect host OS for opencode download"
|
||||
[[ -n $arch ]] || error "failed to detect host architecture for opencode download"
|
||||
case "$os" in
|
||||
Linux|Darwin) opencode_os="unknown-linux-gnu" ;;
|
||||
*) error "unsupported host OS '$os' for opencode download" ;;
|
||||
esac
|
||||
case "$arch" in
|
||||
x86_64|amd64) opencode_arch="x86_64" ;;
|
||||
arm64|aarch64) opencode_arch="arm64" ;;
|
||||
*) error "unsupported host architecture '$arch' for opencode download" ;;
|
||||
esac
|
||||
printf 'opencode-%s-%s' "$opencode_arch" "$opencode_os"
|
||||
detect_opencode_asset_name() {
|
||||
local arch
|
||||
arch=$(uname -m 2>/dev/null || true)
|
||||
[[ -n $arch ]] || error "failed to detect host architecture for opencode download"
|
||||
|
||||
case "$arch" in
|
||||
x86_64|amd64)
|
||||
printf 'opencode-linux-x64.tar.gz'
|
||||
;;
|
||||
arm64|aarch64)
|
||||
printf 'opencode-linux-arm64.tar.gz'
|
||||
;;
|
||||
*)
|
||||
error "unsupported host architecture '$arch' for opencode download"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
fetch_latest_opencode_digest() {
|
||||
local api_url="https://api.github.com/repos/anomalyco/opencode/releases/latest"
|
||||
local target_asset="${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"
|
||||
[[ -n $SLOPTRAP_CODEX_BIN_NAME ]] || error "opencode binary name is not set"
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
error "jq is required to verify the opencode binary digest"
|
||||
fi
|
||||
local response
|
||||
if ! response=$(curl -fsSL "$api_url"); then
|
||||
error "failed to download opencode release metadata from GitHub"
|
||||
fi
|
||||
local digest_line
|
||||
digest_line=$(jq -r --arg name "$target_asset" '.assets[] | select(.name == $name) | .digest' <<<"$response" | head -n 1)
|
||||
if [[ -z $digest_line || $digest_line == "null" ]]; then
|
||||
error "failed to resolve opencode digest from GitHub response"
|
||||
fi
|
||||
digest_line=${digest_line#sha256:}
|
||||
printf '%s' "$digest_line"
|
||||
local api_url="https://api.github.com/repos/anomalyco/opencode/releases/latest"
|
||||
local target_asset
|
||||
target_asset=$(detect_opencode_asset_name)
|
||||
[[ -n $target_asset ]] || error "opencode binary name is not set"
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
error "jq is required to verify the opencode binary digest"
|
||||
fi
|
||||
local response
|
||||
if ! response=$(curl -fsSL "$api_url"); then
|
||||
error "failed to download opencode release metadata from GitHub"
|
||||
fi
|
||||
local digest_line
|
||||
digest_line=$(jq -r --arg name "$target_asset" '.assets[] | select(.name == $name) | .digest' <<<"$response" | head -n 1)
|
||||
if [[ -z $digest_line || $digest_line == "null" ]]; then
|
||||
error "failed to resolve opencode digest from GitHub response (looking for $target_asset)"
|
||||
fi
|
||||
digest_line=${digest_line#sha256:}
|
||||
printf '%s' "$digest_line"
|
||||
}
|
||||
|
||||
ensure_codex_binary() {
|
||||
@@ -1285,28 +1470,30 @@ ensure_opencode_binary() {
|
||||
if [[ -x $CODEX_BIN_PATH ]]; then
|
||||
return 0
|
||||
fi
|
||||
local tar_transform="s/${SLOPTRAP_CODEX_BIN_NAME}/${SLOPTRAP_CODEX_BIN_NAME}/"
|
||||
local target_asset
|
||||
target_asset=$(detect_opencode_asset_name)
|
||||
local download_dir
|
||||
download_dir=$(create_temp_dir "opencode")
|
||||
local tmp_archive="$download_dir/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"
|
||||
local tmp_archive="$download_dir/$target_asset"
|
||||
local opencode_url="https://github.com/anomalyco/opencode/releases/latest/download/${target_asset}"
|
||||
if $DRY_RUN; then
|
||||
print_command curl -Lso "$tmp_archive" "https://github.com/anomalyco/opencode/releases/latest/download/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"
|
||||
print_command curl -Lso "$tmp_archive" "$opencode_url"
|
||||
print_command sha256sum -c -
|
||||
print_command tar -xzf "$tmp_archive" --transform="$tar_transform" -C "$SLOPTRAP_BUILD_CONTEXT"
|
||||
print_command tar -xzf "$tmp_archive" --transform="s/opencode/$SLOPTRAP_CODEX_BIN_NAME/" -C "$SLOPTRAP_BUILD_CONTEXT"
|
||||
print_command chmod 0755 "$CODEX_BIN_PATH"
|
||||
return 0
|
||||
fi
|
||||
if ! curl -Lso "$tmp_archive" "https://github.com/anomalyco/opencode/releases/latest/download/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"; then
|
||||
if ! curl -Lso "$tmp_archive" "$opencode_url"; then
|
||||
rm -rf "$download_dir"
|
||||
error "failed to download opencode binary from https://github.com/anomalyco/opencode/releases/latest/download/${SLOPTRAP_CODEX_BIN_NAME}.tar.gz"
|
||||
error "failed to download opencode binary from '$opencode_url'"
|
||||
fi
|
||||
local expected_digest
|
||||
expected_digest=$(fetch_latest_opencode_digest)
|
||||
if ! printf "%s %s\n" "$expected_digest" "$tmp_archive" | sha256sum -c - >/dev/null 2>&1; then
|
||||
rm -rf "$download_dir" "$CODEX_BIN_PATH"
|
||||
error "checksum verification failed for ${SLOPTRAP_CODEX_BIN_NAME}"
|
||||
error "checksum verification failed for $SLOPTRAP_CODEX_BIN_NAME"
|
||||
fi
|
||||
if ! tar -xzf "$tmp_archive" --transform="$tar_transform" -C "$SLOPTRAP_BUILD_CONTEXT"; then
|
||||
if ! tar -xzf "$tmp_archive" --transform="s/opencode/$SLOPTRAP_CODEX_BIN_NAME/" -C "$SLOPTRAP_BUILD_CONTEXT"; then
|
||||
rm -rf "$download_dir"
|
||||
error "failed to extract opencode binary"
|
||||
fi
|
||||
@@ -1345,7 +1532,13 @@ normalize_package_list() {
|
||||
[[ -z $raw ]] && return 0
|
||||
local -a tokens=()
|
||||
read -r -a tokens <<< "$raw"
|
||||
printf '%s' "${tokens[*]}"
|
||||
local -a normalized=()
|
||||
local token
|
||||
for token in "${tokens[@]}"; do
|
||||
# Expand package aliases
|
||||
normalized+=("$(expand_package_alias "$token")")
|
||||
done
|
||||
printf '%s' "${normalized[*]}"
|
||||
}
|
||||
|
||||
targets_need_build() {
|
||||
@@ -1364,6 +1557,30 @@ targets_need_build() {
|
||||
return 1
|
||||
}
|
||||
|
||||
normalize_local_server_url() {
|
||||
local url=$1
|
||||
local replacement_host=$2
|
||||
if [[ $url =~ ^([A-Za-z][A-Za-z0-9+.-]*://)(localhost|127\.0\.0\.1|\[::1\])([:/?#].*)?$ ]]; then
|
||||
printf '%s%s%s' "${BASH_REMATCH[1]}" "$replacement_host" "${BASH_REMATCH[3]}"
|
||||
return 0
|
||||
fi
|
||||
printf '%s' "$url"
|
||||
}
|
||||
|
||||
ensure_host_loopback_network_access() {
|
||||
if [[ $SLOPTRAP_NETWORK_NAME != slirp4netns* ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ $SLOPTRAP_NETWORK_NAME == *allow_host_loopback=* ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ $SLOPTRAP_NETWORK_NAME == "slirp4netns" ]]; then
|
||||
SLOPTRAP_NETWORK_NAME="slirp4netns:allow_host_loopback=true"
|
||||
else
|
||||
SLOPTRAP_NETWORK_NAME="${SLOPTRAP_NETWORK_NAME},allow_host_loopback=true"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_container_runtime() {
|
||||
resolve_container_workdir
|
||||
SLOPTRAP_PACKAGES_EXTRA_RESOLVED=$PACKAGES_EXTRA
|
||||
@@ -1390,7 +1607,7 @@ prepare_container_runtime() {
|
||||
SLOPTRAP_DOCKERFILE_SOURCE=""
|
||||
fi
|
||||
|
||||
SLOPTRAP_PACKAGES_BASE=$(get_env_default "SLOPTRAP_PACKAGES" "curl bash ca-certificates libstdc++6 git ripgrep xxd file procps util-linux")
|
||||
SLOPTRAP_PACKAGES_BASE=$(get_env_default "SLOPTRAP_PACKAGES" "curl bash ca-certificates libstdc++6 wget git ripgrep xxd file procps util-linux")
|
||||
validate_package_list "SLOPTRAP_PACKAGES" "$SLOPTRAP_PACKAGES_BASE" "SLOPTRAP_PACKAGES"
|
||||
local default_codex_archive
|
||||
default_codex_archive=$(detect_codex_archive_name)
|
||||
@@ -1413,8 +1630,15 @@ prepare_container_runtime() {
|
||||
SLOPTRAP_CODEX_ARCHIVE=$inferred_archive
|
||||
fi
|
||||
fi
|
||||
SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "codex")
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "opencode")
|
||||
else
|
||||
SLOPTRAP_CODEX_BIN_NAME=$(get_env_default "SLOPTRAP_CODEX_BIN" "codex")
|
||||
fi
|
||||
SLOPTRAP_CODEX_HOME_CONT=$(get_env_default "SLOPTRAP_CODEX_HOME_CONT" "/codex")
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
OPENCODE_CONFIG_CONT="$SLOPTRAP_CODEX_HOME_CONT/config/opencode/opencode.json"
|
||||
fi
|
||||
SLOPTRAP_CODEX_UID=$(get_env_default "SLOPTRAP_CODEX_UID" "1337")
|
||||
SLOPTRAP_CODEX_GID=$(get_env_default "SLOPTRAP_CODEX_GID" "1337")
|
||||
SLOPTRAP_SECURITY_OPTS_EXTRA=$(get_env_default "SLOPTRAP_SECURITY_OPTS_EXTRA" "")
|
||||
@@ -1432,6 +1656,9 @@ prepare_container_runtime() {
|
||||
elif [[ $SLOPTRAP_NETWORK_NAME == "host" ]]; then
|
||||
error "$MANIFEST_PATH: host networking requires allow_host_network=true"
|
||||
fi
|
||||
if [[ "$BACKEND" == "opencode" && $ALLOW_HOST_NETWORK == false ]]; then
|
||||
ensure_host_loopback_network_access
|
||||
fi
|
||||
SLOPTRAP_LIMITS_PID=$(get_env_default "SLOPTRAP_LIMITS_PID" "1024")
|
||||
SLOPTRAP_LIMITS_RAM=$(get_env_default "SLOPTRAP_LIMITS_RAM" "1024m")
|
||||
SLOPTRAP_LIMITS_SWP=$(get_env_default "SLOPTRAP_LIMITS_SWP" "1024m")
|
||||
@@ -1510,6 +1737,7 @@ prepare_container_runtime() {
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
env_args+=(
|
||||
-e "OPENCODE_HOME=$SLOPTRAP_CODEX_HOME_CONT"
|
||||
-e "OPENCODE_CONFIG=$OPENCODE_CONFIG_CONT"
|
||||
-e "OPENCODE_SERVER=$OPENCODE_SERVER"
|
||||
-e "OPENCODE_MODEL=$OPENCODE_MODEL"
|
||||
)
|
||||
@@ -1527,6 +1755,16 @@ prepare_container_runtime() {
|
||||
env_args+=(-e "SLOPTRAP_HOST_USER=$user")
|
||||
fi
|
||||
local -a network_opts=(--network "$SLOPTRAP_NETWORK_NAME")
|
||||
if [[ $ALLOW_HOST_NETWORK == false ]]; then
|
||||
SLOPTRAP_HOST_ALIAS="sloptrap.host"
|
||||
network_opts+=(--add-host "$SLOPTRAP_HOST_ALIAS:host-gateway")
|
||||
env_args+=(-e "SLOPTRAP_HOST_ALIAS=$SLOPTRAP_HOST_ALIAS")
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
OPENCODE_SERVER=$(normalize_local_server_url "$OPENCODE_SERVER" "$SLOPTRAP_HOST_ALIAS")
|
||||
fi
|
||||
else
|
||||
SLOPTRAP_HOST_ALIAS=""
|
||||
fi
|
||||
local -a user_opts=("--user" "$uid:$gid")
|
||||
if [[ $CONTAINER_ENGINE == "podman" ]]; then
|
||||
user_opts=(--userns="keep-id:uid=$uid,gid=$gid" "${user_opts[@]}")
|
||||
@@ -1560,55 +1798,57 @@ prepare_container_runtime() {
|
||||
}
|
||||
|
||||
build_image() {
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
ensure_opencode_binary
|
||||
else
|
||||
ensure_codex_binary
|
||||
fi
|
||||
if [[ $SKIP_BUILD_BANNER != true ]]; then
|
||||
print_banner
|
||||
fi
|
||||
print_manifest_summary
|
||||
local extra_packages_arg
|
||||
extra_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_EXTRA_RESOLVED")
|
||||
if ! $DRY_RUN; then
|
||||
status_line "Building %s\n" "$SLOPTRAP_IMAGE_NAME"
|
||||
fi
|
||||
local -a cmd=(
|
||||
"$CONTAINER_ENGINE" build --quiet
|
||||
-t "$SLOPTRAP_IMAGE_NAME"
|
||||
-f "$SLOPTRAP_DOCKERFILE_PATH"
|
||||
--network "$SLOPTRAP_NETWORK_NAME"
|
||||
--label "$SLOPTRAP_IMAGE_LABEL"
|
||||
--build-arg "BASE_PACKAGES=$SLOPTRAP_PACKAGES_BASE"
|
||||
--build-arg "CODEX_BIN=$SLOPTRAP_CODEX_BIN_NAME"
|
||||
--build-arg "CODEX_UID=$SLOPTRAP_CODEX_UID"
|
||||
--build-arg "CODEX_GID=$SLOPTRAP_CODEX_GID"
|
||||
)
|
||||
if [[ -n $extra_packages_arg ]]; then
|
||||
cmd+=(--build-arg "EXTRA_PACKAGES=$extra_packages_arg")
|
||||
fi
|
||||
cmd+=("$SLOPTRAP_BUILD_CONTEXT")
|
||||
if $DRY_RUN; then
|
||||
run_or_print "${cmd[@]}"
|
||||
return
|
||||
fi
|
||||
prepare_build_context
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
ensure_opencode_binary
|
||||
else
|
||||
ensure_codex_binary
|
||||
fi
|
||||
if [[ $SKIP_BUILD_BANNER != true ]]; then
|
||||
print_banner
|
||||
fi
|
||||
print_manifest_summary
|
||||
local extra_packages_arg
|
||||
extra_packages_arg=$(normalize_package_list "$SLOPTRAP_PACKAGES_EXTRA_RESOLVED")
|
||||
if ! $DRY_RUN; then
|
||||
status_line "Building %s\n" "$SLOPTRAP_IMAGE_NAME"
|
||||
fi
|
||||
local -a cmd=(
|
||||
"$CONTAINER_ENGINE" build --quiet
|
||||
-t "$SLOPTRAP_IMAGE_NAME"
|
||||
-f "$SLOPTRAP_DOCKERFILE_PATH"
|
||||
--network "$SLOPTRAP_NETWORK_NAME"
|
||||
--label "$SLOPTRAP_IMAGE_LABEL"
|
||||
--build-arg "BASE_PACKAGES=$SLOPTRAP_PACKAGES_BASE"
|
||||
--build-arg "CODEX_BIN=$SLOPTRAP_CODEX_BIN_NAME"
|
||||
--build-arg "CODEX_UID=$SLOPTRAP_CODEX_UID"
|
||||
--build-arg "CODEX_GID=$SLOPTRAP_CODEX_GID"
|
||||
)
|
||||
if [[ -n $extra_packages_arg ]]; then
|
||||
cmd+=(--build-arg "EXTRA_PACKAGES=$extra_packages_arg")
|
||||
fi
|
||||
cmd+=("$SLOPTRAP_BUILD_CONTEXT")
|
||||
if $DRY_RUN; then
|
||||
run_or_print "${cmd[@]}"
|
||||
return
|
||||
fi
|
||||
|
||||
local build_output
|
||||
if ! build_output=$("${cmd[@]}"); then
|
||||
return 1
|
||||
fi
|
||||
build_output=$(trim "$build_output")
|
||||
if [[ -n $build_output ]]; then
|
||||
comment_line "Image %s\n" "$build_output"
|
||||
fi
|
||||
local build_output
|
||||
if ! build_output=$("${cmd[@]}"); then
|
||||
return 1
|
||||
fi
|
||||
build_output=$(trim "$build_output")
|
||||
if [[ -n $build_output ]]; then
|
||||
comment_line "Image %s\n" "$build_output"
|
||||
fi
|
||||
}
|
||||
|
||||
rebuild_image() {
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
ensure_opencode_binary
|
||||
else
|
||||
ensure_codex_binary
|
||||
prepare_build_context
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
ensure_opencode_binary
|
||||
else
|
||||
ensure_codex_binary
|
||||
fi
|
||||
if [[ $SKIP_BUILD_BANNER != true ]]; then
|
||||
print_banner
|
||||
@@ -1720,15 +1960,14 @@ run_codex_command() {
|
||||
local -a auth_mount=()
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
ensure_opencode_storage_paths
|
||||
ensure_opencode_config
|
||||
else
|
||||
ensure_codex_storage_paths
|
||||
fi
|
||||
append_auth_mount_arg false auth_mount
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" "$BACKEND")
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}")
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
cmd+=("--server" "$OPENCODE_SERVER")
|
||||
cmd+=("--model" "$OPENCODE_MODEL")
|
||||
cmd+=("--sandbox" "workspace-write")
|
||||
true
|
||||
else
|
||||
if [[ ${#CODEX_ARGS_ARRAY[@]} -gt 0 ]]; then
|
||||
cmd+=("${CODEX_ARGS_ARRAY[@]}")
|
||||
@@ -1744,9 +1983,13 @@ run_codex() {
|
||||
if ! $DRY_RUN; then
|
||||
status_line "Running %s\n" "$SLOPTRAP_IMAGE_NAME"
|
||||
fi
|
||||
local runtime_prompt
|
||||
runtime_prompt=$(build_runtime_context_prompt)
|
||||
run_codex_command "$runtime_prompt"
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
run_codex_command
|
||||
else
|
||||
local runtime_prompt
|
||||
runtime_prompt=$(build_runtime_context_prompt)
|
||||
run_codex_command "$runtime_prompt"
|
||||
fi
|
||||
}
|
||||
|
||||
run_login_target() {
|
||||
@@ -1757,19 +2000,23 @@ run_login_target() {
|
||||
status_line "Login %s\n" "$SLOPTRAP_IMAGE_NAME"
|
||||
fi
|
||||
append_auth_mount_arg true auth_mount
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" "codex" login)
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" login)
|
||||
run_runtime_container_cmd "${cmd[@]}"
|
||||
}
|
||||
|
||||
run_shell_target() {
|
||||
ensure_codex_storage_paths
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
ensure_opencode_storage_paths
|
||||
else
|
||||
ensure_codex_storage_paths
|
||||
fi
|
||||
local -a source_args=("$SLOPTRAP_IMAGE_NAME")
|
||||
local -a auth_mount=()
|
||||
if ! $DRY_RUN; then
|
||||
status_line "Shell %s\n" "$SLOPTRAP_IMAGE_NAME"
|
||||
fi
|
||||
append_auth_mount_arg false auth_mount
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" "${auth_mount[@]}" "${source_args[@]}" /bin/bash)
|
||||
local -a cmd=("${BASE_CONTAINER_CMD[@]}" --entrypoint /bin/bash "${auth_mount[@]}" "${source_args[@]}")
|
||||
run_runtime_container_cmd "${cmd[@]}"
|
||||
}
|
||||
|
||||
@@ -1778,7 +2025,11 @@ run_resume_target() {
|
||||
if ! $DRY_RUN; then
|
||||
status_line "Resume %s (%s)\n" "$SLOPTRAP_IMAGE_NAME" "$session_id"
|
||||
fi
|
||||
run_codex_command resume "$session_id"
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
run_codex_command --session "$session_id"
|
||||
else
|
||||
run_codex_command resume "$session_id"
|
||||
fi
|
||||
}
|
||||
|
||||
process_resume_target() {
|
||||
@@ -1930,10 +2181,6 @@ ensure_ignore_helper_root
|
||||
IGNORE_STUB_BASE="$IGNORE_HELPER_ROOT/session-${BASHPID:-$$}"
|
||||
resolve_sloptrap_ignore "$CODE_DIR"
|
||||
resolve_container_workdir
|
||||
NEED_LOGIN=false
|
||||
if [[ ! -s "$CODEX_AUTH_FILE_HOST" ]]; then
|
||||
NEED_LOGIN=true
|
||||
fi
|
||||
|
||||
TARGETS=("${TARGETS_INPUT[@]}")
|
||||
if [[ ${#TARGETS[@]} -eq 0 ]]; then
|
||||
@@ -1942,7 +2189,7 @@ fi
|
||||
|
||||
DEFAULT_TARGETS=("${TARGETS[@]}")
|
||||
|
||||
PACKAGES_EXTRA=${MANIFEST[packages_extra]-}
|
||||
PACKAGES_EXTRA=$(normalize_package_list "${MANIFEST[packages_extra]:-}")
|
||||
if [[ -n ${MANIFEST[allow_host_network]-} ]]; then
|
||||
case "${MANIFEST[allow_host_network],,}" in
|
||||
1|true|yes)
|
||||
@@ -1982,22 +2229,29 @@ select_backend() {
|
||||
opencode) BACKEND="opencode" ;;
|
||||
codex|*) BACKEND="codex" ;;
|
||||
esac
|
||||
|
||||
if [[ "$BACKEND" == "opencode" && ! -x "$(command -v opencode)" ]]; then
|
||||
error "opencode CLI not found; install from https://opencode.ai"
|
||||
fi
|
||||
}
|
||||
|
||||
select_backend
|
||||
|
||||
if [[ "$BACKEND" == "opencode" ]]; then
|
||||
OPENCODE_SERVER="${MANIFEST[opencode_server]:-http://localhost:11434}"
|
||||
OPENCODE_MODEL="${MANIFEST[opencode_model]:-llama3}"
|
||||
OPENCODE_SERVER="${MANIFEST[opencode_server]:-http://localhost:8080}"
|
||||
OPENCODE_MODEL="${MANIFEST[opencode_model]:-bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0}"
|
||||
OPENCODE_CONTEXT="${MANIFEST[opencode_context]:-256K}"
|
||||
OPENCODE_STATE_HOME_HOST="$CODEX_STATE_HOME_HOST/opencode"
|
||||
OPENCODE_CONFIG_HOST="$CODEX_STATE_HOME_HOST/config/opencode/opencode.json"
|
||||
OPENCODE_CONFIG_CONT=""
|
||||
else
|
||||
OPENCODE_SERVER=""
|
||||
OPENCODE_MODEL=""
|
||||
OPENCODE_CONTEXT=""
|
||||
OPENCODE_STATE_HOME_HOST=""
|
||||
OPENCODE_CONFIG_HOST=""
|
||||
OPENCODE_CONFIG_CONT=""
|
||||
fi
|
||||
|
||||
NEED_LOGIN=false
|
||||
if [[ "$BACKEND" != "opencode" && ! -s "$CODEX_AUTH_FILE_HOST" ]]; then
|
||||
NEED_LOGIN=true
|
||||
fi
|
||||
|
||||
CONTAINER_ENGINE="$(detect_container_engine)"
|
||||
|
||||
@@ -189,6 +189,10 @@ EOF
|
||||
cat >"$STUB_BIN/jq" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [[ ${1-} == "-n" ]]; then
|
||||
shift
|
||||
exec /usr/bin/jq -n "$@"
|
||||
fi
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-r)
|
||||
@@ -454,12 +458,28 @@ run_resume_omits_runtime_context() {
|
||||
if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then
|
||||
record_failure "resume_omits_runtime_context: resume should not receive startup prompt"
|
||||
fi
|
||||
if ! grep -q -- "codex --sandbox danger-full-access --ask-for-approval never resume $session_id" "$STUB_LOG"; then
|
||||
if ! grep -q -- "--sandbox danger-full-access --ask-for-approval never resume $session_id" "$STUB_LOG"; then
|
||||
record_failure "resume_omits_runtime_context: resume invocation missing"
|
||||
fi
|
||||
teardown_stub_env
|
||||
}
|
||||
|
||||
run_shell_target_uses_entrypoint() {
|
||||
local scenario_dir="$TEST_ROOT/opencode_localhost"
|
||||
printf '==> shell_target_uses_entrypoint\n'
|
||||
setup_stub_env
|
||||
if ! PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||
"$SLOPTRAP_BIN" "$scenario_dir" shell </dev/null >/dev/null 2>&1; then
|
||||
record_failure "shell_target_uses_entrypoint: shell target failed"
|
||||
teardown_stub_env
|
||||
return
|
||||
fi
|
||||
if ! grep -q -- "--entrypoint /bin/bash" "$STUB_LOG"; then
|
||||
record_failure "shell_target_uses_entrypoint: missing entrypoint override"
|
||||
fi
|
||||
teardown_stub_env
|
||||
}
|
||||
|
||||
run_auth_file_mount() {
|
||||
local scenario_dir
|
||||
scenario_dir=$(cd "$TEST_ROOT/resume_target" && pwd -P)
|
||||
@@ -732,6 +752,125 @@ run_wizard_build_trigger() {
|
||||
fi
|
||||
}
|
||||
|
||||
run_opencode_build_downloads_release_cli() {
|
||||
local scenario_dir="$TEST_ROOT/opencode_build"
|
||||
printf '==> opencode_build_downloads_release_cli\n'
|
||||
setup_stub_env
|
||||
mkdir -p "$scenario_dir"
|
||||
cat > "$scenario_dir/.sloptrap" <<'EOF'
|
||||
name=opencode-build
|
||||
packages_extra=
|
||||
agent=opencode
|
||||
allow_host_network=false
|
||||
EOF
|
||||
if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||
"$SLOPTRAP_BIN" "$scenario_dir" build >/dev/null 2>&1; then
|
||||
record_failure "opencode_build_downloads_release_cli: build failed"
|
||||
teardown_stub_env
|
||||
return
|
||||
fi
|
||||
if ! grep -q -- "FAKE PODMAN: build " "$STUB_LOG"; then
|
||||
record_failure "opencode_build_downloads_release_cli: build not invoked"
|
||||
fi
|
||||
teardown_stub_env
|
||||
}
|
||||
|
||||
run_opencode_localhost_rewrite() {
|
||||
local scenario_dir="$TEST_ROOT/opencode_localhost"
|
||||
printf '==> opencode_localhost_rewrite\n'
|
||||
setup_stub_env
|
||||
mkdir -p "$scenario_dir"
|
||||
cat > "$scenario_dir/.sloptrap" <<'EOF'
|
||||
name=opencode-localhost
|
||||
packages_extra=
|
||||
agent=opencode
|
||||
opencode_server=http://localhost:8080
|
||||
allow_host_network=false
|
||||
EOF
|
||||
cat > "$STUB_BIN/slirp4netns" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$STUB_BIN/slirp4netns"
|
||||
if ! env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||
"$SLOPTRAP_BIN" "$scenario_dir" </dev/null >/dev/null 2>&1; then
|
||||
record_failure "opencode_localhost_rewrite: run failed"
|
||||
teardown_stub_env
|
||||
return
|
||||
fi
|
||||
local config_path
|
||||
config_path=$(find "$STUB_HOME" -path '*/config/opencode/opencode.json' | head -n 1 || true)
|
||||
if [[ -z $config_path || ! -f $config_path ]]; then
|
||||
record_failure "opencode_localhost_rewrite: opencode config not written"
|
||||
teardown_stub_env
|
||||
return
|
||||
fi
|
||||
if ! grep -q -- "--network slirp4netns:allow_host_loopback=true" "$STUB_LOG"; then
|
||||
record_failure "opencode_localhost_rewrite: slirp host loopback not enabled"
|
||||
fi
|
||||
if ! grep -q -- "--add-host sloptrap.host:host-gateway" "$STUB_LOG"; then
|
||||
record_failure "opencode_localhost_rewrite: host alias not injected"
|
||||
fi
|
||||
if ! grep -q -- "OPENCODE_CONFIG=/codex/config/opencode/opencode.json" "$STUB_LOG"; then
|
||||
record_failure "opencode_localhost_rewrite: opencode config path not exported"
|
||||
fi
|
||||
if ! grep -q -- '"baseURL": "http://sloptrap.host:8080/v1"' "$config_path"; then
|
||||
record_failure "opencode_localhost_rewrite: localhost server not rewritten in config"
|
||||
fi
|
||||
if [[ $(jq -r '.provider["llama.cpp"].name' "$config_path") != "llama-server (local)" ]]; then
|
||||
record_failure "opencode_localhost_rewrite: provider name not merged into config"
|
||||
fi
|
||||
if [[ $(jq -r '.model' "$config_path") != "llama.cpp/bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0 - 262144" ]]; then
|
||||
record_failure "opencode_localhost_rewrite: opencode model not written to config"
|
||||
fi
|
||||
if [[ $(jq -r '.enabled_providers[0]' "$config_path") != "llama.cpp" ]]; then
|
||||
record_failure "opencode_localhost_rewrite: enabled_providers not merged into config"
|
||||
fi
|
||||
if [[ $(jq -r '.provider["llama.cpp"].models["bartowski/Qwen_Qwen3.5-9B-GGUF:Q8_0 - 262144"].limit.context' "$config_path") != "262144" ]]; then
|
||||
record_failure "opencode_localhost_rewrite: opencode context not written to config"
|
||||
fi
|
||||
if grep -q -- "--server " "$STUB_LOG"; then
|
||||
record_failure "opencode_localhost_rewrite: deprecated --server flag should not be passed"
|
||||
fi
|
||||
if grep -q -- "--sandbox workspace-write" "$STUB_LOG"; then
|
||||
record_failure "opencode_localhost_rewrite: codex sandbox args leaked into opencode run"
|
||||
fi
|
||||
if grep -q -- "You are running inside sloptrap" "$STUB_LOG"; then
|
||||
record_failure "opencode_localhost_rewrite: codex-style startup prompt should not be passed to opencode"
|
||||
fi
|
||||
teardown_stub_env
|
||||
}
|
||||
|
||||
run_opencode_print_config_runtime_flags() {
|
||||
local scenario_dir="$TEST_ROOT/opencode_print_config"
|
||||
printf '==> opencode_print_config_runtime_flags\n'
|
||||
setup_stub_env
|
||||
mkdir -p "$scenario_dir"
|
||||
cat > "$scenario_dir/.sloptrap" <<'EOF'
|
||||
name=opencode-print-config
|
||||
packages_extra=
|
||||
agent=opencode
|
||||
allow_host_network=false
|
||||
EOF
|
||||
local output
|
||||
if ! output=$(env PATH="$STUB_BIN:$PATH" HOME="$STUB_HOME" FAKE_PODMAN_LOG="$STUB_LOG" FAKE_PODMAN_INSPECT_FAIL=1 \
|
||||
"$SLOPTRAP_BIN" --print-config "$scenario_dir" 2>/dev/null); then
|
||||
record_failure "opencode_print_config_runtime_flags: print-config failed"
|
||||
teardown_stub_env
|
||||
return
|
||||
fi
|
||||
if ! grep -q 'runtime_flags=' <<<"$output"; then
|
||||
record_failure "opencode_print_config_runtime_flags: runtime_flags line missing for opencode"
|
||||
fi
|
||||
if ! grep -q 'opencode_config=' <<<"$output"; then
|
||||
record_failure "opencode_print_config_runtime_flags: opencode config path missing"
|
||||
fi
|
||||
if grep -q -- '--sandbox danger-full-access --ask-for-approval never' <<<"$output"; then
|
||||
record_failure "opencode_print_config_runtime_flags: codex runtime flags leaked into opencode config"
|
||||
fi
|
||||
teardown_stub_env
|
||||
}
|
||||
|
||||
run_symlink_escape
|
||||
run_manifest_injection
|
||||
run_helper_symlink
|
||||
@@ -740,6 +879,7 @@ run_resume_target
|
||||
run_runtime_context_prompt
|
||||
run_sh_reexec
|
||||
run_resume_omits_runtime_context
|
||||
run_shell_target_uses_entrypoint
|
||||
run_auth_file_mount
|
||||
run_codex_home_override
|
||||
run_project_state_isolation
|
||||
@@ -749,6 +889,9 @@ run_root_directory_project
|
||||
run_wizard_create_manifest
|
||||
run_wizard_existing_defaults
|
||||
run_wizard_build_trigger
|
||||
run_opencode_build_downloads_release_cli
|
||||
run_opencode_localhost_rewrite
|
||||
run_opencode_print_config_runtime_flags
|
||||
|
||||
if [[ ${#failures[@]} -gt 0 ]]; then
|
||||
printf '\nTest failures:\n'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name=wizard_build
|
||||
packages_extra=
|
||||
capabilities=
|
||||
agent=codex
|
||||
allow_host_network=false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name=wizard_empty
|
||||
packages_extra=
|
||||
capabilities=
|
||||
agent=codex
|
||||
allow_host_network=false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name=custom-wizard
|
||||
packages_extra=make git
|
||||
capabilities=apt-install
|
||||
agent=codex
|
||||
allow_host_network=true
|
||||
|
||||
Reference in New Issue
Block a user