Skip to content

govtech42/harness

Repository files navigation

harness

Idempotent shell-script installers that bootstrap a fresh Ubuntu 22.04 VPS for one of its AI-agent profiles, with a one-profile-per-host mutex, shared base tooling (incl. gh), a headless browser, opt-in DB clients and MCP servers, and an opt-in plugin layer shared across every CLI agent.

License: MIT Shell: bash Target: Ubuntu 22.04 CI Release

What is this

You rent a Lightsail/Hetzner/DO box. You want one of these stacks on it, fully configured, in a single command:

Profile What you get
cli-bundle Claude Code + OpenAI Codex + Google Antigravity + Cursor + OpenCode CLIs (any combination)
hermes Hermes Agent + dashboard + WebUI — Docker stack behind Traefik + Cloudflare (or local Python+uv)
paperclip Paperclip — Node API + embedded Postgres :3100

Plus, on every profile:

  • Common Linux toolkit (tmux, git, vim, jq, ripgrep, fd, fzf, htop, …)
  • gh (GitHub CLI) from the official apt repo
  • Headless browser (chromium via apt — falls back to Playwright's bundled Chromium)
  • DB clients psql + clickhouse-clientopt-in (off by default; set INSTALL_DB_CLIENTS=true)
  • Timezone, swap file, npm user-global prefix, ~/.bashrc PATH
  • A mutex marker so you don't accidentally stack two agent runtimes on one host

cli-bundle additionally bundles:

  • MCP servers for Claude — all opt-in, none on by default (Context7, Linear, Slack, GitHub, Supabase, Sentry, Playwright, Filesystem, Firecrawl)
  • Pluginsgraphify (default, per CLI), superpowers, OpenSpec, agent-skills, agent-browser, gbrain, gstack, plus the official Anthropic marketplace (Linear/Slack/GitHub/Notion/Atlassian/Asana/Figma/Sentry/Supabase/Vercel)

Repo layout

harness/
├── install.sh                 # top-level launcher (menu, arg, --status, --force)
├── lib/
│   ├── common.sh              # mutex_check, mutex_set, load_env, banner
│   ├── base-packages.sh       # install_base_packages, install_github_cli,
│   │                          # install_db_clients, install_headless_browser,
│   │                          # install_node, install_pnpm, install_uv
│   └── plugins.sh             # claude_headless, install_official_claude_plugin,
│                              # install_claude_plugin, install_openspec,
│                              # install_agent_skills, install_agent_browser,
│                              # install_gbrain, install_gstack_for,
│                              # print_manual_install_hint
├── docker/
│   ├── gbrain-supabase/      # local Postgres+pgvector for gbrain (127.0.0.1 only)
│   ├── hermes-stack/         # agent + dashboard + WebUI,
│   │                          # Traefik + Cloudflare (live; one shared edge)
│   └── paperclip-stack/      # Paperclip (built from source), Traefik + Cloudflare
└── profiles/
    ├── cli-bundle/            # 01-system → 02-claude → 03-codex → 04-antigravity
    │                          # → 05-cursor → 05b-opencode
    │                          # → 06-mcp → 09-plugins
    ├── hermes/                # 01-system → 02-hermes (local) | see docker/hermes-stack (hosted)
    └── paperclip/             # 01-system → 02-paperclip

Per-profile docs live in each profiles/<name>/README.md. Architecture + rationale in .claude/PLAN.md. Decision log in CHANGELOG.md. Open design decisions in .claude/RECOMMENDATIONS.md. Open work items in .claude/TODO.md.

Operational runbooks + references live in doc/:

Quick start

git clone https://github.com/govtech42/harness.git ~/harness
cd ~/harness
cp profiles/<profile>/.env.example profiles/<profile>/.env
nano profiles/<profile>/.env           # fill provider keys, toggles, etc.
./install.sh                           # interactive menu
# or jump straight in:
./install.sh cli-bundle

Example menu:

No profile installed yet.

Pick a profile to install:
1) cli-bundle
2) hermes
3) paperclip
4) Quit
#?

Launcher

./install.sh                    # interactive picker
./install.sh <profile>          # install named profile
./install.sh <profile> --force  # override mutex (see below)
./install.sh --status           # show installed profile, if any
./install.sh --help             # usage

<profile> is one of: cli-bundle, hermes, paperclip.

Selecting which CLIs (cli-bundle)

cli-bundle installs any subset of its six CLIs — all at once, or 1, 2, 3… at a time. Pass a selection on the command line, use the interactive menu, or fall back to the .env toggles:

./install.sh cli-bundle --all                      # all six
./install.sh cli-bundle --clis claude,codex        # just these two
./install.sh cli-bundle claude codex antigravity   # bare names (three)
./install.sh cli-bundle --env                      # use .env toggles, no prompt
./install.sh cli-bundle                            # interactive multi-select menu

Precedence: CLI args → interactive menu (TTY) → .env toggles. A passed selection overrides the INSTALL_* toggles for that run. Re-running with a different selection is idempotent, so you can install claude now and add codex later without reinstalling anything else.

Mutex (one profile per host)

The launcher writes the profile name to ~/.harness-profile (chmod 600) when an install completes. Any subsequent run for a different profile refuses with exit code 2:

ERROR: profile 'cli-bundle' already installed on this host.
       Refusing to install 'hermes'.
       Override:  --force   (not recommended; profiles are not designed to coexist)

Why: these stacks compete for PATH entries, ports, systemd unit names, and memory on small VPSes. Coexistence isn't supported. If you really know better, --force bypasses the check.

The cli-bundle profile is the exception: it intentionally stacks five thin CLI clients (Claude, Codex, Antigravity, Cursor, OpenCode) which have disjoint config dirs and no port binds.

Pre-requisites

Requirement Detail
OS Ubuntu 22.04 LTS (other Debian-likes may work but are untested)
RAM 2 GB minimum; 4 GB recommended for cli-bundle with MCPs + plugins
Disk 10 GB free
Network Outbound HTTPS to npm, GitHub, ClickHouse repo, each agent provider
Inbound Static IP / SSH on :22; OAuth callbacks for some agents
User ubuntu (or any user with sudo and a writable $HOME)

What each profile installs

cli-bundle

Five CLIs, installable in any combination (all at once or a few at a time — see Selecting which CLIs). The .env toggles below are the default selection; a command-line selection overrides them. Defaults: all five true (Claude, Codex, Antigravity, Cursor, OpenCode).

Toggle CLI Install path
INSTALL_CLAUDE Claude Code npm i -g @anthropic-ai/claude-code
INSTALL_CODEX OpenAI Codex npm i -g @openai/codex
INSTALL_ANTIGRAVITY Google Antigravity upstream installer (curl … | bash)
INSTALL_CURSOR Cursor agent upstream installer (curl … | bash)
INSTALL_OPENCODE OpenCode upstream installer (curl -fsSL https://opencode.ai/install | bash)

Plus:

  • MCP servers registered for Claude (06-mcp.sh) — all off by default: Context7, Linear, Slack, GitHub, Supabase, Sentry, Playwright, Filesystem, Firecrawl.
  • Plugins (09-plugins.sh) — see Plugins below.

Full docs: profiles/cli-bundle/README.md.

hermes

Two ways to run Hermes:

  • Hosted (current deployment) — docker/hermes-stack/. Dockerized nesquena/hermes-webui three-container layout: official agent + official dashboard + custom WebUI, behind a Traefik edge and Cloudflare (proxied DNS + Zero Trust Access), with Let's Encrypt certs via DNS-01. This is what runs live (chat.code42.dev

    • hermes.code42.dev). Config in docker/hermes-stack/.env.local; bring up with ./deploy.sh.
  • Local install — profiles/hermes (./install.sh hermes). Upstream curl|bash installer (HERMES_USE_UPSTREAM_INSTALLER=true) sets up Python 3.11 via uv and puts hermes on PATH. Also installs gbrain and wires it into Hermes by default (03-gbrain.sh, INSTALL_GBRAIN=true) as a local stdio MCP server in ~/.hermes/config.yaml. Opt-in INSTALL_GBRAIN_SUPABASE=true (04-gbrain-db.sh) turns the VPS into a brain-host (Dockerized Supabase/Postgres + gbrain serve --http OAuth). See the Hermes README.

paperclip

Two ways to run Paperclip:

  • Local install — profiles/paperclip (./install.sh paperclip). git clone + pnpm install + pnpm build. Embedded PostgreSQL is provisioned by the app on first run; no external DB needed. Optional systemd unit for the API server on port 3100.
  • Hosted (Docker) — docker/paperclip-stack/. Builds Paperclip from source and runs it behind a Traefik edge and Cloudflare (proxied DNS + Zero Trust Access), the same pattern as the Hermes stack. Embedded PostgreSQL + state persist on a Docker volume. Config in docker/paperclip-stack/.env.local; bring up with ./deploy.sh.

Plugins

Available in cli-bundle via 09-plugins.sh. Headless install where the CLI supports it; printed manual hint otherwise.

Plugin Toggle How it installs
graphify INSTALL_GRAPHIFY (default on) uv tool install graphifyy, then graphify install registered for every installed CLI
superpowers INSTALL_SUPERPOWERS Claude headless; Codex/Cursor/OpenCode manual hints; Antigravity not documented
OpenSpec INSTALL_OPENSPEC universal npm-global (/opsx:* slash commands from any CLI)
agent-skills INSTALL_AGENT_SKILLS non-interactive CLI install across claude-code / codex / cursor / opencode
agent-browser INSTALL_AGENT_BROWSER npm i -g agent-browser + cross-CLI skill (npx skills add vercel-labs/agent-browser)
gbrain INSTALL_GBRAIN bun install -g github:garrytan/gbrain (bun bootstrapped if missing)
gstack INSTALL_GSTACK git-clone garrytan/gstack into each GSTACK_TARGETS host's skills dir

Firecrawl is wired as an MCP server (INSTALL_FIRECRAWL in 06-mcp.sh, package firecrawl-mcp, needs FIRECRAWL_API_KEY) since it is an MCP, not a plugin.

agent-skills (Tech Leads Club)

agent-skills is a curated, security-validated skill registry. INSTALL_AGENT_SKILLS=true installs a default set tuned for web/mobile product work (Next.js + React Native, NestJS + Nx monorepo, accessibility + security) globally to every installed CLI that supports it. Override the skill list with AGENT_SKILLS_LIST and the target CLIs with AGENT_SKILLS_AGENTS in .env. Browse the catalog with npx @tech-leads-club/agent-skills list.

Plus the official Anthropic marketplace (Claude-only, bundles pre-configured MCP + skills + slash commands):

INSTALL_LINEAR_PLUGIN, INSTALL_SLACK_PLUGIN, INSTALL_GITHUB_PLUGIN, INSTALL_NOTION_PLUGIN, INSTALL_ATLASSIAN_PLUGIN, INSTALL_ASANA_PLUGIN, INSTALL_FIGMA_PLUGIN, INSTALL_SENTRY_PLUGIN, INSTALL_SUPABASE_PLUGIN, INSTALL_VERCEL_PLUGIN.

These coexist with the raw MCP toggles in 06-mcp.sh (e.g. INSTALL_LINEAR=true registers the bare MCP endpoint); the official plugin is the richer path.

GitHub CLI (gh)

Every profile's 01-system.sh installs gh via install_github_cli() (lib/base-packages.sh). gh is not in the stock Ubuntu repos, so it's wired from the official GitHub apt repo with a signed keyring at /etc/apt/keyrings/githubcli-keyring.gpg — the same pattern as the ClickHouse client. Toggle off with INSTALL_GH=false.

Database clients

Off by default — set INSTALL_DB_CLIENTS=true in the profile's .env to have 01-system.sh install:

  • postgresql-client (Ubuntu apt) → psql
  • clickhouse-client from the official ClickHouse deb repo (signed keyring at /etc/apt/keyrings/clickhouse-keyring.gpg)

Granular control: INSTALL_POSTGRES_CLIENT, INSTALL_CLICKHOUSE_CLIENT (both also default false).

Headless browser

install_headless_browser() in lib/base-packages.sh tries chromium-browser then chromium via apt; warns (does not fail) if no apt package resolves. Used for general agent shell work. Default INSTALL_HEADLESS_BROWSER=true.

Playwright is a separate concern: INSTALL_PLAYWRIGHT=true in 06-mcp.sh does its own playwright install-deps + bundled-Chromium download + @playwright/mcp@latest MCP registration.

Secrets

  • Each profile's .env (copied from .env.example) holds provider keys and toggles. The loader chmod 600s it on first run.
  • ANTHROPIC_API_KEY (and friends) get appended to ~/.bashrc; the file is re-chmoded to 600 after the write.
  • ~/.harness-profile is chmod 600.
  • Tokens (OAuth, etc.) live under ~/.claude/, ~/.codex/, etc. — back these up before destroying the VPS:
    tar czf harness-backup.tgz ~/.claude ~/.opencode \
          ~/.codex ~/.cursor ~/.antigravity ~/.harness-profile

Secret-handling rules and how to report a vulnerability live in SECURITY.md.

Maintenance

# show installed profile
./install.sh --status

# re-run a profile (idempotent — re-applies .env changes)
./install.sh <profile>

# wipe the marker if you intend a clean swap (combine with --force)
rm ~/.harness-profile && ./install.sh <other-profile>

# update an npm-global CLI
npm update -g @anthropic-ai/claude-code
npm update -g @openai/codex
npm update -g @fission-ai/openspec     # if INSTALL_OPENSPEC=true

Tests + CI

Local test harness in tests/:

# install deps (Ubuntu/Debian)
sudo apt-get install -y shellcheck bats
# install deps (macOS)
brew install shellcheck bats-core

bash tests/lint.sh
bash tests/check_env_completeness.sh
bats tests/

GitHub Actions:

  • ci.yml — runs lint, env-completeness, and bats jobs in parallel on every push and PR.
  • release.yml — on v* tag, reruns CI then publishes a GitHub release with notes extracted from CHANGELOG.md.
  • deploy.yml — on push to main (or manual workflow_dispatch), rsyncs the repo to the VPS at /opt/harness, owned by the ubuntu user. See Deploy.

Deploy

deploy.yml ships the repo to a server over SSH and lands it at /opt/harness, owned by ubuntu (it sudo-creates the dir once, then syncs unprivileged so files land with the login user's ownership). It runs on every push to main and can be triggered manually from the Actions tab.

Credentials are GitHub Actions secrets — never committed (this repo is public). Set them once (Settings → Secrets and variables → Actions, or via CLI):

printf '<server-ip>' | gh secret set DEPLOY_HOST
printf 'ubuntu'      | gh secret set DEPLOY_USER
printf '22'          | gh secret set DEPLOY_PORT
gh secret set DEPLOY_SSH_KEY < ~/.ssh/your_key.pem   # full PEM, unprivileged user with passwordless sudo

.env_secrets is a gitignored local note of these same values; it is never pushed. The deploy uses SSH only — no GitHub PAT is involved. rsync excludes server-side runtime state (.env, .env_secrets, .harness-profile) so a deploy never overwrites a host's real config.

Troubleshooting

Symptom Fix
command not found: claude (or any CLI) source ~/.bashrc (PATH update lives there)
Refusing to install '<other>' Expected — see Mutex
MCP OAuth callback never returns Open the URL on your laptop, not on the VPS
Playwright MCP install fails sudo npx playwright install-deps
Out of RAM mid-install Bump SWAP_SIZE_GB in .env, re-run
ClickHouse repo signature error Delete /etc/apt/keyrings/clickhouse-keyring.gpg and re-run
GitHub CLI repo signature error Delete /etc/apt/keyrings/githubcli-keyring.gpg and re-run
Firecrawl MCP returns 401 Set FIRECRAWL_API_KEY in .env (key from firecrawl.dev)
Plugin install hangs in claude -p Check ~/.claude/logs/ and retry with --dangerously-skip-permissions
chromium package missing Rely on Playwright's bundled Chromium (set INSTALL_PLAYWRIGHT=true)

Development

# syntax-check every shell script
find . -name '*.sh' -exec bash -n {} \; -print

# shellcheck (optional)
find . -name '*.sh' -exec shellcheck -x -S warning -e SC1090,SC1091 {} \;

Conventions baked into every script:

  • set -euo pipefail
  • Idempotent (mkdir -p, grep -q guards, git pull --ff-only, claude mcp remove before add)
  • Pure bash, no perl/python dependencies for the launcher itself
  • .env always sourced via the shared load_env helper

Status & roadmap

Profile Install path Real-VPS verified
cli-bundle npm global + upstream installers (5 CLIs + plugins) ✅ (partial)
hermes (stack) Docker 3-container + Traefik + Cloudflare Access ✅ live (*.code42.dev)
hermes (local) upstream curl | bash ⚠️ pending
paperclip (local) git clone + pnpm build ⚠️ pending
paperclip (stack) Docker (built from source) + Traefik + Cloudflare ⚠️ pending

Releases: v1.0.5 (latest) — see CHANGELOG.md for the full reverse-chrono history.

Open design decisions awaiting review: .claude/RECOMMENDATIONS.md. Open work items: .claude/TODO.md.

License

MIT — see LICENSE.md.

About

Idempotent VPS installers for AI coding agents — Claude Code, Codex, Antigravity, Cursor, OpenClaw, Hermes, Paperclip. One-profile-per-host mutex, shared base tooling, psql + clickhouse-client baked in.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors