diff --git a/.opencode/commands/git-commit.md b/.opencode/commands/git-commit.md new file mode 100644 index 0000000..80e20b6 --- /dev/null +++ b/.opencode/commands/git-commit.md @@ -0,0 +1,51 @@ +--- +description: Review, document, and commit approved changes using Conventional Commits +agent: build +subtask: true +--- + +Commit the current approved changes using Conventional Commits. + +Workflow: + +1. Review `git status --short`. +2. Inspect the staged and unstaged diff for the files that will be committed. +3. Do not commit credentials, secrets, generated caches, local editor files, or unrelated changes. +4. If unrelated changes are present, ask the user whether to include or exclude them before committing. +5. If `CHANGELOG.md` already exists and the change is user-facing, update it before committing. Do not create a new changelog unless the user explicitly asks for one. +6. Read `WORKLOG.md` if it exists. +7. Run the smallest useful verification command available in the project. If no verification command exists, say that explicitly. +8. Append a concise entry to `WORKLOG.md` before committing. +9. Stage only approved files. +10. Use a concise Conventional Commit message, for example `feat: add sandbox commands` or `fix: preserve opencode home path`. +11. After committing, show the commit hash and final `git status --short`. + +Use this exact timestamp command for the worklog: + +```bash +TZ=Europe/Berlin date "+%d.%m.%Y, %H:%M (%Z)" +``` + +Append this shape to `WORKLOG.md`: + +```markdown +--- + +## Commit: + +**Status:** Done +**Date:** + +### What Changed + +- File: `` — + +### Findings + +- Verification: +- Commit: `` + +### Pending + +- +``` diff --git a/.opencode/commands/refactor-apply.md b/.opencode/commands/refactor-apply.md new file mode 100644 index 0000000..c8cb479 --- /dev/null +++ b/.opencode/commands/refactor-apply.md @@ -0,0 +1,50 @@ +--- +description: Apply one approved refactor, verify it, and update WORKLOG.md +agent: build +subtask: true +--- + +Apply only the refactor requested in `$ARGUMENTS`. + +If `$ARGUMENTS` is empty or the requested scope is unclear, ask the user to name the approved refactor before editing files. + +Workflow: + +1. Read `WORKLOG.md` if it exists. +2. Identify the smallest file/symbol scope needed for `$ARGUMENTS`. +3. Keep behavior unchanged. +4. Keep changes focused to the approved files or symbols. +5. Do not include unrelated cleanup. +6. Run the smallest useful verification command available in the project. If none exists, say that explicitly. +7. Append a concise entry to `WORKLOG.md` before finishing. +8. Summarize changed files and verification results. + +Use this exact timestamp command for the worklog: + +```bash +TZ=Europe/Berlin date "+%d.%m.%Y, %H:%M (%Z)" +``` + +Append this shape to `WORKLOG.md`: + +```markdown +--- + +## Refactor: + +**Status:** Done +**Date:** + +### What Changed + +- File: `` — + +### Findings + +- +- Verification: + +### Pending + +- +``` diff --git a/.opencode/commands/refactor-audit.md b/.opencode/commands/refactor-audit.md new file mode 100644 index 0000000..ec5ac45 --- /dev/null +++ b/.opencode/commands/refactor-audit.md @@ -0,0 +1,20 @@ +--- +description: Audit refactor opportunities without editing files +agent: plan +subtask: true +--- + +Analyze the target given in `$ARGUMENTS` for code smells, duplicate code, unnecessary complexity, and technical debt. + +If `$ARGUMENTS` is empty, inspect the current git diff first and then the smallest relevant surrounding files. + +Do not edit files. Do not update `WORKLOG.md`; this is a read-only planning command unless the user explicitly asks you to document the audit. + +Produce a concise audit with: + +1. Findings ordered by impact +2. Exact files and symbols involved +3. Suggested refactor steps +4. Behavioral risks and verification needed + +Ask the user which items should be implemented before making changes. diff --git a/.opencode/skills/write-worklog/SKILL.md b/.opencode/skills/write-worklog/SKILL.md index 29fc318..bbc28bf 100644 --- a/.opencode/skills/write-worklog/SKILL.md +++ b/.opencode/skills/write-worklog/SKILL.md @@ -1,27 +1,27 @@ --- name: write-worklog -description: Maintain a WORKLOG.md file in the project directory to track task progress, decisions, and outcomes. Use when completing tasks, making file changes, or working on multi-step project work. +description: Append structured WORKLOG.md entries for task progress, decisions, and outcomes. Use when a project requires a detailed worklog entry or the user asks for one. --- # Write Worklog -Maintain a file called `WORKLOG.md` in the current project working directory at all times. This logs every task you complete, keeping context available for later sessions. +Append structured entries to `WORKLOG.md` in the current project working directory. This keeps concrete task context available for later sessions. ## Core rules ### Before starting a task -1. Read `WORKLOG.md` to understand prior context and what is still pending. -2. If the file does not exist, create it before doing anything else. +1. Read `WORKLOG.md` if it exists to understand prior context and pending work. +2. If the file does not exist, create it when a worklog entry is required. ### After completing a task -1. Append a new entry to `WORKLOG.md` immediately. -2. Do NOT proceed to the next task until the entry is written. +1. Append a new entry to `WORKLOG.md`. +2. Keep the entry concise and specific. ### Workflow checklist -Copy this checklist and track progress before moving on: +Use this checklist: ``` Worklog: @@ -30,7 +30,7 @@ Worklog: - [ ] Include files changed and exact lines modified - [ ] Add concrete findings, not vague summaries - [ ] List pending items -- [ ] Verify entry is written before continuing +- [ ] Verify entry is written ``` ## Entry format @@ -38,11 +38,17 @@ Worklog: Each entry MUST include all of the following: - Task heading (descriptive summary) -- **Status** and **Date** (DD.MM.YYYY, HH:MM - CET/CEZ, Europe/Berlin) +- **Status** and **Date** (DD.MM.YYYY, HH:MM - CET/CEST, Europe/Berlin) - **What was done** (files changed, exact lines modified) - **What was found** (issues, bugs, observations with concrete specifics) - **Pending** (outstanding items or follow-ups) +For the timestamp, use: + +```bash +TZ=Europe/Berlin date "+%d.%m.%Y, %H:%M (%Z)" +``` + Use this structure: ```markdown @@ -51,15 +57,15 @@ Use this structure: ## **Status:** Done | In Progress | Pending -**Date:** DD.MM.YYYY, HH:MM (CET/CEZ) +**Date:** DD.MM.YYYY, HH:MM (CET/CEST) -### What was done +### What Changed - File: `path/to/file` — changed / created / removed - Specific detail of what was modified - Exact lines or sections if applicable -### What was found +### Findings - Concrete issues, bugs, or observations - Specific file paths and line numbers @@ -70,9 +76,7 @@ Use this structure: - Any blockers or follow-ups ``` -## Do not +## Avoid -- Do NOT proceed to the next task without updating the worklog. - Do NOT write vague summaries — include exact details. -- Do NOT skip entries, even for small tasks. - Do NOT use relative time references without timestamps. diff --git a/Dockerfile b/Dockerfile index 6659cbb..6ff407e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM ubuntu:26.04@sha256:f3d28607ddd78734bb7f71f117f3c6706c666b8b76cbff7c9ff6e5718d46ff64 -ARG ENABLE_NODEJS=true +ARG ENABLE_NODEJS=false ARG ENABLE_PYTHON=false ARG ENABLE_RUST=false ARG PYTHON_VERSION=3.13 @@ -14,7 +14,7 @@ ENV DEBIAN_FRONTEND=noninteractive \ ENABLE_PYTHON=${ENABLE_PYTHON} \ ENABLE_RUST=${ENABLE_RUST} \ PYTHON_VERSION=${PYTHON_VERSION} \ - # Pin tool data dirs explicitly — survives the HOME override in entrypoint.sh + # Pin tool data dirs explicitly so subprocesses find language toolchains reliably CARGO_HOME=/home/opencode/.cargo \ RUSTUP_HOME=/home/opencode/.rustup \ # All user tool bins in PATH — inherited by every subprocess after exec gosu @@ -27,6 +27,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ git \ gosu \ + ripgrep \ + tzdata \ openssh-client \ && apt-get autoremove -y \ && apt-get clean \ diff --git a/README.md b/README.md index a817c5f..982bc29 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# A hardend Docker sandbox setup for opencode +# A hardened Docker sandbox setup for opencode -With OpenCode + vLLM (Gemma 4 26B MoE) configs +With OpenCode + vLLM local model configs -Run OpenCode as a sandboxed, hardenend non-root Docker container connected to a self-hosted vLLM inference server. No cloud API keys required. +Run OpenCode as a sandboxed, hardened non-root Docker container connected to a self-hosted vLLM inference server. No cloud API keys required. --- ## Table of Contents -- [A hardend Docker sandbox setup for opencode](#a-hardend-docker-sandbox-setup-for-opencode) +- [A hardened Docker sandbox setup for opencode](#a-hardened-docker-sandbox-setup-for-opencode) - [Table of Contents](#table-of-contents) - [Prerequisites](#prerequisites) - [Directory Structure](#directory-structure) @@ -22,6 +22,7 @@ Run OpenCode as a sandboxed, hardenend non-root Docker container connected to a - [Security Notes](#security-notes) - [Feature Toggles](#feature-toggles) - [Using Tools and Skills](#using-tools-and-skills) + - [Commands](#commands) - [Skills](#skills) @@ -37,9 +38,9 @@ Verify your vLLM is reachable before starting: curl http://10.0.0.13:8000/v1/models ``` -You should see your model ID in the response (e.g. `gemma4-26b-a4b`). +You should see your model ID in the response (e.g. `qwen3.6-35b`). -Use the exact `"id"` value from the response — e.g. `gemma4-26b-a4b`. +Use the exact `"id"` value from the response — e.g. `qwen3.6-35b`. **Finding your context size:** The `max_model_len` field in the `/v1/models` response is your context limit. Use that value for `"context"`. @@ -57,9 +58,10 @@ opencode-sandbox/ ├── start.sh ├── config/ │ ├── opencode.json +│ ├── AGENTS.md ← global sandbox rules (mounted read-only) │ └── auth.json ← provider auth tokens (mounted read-only) ├── data/ ← opencode session state, persisted across runs -├── .opencode/ ← skills and agent config (optional) +├── .opencode/ ← global sandbox commands and skills (mounted read-only) └── workspace/ ← put your code projects here ``` @@ -88,7 +90,7 @@ Inside the TUI: 1. Press `/model` — your model should appear under your provider name with an orange dot 2. Type `hello, what model are you?` — the response should mention your model ID -3. Check the status bar at the bottom — it should show `Gemma 4 26B MoE · vLLM (Gemma4 local)` +3. Check the status bar at the bottom — it should show your configured model, for example `Qwen3.6 35B A3B · vLLM` 4. Check the right panel — `$0.00 spent` confirms no cloud API is being used --- @@ -97,7 +99,7 @@ Inside the TUI: ### Working with files -Drop files into `./workspace/` on your host. They appear at `~/workspace/` inside the container. OpenCode operates within this directory and cannot access anything outside it. +Drop files into `./workspace/` on your host. They appear at `/home/opencode/workspace/` inside the container. OpenCode starts in this directory; the global config still lives under `/home/opencode/.config/opencode/`. ```bash # Copy a project into the sandbox @@ -146,7 +148,7 @@ If this fails, your vLLM IP is unreachable from the container. Use the actual ho **Tool calling loops or model halts mid-task** -This is a known Gemma 4 behavior with agentic tool use. Mitigations: +Some local models can struggle with long agentic tool-use loops. Mitigations: - Prefer **Ask mode** for questions and code review that don't require file editing - For Build mode, give explicit step-by-step instructions rather than open-ended goals @@ -165,7 +167,7 @@ The container starts as root to handle setup (creating the user, fixing file own - **`cap_add: CHOWN, SETUID, SETGID, DAC_OVERRIDE`** — only the four capabilities the entrypoint actually needs for its setup phase are added back. Once `gosu` drops to the non-root user, the kernel automatically clears the effective capability set on the UID transition, and `no-new-privileges` blocks any path to reclaiming them. - **`PUID` / `PGID`** — the in-container user is created at runtime with the same UID/GID as your host user. This ensures bind-mounted files in `./workspace` and `./data` have correct ownership on both sides of the mount. - Bridge networking only — isolated from the host network -- Filesystem access limited to `./workspace` and `./data` on the host +- Writable filesystem access is limited to `./workspace` and `./data` on the host. Config, commands, skills, and auth are mounted read-only. The model runs entirely on your local vLLM server. No data leaves your network. @@ -187,18 +189,34 @@ docker compose build --build-arg ENABLE_PYTHON=true --build-arg PYTHON_VERSION=3 ./start.sh --no-cache # rebuild with new toggles ``` -All runtimes are installed at **build time** under the `opencode` user, so the container starts instantly with no network downloads at runtime. The tool binaries are on `PATH` and their data directories (`CARGO_HOME`, `RUSTUP_HOME`) are pinned via environment variables so they survive the `HOME` override that redirects opencode's session state to the mounted workspace. +All runtimes are installed at **build time** under the `opencode` user, so the container starts instantly with no language runtime downloads at startup. Base tooling includes `ripgrep` for OpenCode search tools and `tzdata` for correct Europe/Berlin timestamps. The tool binaries are on `PATH` and their data directories (`CARGO_HOME`, `RUSTUP_HOME`) are pinned via environment variables. --- ## Using Tools and Skills +### Commands + +Sandbox-wide commands and skills are mounted globally: + +```yaml +- ./config/AGENTS.md:/home/opencode/.config/opencode/AGENTS.md:ro +- ./.opencode/commands:/home/opencode/.config/opencode/commands:ro +- ./.opencode/skills:/home/opencode/.config/opencode/skills:ro +``` + +This makes the commands available regardless of which project under `./workspace/` you open. Project-specific commands, skills, and `AGENTS.md` files can still live inside the project directory. + +`AGENTS.md` is intentionally short: it gives global orientation. Repeatable process requirements live directly in the commands, because local models follow concrete command workflows more reliably than broad standing instructions. + +Available sandbox commands: + +- `/refactor-audit ` — analyze refactor opportunities without editing files +- `/refactor-apply ` — apply one focused approved refactor, verify it, and update `WORKLOG.md` +- `/git-commit` — review, document, and commit approved changes using Conventional Commits + ### Skills -Skills a special capabilities for an Agent that tells him how to do things or how to handle tools. -They have a specific Format and a SKILL.md file is mandatory. -Read more about Skills here https://agentskills.io/home +Skills are reusable on-demand capabilities for an agent. They use one directory per skill with a mandatory `SKILL.md`. -You have 2 options to use them -- just copy over the `.opencode` folder into your workspace. Opencode will then recognize them -- just add them to the worklog example skill in the `compose.yml` \ No newline at end of file +The included `write-worklog` skill provides a structured `WORKLOG.md` entry format for ad-hoc tasks. Command-driven workflows inline their own worklog format so they do not depend on automatic skill selection. diff --git a/compose.yml b/compose.yml index ceb7dfc..600167a 100644 --- a/compose.yml +++ b/compose.yml @@ -17,14 +17,16 @@ services: volumes: # Config files (read-only mounts) - ./config/opencode.json:/home/opencode/.config/opencode/opencode.json:ro - - ./.opencode/skills/write-worklog/SKILL.md:/home/opencode/.config/opencode/skills/write-worklog/SKILL.md:ro - - ./config/auth.json:/home/opencode/.local/share/opencode/auth.json:ro + - ./config/AGENTS.md:/home/opencode/.config/opencode/AGENTS.md:ro + - ./.opencode/commands:/home/opencode/.config/opencode/commands:ro + - ./.opencode/skills:/home/opencode/.config/opencode/skills:ro # Your project workspace (read-write so opencode can edit files) - ./workspace:/home/opencode/workspace:rw - ./data:/home/opencode/.local/share/opencode + - ./config/auth.json:/home/opencode/.local/share/opencode/auth.json:ro # No network_mode: host — use bridge so container is isolated # but can still reach your vLLM server via host IP extra_hosts: - "host.docker.internal:host-gateway" environment: - - HOME=/home/opencode \ No newline at end of file + - HOME=/home/opencode diff --git a/config/AGENTS.md b/config/AGENTS.md new file mode 100644 index 0000000..4fee28d --- /dev/null +++ b/config/AGENTS.md @@ -0,0 +1,23 @@ +# OpenCode Sandbox Rules + +All project work happens under `/home/opencode/workspace`. Treat this directory as the sandbox boundary. Do not read or modify files outside it unless the user explicitly asks for that. + +When starting work in an existing project directory, check whether `WORKLOG.md` exists and read it for prior context. Commands that change files define their own required worklog steps; follow the command workflow exactly. + +Create a new subdirectory under `/home/opencode/workspace` only when the user asks for a new standalone task or project and no suitable project directory already exists. + +Prefer focused changes over broad rewrites. Before large refactors, first produce a short audit and wait for the user to choose what should be changed. + +## Sandbox Commands + +Use the mounted sandbox commands for repeatable workflows when they fit the task: + +- `/refactor-audit `: inspect a target for refactor opportunities without editing files. +- `/refactor-apply `: apply one focused refactor after the user has approved the scope, then verify and update `WORKLOG.md`. +- `/git-commit`: review, document, and commit approved changes using Conventional Commits. + +Do not treat commands as mandatory for every task. They are shortcuts for user-invoked workflows and should not replace direct, focused work when the user has already given a clear instruction. + +## Skills + +Use the `write-worklog` skill only when a detailed `WORKLOG.md` entry is needed or the user asks for worklog formatting. For command-driven workflows, prefer the worklog format embedded in the command itself. diff --git a/config/opencode-with-vllm-side.json b/config/opencode-with-vllm-side.json index c3e4077..fba9ee0 100644 --- a/config/opencode-with-vllm-side.json +++ b/config/opencode-with-vllm-side.json @@ -3,7 +3,7 @@ "provider": { "vllm": { "npm": "@ai-sdk/openai-compatible", - "name": "vLLM (Gemma4 local)", + "name": "vLLM (Qwen3.6 local)", "options": { "baseURL": "http://10.0.0.13:8000/v1" }, @@ -41,7 +41,7 @@ "small_model": "vllm-side/qwen3-14b", "agent": { "build": { - "prompt": "You must maintain a file called WORKLOG.md in the current project working directory at all times.\\n\\nRules:\\n- Before starting any task, check if WORKLOG.md exists and read it to understand prior context.\\n- After completing EACH task, immediately update WORKLOG.md with: what was done (specific files changed, exact lines modified), what was found (exact issues, not vague summaries), and what still needs doing with current date and time in german timezone. Do NOT proceed to the next task until WORKLOG.md has been updated.\\n- If WORKLOG.md does not exist, create it before doing anything else.", + "prompt": "Follow the sandbox rules from /home/opencode/.config/opencode/AGENTS.md. Before finishing any task that changes files, read WORKLOG.md if present and append a concise entry with the current Europe/Berlin timestamp from `TZ=Europe/Berlin date \"+%d.%m.%Y, %H:%M (%Z)\"`, changed files, concrete findings, and pending follow-ups.", "permission": { "webfetch": "ask", "websearch": "allow" diff --git a/config/opencode.json b/config/opencode.json index dd813ac..bc39998 100644 --- a/config/opencode.json +++ b/config/opencode.json @@ -25,7 +25,7 @@ "small_model": "vllm/qwen3.6-35b", "agent": { "build": { - "prompt": "All project are located under /home/opencode/workspace, you will only work there and create a new subdirectory for each task or project, NO EXCEPTIONS. You must maintain a file called WORKLOG.md in the current project working directory at all times.\\n\\nRules:\\n- Before starting any task, check if WORKLOG.md exists and read it to understand prior context.\\n- After completing EACH task, immediately update WORKLOG.md with: what was done (specific files changed, exact lines modified), what was found (exact issues, not vague summaries), and what still needs doing with current date and time in german timezone. Do NOT proceed to the next task until WORKLOG.md has been updated.\\n- If WORKLOG.md does not exist, create it before doing anything else.", + "prompt": "Follow the sandbox rules from /home/opencode/.config/opencode/AGENTS.md. Before finishing any task that changes files, read WORKLOG.md if present and append a concise entry with the current Europe/Berlin timestamp from `TZ=Europe/Berlin date \"+%d.%m.%Y, %H:%M (%Z)\"`, changed files, concrete findings, and pending follow-ups.", "permission": { "webfetch": "ask", "websearch": "allow" diff --git a/entrypoint.sh b/entrypoint.sh index bf4495b..acadd35 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -51,10 +51,8 @@ if [[ "$NEEDS_CHOWN" = "true" ]]; then find "$APP_HOME" -xdev -exec chown "$APP_USER":"$APP_GROUP" {} + fi -# HOME → workspace so opencode session state lands on the mounted volume -# CARGO_HOME/RUSTUP_HOME are pinned in the image ENV, so tools still find their data OPENCODE_WORKSPACE="/home/opencode/workspace" -echo "> Set HOME to $OPENCODE_WORKSPACE (mounted workspace volume)" -export HOME="$OPENCODE_WORKSPACE" +echo "> Starting opencode in $OPENCODE_WORKSPACE" +cd "$OPENCODE_WORKSPACE" -exec gosu $APP_USER:$APP_GROUP "opencode" +exec gosu $APP_USER:$APP_GROUP opencode "$@" diff --git a/scripts/cleanup.sh b/scripts/cleanup.sh new file mode 100755 index 0000000..c2d66b8 --- /dev/null +++ b/scripts/cleanup.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" +ASSUME_YES=false + +usage() { + cat <<'USAGE' +Usage: scripts/cleanup.sh [options] + +Remove all generated OpenCode sandbox state from: + - workspace/ + - data/ + +The script preserves: + - workspace/.gitkeep + - data/.gitkeep + +Options: + -y, --yes Run without the interactive confirmation prompt. + -h, --help Show this help message. +USAGE +} + +while [[ $# -gt 0 ]]; do + case "$1" in + -y|--yes) + ASSUME_YES=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +confirm_cleanup() { + if [[ "$ASSUME_YES" == "true" ]]; then + return + fi + + cat <