No description
  • Go 80.3%
  • Nix 14.5%
  • Shell 5.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Conrad Kramer b132b335d5
Some checks failed
CI / macOS (push) Successful in 18s
CI / Linux (push) Failing after 58s
CI / Live smoke (push) Has been skipped
Recognize Forgejo idle runners
2026-08-23 00:37:51 -07:00
.forgejo/workflows forgejo-nsc: harden runner bootstrap and CI 2026-04-03 00:28:02 -07:00
cmd Add user scoped Forgejo runner routing 2026-04-03 04:35:56 -07:00
deploy forgejo-nsc: harden runner bootstrap and CI 2026-04-03 00:28:02 -07:00
internal Recognize Forgejo idle runners 2026-08-23 00:37:51 -07:00
nix Reserve capacity while runners register 2026-08-23 00:35:12 -07:00
scripts scripts/ci: seed nixbld accounts for root runners 2026-04-03 00:57:37 -07:00
.gitignore forgejo-nsc: harden runner bootstrap and CI 2026-04-03 00:28:02 -07:00
autoscaler.example.yaml Reserve capacity while runners register 2026-08-23 00:35:12 -07:00
config.example.yaml Add user scoped Forgejo runner routing 2026-04-03 04:35:56 -07:00
flake.lock bootstrap nsc-autoscaler 2026-03-28 23:57:07 -07:00
flake.nix forgejo-nsc: harden runner bootstrap and CI 2026-04-03 00:28:02 -07:00
go.mod bootstrap nsc-autoscaler 2026-03-28 23:57:07 -07:00
go.sum bootstrap nsc-autoscaler 2026-03-28 23:57:07 -07:00
README.md Reserve capacity while runners register 2026-08-23 00:35:12 -07:00

nsc-autoscaler

nsc-autoscaler turns queued Forgejo Actions jobs into short-lived Namespace runners.

It ships two binaries:

  • forgejo-nsc-dispatcher: accepts explicit dispatch requests and launches runners for a Forgejo scope plus label set.
  • forgejo-nsc-autoscaler: polls Forgejo queues, manages workflow_job webhooks, and asks the dispatcher for runners when jobs queue up.

Layout

.
├── cmd/forgejo-nsc-dispatcher
├── cmd/forgejo-nsc-autoscaler
├── internal/
├── config.example.yaml
├── autoscaler.example.yaml
└── deploy/

Local use

cp config.example.yaml config.yaml
cp autoscaler.example.yaml autoscaler.yaml
nix develop
# ensure the bare `nsc` CLI is installed and authenticated, or set
# `namespace.nsc_binary` to an explicit path
go run ./cmd/forgejo-nsc-dispatcher --config config.yaml
go run ./cmd/forgejo-nsc-autoscaler --config autoscaler.yaml

Smoke checks

./scripts/smoke-local.sh

For a live instance smoke against a real dispatcher or autoscaler, set the required environment variables and run:

DISPATCHER_URL=http://127.0.0.1:8080 ./scripts/smoke-live.sh

To exercise a deployed pair from Forgejo itself, use the manual workflow_dispatch inputs on /.forgejo/workflows/ci.yml. The live_smoke job is opt-in and reuses the same scripts/runtime-http-smoke.sh entrypoint.

This repo uses Forgejo Actions only. The canonical in-repo CI entrypoint is /.forgejo/workflows/ci.yml, and it intentionally uses the same namespace-profile-* runner contract that this autoscaler maps onto Namespace profiles. The workflow is script-driven and only uses Forgejo- hosted checkout and setup-go actions; Nix bootstrap lives in scripts/ci/.

The dispatcher prefers the bare nsc CLI as the lifecycle boundary for Namespace instances. Linux and Windows already run entirely through the CLI, and macOS now prefers the CLI create/instance upload/ssh flow as well, with the older Compute path retained only as a fallback if the CLI bootstrap fails.

The checked-in examples are placeholders you should adapt to your environment:

  • Forgejo API on http://127.0.0.1:3001
  • public runner registration URL https://forgejo.example.com
  • Namespace label allowlists for Linux, macOS, and Windows
  • a user-scoped controller for conrad
  • an organization-scoped autoscaler controller for example-org

The dispatcher also supports optional scope-routed credentials. This lets one dispatcher choose a Forgejo PAT and Namespace auth context per user or organization scope instead of assuming one shared credential set for every request.

The autoscaler examples use loopback webhook URLs, which works well when Forgejo and the autoscaler run on the same machine.

Each target's startup_grace reserves newly dispatched capacity while a runner boots and registers. The default is three minutes; this prevents a 30-second polling loop from launching duplicate machines during normal startup.

Nix outputs

nix build .#forgejo-nsc-dispatcher
nix build .#forgejo-nsc-autoscaler
nix build .#container-amd64

The container output packages the dispatcher plus the nsc CLI for Linux deployments. The autoscaler is intended to run next to it as a separate process or systemd unit.

NixOS module

This flake exports a consumable NixOS module at nixosModules.default (also nixosModules.forgejo-nsc). Import it at the host or flake modules = [ ... ] layer, not via a module argument inside another module's imports.

For existing deployments that already manage encrypted YAML configs, raw-config mode is the lowest-friction path:

{
  imports = [ inputs.nsc_autoscaler.nixosModules.default ];

  services.forgejo-nsc = {
    enable = true;
    nscTokenFile = config.age.secrets.nscToken.path;
    dispatcher.configFile = config.age.secrets.dispatcherConfig.path;
    autoscaler = {
      enable = true;
      configFile = config.age.secrets.autoscalerConfig.path;
      allowPending = true;
    };
  };
}

For new deployments, the module can also generate dispatcher and autoscaler config from structured Nix options and will automatically reuse services.forgejo.settings.server.LOCAL_ROOT_URL and ROOT_URL when services.forgejo.enable = true.

Operational compatibility options carried over from existing deployments:

  • nscTokenFile, nscTokenSpecFile, and nscEndpoint feed the bare nsc CLI environment.
  • the module copies nscTokenFile into the service state dir before startup so the service user owns the runtime token path
  • extraEnv and extraPath let host configs extend the runtime boundary
  • dispatcher.allowPending and autoscaler.allowPending preserve placeholder config workflows during staged rollouts