Skip to content

Releases: bojanrajkovic/runny

v1.1.0

Choose a tag to compare

@bojan-s-releaser-bot bojan-s-releaser-bot released this 10 Jul 19:03
57bfbbe

Release Notes

Every runner cycle is now traced & emits metrics. 1.1's headline is OpenTelemetry: each cycle emits a span tree covering image resolution, pull, SSH provisioning, runner launch, and teardown, plus spans for the underlying HTTP egress (GitHub API, registry, tarball CDN). Opt in via observability.otlp in config.

The second theme is operator access and audit: grant/revoke/list operator accounts at runtime, revocation enforced as soon as possible, and every debug session stamped with the authenticated UID and recorded as a cycle artifact.

Also in this release:

  • ssh_hardening: scramble for randomized guest passwords
  • per-pool guest_env/guest_setup provisioning (thanks to @paul-calnan-simplisafe for his contribution!)
  • runnyctl prune for on-demand disk reclaim
  • a fix for tart-guest-agent behaving oddly and killing system processes when a console device wasn't attached (thanks to @withzombies for his contribution!)

Changelog

1.1.0 (2026-07-10)

Features

  • app: render benign cycle endings distinctly in the timeline (#239) (798243f)
  • ci: report combined Go+Swift coverage to Codecov (#281) (427abbf), closes #277
  • config: add per-pool guest_env and guest_setup for guest provisioning (#294) (15a5b2e)
  • cycle: persist the ending classification on the record (#237) (68f8adf)
  • guest: add ssh_hardening: scramble to randomize guest passwords (#215) (1bc753d), closes #210
  • guest: record operator debug sessions as cycle artifacts (#211) (c1c8bea)
  • home: add opt-in observability.otlp config block (#236) (f77d859)
  • images: action events and ensurer-scope pull metrics (#246) (1d1f998)
  • obs,images: pull-scoped events from the shared image puller (#265) (53d7f99), closes #257
  • obs: add the cycle observability event stream and Action wrapper (#238) (c7c6c7b)
  • obs: emit spans for HTTP egress (#248) (9d18860)
  • otlp: send custom headers on OTLP exports, with ${env:VAR} expansion (#273) (283bee4)
  • release: add a beta channel formula/cask pair to the tap (#268) (6cdb3c2)
  • runnyctl: on-demand disk reclaim via runnyctl prune (#206) (5596ab9)
  • runnyctl: stage config on install, add edit-config (#274) (6d467ca)
  • runnyd: add the OTLP telemetry runtime — providers, resource, bounded shutdown (#242) (cd78eca)
  • socket: enforce operator revocation at RPC-start, not just connect() (#270) (e68ca57)
  • socket: grant/revoke/list operator accounts at runtime (#220) (8979276), closes #209
  • socket: log the authenticated operator on lifecycle commands (#272) (74e3080)
  • socket: stamp the authenticated operator uid on injected_keys (#219) (c7a2786)
  • statemachine: action events for teardown, rotation, and provision sub-steps (#244) (127239a), closes #231
  • statemachine: emit observability events from the record helpers (#241) (a3a5f37), closes #224
  • telemetry: add the trace emitter (#243) (fe9b82f), closes #227
  • telemetry: fold pull events into traces and metrics; delete images.Metrics (#266) (1a89323), closes #258
  • telemetry: metrics emitter — cycle instruments and slot gauges (#245) (d947877)

Bug Fixes

  • address post-1.0 security audit findings (#201) (ba32aae)
  • collapse the runner-tarball cache to per-cycle owned clones (#205) (973b031)
  • deps: update module golang.org/x/sync to v0.22.0 (#293) (698859c)
  • deps: update module golang.org/x/sys to v0.47.0 (#292) (0a70309)
  • deps: update module google.golang.org/grpc to v1.82.0 (#289) (e8d95bb)
  • harden daemon failure paths — force-stop, gRPC, stall, runner cache (#203) (a672c37)
  • home: validate retention.cycles_per_slot, share the positive-duration check (#286) (f118682)
  • release: pin version.sh's svu baseline to the last stable tag (da62db0)
  • sshx: stop WaitFor's rejection test racing real SSH timing (#216) (e391020)
  • statemachine: stop daemon shutdown from relabeling a decided cycle's ending (#240) (f46bd09)
  • sysdaemon: parse the -test-config verdict at install time (#282) (0a35a2b)
  • vm: reuse the persisted ECID and attach the tart-guest-agent's console port (#222) (efcc368)

Refactoring

  • apply the over-e...
Read more

v1.1.0-beta.da62db09

v1.1.0-beta.da62db09 Pre-release
Pre-release

Choose a tag to compare

@bojanrajkovic bojanrajkovic released this 06 Jul 00:16
da62db0

What's Changed

  • feat(otlp): send custom headers on OTLP exports, with ${env:VAR} expansion by @bojanrajkovic in #273
  • feat(runnyctl): stage config on install, add edit-config by @bojanrajkovic in #274

Full Changelog: v1.1.0-beta.74e3080a...v1.1.0-beta.da62db09

v1.1.0-beta.74e3080a

v1.1.0-beta.74e3080a Pre-release
Pre-release

Choose a tag to compare

@bojanrajkovic bojanrajkovic released this 03 Jul 20:54
74e3080

What's Changed

  • fix: address post-1.0 security audit findings by @bojanrajkovic in #201
  • fix: harden daemon failure paths — force-stop, gRPC, stall, runner cache by @bojanrajkovic in #203
  • fix: collapse the runner-tarball cache to per-cycle owned clones by @bojanrajkovic in #205
  • feat(runnyctl): on-demand disk reclaim via runnyctl prune by @bojanrajkovic in #206
  • feat(guest): record operator debug sessions as cycle artifacts by @bojanrajkovic in #211
  • chore(deps): update commitlint monorepo to v21.1.0 by @renovate[bot] in #212
  • chore(deps): update actions/cache action to v6 by @renovate[bot] in #213
  • feat(guest): add ssh_hardening: scramble to randomize guest passwords by @bojanrajkovic in #215
  • fix(sshx): stop WaitFor's rejection test racing real SSH timing by @bojanrajkovic in #216
  • refactor(guest): extract a shared per-OS dispatch helper by @bojanrajkovic in #217
  • feat(socket): stamp the authenticated operator uid on injected_keys by @bojanrajkovic in #219
  • feat(socket): grant/revoke/list operator accounts at runtime by @bojanrajkovic in #220
  • fix(vm): reuse the persisted ECID and attach the tart-guest-agent's console port by @withzombies in #222
  • docs(adr): record the observability event seam and OTLP consumer decision by @bojanrajkovic in #235
  • feat(home): add opt-in observability.otlp config block by @bojanrajkovic in #236
  • feat(cycle): persist the ending classification on the record by @bojanrajkovic in #237
  • feat(obs): add the cycle observability event stream and Action wrapper by @bojanrajkovic in #238
  • fix(statemachine): stop daemon shutdown from relabeling a decided cycle's ending by @bojanrajkovic in #240
  • feat(app): render benign cycle endings distinctly in the timeline by @bojanrajkovic in #239
  • feat(statemachine): emit observability events from the record helpers by @bojanrajkovic in #241
  • feat(runnyd): add the OTLP telemetry runtime — providers, resource, bounded shutdown by @bojanrajkovic in #242
  • feat(telemetry): add the trace emitter by @bojanrajkovic in #243
  • feat(statemachine): action events for teardown, rotation, and provision sub-steps by @bojanrajkovic in #244
  • feat(telemetry): metrics emitter — cycle instruments and slot gauges by @bojanrajkovic in #245
  • feat(images): action events and ensurer-scope pull metrics by @bojanrajkovic in #246
  • feat(obs): emit spans for HTTP egress by @bojanrajkovic in #248
  • refactor(statemachine): extract the slot's mutex-guarded state into a statusCell by @bojanrajkovic in #259
  • refactor(statemachine): hoist runCycle's working state into a per-cycle run struct by @bojanrajkovic in #260
  • refactor(statemachine): unify the step bracket and the record/status/event writes by @bojanrajkovic in #261
  • refactor(images): collapse parameter lists into Ensurer methods by @bojanrajkovic in #262
  • refactor(telemetry): delete the deterministic trace-ID machinery by @bojanrajkovic in #263
  • refactor(obs,telemetry): durations on step/job events; stateless metrics consumer by @bojanrajkovic in #264
  • feat(obs,images): pull-scoped events from the shared image puller by @bojanrajkovic in #265
  • feat(telemetry): fold pull events into traces and metrics; delete images.Metrics by @bojanrajkovic in #266
  • refactor(obs): drop the unused Event.Seq / per-scope counter by @bojanrajkovic in #267
  • feat(release): add a beta channel formula/cask pair to the tap by @bojanrajkovic in #268
  • docs: retire "crash-only" for the concrete guarantees it stood for by @bojanrajkovic in #269
  • feat(socket): enforce operator revocation at RPC-start, not just connect() by @bojanrajkovic in #270
  • chore(deps): update actions/cache action to v6.1.0 by @renovate[bot] in #271
  • feat(socket): log the authenticated operator on lifecycle commands by @bojanrajkovic in #272

New Contributors

Full Changelog: v1.0.2...v1.1.0-beta.74e3080a

v1.0.2

Choose a tag to compare

@bojan-s-releaser-bot bojan-s-releaser-bot released this 29 Jun 03:31
0b0be8f

1.0.2 (2026-06-29)

Bug Fixes

  • diskfree: fall back to statfs(2) when CF API returns 0 (#199) (12a986a)

v1.0.1

Choose a tag to compare

@bojan-s-releaser-bot bojan-s-releaser-bot released this 29 Jun 02:43
094c532

1.0.1 (2026-06-29)

Bug Fixes

  • doctor: skip disk-headroom for already-cached images (#197) (f7eca3b)

v1.0.0

Choose a tag to compare

@bojan-s-releaser-bot bojan-s-releaser-bot released this 29 Jun 02:07
45508d1

1.0.0 (2026-06-29)

⚠ BREAKING CHANGES

  • graduate to stable 1.0

Features

Bug Fixes

  • deploy: preflight block blocks cask install when formula is present (b93a086)

Build

  • drop --v0 from version.sh for 1.0 graduation (825540d)

v0.3.0

v0.3.0 Pre-release
Pre-release

Choose a tag to compare

@bojanrajkovic bojanrajkovic released this 29 Jun 01:11
ac9d4df

What's Changed

  • ci: gate workflows with zizmor at the auditor persona by @bojanrajkovic in #26
  • chore: add CODEOWNERS backing the Protect Main ruleset by @bojanrajkovic in #27
  • docs: add docs/security.md as the canonical security posture by @bojanrajkovic in #29
  • ci: split App-token and signing trust domains into separate environments by @bojanrajkovic in #30
  • ci: auto-regenerate MODULE.bazel.lock on Renovate PRs by @bojanrajkovic in #31
  • ci: harden Renovate with best-practices preset, cooldown, and tiered automerge by @bojanrajkovic in #32
  • chore(deps): update actions/create-github-app-token action to v3 by @renovate[bot] in #35
  • chore(deps): pin dependencies by @renovate[bot] in #33
  • fix: trigger lockfile regen on push instead of pull_request by @bojanrajkovic in #36
  • ci(renovate): automerge pin and pinDigest conversions unconditionally by @bojanrajkovic in #37
  • feat(sshx): publickey auth and host-key pinning by @bojanrajkovic in #40
  • feat(sign): developer ID signing and notarization as Bazel targets by @bojanrajkovic in #43
  • feat(runnyctl): show each slot's image in status and why by @bojanrajkovic in #48
  • fix(runnyctl): strip pins in why and tidy image-display internals by @bojanrajkovic in #50
  • feat(runnyd): graceful config reload via validated drain-and-respawn by @bojanrajkovic in #49
  • ci(release): stop referencing secrets in step if: conditions by @bojanrajkovic in #54
  • feat(runnyctl): on-demand guest SSH key injection with mid-job debug holds by @bojanrajkovic in #52
  • feat(runnyctl): show runner tarball version in why and status -json by @bojanrajkovic in #59
  • feat(runnyd): surface image digest as soon as it resolves, before pull by @bojanrajkovic in #60
  • feat(app): the Runny menu-bar and main-window app by @bojanrajkovic in #61
  • feat(timeline): stream in-flight cycle per-state timings via SlotStatus by @bojanrajkovic in #68
  • feat(why): expose cycle artifact directory in CycleRecord by @bojanrajkovic in #69
  • fix(drain): close Resume-vs-Start race window by @bojanrajkovic in #70
  • feat: confirm pause/resume by echoed command id by @bojanrajkovic in #71
  • fix(oci): document the pull progress concurrency contract and race-test CI by @bojanrajkovic in #72
  • feat(reload): confirm config-reload convergence with -wait by @bojanrajkovic in #73
  • docs: bundle the daemon+CLI in the app; app installs the daemon by @bojanrajkovic in #82
  • feat(app): detect and surface app/daemon version skew by @bojanrajkovic in #83
  • refactor: make ~/.runny a fixed, non-configurable runny home by @bojanrajkovic in #85
  • feat(app): bundle runnyd/runnyctl in Runny.app and vend the CLI by @bojanrajkovic in #87
  • feat(app): install and manage runnyd as a per-user LaunchAgent by @bojanrajkovic in #95
  • feat: fixed-home follow-ups and per-user agent lifecycle polish by @bojanrajkovic in #98
  • feat(app): detect-and-defer daemon ownership by @bojanrajkovic in #99
  • feat(app): tell a stale socket from a live one for ownership detection by @bojanrajkovic in #103
  • feat(app): surface and clean up every competing daemon owner by @bojanrajkovic in #104
  • feat(app): detect-and-defer and reconcile the runnyctl CLI symlink by @bojanrajkovic in #105
  • fix(runnyctl): honor -json after the subcommand and reject unknown flags by @bojanrajkovic in #106
  • feat(config): generate a JSON Schema for config.yaml editor support by @bojanrajkovic in #107
  • fix(runnyctl): align status columns by display width, not rune count by @bojanrajkovic in #108
  • docs: correct the inverted Local Network launch-context premise by @bojanrajkovic in #110
  • feat(runnyd): detect launch context to enforce the never-self-daemonize invariant by @bojanrajkovic in #111
  • feat(home): resolve a shared control-socket path for the system daemon by @bojanrajkovic in #113
  • feat(Runny): reach and defer to a system daemon by @bojanrajkovic in #114
  • feat(home): deployment-resolve the home between system and per-user by @bojanrajkovic in #116
  • feat(app): deployment-resolve the Swift client home by @bojanrajkovic in #117
  • feat: install runnyd as a non-root system LaunchDaemon by @bojanrajkovic in #118
  • feat(runnyctl): accept --operator on install-daemon by @bojanrajkovic in #122
  • feat(app): install the runnyd system daemon via a brokered admin prompt by @bojanrajkovic in #123
  • fix(app): move detail-pane title and actions into the window toolbar by @bojanrajkovic in #130
  • fix(doctor): make disk-headroom image-size-aware by @bojanrajkovic in #133
  • fix(statemachine): clear LastFailure when slot reaches LISTENING by @bojanrajkovic in #132
  • fix(app): split recycle confirmation bound from pause/resume (30s vs 10s) by @bojanrajkovic in #131
  • refactor(sysdaemon): use howett.net/plist to marshal the LaunchDaemon plist by @bojanrajkovic in #134
  • feat(images): share one image pull and its outcome across concurrent slots by @bojanrajkovic in #136
  • fix(sysdaemon): replace ValidateOperatorName regex with user.Lookup by @bojanrajkovic in #137
  • feat(localnet): distinct grant state for a self-daemonized daemon by @bojanrajkovic in #138
  • chore(deploy): remove the per-user shell installer by @bojanrajkovic in #139
  • chore(deploy): make the tap formula scream the migration by @bojanrajkovic in #140
  • refactor(lifecycle): rename foreignSystem to systemManaged by @bojanrajkovic in #143
  • refactor(lifecycle): collapse to the five-verdict ownership model by @bojanrajkovic in #145
  • feat(doctor): surface a leftover or duplicate runnyd registration by @bojanrajkovic in #147
  • feat(runnyctl): guard install-daemon against a live per-user agent by @bojanrajkovic in #148
  • docs: record the five-verdict ownership model and amend the ownership ADRs by @bojanrajkovic in #149
  • test(guest): exercise stopRunnerScript shell logic directly by @bojanrajkovic in #150
  • docs: define crash-only precisely and reconcile its loose usages by @bojanrajkovic in #152
  • chore(deps): update jdx/mise-action action to v4.2.0 by @renovate[bot] in #155
  • fix(statemachine): record a failed teardown cleanup as a warn, not silent ok by @bojanrajkovic in #154
  • refactor(lifecycle): trim LaunchdProbe to system-only by @bojanrajkovic in #156
  • test(sshx): widen WaitFor rejection-expiry budget so it can't straddle the deadline by @bojanrajkovic in #157
  • docs(adr): app-driven runnyd updates with a config-compat gate by @bojanrajkovic in #172
  • feat(home): add a non-fatal config warnings channel with two soft-validations by @bojanrajkovic in #173
  • feat(app): add an atomic, update-only re-stage script for the system daemon by @bojanrajkovic in #174
  • feat(runnyd): add -test-config, the local-only JSON config-compat gate by @bojanrajkovic in #175
  • feat(app): add config-compat gate plumbing (exec bundled runnyd -test-config, parse verdict) by @bojanrajkovic in #176
  • feat(runnyctl): add ...
Read more

v0.2.0-beta.57f3d402

v0.2.0-beta.57f3d402 Pre-release
Pre-release

Choose a tag to compare

@bojanrajkovic bojanrajkovic released this 11 Jun 02:48
57f3d40

Second pre-release. darwin/arm64, ad-hoc signed with the
com.apple.security.virtualization entitlement (asserted in CI).

Changes since 0.1.0-beta

  • fix(guest): the darwin runner now launches with a full login PATH
    (path_helper + Homebrew), so job steps find tools installed into
    /usr/local/bin — fixes aws: command not found immediately after a
    successful install.
  • feat(home): per-pool guest CPU/RAM sizing (cpu_cores, ram_gb);
    zero/omitted keeps the image's baked values.
  • feat(home)!: the runner-name prefix is now derived
    (<slug(hostname)>-<rand8>, persisted in ~/.runny/instance-id), not
    configured. BREAKING: the top-level name_prefix config key is removed;
    a config that sets it now fails to parse.

Install / upgrade

brew upgrade bojanrajkovic/tap/runny   # or: brew install bojanrajkovic/tap/runny

Then runnyd -doctor, and start as a LaunchAgent from a GUI login:
brew services start runny. See the setup gist for config + walkthrough.

v0.1.0-beta.83a2f0fd

v0.1.0-beta.83a2f0fd Pre-release
Pre-release

Choose a tag to compare

@bojanrajkovic bojanrajkovic released this 11 Jun 02:20
83a2f0f

First pre-release of runny — an observable, crash-only macOS GitHub Actions
runner daemon on Virtualization.framework (tart-format compatible, no tart
binary at runtime).

Scope: for testing only. darwin/arm64, ad-hoc signed with the
com.apple.security.virtualization entitlement (asserted in CI).

Install

brew install bojanrajkovic/tap/runny

…or download runny_0.1.0-beta.83a2f0fd_darwin_arm64.tar.gz below (build
provenance attested).

Write ~/.runny/config.yaml, then runnyd -doctor. Start as a LaunchAgent
from a GUI login so macOS grants Local Network access:

brew services start runny

Runner-onboarding config + walkthrough: see the shared setup gist.