Documentation
¶
Overview ¶
Package usb generates the artifacts needed to boot a bare-metal laptop off a Ventoy USB stick and have it self-provision a K3s node that joins an existing devx-managed cluster.
The package is organized around a single source of truth — the canonical join script produced by RenderJoinScript from a JoinSpec. Every OS renderer (FCOS/Ignition, Ubuntu/cloud-init, prebaked image) only re-packages that same script for its boot medium, so the join *logic* lives in exactly one place.
Index ¶
- Constants
- func RenderJoinScript(s JoinSpec) (string, error)
- func RendererNames() []string
- func VentoyJSON(entries []BootEntry) ([]byte, error)
- type Artifact
- type ArtifactSink
- type BakedRenderer
- type BootEntry
- type BuildOptions
- type BuildResult
- type Coordinates
- type DirSink
- type FCOSRenderer
- type Injection
- type InjectionKind
- type JoinSpec
- type Mode
- type PlanEntry
- type PruneOptions
- type PruneResult
- type Renderer
- type Role
- type ScratchConfig
- type ScratchStrategy
- type UbuntuRenderer
Constants ¶
const BakedImage = "devx-node.x86_64.img"
BakedImage is the prebuilt, self-contained node image the baked entries boot. It already contains k3s + Tailscale + a generic devx-join unit; only the per-cluster secrets (the join script) live on the separate config partition, so the image itself carries no credentials. Built by CI / a future `devx cluster usb bake`.
const EphemeralLabel = "devx.io/ephemeral"
EphemeralLabel marks nodes that joined via an ephemeral live-boot. The prune reaper uses it to distinguish disposable nodes from permanent ones.
const FCOSImage = "fedora-coreos-live.x86_64.iso"
FCOSImage is the Fedora CoreOS live ISO filename the FCOS boot entries expect on the Ventoy stick. The operator supplies the actual ISO (it is hundreds of MB and not vendored).
const UbuntuImage = "ubuntu-24.04-live-server-amd64.iso"
UbuntuImage is the Ubuntu live-server ISO the Ubuntu boot entries expect. The server ISO has first-class cloud-init/autoinstall support and the broadest laptop Wi-Fi/hardware coverage, making it the Wi-Fi-friendly renderer.
Variables ¶
This section is empty.
Functions ¶
func RenderJoinScript ¶
RenderJoinScript produces the canonical first-boot join script. The script is deterministic for a given spec (golden-tested) and OS-agnostic — renderers drop it onto their boot medium and arrange to run it once at first boot.
func RendererNames ¶
func RendererNames() []string
RendererNames returns the built-in renderer names, sorted.
func VentoyJSON ¶
VentoyJSON renders a deterministic ventoy.json from the boot entries. It aliases each base image and, for cloud-init/autoinstall entries, registers their templates so Ventoy offers the ephemeral/install choice at boot.
Note: ventoy.json is a field-tunable starting point. FCOS Ignition is delivered by embedding it into the live ISO (coreos-installer iso ignition embed) at stick-prep time, so it needs no Ventoy plugin entry.
Types ¶
type Artifact ¶
type Artifact struct {
Path string // relative path under the output root (forward slashes)
Contents []byte // file contents
Mode fs.FileMode // 0 means 0644
}
Artifact is a single file a renderer wants written under the staging root.
type ArtifactSink ¶
ArtifactSink receives the files a renderer produces.
type BakedRenderer ¶
type BakedRenderer struct{}
BakedRenderer separates a prebuilt OS image (with k3s+Tailscale baked in) from the per-cluster secrets, which it emits onto a small FAT config partition the image reads at boot. Fastest/most offline-robust boot at the cost of a build pipeline for the image.
func (BakedRenderer) Render ¶
func (BakedRenderer) Render(spec JoinSpec, m Mode, sink ArtifactSink) ([]BootEntry, error)
Render implements Renderer. It emits the config-partition payload: the join script and a mode flag the baked image's generic unit reads at boot. No credentials are in the image — only here.
type BootEntry ¶
type BootEntry struct {
MenuTitle string // shown in the Ventoy boot menu
BaseImage string // ISO/IMG filename this entry boots (operator supplies)
Renderer string // provenance: renderer Name()
Mode Mode // provenance: lifecycle mode
InstallToDisk bool // true if this entry persists the OS to the internal disk
Injection Injection // how the join payload attaches
}
BootEntry is one line in the Ventoy menu plus the metadata the assembler needs to wire its provisioning payload.
type BuildOptions ¶
type BuildOptions struct {
OutputDir string // staging directory for artifacts (required unless DryRun)
Renderers []string // subset of fcos|ubuntu|baked; empty uses config/all
Modes []Mode // subset of ephemeral|install; empty uses both
Role Role // role for install entries (ephemeral always joins as agent)
DryRun bool // plan only; do not resolve coordinates or write files
}
BuildOptions configures a `devx cluster usb build`.
type BuildResult ¶
type BuildResult struct {
OutputDir string `json:"output_dir,omitempty"`
DryRun bool `json:"dry_run"`
Entries []PlanEntry `json:"entries"`
RequiredImages []string `json:"required_images"`
}
BuildResult summarizes a build for human and --json output.
func Build ¶
func Build(ctx context.Context, cfg *config.Config, opts BuildOptions) (*BuildResult, error)
Build resolves the join coordinates, runs the selected renderers across the selected modes into OutputDir, and writes ventoy.json plus an operator manifest. With DryRun it produces only the plan (no cluster access, no files).
type Coordinates ¶
type Coordinates struct {
LANServerURL string
TailnetServerURL string
Token string
K3sVersion string
}
Coordinates are the cluster-derived facts a USB node needs to join: where the API server is (on the LAN and/or the tailnet), the join token, and the k3s version to match.
func ResolveCoordinates ¶
ResolveCoordinates finds the first healthy server node, reads its join token and reachable API endpoints. It mirrors the healthy-server discovery in cluster.Join (kept separate to avoid changing Join's behavior; deduping the two is a noted follow-up).
If cfg.Cluster.USB.AgentToken is set it is preferred over the server node-token, so operators can bake a least-privilege agent token that cannot add control-plane servers.
type DirSink ¶
type DirSink struct {
Root string
}
DirSink is an ArtifactSink that writes artifacts as files under Root.
type FCOSRenderer ¶
type FCOSRenderer struct{}
FCOSRenderer packages the canonical join script as a Fedora CoreOS Ignition config (authored as Butane). This is the highest-reuse renderer — it rides the same FCOS/Ignition/butane stack devx already uses in internal/ignition.
func (FCOSRenderer) Render ¶
func (FCOSRenderer) Render(spec JoinSpec, m Mode, sink ArtifactSink) ([]BootEntry, error)
Render implements Renderer. It emits a Butane file (.bu) that writes the join script and a oneshot systemd unit to run it. The build layer compiles the .bu to Ignition with butane (gated) and embeds it into the live ISO.
type Injection ¶
type Injection struct {
Kind InjectionKind
PayloadPath string // relative path of the payload artifact in the sink
Notes string // operator-facing notes (also surfaced in ventoy.json)
}
Injection describes the provisioning payload attached to one boot entry.
type InjectionKind ¶
type InjectionKind string
InjectionKind identifies how a boot entry's provisioning payload attaches to its boot medium.
const ( // InjectIgnition delivers a Fedora CoreOS Ignition (compiled from Butane). InjectIgnition InjectionKind = "ignition" // InjectCloudInit delivers a cloud-init NoCloud/autoinstall seed. InjectCloudInit InjectionKind = "cloud-init" // InjectConfigPartition delivers a small FAT partition image a prebaked OS // reads at boot. InjectConfigPartition InjectionKind = "config-partition" )
type JoinSpec ¶
type JoinSpec struct {
Role Role
Mode Mode
K3sVersion string // e.g. "v1.30.4+k3s1"; empty installs the k3s default
Pool string // node pool label value
// NodeNamePrefix is combined at boot with the MAC address and hostname to
// produce a name that is unique per laptop and per boot.
NodeNamePrefix string
Token string // K3s join token (prefer a dedicated agent token)
LANServerURL string // e.g. "https://10.0.0.5:6443"; tried first if reachable
TailnetServerURL string // e.g. "https://100.64.0.5:6443"; Tailscale fallback
TailscaleAuthKey string // ephemeral, ACL-tagged; empty disables Tailscale
Ephemeral bool // add the EphemeralLabel and run as a disposable node
Scratch ScratchConfig
ExtraLabels map[string]string
ExtraArgs []string // appended verbatim to the k3s install args
WiFiSSID string // optional; bring up Wi-Fi before joining (for laptops w/o Ethernet)
WiFiPSK string
}
JoinSpec is the fully-resolved description of how one boot entry should join the cluster. It is the input to RenderJoinScript and to every Renderer.
type PlanEntry ¶
type PlanEntry struct {
Renderer string `json:"renderer"`
Mode Mode `json:"mode"`
MenuTitle string `json:"menu_title"`
BaseImage string `json:"base_image"`
Injection string `json:"injection"`
}
PlanEntry is one boot-menu entry in a build result (JSON-friendly).
type PruneOptions ¶
type PruneOptions struct {
TTL time.Duration // remove NotReady ephemeral nodes whose Ready transition is older than this
DryRun bool
}
PruneOptions configures the ephemeral-node reaper.
type PruneResult ¶
type PruneResult struct {
DryRun bool `json:"dry_run"`
Stale []string `json:"stale"` // nodes selected for removal
Removed []string `json:"removed"` // nodes actually drained+deleted
}
PruneResult reports what the reaper found and did (JSON-friendly).
func Prune ¶
func Prune(ctx context.Context, cfg *config.Config, opts PruneOptions) (*PruneResult, error)
Prune removes ephemeral USB nodes that have gone NotReady (e.g. the laptop was unplugged) and stayed that way past the TTL — the disposable-node cleanup that keeps `kubectl get nodes` free of ghosts. It only ever touches nodes carrying the EphemeralLabel, so permanent nodes are never reaped.
type Renderer ¶
type Renderer interface {
Name() string
Modes() []Mode
Render(spec JoinSpec, m Mode, sink ArtifactSink) ([]BootEntry, error)
}
Renderer turns a JoinSpec into boot artifacts for one OS/packaging. Renderers are pure: they only produce in-memory artifacts and never shell out, so they are fully unit-testable. External tooling (butane compile, ISO embedding, USB writes) lives in the build/assemble layer and is gated on tool presence.
func AllRenderers ¶
func AllRenderers() []Renderer
AllRenderers returns the built-in renderers keyed by Name(), in a stable order.
func RendererByName ¶
RendererByName looks up a built-in renderer.
type Role ¶
type Role string
Role is the K3s role a USB-booted node joins as.
const ( // RoleAgent joins as a worker. This is the default and the only safe choice // for ephemeral nodes: they must never become etcd voters. RoleAgent Role = "agent" // RoleServer joins as an additional control-plane (etcd) member. Only valid // for persistent installs. RoleServer Role = "server" )
type ScratchConfig ¶
type ScratchConfig struct {
Strategy ScratchStrategy
Source string // for ScratchExisting: "LABEL=...", "UUID=..." or "/dev/..."
ForceFormat bool // allow formatting an existing partition (destructive)
}
ScratchConfig describes ephemeral on-disk scratch storage.
type ScratchStrategy ¶
type ScratchStrategy string
ScratchStrategy selects how an ephemeral node obtains on-disk scratch space for containerd state and local-path volumes without destroying the operator's existing OS/data.
const ( // ScratchNone keeps everything in RAM (smallest footprint). ScratchNone ScratchStrategy = "none" // ScratchExisting mounts a pre-existing partition the operator designates by // LABEL=, UUID= or /dev/ path. Never formatted unless ForceFormat is set. ScratchExisting ScratchStrategy = "existing" // ScratchFreeSpace carves a new partition from unallocated space only. It // refuses to shrink or touch any existing partition. ScratchFreeSpace ScratchStrategy = "free-space" )
type UbuntuRenderer ¶
type UbuntuRenderer struct{}
UbuntuRenderer packages the canonical join script as a cloud-init NoCloud seed (ephemeral) or a subiquity autoinstall (install). Chosen when broad hardware support matters more than FCOS's immutability.
func (UbuntuRenderer) Render ¶
func (UbuntuRenderer) Render(spec JoinSpec, m Mode, sink ArtifactSink) ([]BootEntry, error)
Render implements Renderer. It emits a NoCloud seed (user-data + meta-data) and the raw join script. The build/assembler layer points the kernel's cloud-init datasource at the seed directory.