feat: repurpose .openharness as install root

Deletes the in-repo .openharness/ subdir, moves composeOverrides config
to repo-root config.json (gitignored, seeded from config.example.json),
and reworks scripts/install.sh to clone into ~/.openharness/ with an
in-place migration for existing ~/openharness/ checkouts.

Closes #251

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
OpenHarness BotandClaude Opus 4.7 committed 2026-05-07 15:52:08 -06:00
1 parent f27e3aadf1
commit 7bb3c4b3f0
31 files changed
+124 -90

No files matched your search

-1
View File
@@ -136,7 +136,6 @@ LABEL devcontainer.metadata='[{"remoteUser":"sandbox","workspaceFolder":"/home/s
# ─── Copy install scripts + workspace (for CI; bind mount overrides at runtime)
COPY --chown=sandbox:sandbox install/ /home/sandbox/install/
RUN chmod +x /home/sandbox/install/*.sh
COPY --chown=sandbox:sandbox .openharness/ /home/sandbox/.openharness/
COPY --chown=sandbox:sandbox workspace/ /home/sandbox/harness/workspace/
# ─── Entrypoint ────────────────────────────────────────────────────
+1 -1
View File
@@ -42,7 +42,7 @@
# rewrite host file ownership). The chown loop only matches directories,
# so ~/.claude.json (a file) is untouched regardless.
#
# Usage: enable via .openharness/config.json composeOverrides[]:
# Usage: enable via config.json composeOverrides[]:
# ".devcontainer/docker-compose.claude-host.yml"
services:
+2 -2
View File
@@ -27,7 +27,7 @@
# into a container that may execute untrusted agent code. Enable only for
# trusted workflows. OAuth login is the expected default auth path for
# codex in this harness, so the overlay is enabled by default in
# .openharness/config.json.
# config.json.
#
# Bleed-through: ~/.codex/sessions/, history.jsonl, and memories/ from
# other host work will be visible inside the sandbox. Acknowledged cost
@@ -43,7 +43,7 @@
# skips its recursive chown on /home/sandbox/.codex (which would otherwise
# rewrite host file ownership).
#
# Usage: enable via .openharness/config.json composeOverrides[]:
# Usage: enable via config.json composeOverrides[]:
# ".devcontainer/docker-compose.codex-host.yml"
services:
@@ -36,7 +36,7 @@
# Provider keys are NOT OAuth tokens — they typically have no expiry and
# no per-request scope, so a leak from this overlay has a wider blast
# radius than `claude-host` or `codex-host`. The overlay is opt-in: it is
# NOT listed in the default `.openharness/config.json` `composeOverrides`.
# NOT listed in the default `config.json` `composeOverrides`.
#
# Bleed-through: ~/.deepagents/sessions/, memory, and skills from other
# host work will be visible inside the sandbox. Acknowledged cost of
@@ -48,7 +48,7 @@
# stays in the base compose — it becomes orphaned (unmounted) while this
# overlay is active and is reinstated when the overlay is removed. To
# return to the named volume, remove this overlay from
# `.openharness/config.json` `composeOverrides` and rerun `make sandbox`
# `config.json` `composeOverrides` and rerun `make sandbox`
# (or `make destroy && make sandbox` if the container is already up).
# Resetting the `deepagents-auth` named volume only (without touching
# host `~/.deepagents`) is `docker volume rm <project>_deepagents-auth`.
@@ -57,7 +57,7 @@
# entrypoint.sh skips its recursive chown on /home/sandbox/.deepagents
# (which would otherwise rewrite host file ownership).
#
# Usage: enable via .openharness/config.json composeOverrides[]:
# Usage: enable via config.json composeOverrides[]:
# ".devcontainer/docker-compose.deepagents-host.yml"
services:
@@ -23,7 +23,7 @@
# into a container that may execute untrusted agent code. Enable only for
# trusted workflows. OpenAI OAuth through OpenCode's /connect flow is the
# expected default auth path for opencode in this harness, so the overlay
# is enabled by default in .openharness/config.json.
# is enabled by default in config.json.
#
# Permissions: Read-write (sandbox writes host sessions/provider state).
# Replaces: opencode-auth named volume from base docker-compose.yml at the
@@ -36,7 +36,7 @@
# entrypoint.sh skips recursive chown on /home/sandbox/.local/share/opencode
# (which would otherwise rewrite host file ownership).
#
# Usage: enable via .openharness/config.json composeOverrides[]:
# Usage: enable via config.json composeOverrides[]:
# ".devcontainer/docker-compose.opencode-host.yml"
services:
+2 -2
View File
@@ -23,7 +23,7 @@
# into a container that may execute untrusted agent code. Enable only
# for trusted workflows. OAuth login is the expected default auth path
# for pi in this harness, so the overlay is enabled by default in
# .openharness/config.json.
# config.json.
#
# Bleed-through: sessions and identities from other host work will be
# visible inside the sandbox. Acknowledged cost of full-mount convenience.
@@ -38,7 +38,7 @@
# skips its recursive chown on /home/sandbox/.pi (which would otherwise
# rewrite host file ownership).
#
# Usage: enable via .openharness/config.json composeOverrides[]:
# Usage: enable via config.json composeOverrides[]:
# ".devcontainer/docker-compose.pi-host.yml"
services:
+1 -1
View File
@@ -3,7 +3,7 @@
# Purpose: Run sshd as the main process, replacing the sleep-infinity default.
# Enables: Remote SSH access to the sandbox container (host port 2222 → container port 22)
# Auth: Entrypoint auto-configures password auth (SANDBOX_PASSWORD env var) and host keys on startup
# Requirements: Add to .openharness/config.json composeOverrides to activate
# Requirements: Add to config.json composeOverrides to activate
# Usage: docker compose -f docker-compose.yml -f docker-compose.sshd.yml up
# Provides: SSH access at ssh -p 2222 sandbox@localhost with password from SANDBOX_PASSWORD
+2 -4
View File
@@ -5,7 +5,6 @@
#
# Core volumes:
# - ..:/home/sandbox/harness — Project root (bind-mount)
# - ../.openharness:/home/sandbox/.openharness — Config overlay
# - /var/run/docker.sock:/var/run/docker.sock — Docker daemon access (nested docker)
# - claude-auth:/home/sandbox/.claude — Persistent Claude CLI auth
# - codex-auth:/home/sandbox/.codex — Persistent Codex CLI auth
@@ -19,7 +18,7 @@
# - cloudflared-auth:/home/sandbox/.cloudflared — Cloudflare credentials
# - gh-config:/home/sandbox/.config/gh — GitHub CLI config
#
# Core env vars (see .openharness/config.json for overlay toggles):
# Core env vars (see config.json for overlay toggles):
# - TZ: Timezone (default America/Denver)
# - GIT_USER_NAME, GIT_USER_EMAIL: Git commit author
# - GH_TOKEN: GitHub token for `gh auth` automation
@@ -35,7 +34,7 @@
# -f .devcontainer/docker-compose.postgres.yml \
# -f .devcontainer/docker-compose.slack.yml up -d
#
# Overlays in .openharness/config.json composeOverrides are auto-loaded by `make sandbox` and `scripts/install.sh` via jq.
# Overlays in config.json composeOverrides are auto-loaded by `make sandbox` and `scripts/install.sh` via jq.
name: ${SANDBOX_NAME:-openharness}
@@ -47,7 +46,6 @@ services:
dockerfile: .devcontainer/Dockerfile
volumes:
- ..:/home/sandbox/harness
- ../.openharness:/home/sandbox/.openharness
- /var/run/docker.sock:/var/run/docker.sock
- claude-auth:/home/sandbox/.claude
- codex-auth:/home/sandbox/.codex
+1 -1
View File
@@ -18,7 +18,7 @@ fi
# host file ownership (see docker-compose.{claude,codex,pi,deepagents,opencode}-host.yml).
# Harness packs that introduce their own host-mount overlays should add
# their own skip clauses or run a *-entrypoint-hook.sh.
for dir in .claude .codex .pi .deepagents .cloudflared .config/gh .ssh .openharness; do
for dir in .claude .codex .pi .deepagents .cloudflared .config/gh .ssh; do
if [ -d "/home/sandbox/$dir" ]; then
if [ "$dir" = ".claude" ] && [ "${CLAUDE_HOST_BIND_MOUNT:-0}" = "1" ]; then
continue
+1
View File
@@ -1,4 +1,5 @@
**/.env*
/config.json
.worktrees/*
.claude/worktrees/
tasks/*/archive/
-1
View File
@@ -1 +0,0 @@
-22
View File
@@ -1,22 +0,0 @@
# `.openharness/`
Host-side configuration mounted into the sandbox container at `/home/sandbox/.openharness/`. Read by the install flow and the container build; the directory is bind-mounted by `.devcontainer/docker-compose.yml`.
## Files
| File | Tracked? | Consumer | Purpose |
|---|---|---|---|
| `config.json` | yes | `install.sh`, `install/banner.sh`, all `docker-compose.*-host.yml` headers | `composeOverrides[]` — list of overlay compose files to apply on top of the base `docker-compose.yml`. Modified by `install.sh` at install time when host UID ≠ 1000. |
| `.gitignore` | yes | git | Excludes per-host runtime artifacts. |
| `agent/auth.json` | no (gitignored via root `**/auth.json`) | mifune pi pack runtime | Per-user credential symlink. Only present if a pack that needs it is installed. |
## Conventions
- **Hand-edit `config.json`** to opt into / out of overlays. Each overlay's own header documents its requirements.
- **Do not commit** anything in this directory beyond `config.json`, `.gitignore`, and this README.
## See also
- `.devcontainer/docker-compose.yml` (mount point + base service)
- `.devcontainer/Dockerfile` (`COPY .openharness/`)
- `docs/guide/overlays.md` (overlay catalog)
-1
View File
@@ -1 +0,0 @@
../workspace/.claude/rules
+4
View File
@@ -21,6 +21,10 @@ Update policy and release automation live in [`.claude/rules/git.md`](.claude/ru
- Top-level `Makefile` wrapping `docker compose -f .devcontainer/docker-compose.yml` so users type `make sandbox` / `make shell` / `make destroy` instead of the 50-character compose lines. Targets: `sandbox`, `shell`, `destroy`, `stop`, `logs`, `ps`, `restart`, `help` (default, self-documenting via awk). Uses `-include .devcontainer/.env` so `SANDBOX_NAME` flows through. No agent-CLI targets — the harness's value is sandbox isolation, agent choice (`claude` / `codex` / `pi`) happens inside `make shell`.
### Changed
- **BREAKING**: Installer now clones to `~/.openharness/` (was `~/openharness/`); `scripts/install.sh` auto-migrates existing `~/openharness/` checkouts in place.
- **BREAKING**: In-repo `.openharness/` subdirectory removed entirely.
- **BREAKING**: `composeOverrides[]` config moved from `.openharness/config.json` to repo-root `config.json` (gitignored, seeded from tracked `config.example.json`).
- **BREAKING**: `pi-host.yml` dropped from default `composeOverrides` — Pi users add it back manually in `config.json`.
- `/cloudflared-tunnel` skill reconstructs a missing `<tunnel-id>.json` from `cloudflared tunnel token` instead of failing — fixes the case where a tunnel exists in the Cloudflare account (e.g. created on another host) but only `cert.pem` is on the running machine. Documents that token-mode runs (`--token <T>`) silently drop the local `--url` flag, leaving Cloudflare to return 530s while connections register. ([#249](https://github.com/ryaneggz/open-harness/issues/249))
- Quickstart collapsed from two parallel paths (installer + manual) to a single linear flow in `README.md` and `docs/quickstart.md`. Manual setup demoted to a `<details>` block. New "Configuration" section in both docs surfaces `.devcontainer/.env` as the customization surface (`make destroy && make sandbox` to re-apply).
- `install.sh` moved to `scripts/install.sh` to colocate with `cron-runtime.ts` and `ralph.sh` per the orchestrator-scripts convention. Repo-detection logic and help-text self-references updated for the new path. **Action required for the curl-pipe URL**: update the `https://oh.mifune.dev/install.sh` redirect to `https://raw.githubusercontent.com/ryaneggz/open-harness/refs/heads/main/scripts/install.sh`.
+9 -4
View File
@@ -6,20 +6,25 @@
SANDBOX_NAME ?= openharness
COMPOSE_BASE := -f .devcontainer/docker-compose.yml
COMPOSE_OVERRIDES := $(shell jq -r '.composeOverrides[]?' \
.openharness/config.json 2>/dev/null | sed 's|^|-f |' | tr '\n' ' ')
config.json 2>/dev/null | sed 's|^|-f |' | tr '\n' ' ')
COMPOSE := docker compose $(COMPOSE_BASE) $(COMPOSE_OVERRIDES)
.DEFAULT_GOAL := help
.PHONY: sandbox shell destroy stop logs ps restart help _check-jq
.PHONY: sandbox shell destroy stop logs ps restart help _check-jq _check-config
_check-jq:
@command -v jq >/dev/null 2>&1 || { \
echo "ERROR: jq is required to expand .openharness/config.json composeOverrides."; \
echo "ERROR: jq is required to expand config.json composeOverrides."; \
echo "Install jq (e.g. apt install jq, brew install jq) and re-run."; \
exit 1; }
sandbox: _check-jq ## Provision and start the sandbox
_check-config:
@[ -f config.json ] || { \
echo "ERROR: config.json missing. Run scripts/install.sh, OR: cp config.example.json config.json"; \
exit 1; }
sandbox: _check-jq _check-config ## Provision and start the sandbox
$(COMPOSE) up -d --build
shell: ## Connect to the sandbox (agent choice happens inside)
+2 -2
View File
@@ -16,14 +16,14 @@
curl -fsSL https://oh.mifune.dev/install.sh | bash
```
The installer clones into `~/openharness`, offers to share your host
The installer clones into `~/.openharness`, offers to share your host
`gh` token, writes `.devcontainer/.env`, and builds the image (~10 min
cold, ~30s warm). Only host dependency: [Docker](https://docs.docker.com/get-docker/).
## 🚀 Use it
```bash
cd ~/openharness
cd ~/.openharness
make shell # enter the isolated sandbox
# inside the sandbox, launch any core agent:
# claude # Claude Code (default)
@@ -2,7 +2,6 @@
"composeOverrides": [
".devcontainer/docker-compose.claude-host.yml",
".devcontainer/docker-compose.codex-host.yml",
".devcontainer/docker-compose.pi-host.yml",
".devcontainer/docker-compose.opencode-host.yml"
]
}
+2 -4
View File
@@ -36,7 +36,7 @@ Key configuration choices:
- `command: sleep infinity` — keeps the container alive; actual work happens inside named tmux sessions, not in the foreground process.
- `stdin_open: true` and `tty: true` — required for interactive shells via `docker exec`.
Optional overlays in `.devcontainer/` add services (Postgres, Cloudflare tunnel, SSH daemon, Slack bot) and are activated by listing them in `.openharness/config.json` under `composeOverrides`. `make sandbox` and `scripts/install.sh` read that file via `jq` and pass the corresponding `-f` flags to `docker compose up`.
Optional overlays in `.devcontainer/` add services (Postgres, Cloudflare tunnel, SSH daemon, Slack bot) and are activated by listing them in `config.json` (at the repo root, gitignored — copy from `config.example.json` if missing) under `composeOverrides`. `make sandbox` and `scripts/install.sh` read that file via `jq` and pass the corresponding `-f` flags to `docker compose up`.
## Bind Mounts
@@ -45,7 +45,6 @@ The container mounts the project root and several named volumes:
| Mount | Host path | Container path | Purpose |
|-------|-----------|---------------|---------|
| Project root | `..` (repo root) | `/home/sandbox/harness` | Source code, worktrees, workspace |
| OH config | `../.openharness` | `/home/sandbox/.openharness` | Sandbox config, compose overlay selection |
| Docker socket | `/var/run/docker.sock` | `/var/run/docker.sock` | Nested docker for the `oh` CLI |
| Claude auth | named volume `claude-auth` | `/home/sandbox/.claude` | Claude Code credentials — persists across rebuilds |
| Pi auth | named volume `pi-auth` | `/home/sandbox/.pi` | Pi Agent OAuth tokens |
@@ -98,14 +97,13 @@ open-harness/
├── .github/
│ ├── workflows/ # CI: lint, test, build, release, docs deploy
│ └── ISSUE_TEMPLATE/ # agent / audit / bug / feature / skill / task
├── config.json # composeOverrides[] consumed by Makefile + install.sh (gitignored; copy from config.example.json)
├── .devcontainer/
│ ├── Dockerfile # Debian + Node 22 + agent CLIs + pnpm
│ ├── docker-compose.yml # base compose
│ ├── docker-compose.*.yml # overlays (postgres, cloudflared, ssh, …)
│ ├── entrypoint.sh # boot: docker GID, cron runtime, banner
│ └── .example.env # template copied by install.sh on first run
├── .openharness/
│ └── config.json # composeOverrides[] consumed by Makefile + install.sh
├── .claude/
│ ├── rules/ # auto-loaded coding/process rules
│ ├── skills/ # /release, /ci-status, /cloudflared-tunnel, /agent-browser
+3 -3
View File
@@ -10,7 +10,7 @@ Clone the pack into your sandbox workspace and follow its README:
git clone https://github.com/<owner>/<repo> workspace/<pack-name>
```
The pack's README is the source of truth for what to do next — typically: register any compose overlays in `.openharness/config.json`, run an install script to pull agent CLIs, and copy any seed files into `workspace/`. Each pack documents its own steps so the harness itself stays minimal.
The pack's README is the source of truth for what to do next — typically: register any compose overlays in `config.json` (at the repo root, gitignored — copy from `config.example.json` if missing), run an install script to pull agent CLIs, and copy any seed files into `workspace/`. Each pack documents its own steps so the harness itself stays minimal.
## Pack layout
@@ -22,7 +22,7 @@ A pack is a self-contained directory. Conventional layout:
harness.json # Manifest (optional, informational)
install-hook.sh # Pulls agent CLIs / sets up the sandbox
entrypoint-hook.sh # Sourced by openharness entrypoint at runtime
overlays/*.yml # Compose overlays — register in .openharness/config.json
overlays/*.yml # Compose overlays — register in config.json
workspace-seed/ # Files to copy into the sandbox workspace
Dockerfile # OPTIONAL: derived image FROM openharness base
```
@@ -53,7 +53,7 @@ A pack MAY ship a `harness.json` describing itself for discoverability. The file
| `description` | string | One-line summary. |
| `openharness` | string | Minimum compatible openharness version (semver range). |
| `agents` | string[] | Agent CLIs the pack wires in. Informational. |
| `compose_overlays` | string[] | Paths-within-pack to docker-compose YAML overlays — user adds these to `.openharness/config.json`. |
| `compose_overlays` | string[] | Paths-within-pack to docker-compose YAML overlays — user adds these to `config.json`. |
| `prebuilt_image` | string? | Optional GHCR image tag for a fast Docker-only on-ramp. |
## Distribution
+1 -1
View File
@@ -55,7 +55,7 @@ Only applies when the `deepagents-host` overlay is enabled. See [overlays](./ove
| Variable | Default | Description |
|----------|---------|-------------|
| `HOST_DEEPAGENTS_DIR` | `~/.deepagents` | Path to your host DeepAgents directory. The overlay bind-mounts it RW into `/home/sandbox/.deepagents`. **Setting this alone does nothing** — the overlay must also be listed in `.openharness/config.json` `composeOverrides`. |
| `HOST_DEEPAGENTS_DIR` | `~/.deepagents` | Path to your host DeepAgents directory. The overlay bind-mounts it RW into `/home/sandbox/.deepagents`. **Setting this alone does nothing** — the overlay must also be listed in `config.json` `composeOverrides`. |
### SSH (overlay)
+1 -1
View File
@@ -22,7 +22,7 @@ Run multiple sandboxes on a single host, each reachable via SSH on a unique port
### 1. Enable the sshd overlay
Add to each sandbox's `.openharness/config.json`:
Add to each sandbox's `config.json` (at the repo root, gitignored — copy from `config.example.json` if missing):
```json
{
+5 -5
View File
@@ -6,7 +6,7 @@ sidebar_position: 2
All sandboxes are built from `.devcontainer/Dockerfile` — a Debian Bookworm image with Node.js 22, pnpm, agent CLIs (Claude Code, Codex, OpenCode, Pi), and dev tools pre-installed.
Compose overlays in `.devcontainer/` add optional services or host-state mounts. Enable or disable them in `.openharness/config.json`.
Compose overlays in `.devcontainer/` add optional services or host-state mounts. Enable or disable them in `config.json` (at the repo root, gitignored — copy from `config.example.json` if missing).
## Available overlays
@@ -27,7 +27,7 @@ Compose overlays in `.devcontainer/` add optional services or host-state mounts.
## Configuration
Edit `.openharness/config.json` to enable or disable overlays:
Edit `config.json` (at the repo root, gitignored — copy from `config.example.json` if missing) to enable or disable overlays:
```json
{
@@ -72,7 +72,7 @@ The password is set from the `SANDBOX_PASSWORD` environment variable (default: `
To add PostgreSQL to your sandbox:
1. Add `".devcontainer/docker-compose.postgres.yml"` to `composeOverrides` in `.openharness/config.json`
1. Add `".devcontainer/docker-compose.postgres.yml"` to `composeOverrides` in `config.json`
2. Run `make sandbox`
3. The database is available at `postgresql://sandbox:sandbox@postgres:5432/sandbox`
@@ -184,7 +184,7 @@ your host state:
`config.toml` (CLI defaults), memory, skills, sessions
The overlay is **opt-in** and is NOT listed in the default
`.openharness/config.json` `composeOverrides`. DeepAgents itself is an
`config.json` `composeOverrides`. DeepAgents itself is an
optional supported runtime; Claude remains the default agent path.
Enable it by adding the overlay path to your existing
@@ -239,7 +239,7 @@ mkdir -p ~/.deepagents
**Disabling the overlay (returning to the named volume):**
1. Remove `".devcontainer/docker-compose.deepagents-host.yml"` from
`composeOverrides` in `.openharness/config.json`.
`composeOverrides` in `config.json`.
2. Run `make sandbox` (or `make destroy && make sandbox`).
The base `deepagents-auth` named volume reattaches at
+1 -1
View File
@@ -49,7 +49,7 @@ The `docker` compose overlay mounts `/var/run/docker.sock` into the container, a
- Manage sibling containers on the same host
- Access `host.docker.internal` for host networking
This is enabled by default. If your agent doesn't need Docker access, remove the docker overlay from `.openharness/config.json`.
This is enabled by default. If your agent doesn't need Docker access, remove the docker overlay from `config.json`.
## Isolation model
+1 -1
View File
@@ -25,7 +25,7 @@ curl -fsSL https://oh.mifune.dev/install.sh | bash
The installer:
1. Verifies Docker and git are present.
2. Clones the repo into `~/openharness` (or pulls latest if the directory already exists).
2. Clones the repo into `~/.openharness` (or pulls latest if the directory already exists).
3. Prompts for `SANDBOX_NAME` and `SANDBOX_PASSWORD`, then writes `.devcontainer/.env`.
4. Runs `docker compose -f .devcontainer/docker-compose.yml up -d --build`.
5. Prints the next-step commands (open a shell, stop, tear down).
+1 -1
View File
@@ -43,7 +43,7 @@ deepagents # optional runtime; configure provider keys (see below)
OpenCode's recommended default for ChatGPT Plus and Pro users is OpenAI OAuth through `/connect` → OpenAI. It writes auth to `~/.local/share/opencode/auth.json`.
Each agent writes tokens to its directory under the sandbox home (`~/.claude/`, `~/.codex/`, `~/.local/share/opencode/`, `~/.pi/`, `~/.deepagents/`). With persistent named volumes, credentials survive container rebuilds. The `*-host` overlays (Claude/Codex/OpenCode/Pi enabled by default in `.openharness/config.json`; DeepAgents opt-in) bind-mount matching host directories so your laptop's existing auth carries straight in — see [provision.md](./operations/provision.md).
Each agent writes tokens to its directory under the sandbox home (`~/.claude/`, `~/.codex/`, `~/.local/share/opencode/`, `~/.pi/`, `~/.deepagents/`). With persistent named volumes, credentials survive container rebuilds. The `*-host` overlays (Claude/Codex/OpenCode/Pi enabled by default in `config.json`; DeepAgents opt-in) bind-mount matching host directories so your laptop's existing auth carries straight in — see [provision.md](./operations/provision.md).
DeepAgents is an optional supported runtime; Claude remains the default. DeepAgents reads provider keys from `~/.deepagents/.env`. Create it on first use:
+2 -2
View File
@@ -14,11 +14,11 @@ Stops the container(s), removes the network, and **removes named volumes** decla
## With overlays
If you provisioned with overlays from `.openharness/config.json`, pass them to `down` as well so all services from the merged compose graph are cleaned up:
If you provisioned with overlays from `config.json`, pass them to `down` as well so all services from the merged compose graph are cleaned up:
```bash
COMPOSE_FILES="-f .devcontainer/docker-compose.yml"
for f in $(jq -r '.composeOverrides[]' .openharness/config.json); do
for f in $(jq -r '.composeOverrides[]' config.json); do
COMPOSE_FILES="$COMPOSE_FILES -f $f"
done
docker compose --env-file .devcontainer/.env $COMPOSE_FILES down -v
+3 -3
View File
@@ -12,7 +12,7 @@ That single command builds the image (Node 22, agent CLIs, procps, tmux), starts
## Compose overlays
Overlays live in `.devcontainer/docker-compose.*.yml` and are selected through `.openharness/config.json` → `composeOverrides`. Each entry adds another `-f <file>` to the compose invocation.
Overlays live in `.devcontainer/docker-compose.*.yml` and are selected through `config.json` (at the repo root, gitignored — copy from `config.example.json` if missing) → `composeOverrides`. Each entry adds another `-f <file>` to the compose invocation.
| Overlay | Purpose |
|---|---|
@@ -31,7 +31,7 @@ Overlays live in `.devcontainer/docker-compose.*.yml` and are selected through `
> The `claude-host`, `codex-host`, `opencode-host`, `pi-host`, and `deepagents-host` overlays all require host UID = 1000 and pre-existing host directories (`~/.claude` + `~/.claude.json` for claude; `~/.codex` for codex; `~/.local/share/opencode` for OpenCode; `~/.pi` for pi; `~/.deepagents` for deepagents). See the trust tradeoffs in each `.devcontainer/docker-compose.*-host.yml` file. `scripts/install.sh` pre-creates the host directories and strips `*-host.yml` overlays from `composeOverrides` automatically when host UID is not 1000.
To enable an overlay, add its path to `.openharness/config.json`:
To enable an overlay, add its path to `config.json`:
```json
{
@@ -46,7 +46,7 @@ Then provision with the overlay list expanded:
```bash
COMPOSE_FILES="-f .devcontainer/docker-compose.yml"
for f in $(jq -r '.composeOverrides[]' .openharness/config.json); do
for f in $(jq -r '.composeOverrides[]' config.json); do
COMPOSE_FILES="$COMPOSE_FILES -f $f"
done
docker compose --env-file .devcontainer/.env $COMPOSE_FILES up -d --build
+2 -2
View File
@@ -17,14 +17,14 @@ Install Docker with the Compose plugin: [docs.docker.com/get-docker](https://doc
curl -fsSL https://oh.mifune.dev/install.sh | bash
```
The installer clones into `~/openharness`, prompts to share your host
The installer clones into `~/.openharness`, prompts to share your host
`gh` token, writes `.devcontainer/.env` with safe defaults, and brings
the sandbox up via `docker compose`.
## Enter the sandbox
```bash
cd ~/openharness
cd ~/.openharness
make shell
```
+1 -1
View File
@@ -27,7 +27,7 @@ docker compose -f .devcontainer/docker-compose.yml up -d --build
What happens:
1. Docker reads `.devcontainer/docker-compose.yml` plus any overlays (see `.openharness/config.json` for active overlays).
1. Docker reads `.devcontainer/docker-compose.yml` plus any overlays (see `config.json` at the repo root for active overlays).
2. The image is built or rebuilt as needed.
3. The container starts in detached mode.
4. The healthcheck eventually flips status to `healthy`.
+1 -1
View File
@@ -22,7 +22,7 @@ overlays=""
if command -v jq >/dev/null 2>&1; then
overlays=$(jq -r \
'.composeOverrides[]? | sub("^\\.devcontainer/docker-compose\\."; "") | sub("\\.yml$"; "")' \
"${HOME}/harness/.openharness/config.json" 2>/dev/null \
"${HOME}/harness/config.json" 2>/dev/null \
| paste -sd, -)
fi
[ -z "$overlays" ] && overlays="(none)"
+70 -16
View File
@@ -92,7 +92,7 @@ Usage:
curl -fsSL https://oh.mifune.dev/install.sh | bash [-s -- <flags>]
./scripts/install.sh [<flags>]
Clones (or pulls) the repo into ~/openharness, prepares host auth dirs,
Clones (or pulls) the repo into ~/.openharness, prepares host auth dirs,
and brings up the sandbox via 'docker compose'. Per SPEC v0.7, docker
compose is the canonical substrate; the legacy 'oh' CLI is removed.
@@ -175,18 +175,60 @@ if [ -n "$REPO_CANDIDATE" ] && [ -f "$REPO_CANDIDATE/.devcontainer/docker-compos
REPO_DIR="$REPO_CANDIDATE"
ok "Using local repo: $REPO_DIR"
else
REPO_DIR="$HOME/openharness"
# One-time migration: legacy curl-pipe installs cloned to ~/.openharness,
# which collides visually with the in-repo `.openharness/` config dir.
# If the new path is absent and the old one is a clone, move it.
LEGACY_REPO="$HOME/.openharness"
if [ ! -d "$REPO_DIR" ] && [ -d "$LEGACY_REPO/.git" ]; then
mv "$LEGACY_REPO" "$REPO_DIR"
ok "Migrated $LEGACY_REPO → $REPO_DIR"
# Install target is ~/.openharness (hidden dir, no collision with repo subdirs).
# OLD_REPO is the post-#200 path that was briefly the install target.
OLD_REPO="$HOME/openharness"
REPO_DIR="$HOME/.openharness"
# ── Population classification + migration ─────────────────────────────
__HAS_OLD=0; __HAS_NEW=0
[ -d "$OLD_REPO/.git" ] && __HAS_OLD=1
if [ -d "$REPO_DIR" ] && [ ! -d "$REPO_DIR/.git" ]; then
die "~/.openharness exists but is not a git clone. Inspect and remove it, then re-run."
fi
unset LEGACY_REPO
[ -d "$REPO_DIR/.git" ] && __HAS_NEW=1
if [ "$__HAS_OLD" = "1" ] && [ "$__HAS_NEW" = "1" ]; then
# ── B+C collision: both paths exist ───────────────────────────────────
__OLD_DIRTY=0; __NEW_DIRTY=0
git -C "$OLD_REPO" diff --quiet 2>/dev/null && git -C "$OLD_REPO" diff --cached --quiet 2>/dev/null || __OLD_DIRTY=1
git -C "$REPO_DIR" diff --quiet 2>/dev/null && git -C "$REPO_DIR" diff --cached --quiet 2>/dev/null || __NEW_DIRTY=1
if [ "$__OLD_DIRTY" = "1" ] && [ "$__NEW_DIRTY" = "0" ]; then
# OLD has changes; keep OLD as active (rename to new), archive NEW
__ARCHIVE="${REPO_DIR}.legacy.$(date +%Y%m%d%H%M%S)"
mv "$REPO_DIR" "$__ARCHIVE"
warn "Archived $REPO_DIR → $__ARCHIVE"
git -C "$OLD_REPO" stash push -u -m "install.sh: pre-rename autostash" 2>/dev/null || true
mv "$OLD_REPO" "$REPO_DIR"
ok "Migrated $OLD_REPO → $REPO_DIR (had local changes — autostashed)"
elif [ "$__NEW_DIRTY" = "1" ] && [ "$__OLD_DIRTY" = "0" ]; then
# NEW has changes; keep NEW, archive OLD
__ARCHIVE="${OLD_REPO}.legacy.$(date +%Y%m%d%H%M%S)"
mv "$OLD_REPO" "$__ARCHIVE"
warn "Archived $OLD_REPO → $__ARCHIVE"
ok "Keeping $REPO_DIR (had local changes)"
else
# Neither (or both) dirty: prefer OLD_REPO (post-#200, more recent install)
__ARCHIVE="${REPO_DIR}.legacy.$(date +%Y%m%d%H%M%S)"
mv "$REPO_DIR" "$__ARCHIVE"
warn "Archived $REPO_DIR → $__ARCHIVE"
git -C "$OLD_REPO" stash push -u -m "install.sh: pre-rename autostash" 2>/dev/null || true
mv "$OLD_REPO" "$REPO_DIR"
ok "Migrated $OLD_REPO → $REPO_DIR"
fi
unset __OLD_DIRTY __NEW_DIRTY __ARCHIVE
elif [ "$__HAS_OLD" = "1" ] && [ "$__HAS_NEW" = "0" ]; then
# ── Population B: ~/openharness only — rename to ~/.openharness ────────
git -C "$OLD_REPO" stash push -u -m "install.sh: pre-rename autostash" 2>/dev/null || true
mv "$OLD_REPO" "$REPO_DIR"
ok "Migrated $OLD_REPO → $REPO_DIR"
fi
# Population C (~/.openharness only) and A (neither) fall through to the
# pull / clone logic below.
unset __HAS_OLD __HAS_NEW OLD_REPO
if [ -d "$REPO_DIR/.git" ]; then
# Gate pull on clean working tree — don't abort on local edits.
# ── Pull (clean tree only) ─────────────────────────────────────────────
if git -C "$REPO_DIR" diff --quiet 2>/dev/null && git -C "$REPO_DIR" diff --cached --quiet 2>/dev/null; then
printf " Repository exists — pulling latest changes...\n"
git -C "$REPO_DIR" pull --ff-only
@@ -195,6 +237,7 @@ else
warn "Local changes detected in $REPO_DIR — skipping git pull. Stash or commit them, then re-run if you want the latest main."
fi
else
# ── Population A: fresh clone ──────────────────────────────────────────
if [ -n "${OH_INSTALL_REF:-}" ]; then
git clone --branch "$OH_INSTALL_REF" https://github.com/ryaneggz/open-harness.git "$REPO_DIR"
ok "Repository cloned at ref '$OH_INSTALL_REF': $REPO_DIR"
@@ -203,6 +246,18 @@ else
ok "Repository cloned: $REPO_DIR"
fi
fi
# ── Seed config.json from example if missing ──────────────────────────
if [ ! -f "$REPO_DIR/config.json" ] && [ -f "$REPO_DIR/config.example.json" ]; then
cp "$REPO_DIR/config.example.json" "$REPO_DIR/config.json"
ok "Seeded config.json from config.example.json"
fi
# ── Shell CWD notice (for B migration) ────────────────────────────────
# If the user's shell was open in ~/openharness, that path no longer exists.
printf "\n"
warn "If your current shell is still in ~/openharness, run: cd ~/.openharness"
printf "\n"
fi
cd "$REPO_DIR"
@@ -323,7 +378,7 @@ fi
__HOST_UID="$(id -u)"
if [ "$__HOST_UID" != "1000" ]; then
__OH_CONFIG="$REPO_DIR/.openharness/config.json"
__OH_CONFIG="$REPO_DIR/config.json"
if command -v jq >/dev/null 2>&1 && [ -f "$__OH_CONFIG" ]; then
# Filter *-host.yml entries out of composeOverrides. Mode-0600
# credential files in host agent state dirs can't be read by the
@@ -337,7 +392,6 @@ if [ "$__HOST_UID" != "1000" ]; then
mv "$__OH_TMP" "$__OH_CONFIG"
ok "Host UID $__HOST_UID ≠ 1000 — disabled host-bind overlays in $__OH_CONFIG"
ok "Auth will live in named volumes; first run of each agent inside the sandbox will authenticate"
warn "$__OH_CONFIG now has a local diff — \`git pull\` in $REPO_DIR will be skipped until you commit or revert it"
else
rm -f "$__OH_TMP"
ok "Host UID $__HOST_UID ≠ 1000 — host-bind overlays already disabled"
@@ -356,7 +410,7 @@ if [ "$__HOST_UID" != "1000" ]; then
warn "Credential files in host agent state dirs may be mode 0600;"
warn "the sandbox WILL NOT be able to read them despite the bind-mount."
warn ""
warn "Install jq, OR edit $REPO_DIR/.openharness/config.json"
warn "Install jq, OR edit $REPO_DIR/config.json"
warn "and remove these overlays from composeOverrides (any that you"
warn "have enabled — deepagents-host.yml is opt-in by default):"
warn " .devcontainer/docker-compose.claude-host.yml"
@@ -376,10 +430,10 @@ unset __HOST_UID
banner "Building and starting sandbox"
printf "${CYAN}==> Building image — ~10 min on cold cache, ~30s on warm cache. Compose output below.${NC}\n"
COMPOSE_FILES="-f .devcontainer/docker-compose.yml"
if command -v jq >/dev/null 2>&1 && [ -f "$REPO_DIR/.openharness/config.json" ]; then
if command -v jq >/dev/null 2>&1 && [ -f "$REPO_DIR/config.json" ]; then
while IFS= read -r override; do
[ -n "$override" ] && COMPOSE_FILES="$COMPOSE_FILES -f $override"
done < <(jq -r '.composeOverrides[]?' "$REPO_DIR/.openharness/config.json")
done < <(jq -r '.composeOverrides[]?' "$REPO_DIR/config.json")
fi
docker compose $COMPOSE_FILES up -d --build
ok "Sandbox '$SANDBOX_NAME' started"