Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/workflows/integration-linux-arm64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Integration Tests (Linux ARM64)

on:
pull_request:
paths:
- '**.go'
- 'go.mod'
- 'go.sum'
- 'integration/**'
- '.github/workflows/integration-*.yml'
push:
branches:
- master
paths:
- '**.go'
- 'go.mod'
- 'go.sum'
- 'integration/**'
- '.github/workflows/integration-*.yml'

jobs:
integration:
name: Integration
runs-on: warp-ubuntu-latest-arm64-4x
permissions:
contents: read
packages: read

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
cache: true

- name: Log in to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Pull container image
run: docker pull ghcr.io/gilmanlab/headjack:base

- name: Build hjk binary
run: go build -o hjk .

- name: Run integration tests
env:
HEADJACK_TEST_RUNTIME: docker
HJK_BINARY: ${{ github.workspace }}/hjk
run: go test -v -tags=integration -timeout=30m ./integration/...
56 changes: 56 additions & 0 deletions .github/workflows/integration-linux-x86.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Integration Tests (Linux x86)

on:
pull_request:
paths:
- '**.go'
- 'go.mod'
- 'go.sum'
- 'integration/**'
- '.github/workflows/integration-*.yml'
push:
branches:
- master
paths:
- '**.go'
- 'go.mod'
- 'go.sum'
- 'integration/**'
- '.github/workflows/integration-*.yml'

jobs:
integration:
name: Integration
runs-on: warp-ubuntu-latest-x64-4x
permissions:
contents: read
packages: read

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
cache: true

- name: Log in to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Pull container image
run: docker pull ghcr.io/gilmanlab/headjack:base

- name: Build hjk binary
run: go build -o hjk .

- name: Run integration tests
env:
HEADJACK_TEST_RUNTIME: docker
HJK_BINARY: ${{ github.workspace }}/hjk
run: go test -v -tags=integration -timeout=30m ./integration/...
20 changes: 20 additions & 0 deletions docs/docs/decisions/adr-002-apple-containerization.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ We evaluated several isolation technologies for macOS.

**Docker**
- Industry standard, excellent tooling and ecosystem
- Widely available on macOS via Docker Desktop
- Single-process optimization conflicts with multi-service agent needs
- Docker-in-Docker requires privileged mode, weakening isolation
- Namespace-based isolation, not hypervisor boundary
- Note: Docker is now supported as a runtime option for users who prefer it

**Lima**
- CNCF incubating project, mature and well-documented
Expand Down Expand Up @@ -79,3 +81,21 @@ Use **Apple Containerization Framework** as the isolation technology for Headjac

- By adopting early, we participate in the framework's growth through usage and bug reports
- The iptables-legacy workaround for Docker-in-Docker is stable but adds base image complexity

## Addendum: Multi-Runtime Support

While Apple Containerization Framework remains the recommended runtime for its superior isolation properties, Headjack now supports multiple container runtimes to accommodate different user preferences and environments:

| Runtime | Configuration | Binary | Notes |
|---------|--------------|--------|-------|
| Podman | `runtime.name: podman` | `podman` | Default runtime. Cross-platform, daemonless. |
| Apple | `runtime.name: apple` | `container` | Recommended for macOS 26+. VM-level isolation. |
| Docker | `runtime.name: docker` | `docker` | Industry standard. Requires Docker Desktop on macOS. |

Users can configure their preferred runtime via:

```bash
hjk config runtime.name docker
```

This flexibility allows teams to use familiar tooling while still benefiting from Headjack's instance and session management.
2 changes: 1 addition & 1 deletion docs/docs/explanation/image-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: OCI images approach vs alternatives like Nix

# Image Customization

Headjack runs agents in containers, and those containers need the right tools installed. How do you customize the environment when your project needs specific languages, frameworks, or system packages? Headjack answers this with standard OCI images, delegating all customization to Docker/Podman tooling you already know.
Headjack runs agents in containers, and those containers need the right tools installed. How do you customize the environment when your project needs specific languages, frameworks, or system packages? Headjack answers this with standard OCI images, delegating all customization to Docker, Podman, or Apple Container tooling you already know.

## The Customization Problem

Expand Down
4 changes: 2 additions & 2 deletions docs/docs/how-to/build-custom-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ hjk run feat/auth --base ghcr.io/gilmanlab/headjack:dind

### Prerequisites

- Docker or Podman installed
- Docker, Podman, or Apple Container installed
- Familiarity with Dockerfile syntax

### Create a Dockerfile
Expand Down Expand Up @@ -85,7 +85,7 @@ podman build -t my-custom-headjack:latest -f Dockerfile.headjack .

### Build for multiple architectures

For teams with both Intel and Apple Silicon Macs:
For teams with both Intel and Apple Silicon Macs (using Docker buildx):

```bash
docker buildx build \
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/how-to/install.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This guide is being written. Check back soon!

- macOS 26 or later
- Git
- Container runtime (Podman or Apple Container)
- Container runtime (Podman, Docker, or Apple Container)

## Installation Steps

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/reference/cli/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Common configuration keys:
| `storage.worktrees` | string | Directory for git worktrees |
| `storage.catalog` | string | Path to the instance catalog file |
| `storage.logs` | string | Directory for session logs |
| `runtime.name` | string | Container runtime (`podman`, `apple`) |
| `runtime.name` | string | Container runtime (`podman`, `apple`, `docker`) |

## Configuration File

Expand Down
6 changes: 3 additions & 3 deletions docs/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Container runtime configuration.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `runtime.name` | string | `podman` | Container runtime to use. Valid values: `podman`, `apple`. |
| `runtime.name` | string | `podman` | Container runtime to use. Valid values: `podman`, `apple`, `docker`. |
| `runtime.flags` | map[string]any | `{}` | Additional flags to pass to the container runtime. |

## Example Configuration
Expand Down Expand Up @@ -116,7 +116,7 @@ hjk config storage.worktrees

```bash
hjk config default.agent claude
hjk config runtime.name apple
hjk config runtime.name docker
```

### Edit Configuration File
Expand Down Expand Up @@ -145,7 +145,7 @@ Headjack validates configuration values when loading and setting them:

- `default.agent` must be one of: `claude`, `gemini`, `codex` (or empty)
- `default.base_image` is required and cannot be empty
- `runtime.name` must be one of: `podman`, `apple`
- `runtime.name` must be one of: `podman`, `apple`, `docker`
- All storage paths are required

Invalid values will result in an error message describing the validation failure.
32 changes: 31 additions & 1 deletion docs/docs/reference/images/labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,33 @@ LABEL io.headjack.podman.flags="systemd=always privileged=true"

---

### io.headjack.docker.flags

Specifies additional flags to pass to Docker when running the container.

| Property | Value |
|----------|-------|
| Key | `io.headjack.docker.flags` |
| Value type | String (space-separated key=value pairs) |
| Default | None |

#### Description

This label allows images to specify Docker-specific runtime flags that are required for correct operation. Headjack parses the value and applies the flags when creating the container. The format is the same as `io.headjack.podman.flags`.

#### Example

```dockerfile
# Enable privileged mode
LABEL io.headjack.docker.flags="privileged=true"
```

#### Usage in Official Images

Not currently used in official images.

---

### io.headjack.apple.flags

Reserved for Apple Containerization Framework-specific flags.
Expand Down Expand Up @@ -171,14 +198,17 @@ LABEL io.headjack.init="/usr/local/bin/init.sh"

## Label Inspection

You can inspect image labels using Docker or Podman:
You can inspect image labels using Docker, Podman, or Apple Container:

```bash
# Using Docker
docker inspect ghcr.io/gilmanlab/headjack:systemd --format='{{json .Config.Labels}}' | jq

# Using Podman
podman inspect ghcr.io/gilmanlab/headjack:systemd --format='{{json .Config.Labels}}' | jq

# Using Apple Container
container inspect ghcr.io/gilmanlab/headjack:systemd --format='{{json .Config.Labels}}' | jq
```

Example output:
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/tutorials/custom-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This tutorial takes approximately 30-40 minutes to complete.
Before starting, ensure you have:

- Completed the [Getting Started](./getting-started) tutorial
- Docker or Podman installed on your machine
- Docker, Podman, or Apple Container installed on your machine
- Basic familiarity with Dockerfile syntax
- A project with specific runtime requirements (Python version, Node.js packages, system tools, etc.)

Expand Down Expand Up @@ -183,7 +183,7 @@ podman build -t my-app-headjack:latest -f Dockerfile.headjack .
```

:::note
Build with the same container runtime that Headjack uses. Check your configuration with `hjk config` and look for `runtime.name`. If Headjack uses Podman, build with Podman. Images built with Docker are not automatically available to Podman (and vice versa) unless pushed to a registry.
Build with the same container runtime that Headjack uses. Check your configuration with `hjk config` and look for `runtime.name`. Images built with one runtime (Docker, Podman, or Apple Container) are not automatically available to others unless pushed to a registry.
:::

The build takes several minutes as it compiles Python and Node.js. You will see output for each step:
Expand Down Expand Up @@ -292,7 +292,7 @@ docker buildx build \
-f Dockerfile.headjack .
```

This creates an image that works on both architectures. Docker and Podman automatically pull the correct variant.
This creates an image that works on both architectures. Docker, Podman, and Apple Container automatically pull the correct variant.

## Complete Dockerfile

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/tutorials/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl';

Before starting, ensure you have:

- **macOS with Podman installed** - Headjack uses Podman for containerization by default
- **macOS with a container runtime installed** - Headjack supports Podman (default), Docker, or Apple Container
- **Git installed** - Verify with `git --version`
- **A Claude Pro/Max subscription OR an Anthropic API key** - For Claude Code authentication
- **A git repository to work in** - Any project repository will work
Expand Down
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ go 1.25.4

require (
github.com/99designs/keyring v1.2.2
github.com/creack/pty v1.1.24
github.com/charmbracelet/huh v0.8.0
github.com/docker/docker v28.5.2+incompatible
github.com/go-playground/validator/v10 v10.30.1
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/google/go-containerregistry v0.20.7
github.com/rogpeppe/go-internal v1.14.1
github.com/spf13/cobra v1.10.2
github.com/spf13/viper v1.21.0
github.com/stretchr/testify v1.11.1
Expand All @@ -24,7 +25,6 @@ require (
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
github.com/charmbracelet/bubbletea v1.3.6 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/huh v0.8.0 // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
Expand Down Expand Up @@ -77,4 +77,5 @@ require (
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
golang.org/x/tools v0.39.0 // indirect
)
20 changes: 18 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMb
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
Expand All @@ -22,10 +26,20 @@ github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
github.com/containerd/stargz-snapshotter/estargz v0.18.1 h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8=
github.com/containerd/stargz-snapshotter/estargz v0.18.1/go.mod h1:ALIEqa7B6oVDsrF37GkGN20SuvG/pIMm7FwP7ZmRb0Q=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
Expand Down Expand Up @@ -121,8 +135,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
Expand Down Expand Up @@ -157,6 +171,8 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
Expand Down
Loading