runner

package
v1.3.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 12, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package runner implements `nself runner provision` and `nself runner verify` (G-012): building and auditing self-hosted GitHub Actions CI runner hosts.

Purpose: CI runner hosts were hand-built, so required system dependencies

were discovered only when a job failed mid-run, and two hosts
advertising the identical GitHub Actions labels (self-hosted,Linux,X64)
could silently drift apart — one had `gh`/`zip`/`unzip`, the other
didn't, and the same commit passed or failed depending on which host
claimed the job.

Inputs: a declarative Manifest (manifest.yaml, embedded below) plus an

Executor bound to one host (local or SSH).

Outputs: for provision, packages/user/sudoers/systemd units installed on

that host; for verify, a per-host CheckResult set that composeable
callers turn into a cross-host parity matrix (report.go).

Constraints: this package never talks to a host directly — every side

effect goes through the Executor interface (exec.go), so unit tests
exercise the exact command strings without touching real hardware.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RenderMatrix

func RenderMatrix(reports []HostReport) string

RenderMatrix formats reports as a plain-text parity matrix: one row per check, one column per host, plus a trailing drift summary. This is the primary human-facing output of `nself runner verify` when checking more than one host.

Types

type CheckResult

type CheckResult struct {
	Name   string      `json:"name"`
	Status CheckStatus `json:"status"`
	Detail string      `json:"detail,omitempty"`
}

CheckResult is one named check's outcome on one host.

func VerifyHost

func VerifyHost(ctx context.Context, ex Executor, m *Manifest) []CheckResult

VerifyHost runs every manifest-declared check against one host and returns the full result set. It never returns an error itself — a host that is entirely unreachable should be detected by the caller with a cheap reachability probe first (see VerifyHosts in report.go); if it isn't, every check below simply reports StatusFail with the underlying exec error as Detail, which is still an honest (if noisier) answer.

type CheckStatus

type CheckStatus string

CheckStatus is the outcome of a single verify check.

const (
	// StatusPass means the check found what it expected.
	StatusPass CheckStatus = "pass"
	// StatusFail means the check found the dependency/condition missing or
	// broken — this is what should block a job from being trusted on this
	// host.
	StatusFail CheckStatus = "fail"
	// StatusWarn is informational: the check couldn't run to completion in
	// a meaningful way (e.g. no cached Chromium yet) but that's not itself
	// evidence of drift.
	StatusWarn CheckStatus = "warn"
)

type Dependency

type Dependency struct {
	Name       string `yaml:"name"`
	AptPackage string `yaml:"apt_package"`
	Binary     string `yaml:"binary"`
	Reason     string `yaml:"reason"`
}

Dependency is one required system package, checked either by looking for a binary on PATH (Binary != "") or by asking the package manager whether AptPackage is installed (Binary == "", e.g. a runtime library with no CLI entry point).

func (Dependency) HasBinary

func (d Dependency) HasBinary() bool

HasBinary reports whether this dependency is checked via `command -v` rather than the package manager.

type DriftFinding

type DriftFinding struct {
	CheckName string                 `json:"check"`
	ByHost    map[string]CheckStatus `json:"by_host"`
}

DriftFinding names one check that disagrees across two or more reachable hosts — the thing `nself runner verify` exists to surface.

func DetectDrift

func DetectDrift(reports []HostReport) []DriftFinding

DetectDrift compares every check name present on any reachable host and reports the ones whose status differs across hosts. Manifest checks are identical across hosts by construction, so any disagreement here is real drift, not a different question being asked.

type Executor

type Executor interface {
	Run(ctx context.Context, command string) (output string, err error)
	Label() string
}

Executor runs one shell command against a bound host and reports its label (used in verify's parity matrix and provision's log lines).

type HostReport

type HostReport struct {
	Host   string        `json:"host"`
	Checks []CheckResult `json:"checks,omitempty"`
	// Err is set when the host itself could not be reached at all (SSH
	// connection failure), as opposed to an individual check failing.
	Err string `json:"error,omitempty"`
}

HostReport is one host's full verify result.

func VerifyHosts

func VerifyHosts(ctx context.Context, executors []Executor, m *Manifest) []HostReport

VerifyHosts runs a cheap reachability probe against each executor, then the full VerifyHost check set against every host that answered. Hosts that don't answer get a single HostReport with Err set and no Checks, rather than N near-identical "connection refused" failures.

type LocalExecutor

type LocalExecutor struct{}

LocalExecutor runs commands on the current machine via `bash -c`. Bash (not POSIX sh) is required because the chromium-cache check uses `shopt -s nullglob`; every provisioning target (Ubuntu runner hosts) and every dev machine this ships on has bash available.

func (LocalExecutor) Label

func (LocalExecutor) Label() string

Label implements Executor.

func (LocalExecutor) Run

func (LocalExecutor) Run(ctx context.Context, command string) (string, error)

Run implements Executor.

type Manifest

type Manifest struct {
	SchemaVersion      int          `yaml:"schema_version"`
	RunnerUser         string       `yaml:"runner_user"`
	WorkDir            string       `yaml:"work_dir"`
	Dependencies       []Dependency `yaml:"dependencies"`
	ChromiumCacheGlobs []string     `yaml:"chromium_cache_globs"`
}

Manifest is the parsed form of manifest.yaml: the full declarative dependency set plus the fixed layout conventions (runner user, work dir, where to look for a cached Chromium) that provision and verify both need to agree on.

func LoadEmbeddedManifest

func LoadEmbeddedManifest() (*Manifest, error)

LoadEmbeddedManifest parses the manifest compiled into the binary. Returns an error if the embedded YAML is malformed, which would indicate a build-time bug (a hand edit that broke the schema), not a runtime condition — callers should treat a non-nil error as fatal.

func ParseManifest

func ParseManifest(data []byte) (*Manifest, error)

ParseManifest decodes manifest YAML bytes. Exported so tests (and any future NSELF_RUNNER_MANIFEST override, mirroring the deprecation registry's pattern) can parse an alternate manifest without touching the embedded default.

func (*Manifest) AptPackages

func (m *Manifest) AptPackages() []string

AptPackages returns the apt package names for every dependency, in manifest order, for a single `apt-get install -y <names...>` invocation.

type ProvisionOptions

type ProvisionOptions struct {
	// Instances is how many runner instances to register on this host.
	// Defaults to 1.
	Instances int
	// InstallRoot is the base directory each instance installs into, as
	// InstallRoot/runner-<N>. Defaults to /opt/actions-runner.
	InstallRoot string
	// GithubURL is the repo or org URL runners register against, e.g.
	// https://github.com/nself-org/cli.
	GithubURL string
	// RegToken is a GitHub Actions runner registration token. Sourced from
	// an env var by the caller (cmd/commands/runner_provision.go) — never
	// hardcoded, never included in ProvisionResult.
	RegToken string
	// Labels are extra labels appended after the standard
	// self-hosted,Linux,X64 set.
	Labels []string
}

ProvisionOptions parameterizes one provisioning run.

type ProvisionResult

type ProvisionResult struct {
	Steps []ProvisionStep
}

ProvisionResult is the full record of a Provision run.

func Provision

func Provision(ctx context.Context, ex Executor, m *Manifest, opts ProvisionOptions) (*ProvisionResult, error)

Provision installs the manifest's dependency set, creates the runner user with passwordless sudo, ensures the work directory is a real directory (never a symlink — see verify.go's checkWorkDirNotSymlink for why that matters), and installs opts.Instances runner instances as systemd services.

type ProvisionStep

type ProvisionStep struct {
	Name   string
	Output string
}

ProvisionStep records one step's name and captured output.

type SSHExecutor

type SSHExecutor struct {
	Target deploy.RemoteTarget
}

SSHExecutor runs commands on a remote host over SSH, reusing deploy.RunRemoteCommand so runner provisioning shares exactly one SSH convention (key resolution, StrictHostKeyChecking, ForwardAgent=no) with `nself deploy` and every other remote-targeting command in the CLI.

func NewSSHExecutor

func NewSSHExecutor(sshTarget, keyPath string) SSHExecutor

NewSSHExecutor builds an SSHExecutor from a "user@host" (or "user@host:/path", though runner provisioning ignores the path component) string and an SSH key path. An empty keyPath resolves the same NSELF_DEPLOY_KEY_PATH / NSELF_DEPLOY_SSH_KEY env-var convention `nself deploy` uses, defaulting to ~/.ssh/id_ed25519, so every remote-targeting command in the CLI shares one key-resolution rule.

func (SSHExecutor) Label

func (e SSHExecutor) Label() string

Label implements Executor.

func (SSHExecutor) Run

func (e SSHExecutor) Run(ctx context.Context, command string) (string, error)

Run implements Executor.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL