Hogwild
Hogwild is one Ubuntu host on the LAN and the tailnet. It runs the Harlan GitHub Agent, the GitHub runner supervisor, three public sites behind one Cloudflare tunnel, AdGuard DNS, and Jellyfin.
The hw CLI
scripts/hw.ts in this skill runs the routine tasks below and picks the right account each time. Run it with bun; it has no dependencies. The desktop alias hw points at it.
hw status # host, services, containers, Agent unit
hw logs agent -n 100 # or runner, caddy, tunnel, adguard, jellyfin, docker, status, dragon
hw install fd-find --link fd=fdfind # apt as admin, link as the Agent
hw caddy check # or reload: validate first, then reload
hw runners # live runners.conf against the repo copy, job containers
hw pending # open host items from hogwild/README.md
hw run admin 'sudo iptables -S DOCKER-USER'
hw run agent 'ls ~/.local/bin'
hw --help lists every command. Reach for ssh directly only when hw run does not fit.
Pick the SSH host first
| Command | Account | Use it for |
|---|---|---|
ssh hogwild |
harlan |
Agent state, config, checkouts under ~/pkg and ~/sites, systemctl --user, tool links in ~/.local/bin. No sudo, no docker. |
ssh hogwild-admin |
harlan-admin |
sudo, apt, docker, system systemctl, /etc, /opt, /var/lib. Cannot read /home/harlan. |
If the task needs both, run two commands. Never grant harlan sudo or docker. Never SSH as root. The Agent runs public issue text under harlan; root there is the attack path the split closed on 2026-09-11.
The desktop zsh aliases hogwild, hogwild-lan, hogwild-admin, hogwild-admin-lan use the same key ~/.ssh/id_ed25519_hogwild.
What runs there
| Service | Unit | Account | Listens | Source |
|---|---|---|---|---|
| Harlan GitHub Agent | harlan-github-agent.service (user unit, linger) |
harlan |
127.0.0.1:3210 dashboard, :3211 webhook |
~/.local/share/harlan-github-agent/service |
| GitHub runner supervisor | hogwild-github-runner.service |
github-runner |
none; one Docker container per job | /var/lib/github-runner |
| Status site | hogwild-status.service |
hogwild-status |
127.0.0.1:9100 |
/opt/hogwild-status/current |
| Dragon deck | chasing-the-ai-dragon.service |
dragon-deck |
127.0.0.1:3031 |
/opt/chasing-the-ai-dragon/current |
| Caddy | caddy.service |
caddy |
127.0.0.1:8080 |
/etc/caddy/Caddyfile, routes in /etc/caddy/routes/*.caddy |
| Cloudflare tunnel | cloudflared.service |
cloudflared |
outbound only | /etc/cloudflared/config.yml |
| AdGuard Home | adguardhome.service |
adguardhome |
DNS :53 on the LAN and tailnet addresses, admin :5380 on every interface, ufw blocks it from the LAN |
/opt/AdGuardHome, config in /var/lib/adguardhome |
| Jellyfin | jellyfin.service |
jellyfin |
:8096, ufw allows the LAN and the tailnet |
/srv/jellyfin |
| Docker | docker.service |
root | published ports bind loopback | /etc/docker/daemon.json, DOCKER-USER rules from hogwild-docker-user.service |
Public routes go internet, Cloudflare tunnel, Caddy :8080, then the loopback port above:
hogwild.harlanzw.com, the status sitedragon.harlanzw.com, the deckagent.harlanzw.com, the Agent dashboard behind Basic auth;/webhookreaches the signed webhook listener
Repos and config that own each part
| Part | Repo | Local checkout | Deploy |
|---|---|---|---|
| Agent service code | harlan-zw/harlan-agent-kit packages/harlan-github-agent |
~/pkg/harlan-agent-kit |
pnpm service:hogwild:update from the repo root |
| Agent context, worktrunk, env files | same repo, scripts/ |
same | pnpm sync:context:hogwild, pnpm service:hogwild:sync-env, pnpm service:hogwild:sync-worktrunk |
| Agent config | not in git | hogwild:~/.config/harlan-github-agent/config.yml |
edit, then restart the user unit |
| Agent state | not in git | hogwild:~/.local/share/harlan-github-agent/state.sqlite, journal.sqlite, worktrees/ |
back up before a migration |
| Runner supervisor, runners.conf, Dockerfile, host hardening | harlan-zw/hogwild-gh-runner (private) github-runner/ |
~/pkg/hogwild-gh-runner |
install steps in its README |
| Host security layout, Docker rules, pending items | same repo, hogwild/ |
same | hogwild/README.md |
| Status site | harlan-zw/hogwild.harlanzw.com |
~/pkg/hogwild.harlanzw.com |
GitHub Actions deploy job to hogwild-deploy |
| Caddy routes | scripts/30-agent.caddy for the agent route; others live only on the host |
sudo systemctl reload caddy |
The live runners.conf is /var/lib/github-runner/config/runners.conf. Change it in the repo first, then install it.
Agent checkouts on the host
harlan holds a primary checkout for every repository the Agent works on. They live under /home/harlan/pkg and /home/harlan/sites and must stay clean on main. The Agent creates its worktrees under ~/.local/share/harlan-github-agent/worktrees/.
Not every checkout is mapped. config.yml lists the active repositories and the observe-only external_repositories. Read the file for the current list: ssh hogwild 'rg -n "^ - github:" ~/.config/harlan-github-agent/config.yml'.
If a new repository joins, clone it as harlan into the matching directory, then add a repositories entry and restart the Agent.
Install a tool for the Agent
- Install as admin:
ssh hogwild-admin 'sudo apt-get install -y <package>'. - If the Agent needs it under a different name, link it as harlan:
ssh hogwild 'ln -sf "$(command -v fdfind)" ~/.local/bin/fd'. - Record it in the tools table in
hogwild-gh-runner/hogwild/README.md.
Tools in hogwild:~/.local/bin today: node, pnpm, bun, uv, gh, wt, rg, fd, ripast, opencode, codex, wrangler, sentry-cli, nuxtseo, himalaya, dev-browser. No claude; the Agent uses opencode.
Control the Agent
Prefer the control CLI from the desktop over raw systemctl:
harlan-github-agent control status
harlan-github-agent control pause
harlan-github-agent control restart
If a change needs the process to pick up new groups or a new Node, restart the user manager as admin. That kills in-flight Agent work, so pause first and get Harlan's say so:
ssh hogwild-admin 'sudo systemctl restart user@1000.service'
Logs: ssh hogwild 'journalctl --user -u harlan-github-agent -n 200'.
Docker and firewall
ufw allows SSH, DNS, and Jellyfin from the LAN, and everything on tailscale0. Docker bypasses ufw, so hogwild-docker-user.service fills DOCKER-USER: containers cannot reach the LAN, the tailnet, or link-local; the LAN cannot open published ports. daemon.json binds published ports to loopback. After ufw reload, restart Docker. Verify with sudo iptables -S DOCKER-USER.
Do not
- Do not start
harlan-desktop-github-runner.serviceon the desktop. Hogwild is the only runner host. - Do not add
harlantosudoordocker. - Do not edit a primary checkout under
~/pkgor~/siteson the host. The Agent owns them. - Do not reboot without draining the runner:
sudo systemctl stop hogwild-github-runner.service, thensudo reboot.