Skip to content

Commit 3c7bc00

Browse files
committed
initial version attempt
1 parent 9f36d04 commit 3c7bc00

File tree

5 files changed

+215
-0
lines changed

5 files changed

+215
-0
lines changed

.devcontainer/devcontainer.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2+
// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
3+
{
4+
"name": "Ubuntu",
5+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6+
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04",
7+
8+
// Features to add to the dev container. More info: https://containers.dev/features.
9+
"features": {
10+
"ghcr.io/zendril/features/gemini-cli:1": {}
11+
},
12+
13+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
14+
// "forwardPorts": [],
15+
16+
// Use 'postCreateCommand' to run commands after the container is created.
17+
// "postCreateCommand": "uname -a",
18+
19+
// Configure tool-specific properties.
20+
// "customizations": {},
21+
"runArgs": [
22+
"--name=devcontainer-feature-opencode"
23+
]
24+
25+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
26+
// "remoteUser": "root"
27+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Publish devcontainer features
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
# paths:
9+
# - 'src/**'
10+
11+
jobs:
12+
publish:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: read
16+
packages: write
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v4
20+
21+
- name: Install devcontainer CLI
22+
run: npm install -g @devcontainers/cli
23+
24+
- name: Login to GitHub Container Registry
25+
uses: docker/login-action@v3
26+
with:
27+
registry: ghcr.io
28+
username: ${{ github.actor }}
29+
password: ${{ secrets.GITHUB_TOKEN }}
30+
31+
- name: Publish features
32+
run: |
33+
set +e
34+
output=$(devcontainer features publish \
35+
src/opencode \
36+
--namespace ${{ github.repository_owner }}/features \
37+
--registry ghcr.io 2>&1)
38+
exit_code=$?
39+
echo "$output"
40+
41+
if echo "$output" | grep -q "already exists, skipping"; then
42+
echo "::error::Version already exists! Please bump the version in devcontainer-feature.json"
43+
exit 1
44+
fi
45+
46+
exit $exit_code
47+
48+
- name: Create release summary
49+
run: |
50+
echo "## Published Features" >> $GITHUB_STEP_SUMMARY
51+
echo "" >> $GITHUB_STEP_SUMMARY
52+
echo "- opencode" >> $GITHUB_STEP_SUMMARY
53+
echo "" >> $GITHUB_STEP_SUMMARY
54+
echo "Published to:
55+
---
56+
ghcr.io/${{ github.repository_owner }}/features/opencode" >> $GITHUB_STEP_SUMMARY

plan.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Plan for opencode Dev Container Feature
2+
3+
We will create a new devcontainer feature for `opencode` in `src/opencode`, modeling it after the `gemini-cli` feature but adapting it for the `opencode` installation method (`curl | bash`) and its specific persistence requirements.
4+
5+
## 1. Structure
6+
We will create the following file structure:
7+
```
8+
src/
9+
opencode/
10+
devcontainer-feature.json
11+
install.sh
12+
```
13+
14+
## 2. Configuration & Persistence (`devcontainer-feature.json`)
15+
16+
**Note on Environment Variables:**
17+
The `opencode` install script (`https://opencode.ai/install`) hardcodes the binary installation path to `$HOME/.opencode/bin` and uses standard XDG logic for finding shell configuration files. It **does not** appear to expose environment variables (like `OPENCODE_HOME`) to override the locations of data, config, or cache directories at install time.
18+
Therefore, to ensure persistence for the user's specific paths, we will use **Docker Volumes** mounted to the standard XDG locations used by `opencode`.
19+
20+
**Mounts:**
21+
We will define the following mounts to persist data across container rebuilds:
22+
23+
1. **Credentials, History, Logs (`~/.local/share/opencode`):**
24+
- Source: `opencode-data`
25+
- Target: `/home/vscode/.local/share/opencode` (We will use `$_REMOTE_USER_HOME` in `install.sh` context, but `devcontainer-feature.json` targets are usually absolute or relative to user. We'll stick to standard user home or allow it to be dynamic if the feature supports it, but standard features often assume `/home/vscode` or use `${containerEnv:HOME}` if available, though `devcontainer-feature.json` mounts usually map to a fixed target or rely on the user being `vscode`. Actually, `devcontainer-feature.json` mounts are applied at container creation. We will target the standard paths).
26+
*Correction:* `devcontainer-feature.json` mount targets are absolute paths inside the container. We typically assume the standard user is `vscode` or `node`, but `opencode` installs to `$HOME`. We will map to `/home/vscode/...` for now as the most common default, but we should be aware of `remoteUser`.
27+
28+
*Refinement:* The `gemini-cli` example uses `target: "/home/vscode/.gemini"`. We will follow that pattern for now.
29+
30+
- **Mount 1:** `opencode-share` -> `/home/vscode/.local/share/opencode`
31+
- **Mount 2:** `opencode-config` -> `/home/vscode/.config/opencode`
32+
- **Mount 3:** `opencode-cache` -> `/home/vscode/.cache/opencode`
33+
34+
## 3. Installation Logic (`install.sh`)
35+
36+
The script will:
37+
1. Install necessary system dependencies (e.g., `curl`, `ca-certificates`, `tar` or `unzip` as required by the install script).
38+
2. Execute the installation command: `curl -fsSL https://opencode.ai/install | bash`.
39+
- We can pass `--version` if the user specifies a version in `devcontainer-feature.json` options.
40+
3. Ensure the `bin` directory (`$HOME/.opencode/bin`) is in the `PATH` (The install script tries to update rc files, but for devcontainers we often want to ensure it's in the global environment or `containerEnv`).
41+
- We will likely add `export PATH=$HOME/.opencode/bin:$PATH` to a profile script or rely on `containerEnv` in `devcontainer-feature.json`.
42+
- Actually, `devcontainer-feature.json` has `containerEnv`. We should add `PATH` modification there if possible, or just rely on the script. `gemini-cli` example sets `GEMINI_CONFIG_DIR` but relies on the install script/nanolayer for PATH? Nanolayer handles it. Here we might need to manually ensure it.
43+
- We will add `PATH: "/home/vscode/.opencode/bin:${PATH}"` to `containerEnv`.
44+
45+
## 4. Execution Plan
46+
1. Create `src/opencode` directory.
47+
2. Write `src/opencode/install.sh`.
48+
3. Write `src/opencode/devcontainer-feature.json`.
49+
4. Add GitHub Action for automated publication (`.github/workflows/publish-features.yml`).
50+
5. Test the feature (if possible, or just verify file content).
51+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"id": "opencode",
3+
"version": "1.0.0",
4+
"name": "OpenCode",
5+
"documentationURL": "https://opencode.ai/docs/",
6+
"description": "Installs OpenCode (opencode.ai) with persistent configuration",
7+
"options": {
8+
"version": {
9+
"default": "latest",
10+
"description": "Select the version to install.",
11+
"proposals": [
12+
"latest"
13+
],
14+
"type": "string"
15+
}
16+
},
17+
"mounts": [
18+
{
19+
"source": "opencode-share",
20+
"target": "/home/vscode/.local/share/opencode",
21+
"type": "volume"
22+
},
23+
{
24+
"source": "opencode-config",
25+
"target": "/home/vscode/.config/opencode",
26+
"type": "volume"
27+
},
28+
{
29+
"source": "opencode-cache",
30+
"target": "/home/vscode/.cache/opencode",
31+
"type": "volume"
32+
}
33+
],
34+
"containerEnv": {
35+
"PATH": "/home/vscode/.opencode/bin:${PATH}"
36+
},
37+
"installsAfter": [
38+
"ghcr.io/devcontainers/features/common-utils"
39+
]
40+
}

src/opencode/install.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
# The 'install.sh' entrypoint script is always executed as the root user.
5+
#
6+
# These following environment variables are passed in by the dev container CLI:
7+
# _REMOTE_USER: The user name of the non-root user for the Dev Container.
8+
# _REMOTE_USER_HOME: The home directory of the non-root user for the Dev Container.
9+
10+
USERNAME="${_REMOTE_USER:-vscode}"
11+
USER_HOME="${_REMOTE_USER_HOME:-/home/$USERNAME}"
12+
13+
echo "Installing dependencies..."
14+
apt-get update && apt-get install -y curl ca-certificates tar unzip
15+
16+
echo "Installing OpenCode for user ${USERNAME}..."
17+
18+
# Determine version argument
19+
VERSION_ARG=""
20+
if [ -n "${VERSION}" ] && [ "${VERSION}" != "latest" ]; then
21+
VERSION_ARG="--version ${VERSION}"
22+
fi
23+
24+
# Run the installer as the target user
25+
# We use 'bash -s' to pass arguments to the piped script
26+
su - "$USERNAME" -c "curl -fsSL https://opencode.ai/install | bash -s -- ${VERSION_ARG}"
27+
28+
# Pre-create directories for persistence mounts to ensure correct permissions
29+
# (Docker might create them as root otherwise when mounting volumes)
30+
mkdir -p "$USER_HOME/.local/share/opencode"
31+
mkdir -p "$USER_HOME/.config/opencode"
32+
mkdir -p "$USER_HOME/.cache/opencode"
33+
34+
chown -R "$USERNAME:$USERNAME" "$USER_HOME/.local/share/opencode"
35+
chown -R "$USERNAME:$USERNAME" "$USER_HOME/.config/opencode"
36+
chown -R "$USERNAME:$USERNAME" "$USER_HOME/.cache/opencode"
37+
38+
echo "OpenCode installed successfully."
39+
40+
# Cleanup
41+
rm -rf /var/lib/apt/lists/*

0 commit comments

Comments
 (0)