Compare commits

...

40 Commits

Author SHA1 Message Date
joakimp 3669bec8ff Stop leaking host GIDs into VM via rsync -a
Publish Docker Image / build-base (push) Successful in 39m15s
Publish Docker Image / build-omos (push) Successful in 50m20s
Publish Docker Image / update-description (push) Successful in 16s
Replace 'rsync -az' with 'rsync -rlptDz' (archive minus owner/group
preservation). Running as a non-root user on the VM, rsync can't
preserve UID anyway, but it was successfully preserving GID whenever
the numeric GID happened to exist on the target. That caused synced
dirs (~/.aws, ~/.config/opencode, ~/.config/nvim, ~/.agents/skills,
~/.ssh) to end up with group 1001 on the VM, which was confusing
and, for group-writable mode, potentially insecure.

With -o and -g dropped, received files get the receiving user's
UID:GID (devbox:devbox), which is what you want.
2026-04-20 22:12:19 +02:00
joakimp f210d533eb Fix root-owned parent dirs left behind by nested volume mounts
When a named volume is mounted at a nested path like
/home/developer/.local/state/opencode, Docker creates the parent
directory (.local/state) as root:root. The existing chown loop only
fixed the leaf mount points, leaving parents unwritable by the
developer user.

Add a non-recursive pre-pass that fixes ownership of the common
parent dirs (.local, .local/share, .local/state, .config) so that
anything creating new subdirs beneath them works correctly after a
fresh container recreate. Regression introduced by commit 967ce7d
(devbox-state volume) and only partially addressed by a06dc5f.
2026-04-20 22:12:14 +02:00
joakimp 00d4f1596d Ignore personal deploy/my-cloud-init.yml override 2026-04-20 21:27:05 +02:00
joakimp 3c19b836cf Clarify OMOS-only features and host-mount portability in docs
Two related documentation fixes for users mounting ~/.config/opencode
from the host:

1. Gate oh-my-opencode-slim references (file and agents) to the OMOS
   variant in the Custom opencode config sections and data persistence
   tables. Base-variant users no longer see oh-my-opencode-slim.json
   listed as if it were always present.

2. Add a portability note warning that host-absolute paths in
   opencode.json (e.g. file:///usr/local/lib/node_modules/... or
   file:///opt/homebrew/...) will not resolve inside the Linux
   container, and to prefer bare package specifiers that work on
   both macOS and Linux hosts.
2026-04-20 21:25:44 +02:00
joakimp fffaeffb7a Refresh default model IDs for current providers
Update auto-generated opencode.json defaults to model IDs that are
valid as of April 2026:

- anthropic: claude-sonnet-4-5 -> claude-sonnet-4-6
- openai:    gpt-4o (retired Apr 3 2026) -> gpt-5.4
- bedrock:   anthropic.claude-sonnet-4-5-v1 (invalid) ->
             global.anthropic.claude-sonnet-4-5-20250929-v1:0

The Bedrock ID now uses the global inference profile (no regional
10% premium) and includes the required date stamp and :0 suffix.
2026-04-20 21:25:37 +02:00
joakimp b4d2f09e77 Document rsync in README and DOCKER_HUB tool lists 2026-04-20 20:27:09 +02:00
joakimp d74adc14dc Add rsync to base image 2026-04-20 20:26:24 +02:00
joakimp 9fa8b5c1e3 Fix misleading --rm wording: data loss happens on any container recreation 2026-04-20 15:03:00 +02:00
joakimp 3724519402 Document devbox-state volume for TUI settings persistence 2026-04-20 14:53:07 +02:00
joakimp a06dc5f47c Add state volume to entrypoint ownership fix loop 2026-04-20 14:48:12 +02:00
joakimp 967ce7df49 Add devbox-state volume to persist TUI settings across container recreations 2026-04-20 14:37:58 +02:00
joakimp c209d873ba Bump opencode to v1.14.19
Publish Docker Image / build-base (push) Successful in 39m5s
Publish Docker Image / build-omos (push) Successful in 50m52s
Publish Docker Image / update-description (push) Successful in 14s
2026-04-20 12:26:23 +02:00
joakimp e52ac46237 Document gcc and g++ in README and DOCKER_HUB tool lists 2026-04-20 10:26:52 +02:00
joakimp 83fb3d6de5 Add gcc and g++ to base image for C/C++ compilation support 2026-04-20 10:25:44 +02:00
joakimp d9d3a4c1d2 Fix Bun download URL: remove non-existent LATEST file fetch
Publish Docker Image / build-base (push) Successful in 36m8s
Publish Docker Image / build-omos (push) Successful in 47m45s
Publish Docker Image / update-description (push) Successful in 14s
2026-04-19 23:05:31 +02:00
joakimp 7b8c74852e Add fzf and ripgrep to VM provisioning packages 2026-04-19 23:03:21 +02:00
joakimp c32d50b364 Use Bun baseline build for AVX2-less CPU compatibility (Sandy Bridge)
Publish Docker Image / build-omos (push) Failing after 14m30s
Publish Docker Image / build-base (push) Has been cancelled
Publish Docker Image / update-description (push) Has been cancelled
2026-04-19 22:35:45 +02:00
joakimp dd63607a3f Ensure WORKSPACE_PATH from remote .env exists on VM 2026-04-19 20:15:22 +02:00
joakimp 3852d3b1ad Exclude AWS CLI and SSO cache from sync-to-vm.sh 2026-04-19 20:07:36 +02:00
joakimp ddea23e80a Exclude node_modules and other generated files from sync-to-vm.sh 2026-04-19 19:58:47 +02:00
joakimp 466383b546 Add rsync to installed packages for sync-to-vm.sh support 2026-04-19 19:54:56 +02:00
joakimp f21cf87881 Fix rsync flag for macOS compatibility 2026-04-19 19:30:31 +02:00
joakimp 3c7df3f888 Add sync-to-vm.sh to copy local config directories to remote VM 2026-04-19 19:25:18 +02:00
joakimp 6fc74b1f19 Add bind mount pre-creation note to deploy post-setup instructions 2026-04-19 19:11:36 +02:00
joakimp 05998bd6a2 Add Bedrock setup notes to deploy docs and cloud-init final message 2026-04-19 19:04:15 +02:00
joakimp b1e25a45b2 Default docker-compose.yml to pull from Docker Hub, sync with DOCKER_HUB.md 2026-04-19 18:50:12 +02:00
joakimp 16ff29101e Bump opencode to v1.14.18
Publish Docker Image / build-omos (push) Successful in 41m30s
Publish Docker Image / build-base (push) Successful in 43m45s
Publish Docker Image / update-description (push) Successful in 15s
2026-04-19 18:28:39 +02:00
joakimp 81100fd5bb Add caveats and two-step fallback for inline boot-from-volume command 2026-04-19 18:15:53 +02:00
joakimp 4893be4133 Add locale customization instructions to cloud-init template 2026-04-19 18:09:30 +02:00
joakimp 9ebff2e037 Fix --block-device syntax to match current OpenStack CLI key names 2026-04-19 16:49:26 +02:00
joakimp 5bac08dd03 Fix image name casing to match OpenStack: Debian-13-Trixie 2026-04-19 16:47:10 +02:00
joakimp addccd4a82 Remove --key-name from OpenStack examples, clarify SSH key is in cloud-init 2026-04-19 16:36:15 +02:00
joakimp 7b0f6ed880 Add floating IP instructions to OpenStack deploy docs 2026-04-19 16:22:52 +02:00
joakimp fa3bb12d44 Skip ufw on OpenStack in cloud-init, matching setup-host.sh behavior 2026-04-19 13:22:07 +02:00
joakimp d091b6b50f Add optional console password (chpasswd) to cloud-init and deploy docs 2026-04-19 13:10:12 +02:00
joakimp fb9629db2b Add NVMe performance volume example to OpenStack deploy docs 2026-04-19 11:33:55 +02:00
joakimp 265cbdb14c Document full OpenStack server create command with flavor, image, network 2026-04-19 11:18:31 +02:00
joakimp 68204f573b Skip ufw on OpenStack (auto-detected), add security group setup script
setup-host.sh now detects OpenStack via metadata endpoint and skips ufw.
New setup-openstack-secgroup.sh creates the required security group with
SSH, mosh, and ICMP rules via the OpenStack CLI.
2026-04-19 11:04:09 +02:00
joakimp e0258a928e Add VM host deployment scripts (cloud-init + post-install)
Recommended base: Debian 13 Trixie (matches opencode-devbox base image).
- cloud-init.yml: automated VM provisioning for Proxmox/OpenStack/cloud providers
- setup-host.sh: interactive post-install script for manually-created VMs
- README.md: documents both paths and VM sizing recommendations

Installs Docker (official repo), Compose v2, ufw firewall, mosh support,
and the IPv4 DNS preference workaround for Docker Hub IPv6 issues.
2026-04-19 10:43:41 +02:00
joakimp 4bd543050a Bump opencode to v1.4.17, add file utility to base image
Publish Docker Image / build-omos (push) Successful in 41m7s
Publish Docker Image / build-base (push) Successful in 43m7s
Publish Docker Image / update-description (push) Successful in 15s
2026-04-19 09:31:21 +02:00
13 changed files with 743 additions and 27 deletions
+1 -1
View File
@@ -7,7 +7,7 @@
OPENCODE_PROVIDER=anthropic
# Model override (optional, defaults per provider)
# OPENCODE_MODEL=anthropic/claude-sonnet-4-5
# OPENCODE_MODEL=anthropic/claude-sonnet-4-6
# ── API Keys (set the one matching your provider) ────────────────────
# ANTHROPIC_API_KEY=
+3
View File
@@ -3,3 +3,6 @@
*.swo
*~
.DS_Store
# Personal cloud-init overrides (not shared)
deploy/my-cloud-init.yml
+15 -9
View File
@@ -226,24 +226,26 @@ Understanding what survives container restarts and what doesn't:
| `/home/developer/.ssh` | Host bind mount (ro) | ✅ Yes — lives on host | SSH keys |
| `/home/developer/.aws` | Host bind mount | ✅ Yes — lives on host | AWS credentials/SSO cache |
| `/home/developer/.local/share/opencode` | Named volume (if configured) | ✅ Yes — Docker volume | Session history, memory, auth tokens |
| `/home/developer/.local/state/opencode` | Named volume (if configured) | ✅ Yes — Docker volume | TUI settings (theme, toggles) |
| `/home/developer/.local/share/uv` | Named volume (if configured) | ✅ Yes — Docker volume | Python installs, uv tool installs |
| `/home/developer/.rustup` | Named volume (if configured) | ✅ Yes — Docker volume | Rust toolchains |
| `/home/developer/.cargo` | Named volume (if configured) | ✅ Yes — Docker volume | Cargo binaries, registry cache |
| `/home/developer/.vscode-server` | Named volume (if configured) | ✅ Yes — Docker volume | VS Code server and extensions |
| `/home/developer/.config/opencode` | Host bind mount (if configured) | ✅ Yes — lives on host | opencode.json, oh-my-opencode-slim.json, skills |
| `/home/developer/.config/opencode` | Host bind mount (if configured) | ✅ Yes — lives on host | opencode.json, skills, plus `oh-my-opencode-slim.json` on the OMOS variant |
### Key points
- **Project files** (`/workspace`) are always safe — they're your host filesystem.
- **opencode config** is auto-generated from `OPENCODE_PROVIDER` env var on each start if no existing config is found. To persist config changes, mount the config directory from the host (see Custom opencode Config below).
- **opencode data** (session history, memory) is lost with `--rm` unless you add a named volume.
- **Python installs** via `uv python install` are lost unless you add the `devbox-uv` named volume.
- **Rust toolchains** via `rustup-init` are lost unless you add the `devbox-rustup` and `devbox-cargo` named volumes.
- **opencode data** (session history, memory) is lost on container recreation unless you add a named volume.
- **TUI settings** (theme, toggles) are lost on container recreation unless you add the `devbox-state` named volume.
- **Python installs** via `uv python install` are lost on container recreation unless you add the `devbox-uv` named volume.
- **Rust toolchains** via `rustup-init` are lost on container recreation unless you add the `devbox-rustup` and `devbox-cargo` named volumes.
- **AWS SSO tokens** persist across restarts when `~/.aws` is mounted (recommended for Bedrock users).
## Custom opencode Config
For full control over opencode settings (MCP servers, custom models, oh-my-opencode-slim agents, etc.), mount the entire config directory from the host:
For full control over opencode settings (MCP servers, custom models, and — on the OMOS variant — oh-my-opencode-slim agents), mount the entire config directory from the host:
```bash
docker run -it --rm \
@@ -254,6 +256,8 @@ docker run -it --rm \
This persists all configuration changes across container restarts. When an existing `opencode.json` is found, the `OPENCODE_PROVIDER` auto-config is skipped.
> **Portability note:** The mounted config runs inside a Linux container. Any absolute paths inside `opencode.json` (for example, host-specific `plugin` entries like `file:///usr/local/lib/node_modules/...` or `file:///opt/homebrew/...`) will not resolve inside the container. Prefer bare package specifiers (e.g. `"oh-my-opencode-slim"`) that resolve via `node_modules` lookup, which works on both macOS and Linux hosts.
## Neovim Configuration
The image includes neovim 0.12 with `EDITOR=nvim` set by default. To use your own neovim config (and have plugins auto-install via lazy.nvim on first start), mount it from the host:
@@ -400,6 +404,7 @@ services:
image: joakimp/opencode-devbox:latest
# For multi-agent orchestration, use the omos variant instead:
# image: joakimp/opencode-devbox:latest-omos
container_name: opencode-devbox
stdin_open: true
tty: true
env_file:
@@ -410,8 +415,8 @@ services:
- ~/projects:/workspace
- ~/.ssh:/home/developer/.ssh:ro
- devbox-data:/home/developer/.local/share/opencode
# Optional: persist Python/uv installs across restarts
# - devbox-uv:/home/developer/.local/share/uv
- devbox-state:/home/developer/.local/state/opencode
- devbox-uv:/home/developer/.local/share/uv
# Optional: persist Rust toolchains and cargo data
# - devbox-rustup:/home/developer/.rustup
# - devbox-cargo:/home/developer/.cargo
@@ -428,7 +433,8 @@ services:
volumes:
devbox-data:
# devbox-uv:
devbox-state:
devbox-uv:
# devbox-rustup:
# devbox-cargo:
# devbox-vscode:
@@ -465,7 +471,7 @@ docker compose run --rm devbox bash # interactive shell
- **opencode** — AI coding assistant
- **Node.js 22** — for npx-based MCP servers
- **AWS CLI v2** — SSO and Bedrock authentication
- **Dev tools** — git, git-lfs, git-crypt, age, ssh, ripgrep, fd, fzf, bat, eza, zoxide, uv, rustup, jq, make, curl, wget, neovim 0.12, tmux, htop, tree
- **Dev tools** — git, git-lfs, git-crypt, age, ssh, ripgrep, fd, fzf, bat, eza, zoxide, uv, rustup, jq, make, gcc, g++, curl, wget, neovim 0.12, tmux, htop, tree, rsync
- **Non-root user** — runs as `developer` with UID auto-matched to workspace owner (sudo available)
### OMOS image (`latest-omos`)
+18 -2
View File
@@ -5,7 +5,7 @@ ARG DEBIAN_VERSION=trixie-slim
FROM debian:${DEBIAN_VERSION} AS base
ARG TARGETARCH
ARG OPENCODE_VERSION=1.4.12
ARG OPENCODE_VERSION=1.14.19
LABEL maintainer="joakimp"
LABEL description="Portable opencode developer container"
@@ -34,10 +34,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
diffutils \
git-crypt \
age \
file \
sudo \
locales \
procps \
unzip \
gcc \
g++ \
rsync \
&& ln -s /usr/bin/fdfind /usr/local/bin/fd \
&& rm -rf /var/lib/apt/lists/*
@@ -162,10 +166,22 @@ RUN if [ "${INSTALL_GO}" = "true" ]; then \
# ── Optional: oh-my-opencode-slim (multi-agent orchestration) ────────
# Installs Bun runtime and the oh-my-opencode-slim npm package.
# Runtime activation is controlled by ENABLE_OMOS env var in entrypoint.
# Uses the baseline Bun build (SSE4.2 only) for compatibility with older
# CPUs that lack AVX2 (e.g. Sandy Bridge on OpenStack).
ARG INSTALL_OMOS=false
ARG OMOS_VERSION=latest
RUN if [ "${INSTALL_OMOS}" = "true" ]; then \
curl -fsSL https://bun.sh/install | BUN_INSTALL=/usr/local bash && \
ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then \
BUN_ARCH="x64-baseline"; \
elif [ "$ARCH" = "aarch64" ]; then \
BUN_ARCH="aarch64"; \
fi && \
curl -fsSL "https://github.com/oven-sh/bun/releases/latest/download/bun-linux-${BUN_ARCH}.zip" -o /tmp/bun.zip && \
unzip -o /tmp/bun.zip -d /tmp/bun && \
mv /tmp/bun/bun-linux-${BUN_ARCH}/bun /usr/local/bin/bun && \
chmod +x /usr/local/bin/bun && \
rm -rf /tmp/bun /tmp/bun.zip && \
bun --version && \
npm install -g oh-my-opencode-slim@${OMOS_VERSION}; \
fi
+7 -4
View File
@@ -128,14 +128,16 @@ docker compose exec -u developer devbox aws --version
### Custom opencode config
For full control over opencode settings (MCP servers, custom models, oh-my-opencode-slim agents, etc.), mount the entire config directory from the host:
For full control over opencode settings (MCP servers, custom models, and — on the OMOS variant — oh-my-opencode-slim agents), mount the entire config directory from the host:
```yaml
volumes:
- ~/.config/opencode:/home/developer/.config/opencode
```
This persists all configuration changes across container restarts, including `opencode.json`, `oh-my-opencode-slim.json`, and skills. When an existing `opencode.json` is found, the `OPENCODE_PROVIDER` auto-config is skipped.
This persists all configuration changes across container restarts, including `opencode.json`, skills, and (on the OMOS variant) `oh-my-opencode-slim.json`. When an existing `opencode.json` is found, the `OPENCODE_PROVIDER` auto-config is skipped.
> **Portability note:** The mounted config runs inside a Linux container. Any absolute paths inside `opencode.json` (for example, host-specific `plugin` entries like `file:///usr/local/lib/node_modules/...` or `file:///opt/homebrew/...`) will not resolve inside the container. Prefer bare package specifiers (e.g. `"oh-my-opencode-slim"`) that resolve via `node_modules` lookup, which works on both macOS and Linux hosts.
### Custom skills
@@ -477,7 +479,7 @@ Container (Debian trixie)
├── opencode binary
├── oh-my-opencode-slim (optional — multi-agent orchestration plugin, includes Bun)
├── AWS CLI v2 (SSO + Bedrock auth)
├── neovim 0.12, tmux, htop, bat, eza, zoxide, uv, rustup, make
├── neovim 0.12, tmux, htop, bat, eza, zoxide, uv, rustup, make, gcc, g++, rsync
├── git, git-crypt, age, ssh, ripgrep, fd, fzf, jq, curl, tree
├── Node.js (for MCP servers)
├── Bun (optional — included with oh-my-opencode-slim)
@@ -493,11 +495,12 @@ Container (Debian trixie)
| `/home/developer/.ssh` | Host bind mount (ro) | ✅ Yes | SSH keys |
| `/home/developer/.aws` | Host bind mount (if configured) | ✅ Yes | AWS credentials/SSO cache |
| `/home/developer/.local/share/opencode` | Named volume `devbox-data` | ✅ Yes | Session history, memory |
| `/home/developer/.local/state/opencode` | Named volume `devbox-state` | ✅ Yes | TUI settings (theme, toggles) |
| `/home/developer/.local/share/uv` | Named volume `devbox-uv` (if configured) | ✅ Yes | Python installs, uv tool installs |
| `/home/developer/.rustup` | Named volume `devbox-rustup` (if configured) | ✅ Yes | Rust toolchains |
| `/home/developer/.cargo` | Named volume `devbox-cargo` (if configured) | ✅ Yes | Cargo binaries, registry cache |
| `/home/developer/.vscode-server` | Named volume `devbox-vscode` (if configured) | ✅ Yes | VS Code server and extensions |
| `/home/developer/.config/opencode` | Host bind mount (if configured) | ✅ Yes | opencode.json, oh-my-opencode-slim.json, skills |
| `/home/developer/.config/opencode` | Host bind mount (if configured) | ✅ Yes | opencode.json, skills, plus `oh-my-opencode-slim.json` on the OMOS variant |
**opencode config** (`opencode.json`) is auto-generated from `OPENCODE_PROVIDER` on each start. It sets provider and model only — no MCP servers. To persist config changes and use custom settings, mount the config directory from the host (see Custom opencode config above).
+199
View File
@@ -0,0 +1,199 @@
# Deploy — Host VM setup
Scripts for setting up a fresh Linux VM to host opencode-devbox.
## Files
- **`cloud-init.yml`** — cloud-init user-data template for automated VM provisioning on OpenStack, Proxmox, or any cloud with cloud-init support
- **`setup-host.sh`** — interactive post-install script for VMs that weren't provisioned with cloud-init
- **`setup-openstack-secgroup.sh`** — creates an OpenStack security group with the right rules (SSH, mosh, ICMP)
- **`sync-to-vm.sh`** — syncs local config directories (`~/.aws`, `~/.config/opencode`, etc.) to a remote VM based on which bind mounts are active in its `docker-compose.yml`
## Supported distributions
- **Debian 13 (Trixie)** — recommended (matches opencode-devbox base image)
- **Ubuntu 24.04 LTS** — also works
Other distributions will need manual adaptation.
## Quick start
### Option 1: Cloud-init (automated)
Customize `cloud-init.yml` — replace the SSH public key and optionally the hostname/timezone. Then use it during VM creation:
- **Proxmox**: attach as cloud-init user-data
- **OpenStack**: pass via `--user-data` flag (see full example below)
- **AWS/DigitalOcean/etc**: paste into the "user data" field
#### Full OpenStack example
Cloud-init only handles guest configuration — flavor, image, network, and security group must be specified explicitly at creation time.
> **Note:** Do not use `--key-name` — the SSH key is configured in `cloud-init.yml` under `ssh_authorized_keys` for the `devbox` user. The `--key-name` flag injects into the image's default user (e.g. `debian`), not the `devbox` user created by cloud-init.
```bash
# List available flavors to choose appropriate sizing
openstack flavor list
# Create the security group first (one-time, see below)
./setup-openstack-secgroup.sh
# Basic — boot from default storage
openstack server create \
--flavor c4m8 \
--image Debian-13-Trixie \
--network my-network \
--security-group opencode-devbox \
--user-data cloud-init.yml \
devbox-vm
```
If your cloud offers NVMe-backed (performance) volumes, boot from one for faster Docker and build I/O:
```bash
# Performance — boot from NVMe volume (40GB, preserved on instance deletion)
openstack server create \
--flavor c4m8 \
--network my-network \
--security-group opencode-devbox \
--user-data cloud-init.yml \
--block-device source_type=image,uuid=$(openstack image show Debian-13-Trixie -f value -c id),destination_type=volume,volume_size=40,delete_on_termination=false,boot_index=0,volume_type=performance \
devbox-vm
```
> **Note:** The inline `volume_type` parameter requires API microversion 2.67+. If the server goes to ERROR state, check your volume quota (`openstack quota show`) and try creating the volume separately:
> ```bash
> openstack volume create --image Debian-13-Trixie --size 40 --type performance --bootable devbox-boot-volume
> openstack server create --flavor c4m8 --volume devbox-boot-volume --network my-network --security-group opencode-devbox --user-data cloud-init.yml devbox-vm
> ```
#### Floating IP
OpenStack doesn't support assigning a floating IP at instance creation time — it's a separate step after the VM is active:
```bash
# Allocate a new floating IP from the external network
openstack floating ip create <external-network>
# Assign it to the VM
openstack server add floating ip devbox-vm <floating-ip>
```
To find your external network name: `openstack network list --external`. If you already have an unassigned floating IP, skip the create step.
The VM boots with Docker installed, firewall configured (or skipped on OpenStack), and your SSH key authorized. Log in as the `devbox` user.
### Console password (optional)
The cloud-init template uses SSH key authentication only — no password is set by default. This is sufficient for normal use since the `devbox` user has passwordless `sudo`.
A password is only needed for:
- **Emergency console access** — logging in via OpenStack Horizon console (noVNC) or Proxmox VNC when SSH is unreachable
- **`su - devbox`** — switching to the devbox user from another account
To enable console access, uncomment the `chpasswd` block in `cloud-init.yml` before deploying:
```yaml
chpasswd:
expire: false
users:
- name: devbox
password: your-password-here
type: text
```
For an already-running VM, set a password via SSH:
```bash
sudo passwd devbox
```
### Option 2: Post-install script (manual)
On a fresh Debian/Ubuntu VM:
```bash
curl -fsSL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/deploy/setup-host.sh | bash
```
Or clone and run:
```bash
git clone https://gitea.jordbo.se/joakimp/opencode-devbox
cd opencode-devbox/deploy
./setup-host.sh
```
## What gets installed
- Docker Engine (from Docker's official apt repo, not distro's `docker.io`)
- Docker Compose plugin (v2)
- `tmux`, `mosh`, `git`
- `ufw` firewall with SSH (22) and mosh (UDP 60000-61000) allowed — **skipped on OpenStack** (detected automatically; use security groups instead)
- IPv4 DNS preference (works around Docker Hub IPv6 connectivity issues)
## OpenStack security groups
On OpenStack, firewalling is handled by security groups rather than ufw. The `setup-host.sh` script detects OpenStack automatically and skips ufw configuration.
To create the required security group:
```bash
./setup-openstack-secgroup.sh
```
This creates a security group named `opencode-devbox` with rules for SSH (TCP 22), mosh (UDP 60000-61000), and ICMP. Apply it to your instance:
```bash
# New instance
openstack server create --security-group opencode-devbox ...
# Existing instance
openstack server add security group <instance-name> opencode-devbox
```
## VM sizing recommendations
| Use case | vCPU | RAM | Disk |
|---|---|---|---|
| Minimum | 2 | 4 GB | 20 GB |
| Recommended | 4 | 8 GB | 40 GB |
| Heavy use (Rust/Python builds, multi-project) | 8 | 16 GB | 80 GB |
## After VM setup
If you uncomment any bind mounts in `docker-compose.yml` (e.g. `~/.aws`, `~/.config/opencode`), create the directories first — Docker creates missing bind mount paths as root-owned, which causes permission issues:
```bash
# Only create directories for mounts you uncomment
mkdir -p ~/.aws # AWS Bedrock SSO
mkdir -p ~/.config/opencode # persistent opencode config
mkdir -p ~/.config/nvim # custom neovim config
mkdir -p ~/.agents/skills # opencode agent skills
```
Named volumes (`devbox-data`, `devbox-uv`, etc.) are managed by Docker and need no pre-creation.
```bash
mkdir -p ~/opencode-devbox && cd ~/opencode-devbox
curl -sL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/docker-compose.yml -o docker-compose.yml
curl -sL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/.env.example -o .env
vim .env # configure provider and keys
vim docker-compose.yml # uncomment optional volume mounts
docker compose up -d
docker compose exec -u developer devbox opencode
```
> **AWS Bedrock users:** Uncomment the `~/.aws` volume mount in `docker-compose.yml` before starting. You'll also need to copy your `~/.aws/config` from a machine where SSO is already configured, then authenticate inside the container with `aws sso login`.
### Syncing local config to the VM
After editing `docker-compose.yml` on the VM to uncomment the bind mounts you need, run `sync-to-vm.sh` from your local machine to copy the corresponding directories:
```bash
./deploy/sync-to-vm.sh devbox-affection
```
The script reads `docker-compose.yml` on the remote VM, detects which bind mounts are active, and syncs only those directories from your local machine. It also creates the remote directories if they don't exist.
+110
View File
@@ -0,0 +1,110 @@
#cloud-config
# cloud-init template for opencode-devbox host VM
# Tested on Debian 13 (Trixie) and Ubuntu 24.04
#
# Usage:
# - Proxmox: attach this file as cloud-init user-data in VM config
# - OpenStack: pass as --user-data when creating the instance
# - Cloud providers: paste into "user data" field
#
# Customize the marked sections before use.
# ── Hostname ─────────────────────────────────────────────────────────
hostname: devbox
manage_etc_hosts: true
# ── User ─────────────────────────────────────────────────────────────
users:
- name: devbox
groups: sudo, docker
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
# CUSTOMIZE: replace with your public SSH key.
# This is the only SSH key config needed — do NOT use --key-name with
# openstack server create, as that injects into the image's default
# user (e.g. debian), not the devbox user defined here.
- ssh-ed25519 AAAA... your-key-here
# ── Optional: console password ───────────────────────────────────────
# Uncomment to set a password for the devbox user. Only needed for
# emergency access via the OpenStack/Proxmox console (VNC/noVNC).
# SSH key authentication is used for normal access.
#
# chpasswd:
# expire: false
# users:
# - name: devbox
# password: your-password-here
# type: text
# ── Locale and timezone ──────────────────────────────────────────────
# en_US.UTF-8 is pre-generated on Debian/Ubuntu and works out of the box.
# To use a different locale (e.g. sv_SE.UTF-8), add it to the runcmd
# section before the locale is applied:
# - locale-gen sv_SE.UTF-8
# Then change the locale line below to match.
locale: en_US.UTF-8
timezone: Europe/Stockholm
# ── Package installation ─────────────────────────────────────────────
package_update: true
package_upgrade: true
packages:
- ca-certificates
- curl
- gnupg
- git
- tmux
- mosh
- rsync
- fzf
- ripgrep
- ufw
# ── Commands to run at first boot ────────────────────────────────────
runcmd:
# Install Docker from official repository
- install -m 0755 -d /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg -o /etc/apt/keyrings/docker.asc
- chmod a+r /etc/apt/keyrings/docker.asc
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$(. /etc/os-release && echo \"$ID\") $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" > /etc/apt/sources.list.d/docker.list
- apt-get update
- apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- usermod -aG docker devbox
# Firewall — skip on OpenStack (use security groups instead)
- |
if curl -s --connect-timeout 2 http://169.254.169.254/openstack/ >/dev/null 2>&1; then
echo "OpenStack detected — skipping ufw (use security groups instead)"
else
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 60000:61000/udp
ufw --force enable
fi
# Disable IPv6 preference for Docker (avoids intermittent Docker Hub connectivity issues)
- echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf
# Create projects directory for the user
- mkdir -p /home/devbox/projects
- chown devbox:devbox /home/devbox/projects
# ── Final message ───────────────────────────────────────────────────
final_message: |
opencode-devbox host VM ready.
Next steps:
1. SSH in: ssh devbox@<this-host>
2. Clone your opencode-devbox compose config, or:
mkdir -p ~/opencode-devbox && cd ~/opencode-devbox
curl -sL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/docker-compose.yml -o docker-compose.yml
curl -sL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/.env.example -o .env
3. Edit .env with your provider and keys
4. Edit docker-compose.yml to uncomment optional mounts (e.g. ~/.aws for Bedrock)
5. docker compose up -d
6. docker compose exec -u developer devbox opencode
Cloud-init run completed in $UPTIME seconds.
+145
View File
@@ -0,0 +1,145 @@
#!/bin/bash
# setup-host.sh — Post-install script for opencode-devbox host VM
#
# Run this on a fresh Debian 13 or Ubuntu 24.04 VM to set up everything
# needed to run opencode-devbox containers.
#
# Usage:
# curl -fsSL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/deploy/setup-host.sh | bash
#
# Or clone and run:
# git clone https://gitea.jordbo.se/joakimp/opencode-devbox
# cd opencode-devbox/deploy
# ./setup-host.sh
set -euo pipefail
# ── Colors ──────────────────────────────────────────────────────────
BOLD="\033[1m"; GREEN="\033[32m"; YELLOW="\033[33m"; RED="\033[31m"; RESET="\033[0m"
info() { echo -e "${BOLD}==>${RESET} $*"; }
ok() { echo -e "${GREEN}${BOLD}${RESET} $*"; }
warn() { echo -e "${YELLOW}${BOLD}!${RESET} $*"; }
err() { echo -e "${RED}${BOLD}${RESET} $*" >&2; }
# ── Detect distro ──────────────────────────────────────────────────
if [[ ! -f /etc/os-release ]]; then
err "Cannot detect Linux distribution — /etc/os-release missing"
exit 1
fi
. /etc/os-release
case "$ID" in
debian|ubuntu)
info "Detected $PRETTY_NAME"
;;
*)
err "Unsupported distribution: $ID — this script only supports Debian and Ubuntu"
exit 1
;;
esac
# ── Require sudo ────────────────────────────────────────────────────
if [[ $EUID -eq 0 ]]; then
err "Do not run as root — use a regular user with sudo"
exit 1
fi
if ! sudo -n true 2>/dev/null; then
warn "This script needs sudo access. You may be prompted for your password."
fi
# ── Update packages ─────────────────────────────────────────────────
info "Updating package index..."
sudo apt-get update -qq
info "Installing base packages..."
sudo apt-get install -y --no-install-recommends \
ca-certificates curl gnupg git tmux mosh rsync fzf ripgrep ufw
# ── Docker ──────────────────────────────────────────────────────────
if command -v docker &>/dev/null; then
ok "Docker already installed ($(docker --version))"
else
info "Installing Docker from official repository..."
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL "https://download.docker.com/linux/${ID}/gpg" -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update -qq
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
ok "Docker installed: $(docker --version)"
fi
# ── Add user to docker group ────────────────────────────────────────
if groups | grep -q docker; then
ok "User already in docker group"
else
info "Adding $USER to docker group..."
sudo usermod -aG docker "$USER"
warn "You must log out and back in for docker group to take effect"
warn "Or run: newgrp docker"
fi
# ── Firewall ────────────────────────────────────────────────────────
# Detect OpenStack — if running on OpenStack, skip ufw (security groups handle firewalling)
SKIP_UFW=false
if curl -s --connect-timeout 2 http://169.254.169.254/openstack/ &>/dev/null; then
SKIP_UFW=true
warn "OpenStack detected — skipping ufw (use security groups instead)"
warn "Ensure your security group allows: SSH (22/tcp), mosh (60000-61000/udp)"
fi
if [[ "$SKIP_UFW" == "false" ]]; then
info "Configuring firewall (ufw)..."
sudo ufw default deny incoming >/dev/null
sudo ufw default allow outgoing >/dev/null
sudo ufw allow ssh >/dev/null
sudo ufw allow 60000:61000/udp comment 'mosh' >/dev/null
if ! sudo ufw status | grep -q "Status: active"; then
sudo ufw --force enable
fi
ok "Firewall active — SSH and mosh allowed"
fi
# ── IPv4 preference for Docker Hub ──────────────────────────────────
if ! grep -q 'precedence ::ffff:0:0/96' /etc/gai.conf 2>/dev/null; then
info "Setting IPv4 preference in /etc/gai.conf..."
echo 'precedence ::ffff:0:0/96 100' | sudo tee -a /etc/gai.conf > /dev/null
ok "IPv4 preferred for DNS resolution"
fi
# ── Create projects directory ───────────────────────────────────────
if [[ ! -d "$HOME/projects" ]]; then
mkdir -p "$HOME/projects"
ok "Created ~/projects"
fi
# ── Done ────────────────────────────────────────────────────────────
echo ""
ok "Host setup complete"
echo ""
cat <<EOF
${BOLD}Next steps:${RESET}
1. If you weren't already in the docker group, log out and back in:
exit
ssh <your-user>@<this-host>
2. Set up opencode-devbox:
mkdir -p ~/opencode-devbox && cd ~/opencode-devbox
curl -sL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/docker-compose.yml -o docker-compose.yml
curl -sL https://gitea.jordbo.se/joakimp/opencode-devbox/raw/branch/main/.env.example -o .env
3. Edit .env with your provider and API keys:
vim .env
4. Start and connect:
docker compose up -d
docker compose exec -u developer devbox opencode
EOF
+63
View File
@@ -0,0 +1,63 @@
#!/bin/bash
# setup-openstack-secgroup.sh — Create an OpenStack security group for opencode-devbox
#
# Prerequisites:
# - OpenStack CLI installed (pip install python-openstackclient)
# - Authenticated (source your openrc.sh or clouds.yaml configured)
#
# Usage:
# ./setup-openstack-secgroup.sh [group-name]
#
# Default group name: opencode-devbox
set -euo pipefail
GROUP_NAME="${1:-opencode-devbox}"
BOLD="\033[1m"; GREEN="\033[32m"; YELLOW="\033[33m"; RESET="\033[0m"
info() { echo -e "${BOLD}==>${RESET} $*"; }
ok() { echo -e "${GREEN}${BOLD}${RESET} $*"; }
warn() { echo -e "${YELLOW}${BOLD}!${RESET} $*"; }
if ! command -v openstack &>/dev/null; then
echo "Error: openstack CLI not found. Install with: pip install python-openstackclient"
exit 1
fi
# Check if group already exists
if openstack security group show "$GROUP_NAME" &>/dev/null; then
warn "Security group '$GROUP_NAME' already exists — updating rules"
else
info "Creating security group '$GROUP_NAME'..."
openstack security group create "$GROUP_NAME" \
--description "opencode-devbox: SSH, mosh, HTTPS"
ok "Security group created"
fi
# Add rules (idempotent — OpenStack ignores duplicates)
info "Adding rules..."
# SSH (TCP 22)
openstack security group rule create "$GROUP_NAME" \
--protocol tcp --dst-port 22 --remote-ip 0.0.0.0/0 \
--description "SSH" 2>/dev/null && ok "SSH (TCP 22)" || warn "SSH rule already exists"
# Mosh (UDP 60000-61000)
openstack security group rule create "$GROUP_NAME" \
--protocol udp --dst-port 60000:61000 --remote-ip 0.0.0.0/0 \
--description "mosh" 2>/dev/null && ok "mosh (UDP 60000-61000)" || warn "mosh rule already exists"
# ICMP (ping — useful for diagnostics)
openstack security group rule create "$GROUP_NAME" \
--protocol icmp --remote-ip 0.0.0.0/0 \
--description "ICMP ping" 2>/dev/null && ok "ICMP ping" || warn "ICMP rule already exists"
echo ""
ok "Security group '$GROUP_NAME' ready"
echo ""
echo -e "${BOLD}Apply to a new instance:${RESET}"
echo " openstack server create --security-group $GROUP_NAME ..."
echo ""
echo -e "${BOLD}Apply to an existing instance:${RESET}"
echo " openstack server add security group <instance-name> $GROUP_NAME"
echo ""
+146
View File
@@ -0,0 +1,146 @@
#!/bin/bash
# sync-to-vm.sh — Copy local config to an opencode-devbox VM
#
# Reads docker-compose.yml on the remote VM to detect which bind mounts
# are active, then syncs the corresponding directories from this machine.
#
# Usage:
# ./sync-to-vm.sh <ssh-host>
#
# Examples:
# ./sync-to-vm.sh devbox-affection
# ./sync-to-vm.sh devbox@129.192.68.184
set -euo pipefail
# ── Colors ──────────────────────────────────────────────────────────
BOLD="\033[1m"; GREEN="\033[32m"; YELLOW="\033[33m"; RED="\033[31m"; RESET="\033[0m"
info() { echo -e "${BOLD}==>${RESET} $*"; }
ok() { echo -e "${GREEN}${BOLD}${RESET} $*"; }
warn() { echo -e "${YELLOW}${BOLD}!${RESET} $*"; }
err() { echo -e "${RED}${BOLD}${RESET} $*" >&2; }
# ── Args ────────────────────────────────────────────────────────────
if [[ $# -lt 1 ]]; then
err "Usage: $0 <ssh-host>"
echo " Example: $0 devbox-affection"
exit 1
fi
SSH_HOST="$1"
REMOTE_COMPOSE="~/opencode-devbox/docker-compose.yml"
# ── SSH multiplexing (reuse one connection for all operations) ──────
CTRL_SOCKET=$(mktemp -u /tmp/sync-to-vm-XXXXXX)
SSH_OPTS="-o ControlMaster=auto -o ControlPath=${CTRL_SOCKET} -o ControlPersist=120 -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=3"
cleanup() {
ssh ${SSH_OPTS} -O exit "$SSH_HOST" 2>/dev/null || true
rm -f "$CTRL_SOCKET"
}
trap cleanup EXIT
ssh_cmd() {
ssh ${SSH_OPTS} "$SSH_HOST" "$@"
}
# ── Bind mount patterns to detect ──────────────────────────────────
# Maps: grep pattern → local source → remote destination
declare -a MOUNT_PATTERNS=(
"~/.aws:/home/developer/.aws|$HOME/.aws|~/.aws"
"~/.config/opencode:/home/developer/.config/opencode|$HOME/.config/opencode|~/.config/opencode"
"~/.config/nvim:/home/developer/.config/nvim|$HOME/.config/nvim|~/.config/nvim"
"~/.agents/skills:/home/developer/.agents/skills|$HOME/.agents/skills|~/.agents/skills"
)
# ── Establish persistent SSH connection ─────────────────────────────
info "Connecting to ${SSH_HOST}..."
if ! ssh_cmd true 2>/dev/null; then
err "Cannot connect to ${SSH_HOST}"
exit 1
fi
ok "Connected to ${SSH_HOST}"
# ── Fetch remote docker-compose.yml ─────────────────────────────────
info "Reading docker-compose.yml from ${SSH_HOST}..."
REMOTE_COMPOSE_CONTENT=$(ssh_cmd "cat $REMOTE_COMPOSE 2>/dev/null") || {
err "Could not read ${REMOTE_COMPOSE} on ${SSH_HOST}"
err "Has the VM been set up? Run the post-setup steps first."
exit 1
}
# ── Ensure workspace directory exists on remote ─────────────────────
REMOTE_ENV="~/opencode-devbox/.env"
WORKSPACE_PATH=$(ssh_cmd "grep -E '^\s*WORKSPACE_PATH=' $REMOTE_ENV 2>/dev/null | cut -d= -f2- | tr -d '\"'" 2>/dev/null || true)
if [[ -n "$WORKSPACE_PATH" ]]; then
info "Ensuring WORKSPACE_PATH (${WORKSPACE_PATH}) exists on ${SSH_HOST}..."
ssh_cmd "mkdir -p ${WORKSPACE_PATH}"
ok "Workspace directory ready"
else
# Default from docker-compose.yml is ~/projects or current dir
WORKSPACE_PATH=$(echo "$REMOTE_COMPOSE_CONTENT" | grep -oP 'WORKSPACE_PATH:-[^}]+' | sed 's/WORKSPACE_PATH:-//' || true)
if [[ -n "$WORKSPACE_PATH" && "$WORKSPACE_PATH" != "." ]]; then
info "Ensuring default WORKSPACE_PATH (${WORKSPACE_PATH}) exists on ${SSH_HOST}..."
ssh_cmd "mkdir -p ${WORKSPACE_PATH}"
ok "Workspace directory ready"
fi
fi
# ── Detect active bind mounts ──────────────────────────────────────
SYNCED=0
for entry in "${MOUNT_PATTERNS[@]}"; do
IFS='|' read -r pattern local_path remote_path <<< "$entry"
# Check if the mount is uncommented (active) in docker-compose.yml
# Match lines that start with optional whitespace and a dash, NOT preceded by #
if echo "$REMOTE_COMPOSE_CONTENT" | grep -qE "^\s*-\s+['\"]?${pattern}" 2>/dev/null; then
# Mount is active — check if local source exists
if [[ ! -d "$local_path" ]]; then
warn "Mount active for ${pattern} but ${local_path} does not exist locally — skipping"
continue
fi
# Check if directory has content
if [[ -z "$(ls -A "$local_path" 2>/dev/null)" ]]; then
warn "${local_path} is empty — skipping"
continue
fi
info "Syncing ${local_path}${SSH_HOST}:${remote_path}"
# Ensure remote directory exists
ssh_cmd "mkdir -p ${remote_path}"
# Sync with rsync (fall back to scp if rsync unavailable)
# Exclude generated/cached content that gets recreated on the remote.
# Use -rlptD (archive minus -o -g) so ownership on the remote is set
# by the receiving user (devbox). Preserving host UID/GID with -a
# tagged files with the pusher's numeric GID, which leaked through
# whenever the VM happened to have a matching group (see #group-1001).
if command -v rsync &>/dev/null; then
rsync -rlptDz --progress \
--exclude='node_modules' \
--exclude='__pycache__' \
--exclude='.venv' \
--exclude='*.pyc' \
--exclude='cli/cache' \
--exclude='sso/cache' \
-e "ssh ${SSH_OPTS}" "${local_path}/" "${SSH_HOST}:${remote_path}/"
else
scp -o "ControlPath=${CTRL_SOCKET}" -r "${local_path}/." "${SSH_HOST}:${remote_path}/"
fi
ok "Synced ${local_path}"
SYNCED=$((SYNCED + 1))
fi
done
# ── Summary ─────────────────────────────────────────────────────────
echo ""
if [[ $SYNCED -eq 0 ]]; then
warn "No active bind mounts detected in remote docker-compose.yml"
warn "Uncomment the mounts you need in ${REMOTE_COMPOSE} on the VM, then re-run this script"
else
ok "Synced ${SYNCED} director$([ $SYNCED -eq 1 ] && echo 'y' || echo 'ies') to ${SSH_HOST}"
fi
+15 -7
View File
@@ -10,13 +10,17 @@
services:
devbox:
build:
context: .
args:
INSTALL_PYTHON: "false"
INSTALL_GO: "false"
INSTALL_OMOS: "false"
image: opencode-devbox:latest
image: joakimp/opencode-devbox:latest
# For multi-agent orchestration, use the omos variant instead:
# image: joakimp/opencode-devbox:latest-omos
#
# To build from source instead of pulling from Docker Hub, uncomment:
# build:
# context: .
# args:
# INSTALL_PYTHON: "false"
# INSTALL_GO: "false"
# INSTALL_OMOS: "false"
container_name: opencode-devbox
stdin_open: true
tty: true
@@ -45,6 +49,9 @@ services:
# Optional: persist opencode data (auth, memory, etc.)
- devbox-data:/home/developer/.local/share/opencode
# Optional: persist opencode TUI settings (theme, toggles, etc.)
- devbox-state:/home/developer/.local/state/opencode
# Optional: persist uv data (Python installs, tool installs)
# Without this, 'uv python install' must be re-run after container removal.
- devbox-uv:/home/developer/.local/share/uv
@@ -62,6 +69,7 @@ services:
volumes:
devbox-data:
devbox-state:
devbox-uv:
# devbox-rustup:
# devbox-cargo:
+4 -4
View File
@@ -22,7 +22,7 @@ if [ ! -f "$CONFIG_FILE" ] && [ -n "${OPENCODE_PROVIDER:-}" ]; then
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-anthropic/claude-sonnet-4-5}",
"model": "${OPENCODE_MODEL:-anthropic/claude-sonnet-4-6}",
"share": "disabled",
"autoupdate": false
}
@@ -32,7 +32,7 @@ EOF
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-openai/gpt-4o}",
"model": "${OPENCODE_MODEL:-openai/gpt-5.4}",
"share": "disabled",
"autoupdate": false
}
@@ -42,7 +42,7 @@ EOF
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-amazon-bedrock/anthropic.claude-sonnet-4-5-v1}",
"model": "${OPENCODE_MODEL:-amazon-bedrock/global.anthropic.claude-sonnet-4-5-20250929-v1:0}",
"share": "disabled",
"autoupdate": false,
"provider": {
@@ -60,7 +60,7 @@ EOF
cat > "$CONFIG_FILE" <<EOF
{
"\$schema": "https://opencode.ai/config.json",
"model": "${OPENCODE_MODEL:-anthropic/claude-sonnet-4-5}",
"model": "${OPENCODE_MODEL:-anthropic/claude-sonnet-4-6}",
"share": "disabled",
"autoupdate": false
}
+17
View File
@@ -51,8 +51,25 @@ fi
# developer user can write to them.
FINAL_UID="${TARGET_UID:-$CURRENT_UID}"
FINAL_GID="${TARGET_GID:-$CURRENT_GID}"
# First, fix parent dirs that Docker auto-creates as root:root when it
# materializes nested mount points (e.g. mounting a volume at
# .local/state/opencode creates .local/state as root). Non-recursive —
# we only need the dir node itself; children are handled below or were
# created by the user.
for parent in \
/home/"$USER_NAME"/.local \
/home/"$USER_NAME"/.local/share \
/home/"$USER_NAME"/.local/state \
/home/"$USER_NAME"/.config; do
if [ -d "$parent" ] && [ "$(stat -c '%u' "$parent" 2>/dev/null)" != "$FINAL_UID" ]; then
chown "$FINAL_UID":"$FINAL_GID" "$parent" 2>/dev/null || true
fi
done
for dir in \
/home/"$USER_NAME"/.local/share/opencode \
/home/"$USER_NAME"/.local/state/opencode \
/home/"$USER_NAME"/.local/share/uv \
/home/"$USER_NAME"/.rustup \
/home/"$USER_NAME"/.cargo \