@vertile-ai/gunner
v0.1.0
Published
Config-driven manager for GitHub Actions self-hosted runners
Readme
Gunner
Gunner is a config-driven lifecycle manager for official GitHub Actions self-hosted runners. It creates, observes, reconciles, and removes runner registrations and their host runtime. GitHub Actions remains the workflow control plane.
Product boundary
Gunner is not a CI scheduler, job relay, workflow executor, shell service,
or job-log aggregator. The official runner connects directly to GitHub, which
queues and assigns jobs. A project repository owns its workflow YAML, scripts,
toolchain contract, release and deployment logic, and infrastructure intent.
Gunner owns only the generic runner lifecycle and routing inputs: registration,
labels, runner groups, repository allowlists, local receipts, and the selected
launchd or Docker runtime.
project workflow and scripts -> GitHub Actions -> official runner -> project job
gunner.yaml -> Gunner -> GitHub runner API + host runtime -> official runnerThe fuller boundary, including Terraform, Ansible, and provider ownership, is in the architecture.
0.1.0 support status
0.1.0 is an initial macOS-host release for managing official runners. It supports strict v1 configuration, per-target runner policy overrides, non-mutating planning, ownership receipts and state classification, GitHub repository and organization targets, runner groups and selected-repository allowlists.
- Native macOS runners use
launchd. Creation, update, removal, planned replacement, and terminal replacement recovery are supported by the CLI. - Docker runners run the official GitHub runner image on the current macOS host. Creation, in-place update, and removal are supported.
status,doctor, andapply --dry-runare observation/planning commands; dry-run does not create registration tokens or mutate GitHub, Docker,launchd, or the host filesystem.
The following are deliberately unavailable or blocked rather than silently
attempted: Docker replacement or adoption, data-root migration execution,
unresolved removal recovery, the Web Console, remote/tunnel access, native
Linux-host support, and live Noop cutover. control.enabled: true is rejected.
See known limits and the
active roadmap.
Requirements
- macOS on ARM64 for the evidenced native
launchdpath - Node.js 22 or newer
- GitHub CLI (
gh), authenticated for the declared repository or organization tar; Docker and/or declared capabilities such as Tailscale when required by a target- repository or organization administration sufficient to manage runners
Docker mounts the host Docker socket into a privileged job environment. Use it only for trusted workflows and repositories that share one host-wide trust domain. Keep fork and untrusted pull-request workloads on GitHub-hosted runners.
Use Gunner for a new project
Install the released package, or use a source checkout:
npm install --global @vertile-ai/[email protected] # Source fallback pnpm install --frozen-lockfile pnpm build pnpm gunner --helpThe remaining commands below use
gunner. In a source checkout, replace them withpnpm gunner.Authenticate GitHub CLI and create a tracked, secret-free operator config.
gunner initcreates a minimal file without overwriting an existing one.gh auth login gunner initDeclare the runner target. The project chooses the same label in its own workflow; Gunner never copies or executes that workflow logic.
version: 1 host: dataDirectory: ~/.gunner runner: version: 2.335.1 labels: [trusted-build] targets: - id: my-project-build repository: example-org/my-project runner: runtime: launchd labels: [my-project-build] requires: []In
example-org/my-project, route an eligible trusted workflow to it:jobs: build: runs-on: [self-hosted, my-project-build] steps: - uses: actions/checkout@v4 - run: ./scripts/build-and-testThe checked-out project supplies
./scripts/build-and-test, dependencies, and deployment policy.actions/checkoutpopulates$GITHUB_WORKSPACE; Gunner does not use a developer's existing clone.Inspect first, then apply the exact plan. For any plan that interrupts an existing runtime, the CLI requires the printed plan fingerprint,
--yes, and confirmation of every interrupted target.gunner doctor gunner apply --dry-run gunner apply gunner status
doctor rejects unsafe default-branch workflow routes that send
pull_request or pull_request_target code to self-hosted or a configured
Gunner label. Review all other triggers and repository trust policy before
using a host with privileged capabilities.
Configuration
gunner.yaml is desired state and may be tracked in an operator or project
repository. It never contains GitHub, runner, host, provider, or tunnel
credentials. Per-target runner values override the top-level defaults.
An organization target can restrict a runner group to selected repositories:
version: 1
runner:
runtime: docker
version: 2.335.1
labels: [trusted-build]
requires: [docker]
targets:
- id: shared-build
organization: example-org
group: trusted-build
repositories: [app, api]
runner:
labels: [shared-build]~/.gunner is the recommended root for new installations. Existing
~/.local/share/gunner installations must stay explicitly configured until a
future migration executor is released; do not move runtime files manually.
Commands
| Command | Behavior |
| --- | --- |
| gunner init | Create a minimal v1 configuration without replacing a file. |
| gunner doctor | Validate host/config readiness and check unsafe workflow routing. |
| gunner apply --dry-run | Observe state and print a deterministic non-mutating plan. |
| gunner apply | Reconcile executable lifecycle operations; guarded confirmation is required for interruptions. |
| gunner status | Report desired, local, runtime, and GitHub observations with classification. |
| gunner remove <id> --yes | Plan and execute explicit receipt-bound removal when its operation is executable. |
All lifecycle commands accept --config <path>. gunner --help and
gunner --version work without reading configuration or contacting GitHub;
help lists the exact confirmation options emitted for an interrupted plan.
Known limits
- The package has no Web Console,
gunner webcommand, authentication service, audit UI, ngrok integration, or tunnel support. - Docker replacement and adoption fail closed. Do not attempt to take over an existing Docker runner with 0.1.0.
- The old-root/new-root observation and paired journal protocol exist, but no data-root migration executor is released.
- Noop is not cut over by this release. Its release lane and all live fleet mutations remain subject to the active roadmap's trust and cutover gates.
- Native Linux-host and macOS x64 support are not established release claims.
- Removal and recovery are intentionally conservative; an operation reported blocked or needing recovery requires the reported manual procedure rather than a forced retry.
Development
pnpm test
pnpm typecheck
pnpm build
pnpm pack --pack-destination /tmp/gunner-packSee the changelog, architecture, and active roadmap.
