initconfig

package
v0.10.0 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package initconfig renders a runnable billet.yaml for `billet init`.

IMPORTABLE, RATHER THAN LIVING IN package main, because two callers need it: the CLI, and the end-to-end test that proves a generated config actually launches a job. A generator that only the CLI can reach can only be tested by asserting the file loads, which is exactly the gap that let the docker trial ship refusing every job.

WHAT IT WRITES HAS TO RUN. Copying billet.example.yaml did not: it describes a Firecracker deployment, so the provider, every tier's image, the state directories and the capacity ceiling all had to be edited before anything started. A generated config that also needs editing is the same trap with an extra step. The one thing it cannot know is the GitHub App, because that does not exist yet — it names the org and leaves the ids at zero for `billet github-app create` to fill.

Index

Constants

View Source
const (
	HybridTerraformFile    = "terraform/main.tf"
	HybridInventoryFile    = "inventory.yml"
	HybridSiteFile         = "site.yml"
	HybridRequirementsFile = "requirements.yml"
)

The files a generation writes, relative to the output directory. The runbook is rendered by the CLI, which knows the flags it has to repeat.

View Source
const (
	HybridOutputControlPlanePrivateIP = "control_plane_private_ip"
	HybridOutputLedgerVolumeID        = "ledger_volume_id"
	HybridOutputSubnetID              = "subnet_id"
	HybridOutputRunnerSecurityGroup   = "runner_security_group_id"
	HybridOutputUntrustedRunnerSG     = "untrusted_runner_security_group_id"
	HybridOutputAMIPayloadBucket      = "ami_payload_bucket"
	HybridOutputName                  = "name"
	HybridOutputRegion                = "region"
	HybridOutputCacheBucket           = "cache_bucket"
	HybridOutputCachePrefix           = "cache_prefix"
	HybridOutputAvailabilityZone      = "availability_zone"
)

The Terraform outputs the inventory consumes, by the exact name the generated root declares them under. A placeholder names one of these; ParseTerraformOutput demands them; the structural test proves the rendered root declares each.

View Source
const (
	HeadroomVCPU   = 2
	HeadroomMemory = 4 * config.GiB
)

Headroom is the NOMINAL reservation a generated ceiling leaves for the machine itself — the amount withheld whenever the machine can afford it.

It is not a guaranteed minimum, and saying so was wrong once the cap below landed: CeilingMemory withholds at most half a machine, so a 2GiB host reserves 1GiB rather than this 4GiB. The cap is what keeps the rule coherent at the small end; see CeilingMemory.

The ceiling is what the allocator escrows against, so setting it to everything the host has means billet fills the machine and leaves nothing for the kernel, the container runtime, or the operator's shell. A starting point to raise, not a measurement.

A FLOOR RATHER THAN THE WHOLE RULE, because a fixed reservation means different things at different sizes. Two vCPU is a quarter of an eight-thread laptop and 1.5% of a 128-thread server — and the server is the machine carrying sixty guests, whose supervision, networking and storage clients are what the reservation exists for. A fixed number reserves proportionally least exactly where there is most to do.

View Source
const (
	// DefaultTartMacOSImage is the macOS guest a generated macOS tier boots.
	//
	// A real private-repository Xcode job built an iOS target inside a guest
	// billet launched from this image, and the guest was destroyed afterwards
	// (internal/provider/tart/realguest_test.go). It ships the Actions runner in
	// ~/actions-runner, which is where config.RunnerCommandFor(tart) looks — so
	// the tier needs no `command:` of its own.
	//
	// IT NAMES A macOS RELEASE. cirruslabs publishes a repository per release
	// rather than a tag per release, so this constant is what moves when a
	// deployment moves generation, and a tier that names its own image is
	// unaffected. It is ~87GB in the local store against a 140GB virtual disk.
	DefaultTartMacOSImage = "ghcr.io/cirruslabs/macos-tahoe-xcode:latest"

	// DefaultTartLinuxImage is the native arm64 Linux guest a generated linux
	// tier boots.
	//
	// The `-runner-` image rather than the plain `ubuntu` one, and the difference
	// is not cosmetic: the plain image was measured to carry neither the Actions
	// runner nor Docker. This one carries both, which is what lets a Linux tart
	// tier do the two things a macOS guest on Apple's hypervisor cannot — build a
	// container image, and reach a service container. 11.3GB compressed against a
	// 40GB virtual disk.
	DefaultTartLinuxImage = "ghcr.io/cirruslabs/ubuntu-runner-arm64:latest"
)

The guest images a generated tart config names.

BOTH WERE MEASURED, and that is the whole reason `billet init` can write this backend at all. It refused to for a long time on the grounds that there was no image it could name — true when the only published Linux guest was `ghcr.io/cirruslabs/ubuntu`, which carries neither the Actions runner nor Docker and so cannot run a job at all. Naming an image the operator's jobs cannot use is the exact trap this generator exists to remove.

View Source
const AnsibleVar = "billet_config"

AnsibleVar is the variable the junioryono.billet.host role renders billet.yaml from. ONE constant read by the emitter and by the test that pins it to the role's template, so a rename there cannot silently produce a block the role ignores — which would converge a host with no config change and no error.

View Source
const (
	// DefaultFirecrackerImage is a published guest generation, resolved to a
	// verified one. Unlike a docker image it is not pulled from a registry — it
	// comes from `billet images pull`.
	DefaultFirecrackerImage = "ubuntu-2404-x64@verified"
)

Firecracker conventional values billet cannot detect but the host must provide. They are what `billet images pull` and the host Ansible role install, so a generated config names them and the operator prepares the host to match.

View Source
const DefaultListen = "127.0.0.1:7717"

DefaultListen is the loopback address a generated config binds and dials.

View Source
const DefaultRunnerImage = "ghcr.io/actions/actions-runner:latest"

DefaultRunnerImage is a container image that already contains the runner.

The tier's image is handed straight to `docker run`, so a golden-image name like `ubuntu-2404-x64` — what the Firecracker example uses — is not pullable and every job fails to launch with a message about the image rather than the config.

View Source
const (

	// HybridDefaultLocalSite is what the machine at home is called when the
	// operator does not say. A site is a PLACE, so the default names one.
	// Exported because the CLI prints it in the flag's help.
	HybridDefaultLocalSite = "home"
)

Hybrid conventions shared by the renderers and the runbook.

View Source
const HybridMarker = "written by billet init hybrid"

HybridMarker is the first-line marker every generated file carries, in its own comment syntax, so a re-run can tell billet's own file from one the operator wrote and replace only the former.

View Source
const PlaceholderAMI = "ami-REPLACE-run-billet-ami-build"

PlaceholderAMI is the tier image an ec2 config is written with before an image exists. It PASSES config load, which checks only that an image is named, and FAILS at launch, where DescribeImages cannot resolve it — which is the staged flow on purpose: `billet init --provider ec2` writes this, `billet ami build` produces a real AMI id, and the operator pastes it in. A value that reads as an instruction rather than a plausible id, so a config that reached a launch unedited fails by naming what to do rather than with an opaque AWS error.

View Source
const ServiceGroup = "billet"

ServiceGroup is the group the packaged server unit runs as, and therefore the group a local-service config must be readable by. ONE constant read by the generator, the ownership code and the units-pin test, so the unit files and this name cannot drift apart silently.

Variables

This section is empty.

Functions

func AnsibleCompanions

func AnsibleCompanions(p config.ProviderKind) map[string]bool

AnsibleCompanions are the role variables that must accompany a generation for the role to accept it, keyed by name.

The role provisions a Firecracker host by default — guest bridges and a Ceph cluster — and asserts that those flags MATCH the node's provider before it touches anything. A docker or ec2 node with the defaults left alone fails that assertion, so the two backends that need them off say so in the block itself. A firecracker node needs nothing here: the defaults already describe it, and stating billet_ceph_enabled for it would claim this host should bootstrap a cluster, which is a decision the operator makes and not one a generator can.

func AnsibleVars

func AnsibleVars(body string, companions map[string]bool) (string, error)

AnsibleVars nests a generated config under the variable the host role reads.

The role's template is `{{ billet_config | to_nice_yaml }}` — the value IS the billet.yaml, verbatim — so the only thing between a generated config and a usable inventory entry is this indentation. Without it the operator retypes the generator's output by hand, which is how a ceiling ends up naming more vCPU than the machine has.

INDENTED TEXTUALLY RATHER THAN RE-ENCODED, so the generated comments survive. They say why the ceiling is what it is and what the host still has to provide, and the inventory is the file a person reads and edits. Ansible drops them when it renders, which is correct: they are for the reader, not for the host.

func CeilingMemory

func CeilingMemory(detected config.ByteSize) config.ByteSize

func CeilingVCPU

func CeilingVCPU(detected int) int

CeilingVCPU and CeilingMemory are what billet may spend, leaving the host enough to keep working. NOTHING DETECTED IS NOTHING TO SPEND, and both return zero for it rather than a floor. Subtracting a reservation from a non-positive reading used to yield 1 vCPU — a ceiling ABOVE what was detected, which does not fail, it overcommits. Generate refuses a zero ceiling by name, so the caller gets the reading back rather than a config built on one that never happened.

func CheckListen

func CheckListen(listen string) error

CheckListen refuses a listen value the local profiles cannot honor: anything that is not a well-formed loopback host:port. Same dual-caller contract as CheckProfile.

func CheckProfile

func CheckProfile(p Profile) error

CheckProfile refuses a profile value that is not one of the two shapes, naming the flag. Exported so the CLI can refuse it immediately after flag parsing — before capacity detection or a live AWS fetch — while Generate keeps the same guard for its other callers.

func CheckTartGuestOS

func CheckTartGuestOS(guests []config.GuestOS) ([]config.GuestOS, error)

CheckTartGuestOS validates the requested guest kinds and returns them in the canonical order.

EXPORTED SO ONE RULE SERVES TWO CALLERS. cmd/billet must know the request is meaningful BEFORE it reads anything off the machine: it resolves this host's name from the hostname, and doing that first meant `--guest-os typo` on a Mac whose hostname is not a legal node name reported the node name instead of the typo — the same invalid input getting a different error depending on an unrelated property of the machine. Generate re-applies it because it is reachable without the CLI, and a second copy of the rule is how the two grow apart.

CANONICAL ORDER (macOS first) rather than the order they arrived, so two spellings of one request generate identical bytes: `billet init` re-run against its own output converges on a byte comparison.

func ExistingServerStateDir

func ExistingServerStateDir(body []byte) (string, bool)

ExistingServerStateDir reads the directory holding an existing config's deployment identity, leniently. ok is false only when the file is not YAML at all — the caller must fail CLOSED there, because "cannot read it" is not "there is no deployment to protect". An ABSENT key reports the same default the config layer fills in: a running deployment that omitted it keeps its state exactly there.

IT READS BOTH SPELLINGS, and must. `state_dir` is the shorthand and is what almost every file says; `identity_dir` is what a config naming its ledger backend explicitly writes instead, and a deployment whose ledger is in PostgreSQL has ONLY that one. Reading `state_dir` alone would report the default directory for such a config — and this answer is what protects an existing deployment's CA and identity from being written over.

func Generate

func Generate(p Params) (string, bool, error)

Generate renders a billet.yaml for these parameters and returns it together with whether it produced a trusted pool, having proved it validates. A parameter billet cannot turn into a RUNNABLE config — a Docker trial with no runner-group or workflow policy, a malformed workflow ref, a blank runner group, or an image with stray whitespace — is refused here by the name of the flag that carried it, rather than surfacing later as a config-load error blaming the generated tier (or, worse, loading and then failing to launch).

func HybridPlaceholder added in v0.6.1

func HybridPlaceholder(output string) string

HybridPlaceholder is the text standing in for an apply-time fact in a plan render. It names the output that fills it, so an operator who meets one in a file knows exactly which command produces the value.

func HybridPlaceholders added in v0.6.1

func HybridPlaceholders(text string) []string

HybridPlaceholders reports the output names every placeholder in text names, in order of appearance, so a test can prove each one is declared and a filled render carries none.

func ServiceAccount

func ServiceAccount() string

ServiceAccount is the account the packaged services run as, or EMPTY where they run as the operator who installed them.

Empty is macOS, and it is not an omission. billet ships launch AGENTS rather than daemons because Virtualization.framework needs an unlocked login keychain and tart's image store is per-user — so the services run in a person's own GUI session, own their own files, and there is no service account for an operator to chown anything to.

func ServiceAccountFor

func ServiceAccountFor(goos string) string

ServiceAccountFor is the same answer for a NAMED platform.

func ServiceConfigPath

func ServiceConfigPath() string

ServiceConfigPath is where a local-service config lives — the path the packaged service definitions read.

PLATFORM-DERIVED RATHER THAN A SECOND PROFILE NAME. "The system-service shape" means the same thing to an operator on either platform; what differs is where that shape puts things and which account it runs as. A fourth profile would make them choose between two words for one idea.

func ServiceConfigPathFor

func ServiceConfigPathFor(goos string) string

ServiceConfigPathFor is the same answer for a NAMED platform.

THE PLATFORM IS A PARAMETER because two packages need to ask about a platform that is not the one they are running on: cmd/billet already carries its own `hostOS` seam so its tests can assert both shapes, and a hidden global here would mean those tests asserted whichever machine happened to run them.

func ServiceKeyPathFor

func ServiceKeyPathFor(goos string) string

ServiceKeyPathFor is where a local-service config on a NAMED platform points at the App private key.

EXPORTED BECAUSE THE GUIDANCE MUST NAME THE SAME FILE THE CONFIG DOES. `billet init`'s closing note used to carry a hardcoded /etc/billet/app-private-key.pem, so on a Mac it named a path in a directory that does not exist while the config beside it pointed somewhere else — and the operator has no way to tell which of the two is the real one.

Types

type CodeBuildParams

type CodeBuildParams struct {
	Region  string
	Project string
	// Environment decides this node's guest OS, which billet reports at
	// registration rather than taking a second answer from the config.
	Environment config.CodeBuildEnvironment
	// FleetARN selects reserved capacity. Empty means on-demand compute, which
	// three of the six environments do not offer at all.
	FleetARN string
	// ComputeTypes is the ordered catalogue, most preferred first, each entry
	// declaring what it holds and what it costs.
	ComputeTypes []config.RemoteShape
	// JITParameterPath is where each build's single-use registration is written.
	// It is an IAM boundary rather than a naming preference, so billet does not
	// guess one.
	JITParameterPath string
	JITKMSKeyID      string
	LogGroup         string
	PrivilegedMode   bool
	// AcceptCeiling is the operator having read what CodeBuild's own limits are.
	// Its absence is a refusal; see errCodeBuildNeedsCeiling.
	AcceptCeiling        bool
	BuildTimeoutMinutes  int
	QueuedTimeoutMinutes int
	// FleetCapacity is how many builds this fleet may run at once, and it is
	// REQUIRED for macOS and meaningless elsewhere.
	//
	// A macOS tier with no explicit max_concurrent inherits its host's limit,
	// which defaults to APPLE's two-guests-per-machine allowance — a rule about
	// hardware somebody owns, and not the rule here: on this backend the cap is
	// the fleet capacity, which billet cannot see and AWS defaults to ONE. So the
	// generation asks rather than inventing a number that reads as a licence
	// statement it is not.
	FleetCapacity int
	// NodeName is what this orchestrator is called in the deployment, and it is
	// REQUIRED for macOS for the same reason a tart generation requires one:
	// config validation refuses a macOS tier that names no node, because the
	// per-host guest limit cannot be enforced against a tier that is pinned to
	// nowhere.
	//
	// IT IS NOT THIS MACHINE'S HOSTNAME, and that is the difference from tart. A
	// tart node IS the Mac, so its hostname is at least a candidate; a codebuild
	// node is a small machine somewhere that calls an API, and its hostname says
	// nothing about the fleet the limit is actually about. So it is asked for
	// rather than derived, and a generation that guessed would name a host the
	// operator never chose — which they would meet again the first time
	// `billet ca issue` disagreed.
	NodeName string
}

CodeBuildParams is the CodeBuild placement a generation needs and billet cannot detect.

EVERY FIELD HERE IS SOMEBODY'S DECISION. There is no machine to measure: a codebuild node calls an API and the build appears in a region, so the ceiling is a declared budget and the shapes are a declared catalogue — the node.ec2 rule, for the node.ec2 reason. What is different is the ACKNOWLEDGEMENT, which exists so a sentence is read by a person rather than met as a build that died at hour 36.

type EC2Params

type EC2Params struct {
	Region                  string
	SubnetID                string
	SecurityGroups          []string
	UntrustedSecurityGroups []string
	// Shapes are the instance types billet may buy, each already carrying what it
	// holds and its audited price. Tiers are derived so each fits a declared shape.
	Shapes []config.EC2InstanceType
}

EC2Params is the cloud placement an ec2 config needs and billet cannot detect.

type HostInputs

type HostInputs struct {
	// KernelImage pins node.firecracker.kernel_image.
	KernelImage string
	// CephUser and CephKeyringPath name the RADOS identity, WITHOUT the `client.`
	// prefix. Empty leaves Ceph's own search path, which finds
	// /etc/ceph/ceph.<user>.keyring.
	CephUser        string
	CephKeyringPath string
	// CacheListen and CacheGuestEndpoint are node.cache, and are given together
	// or not at all.
	CacheListen        string
	CacheGuestEndpoint string
}

HostInputs are values a HOST must provide that billet cannot detect and will not guess.

THE MEASURED LIST FROM A REAL FIRECRACKER DEPLOYMENT (2026-08-26). A generated block was diffed against an inventory written by hand months earlier, and everything the generator could know it reproduced exactly: listen, both state directories, the App identity, the provider, the lock directory, both bridges, both Ceph pools, and a 2/4/8 tier ladder unprompted. What it could not know is this — values the operator had set, none of which has a right default:

  • a PINNED kernel, because `<kernel_dir>/vmlinux` is a fallback and a real host names a version;
  • the Ceph identity and its keyring, because `billet` is a default and `admin` — which the rbd command picks for itself — can delete a pool;
  • the cache endpoint, which is a fact about the operator's network.

EVERY ONE IS OPTIONAL. Omitted, the generation writes exactly what it wrote before. Supplied, it is written OUT rather than left as a comment — which is the point: an operator who has answered the question should not have to answer it again by editing the file afterwards.

type HybridFacts added in v0.6.1

type HybridFacts struct {
	ControlPlanePrivateIP          string
	LedgerVolumeID                 string
	SubnetID                       string
	RunnerSecurityGroupID          string
	UntrustedRunnerSecurityGroupID string
	AMIPayloadBucket               string
	// Name and Region are consumed by no rendering — every one takes both from
	// HybridParams. They are read so the two can be COMPARED, because outputs
	// from another root otherwise render a config that signs against one
	// deployment and names another's subnet, security group, buckets and
	// controller, with nothing in the generation saying so.
	//
	// THEY DO NOT BIND TO A ROOT, and nothing available here can. Two generations
	// sharing a name and a region, in different accounts, are indistinguishable
	// from these outputs. What this catches is the ordinary mistake — the wrong
	// outputs.json, or a flag retyped — not a determined one.
	Name   string
	Region string
	// The cache facts, demanded only of a generation that asked for one.
	CacheBucket      string
	CachePrefix      string
	AvailabilityZone string
}

HybridFacts are the apply-time values a filled render carries. The zero value means "not applied yet", and every empty field renders its placeholder.

func ParseTerraformOutput added in v0.6.1

func ParseTerraformOutput(raw []byte, need HybridNeeds) (HybridFacts, error)

ParseTerraformOutput reads `terraform output -json` into the facts a hybrid render consumes.

EVERY CONSUMED OUTPUT IS REQUIRED, BY NAME. A missing one is not a fact billet can leave blank: an empty subnet loads and then launches nothing, and an empty ledger volume id makes the role skip the fail-closed mount and start the controller on the root disk. The optional ones are demanded only where the generation actually reads them, because a root that never declared an output cannot be faulted for not producing it.

type HybridFiles added in v0.6.1

type HybridFiles map[string]string

HybridFiles is the generation: relative path to content.

func GenerateHybrid added in v0.6.1

func GenerateHybrid(p HybridParams) (HybridFiles, bool, error)

GenerateHybrid renders the files and reports whether the tiers are trusted.

Every refusal here names the flag that carried the value, for the reason Generate's do: a value billet cannot turn into a runnable deployment must not surface later as a config-load error blaming a file billet wrote.

type HybridNeeds added in v0.7.0

type HybridNeeds struct {
	// Untrusted demands the untrusted runner group, which only an untrusted
	// generation's root declares.
	Untrusted bool
	// Cache demands the three cache facts, which only a --cache root declares.
	Cache bool
}

HybridNeeds says which optional facts a generation depends on, so ParseTerraformOutput demands exactly what this deployment's shape consumes and nothing it does not.

A STRUCT RATHER THAN BOOLEANS IN A ROW, because two bare bools at a call site are two chances to swap them, and swapping these means demanding an output the root does not declare while accepting one it does.

type HybridParams added in v0.6.1

type HybridParams struct {
	// Name is the Terraform module's name prefix: every AWS resource, the backup
	// bucket and the payload bucket derive from it.
	Name string
	// Region is where the controller and the fallback compute live.
	Region string
	// Org and the trusted-pool policy, exactly as for a single-host generation.
	// No policy renders `trust: untrusted` tiers, which the ec2 side serves from
	// an untrusted security group and the firecracker side from the untrusted
	// bridge.
	Org         string
	Repository  string
	RunnerGroup string
	Workflows   []string

	// ControlPlaneIP is the controller's private address, DECLARED. Empty leaves
	// it to AWS and renders a placeholder that the prepare render fills; a
	// declared one is written into server.listen and every server_addr before
	// the apply, which is the point of declaring it.
	ControlPlaneIP string

	// ControllerName and LocalName are the two hosts' names in the inventory
	// and in their certificates. The controller's is also the co-located ec2
	// node's name.
	ControllerName string
	LocalName      string

	// LocalVCPU and LocalMemory are what the Firecracker host HAS; its
	// contribution is that minus headroom, the same rule a measured host gets.
	LocalVCPU   int
	LocalMemory config.ByteSize

	// CloudVCPU and CloudMemory are the cloud budget, which IS the orchestrator's
	// ceiling: there is no machine to withhold headroom from.
	CloudVCPU   int
	CloudMemory config.ByteSize

	// Shapes are the EC2 instance types billet may buy, each carrying what it
	// holds and its audited price.
	Shapes []config.EC2InstanceType

	// SSHIngressCIDRs open the controller's SSH port to the machine that runs
	// Ansible; empty opens nothing, which is right for a route that ends on the
	// controller itself (the Systems Manager agent, a cloudflared tunnel or
	// route running on the host). IPv4 and canonical, because the root's rule
	// is cidr_ipv4 and AWS normalises host bits into a permanent diff.
	SSHIngressCIDRs []string
	// SSHKeyName is the EC2 key pair the controller launches with. Empty
	// attaches none, and then the only way onto a fresh Canonical image is EC2
	// Instance Connect, which the runbook spells out; with a key, Ansible's
	// ordinary SSH works the moment the instance answers.
	SSHKeyName string
	// LocalAnsibleUser is the account Ansible connects to the Firecracker host
	// as. Empty writes none, leaving the operator's SSH configuration to say:
	// owned hardware has no reason to carry the cloud image's `ubuntu`.
	LocalAnsibleUser string
	// LocalImage is the guest generation every tier boots on the Firecracker
	// host. Empty is DefaultFirecrackerImage, the x64 generation billet
	// publishes; an operator with another architecture or their own signed
	// generation names it, because the generator cannot see that machine.
	LocalImage string

	// Cache turns on the EBS+S3 site cache for the cloud half: the module
	// creates the bucket, the orchestrator gains node.ebs_s3 and the node.cache
	// listener its job instances fetch through, and both hosts declare the site
	// their storage belongs to.
	//
	// THE TWO HALVES CACHE IN DIFFERENT PLACES, which is what makes the sites
	// necessary rather than decorative: the Firecracker host's generations live
	// in its own Ceph pools, and an EC2 job cannot reach them across the WAN, so
	// the cloud half needs a store of its own and cache keys are scoped by site.
	Cache bool
	// LocalSite and CloudSite name those two places. Empty defaults to "home"
	// and to the region, which is what an operator would write anyway.
	LocalSite string
	CloudSite string

	// Builder grants the controller's own role what `billet ami build`
	// performs, so the image can be built ON the controller instead of from a
	// workstation holding an operator's AWS credentials — a second machine to
	// keep trustworthy for one step, on a deployment whose controller may be
	// reachable only through a tunnel. Off by default: it widens the identity
	// every job's instance is launched by.
	Builder bool

	// Host carries the Firecracker host's inputs billet cannot detect.
	Host HostInputs

	// Ref is the release every layer pins: the module's ?ref=, the collection's
	// version and billet_version. `main` for a development build.
	Ref string

	// Facts fill the placeholders; nil is the plan render.
	Facts *HybridFacts
	// Commission adds the ec2 node to the controller and lifts the prepare-only
	// hold. It needs Facts.
	Commission bool
	// AMI is what every tier's launch.ec2.image becomes on the commission
	// render; empty writes PlaceholderAMI, which loads and fails at launch by
	// naming what to do.
	AMI string

	// The App identity carried from an existing config; zero leaves the ids for
	// `billet github-app create`.
	AppID          int64
	InstallationID int64
	ClientID       string
}

HybridParams is everything `billet init hybrid` decided or was told.

func (HybridParams) TargetPath added in v0.10.0

func (p HybridParams) TargetPath() string

TargetPath is the path of the owner these runners serve, or empty when neither an organization nor a repository was given.

type Params

type Params struct {
	// Org is the GitHub organization these runners serve, or Repository the one
	// repository as owner/name — exactly one, or neither at generation time,
	// when `github-app create` supplies it alongside the App ids.
	Org        string
	Repository string
	// Provider is the compute backend. Docker, Firecracker and EC2 are all rendered.
	Provider config.ProviderKind
	// Image is the tier's image, handed verbatim to the backend: a container
	// reference for docker, a published guest generation for firecracker. Empty
	// selects the provider-appropriate default.
	Image string
	// VCPU and Memory are what this machine has, detected by the caller, and the
	// generated ceiling is these minus headroom — EXCEPT for ec2, where there is no
	// host to detect and these carry the operator's declared cloud budget, which
	// IS the ceiling (no headroom is withheld from a machine that does not exist).
	VCPU   int
	Memory config.ByteSize

	// RunnerGroup and Workflows are the trusted-pool policy. REQUIRED for Docker,
	// which shares the host kernel and so refuses any workload that is not trusted
	// — and a trusted pool needs a non-default runner group and an exact workflow
	// allowlist. Empty here is not "untrusted by default": for Docker that is a
	// config that loads and then refuses its first job, so it is refused up front.
	RunnerGroup string
	Workflows   []string

	// Tart carries the Apple-silicon inputs, set only when Provider is
	// ProviderTart: which guest kinds this Mac serves, its name in the deployment,
	// and the image each guest kind boots. There is nothing to fetch and nothing
	// to detect — a Mac's capacity is measured like any other host-run backend —
	// so unlike EC2Params this is entirely what the operator asked for.
	Tart *TartParams

	// CodeBuild carries the CodeBuild placement inputs, set only when Provider is
	// ProviderCodeBuild. Like EC2Params it is entirely declared: there is no
	// machine to measure and no API that reports what a compute type holds, so
	// the shapes arrive already carrying their vcpu, memory and audited price.
	CodeBuild *CodeBuildParams

	// EC2 carries the cloud-backend placement inputs, set only when Provider is
	// ProviderEC2. Its Shapes must arrive fully populated (type, vcpu, memory,
	// price) — resolved by the caller from a live fetch or explicit flags — because
	// billet ships no table of EC2 shapes and a shape smaller than the lease chosen
	// for it overcommits a host nobody can see.
	EC2 *EC2Params

	// State selects where the control-plane LEDGER lives. Nil writes the
	// `state_dir` shorthand, which is what almost every generation wants; set it
	// to render `identity_dir` plus an explicit `state:` block instead. The two
	// spellings are mutually exclusive at load, so this is a choice the generator
	// has to make rather than a value it can add beside the default.
	State *StateParams

	// Profile selects the path shape (see Profile). Empty means ProfileLocal.
	Profile Profile
	// GOOS is the platform whose SERVICE shape a local-service generation is
	// for. Empty means this machine's, which is what a real run wants; a caller
	// generating for another host — or a test asserting both shapes on one
	// machine — names it.
	//
	// THE SERVICE SHAPE ONLY. The user-session shape's state and key paths come
	// from os.UserConfigDir() of the process running this, so they describe THIS
	// machine whatever is named here — there is no target user directory to ask
	// about. tartTargetPlatform is where that distinction decides something.
	GOOS string
	// Host carries the values a host must provide that billet cannot detect. See
	// HostInputs; every one is optional and omitting all of them generates
	// exactly what this package generated before they existed.
	Host HostInputs

	// Listen is the loopback address the server binds and the node dials — ONE
	// value for both, because they must agree or the node dials a listener that
	// does not exist. Empty means DefaultListen. A non-loopback address is
	// refused: a local profile's whole guarantee is that nothing is exposed to
	// the network, and a control plane other machines reach is configured with
	// `billet ca issue` and a hand-written node section instead.
	Listen string
	// contains filtered or unexported fields
}

Params is everything `billet init` decided or was told, for one machine.

func (Params) ServerStateDir

func (p Params) ServerStateDir() string

ServerStateDir is where a generation with these parameters points its control-plane state — what the deployment-identity refusal compares.

func (Params) TargetPath added in v0.10.0

func (p Params) TargetPath() string

TargetPath is the GitHub path these runners serve, or empty when neither an organization nor a repository was given.

type Profile

type Profile string

Profile selects the path shape a generated config is written for.

TWO LOCAL SHAPES, NOT ONE WITH A FLAG PER PATH: the paths stand or fall together. A config whose state lives under $UserConfigDir cannot run under the packaged systemd units (ProtectHome=true makes the home directory unreadable, and the units pin StateDirectory=/var/lib/billet/*), and a config rooted in /var/lib/billet is the wrong shape for a two-terminal trial run as an ordinary user. Mixing them produces a config that starts in neither world.

const (
	// ProfileLocal is the user-session shape: everything under the user config
	// dir, run manually in two terminals. The default, and what `billet init`
	// always generated.
	ProfileLocal Profile = "local"
	// ProfileLocalService is the system-service shape the services billet ships
	// run, and its CONTENTS differ by platform. On Linux: config and App key
	// under /etc/billet, state under /var/lib/billet, the node's deployment lock
	// under /run/billet/locks — the units' own RuntimeDirectory. On macOS the
	// same shape lives under /usr/local, because launchd performs no variable
	// substitution and every path a launch agent uses is a literal in a file
	// billet ships. Written for `billet local up` on either.
	ProfileLocalService Profile = "local-service"
)

type ReRun

type ReRun int

ReRun is what an init re-run may do to the existing file.

const (
	// WriteBeside: the existing file differs from what this run generates;
	// the fresh generation goes to <path>.new. The default whenever equality
	// cannot be PROVED.
	WriteBeside ReRun = iota
	// Regenerate: the existing file is byte-equivalent (canonicalized, App
	// identity aside) to what this run would write, so replacing it loses
	// nothing — not even a comment.
	Regenerate
)

func PlanReRun

func PlanReRun(existing []byte, fresh string) ReRun

PlanReRun decides which move an init re-run may make, comparing the existing file's contents against the fresh generation for this run's parameters.

type StateParams

type StateParams struct {
	// Backend is the engine the ledger lives in.
	Backend config.StateBackend
	// DSNEnv names the environment variable holding the PostgreSQL connection
	// string. Required for the postgres backend and refused for any other, because
	// billet reads the DSN from the environment rather than from the file: it
	// carries a password, and a secret written into a config ends up in a backup.
	DSNEnv string
}

StateParams selects where a generated control plane keeps its LEDGER.

NIL MEANS THE SHORTHAND, which is what almost every generated file wants: `server.state_dir`, one directory holding the ledger, the deployment identity, the node-wire CA, the process lock and the maintenance fence. Set it and the generation writes `identity_dir` plus an explicit `state:` block instead — which is not a decoration, because the two spellings are MUTUALLY EXCLUSIVE at load and a file carrying both is refused.

type TartParams

type TartParams struct {
	// GuestOS are the guest kinds this Mac serves. Rendered in a CANONICAL order
	// (macOS first) rather than the order they arrived, so two spellings of the
	// same request generate identical bytes — `billet init` re-run against its own
	// output converges on a byte comparison, and an order-sensitive catalogue
	// would make that depend on how the flags were typed.
	//
	// macOS first is also the allocation order, and that is not arbitrary: a macOS
	// guest has a hard floor Apple's hypervisor enforces and a per-machine licence
	// cap, so it is the constrained one. Fitting it first means the flag order
	// selects what is generated without also deciding what fits.
	GuestOS []config.GuestOS

	// NodeName is what this Mac calls itself in the deployment.
	//
	// REQUIRED when GuestOS includes macOS, because a macOS tier must pin a host —
	// Apple's limit is two concurrent guests per PHYSICAL machine, so billet has
	// to know which machine to count a guest against — and a pin can only name
	// what the node calls itself. Empty is legal for a linux-only generation,
	// where the node takes its hostname like any other host-run backend.
	NodeName string

	// MacOSImage and LinuxImage override the measured defaults above.
	//
	// TWO FIELDS RATHER THAN Params.Image, because one image is not a coherent
	// idea for a backend that boots two operating systems: a macOS generation and
	// an arm64 Linux generation name different artifacts and no single string can
	// be both. Same reason a multi-provider tier writes launch.<provider>.image
	// instead of image.
	MacOSImage, LinuxImage string
}

TartParams is what an Apple-silicon config needs and billet cannot detect.

Jump to

Keyboard shortcuts

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