kit

package
v0.2026187.2153 Latest Latest
Warning

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

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package kit is the importable contract a HOST-COUPLED plugin candy implements to run against charly's live check engine — the seam that lets a check verb whose logic needs the running deployment (exec-in-container, host TCP dial, host-vantage HTTP) live in its own candy module instead of charly's module.

A host-coupled verb candy implements CheckVerbProvider; charly runs it in EITHER placement, invisibly above the registry: IN-PROCESS (compiled-in — charly passes the live *Runner as a CheckContext) OR OUT-OF-PROCESS (the CheckContext legs are served back to the candy over the host's reverse channel — ExecutorService for Exec + CheckContextService for HTTPDo/AddBackground, F2 — and the scalar legs ride the env_json snapshot). RunVerb is identical in both. This package imports only the stdlib + charly/spec (the generated param/Op types), so a candy module can import it without pulling charly's package main.

Index

Constants

View Source
const (
	UnifiedFileName = "charly.yml" // the ONE box/candy manifest filename
	DefaultBoxDir   = "box"        // discovered box/<name>/ directory
	DefaultCandyDir = "candy"      // discovered candy/<name>/ directory
)

Project path constants shared by core and kit. Core aliases each via `const X = kit.X`.

View Source
const (
	KwRun        spec.StepKeyword = "run"
	KwCheck      spec.StepKeyword = "check"
	KwAgentRun   spec.StepKeyword = "agent-run"
	KwAgentCheck spec.StepKeyword = "agent-check"
	KwInclude    spec.StepKeyword = "include"
)
View Source
const DeployConfigEnv = "CHARLY_DEPLOY_CONFIG"

DeployConfigEnv overrides the per-host deploy-config PATH. A check bed sets it so a disposable run never touches the operator's real ~/.config/charly/charly.yml.

View Source
const LedgerSchemaVersion = "2026.161.1649"

LedgerSchemaVersion is the install-ledger record format version, DECOUPLED from the project schema HEAD so a non-ledger cutover never invalidates a migrated ledger. Read by core's ledger path (ReadDeployRecord/ReadCandyRecord, which hard-reject a record lacking this stamp). Core aliases via `const … = kit.LedgerSchemaVersion`.

Variables

View Source
var ErrLockBusy = errors.New("file lock held by another process")

ErrLockBusy is returned by a NON-blocking AcquireFileLock when another holder already owns the lock. Callers detect it with errors.Is to render a precise "already in progress" message.

Functions

func AcquireFileLock

func AcquireFileLock(path string, blocking bool) (release func() error, err error)

AcquireFileLock takes an advisory flock on path (creating the file + parent dirs on demand) and returns a release closure that unlocks + closes.

blocking selects the contention behavior:

  • true → LOCK_EX: wait until the lock is free (serialize, never fail).
  • false → LOCK_EX|LOCK_NB: return ErrLockBusy immediately when another holder exists.

The lock file is deliberately NOT unlinked on release (unlinking a held lock races a waiter that already opened the prior inode). flock is per-open-file-description, so two acquires of the same path — even within ONE process — contend, which the duplicate-run guard relies on.

func BuildArchExports

func BuildArchExports() string

BuildArchExports emits the BUILD_ARCH=$(uname -m) + ARCH=<buildkit-triplet> shell preamble so a cmd:/download: body can template ${ARCH}/${BUILD_ARCH} at deploy-time the same way the container build gets them from BuildKit's TARGETARCH.

func BuilderCollectContext

func BuilderCollectContext(word string, in spec.BuilderCollectInput) map[string]any

BuilderCollectContext returns the builder-specific stage-context keys for `word` given the host-supplied candy descriptor. An unknown word returns nil (a custom candy builder with no plugin keeps base-only context — the host never invokes a plugin for it).

func BuilderResolve

func BuilderResolve(word string, in spec.BuilderResolveInput) (spec.BuilderResolveReply, error)

BuilderResolve renders `word`'s build-time multi-stage from the host-supplied context, returning the pieces the host splices into the Containerfile: Stage (pre-main-FROM), CopyArtifacts + CopyBinary (post-main-FROM), or InlineFragment (in-candy, inline builders). An unknown word is a LOUD error (never a silent empty stage). This is the ONE render both the box-build plugin OpResolve and the in-proc pod-overlay build-emit call (R3).

func BuilderReverse

func BuilderReverse(word string, in spec.BuilderReverseInput) []spec.ReverseOp

BuilderReverse returns the teardown ops for `word` given its resolved stage context (the BuilderCollectContext output the host stored on the BuilderStep). An unknown word, or a context missing the keys a builder needs, returns nil (no teardown — the same best-effort the in-proc builders had).

func DecodeInput

func DecodeInput(in map[string]any, out any)

DecodeInput decodes an Op's plugin_input (map[string]any) into a candy's CUE-generated typed params struct via a JSON round-trip. A nil/empty input leaves out at its zero value; the host has already validated the input against the served schema.

func DefaultDeployConfigPath added in v0.2026186.124

func DefaultDeployConfigPath() (string, error)

DefaultDeployConfigPath returns the per-host deploy overlay file (~/.config/charly/charly.yml), honoring the DeployConfigEnv override.

func DirExists

func DirExists(path string) bool

DirExists reports whether path exists and is a directory.

func EnsureCharlyInGuest added in v0.2026186.739

func EnsureCharlyInGuest(ctx context.Context, ssh SSHArgs, charlyBin, hostVer, strategyRaw string) (string, error)

EnsureCharlyInGuest is the vm-deploy PrepareVenue-time coordinator: it layers the cloud-init charly_install.strategy over a HOST-SURFACE delivery (os/exec ssh/scp against the managed alias), because at PrepareVenue time the reverse channel has NOT yet served a guest executor. auto/scp: deliver the host charly when the guest's is absent/older; skip: verify presence only.

func EnsureCharlyInVenue added in v0.2026186.739

func EnsureCharlyInVenue(ctx context.Context, ve DeployExecutor, charlyBin, hostVer string) (string, error)

EnsureCharlyInVenue is the GENERIC copy-in over the DeployExecutor abstraction (container podman-cp / local cp / the reverse-channel guest executor) — used by the deploy walk + nested-pod delegation. The venue's PATH charly is used when at least as new as the host (NEVER shadowed/downgraded); otherwise charlyBin (the HOST charly, guaranteed current + from-box-capable, fed from HostEnv.CharlyBin) is delivered to /tmp/charly-<hostVer> (outside $PATH, invoked by explicit path).

func EnsureSshConfigInclude added in v0.2026186.739

func EnsureSshConfigInclude(home string) error

EnsureSshConfigInclude inserts the managed `Include ~/.config/charly/ssh_config` directive at the TOP of the user's ~/.ssh/config (creating it if needed) — the Include MUST be outside any Host block (ssh_config(5) lexical scoping), so it is PREPENDED. Idempotent.

func EnvdDir

func EnvdDir(home string) string

EnvdDir returns the directory where per-candy env files live under a home.

func EnvdFilePath

func EnvdFilePath(home, candyName string) string

EnvdFilePath returns the env file path for a given candy under a home.

func FileExists

func FileExists(path string) bool

FileExists reports whether path exists and is a regular (non-dir) file.

func FindMappingValue

func FindMappingValue(m *yaml.Node, key string) *yaml.Node

FindMappingValue returns the value node for key in a YAML mapping node, or nil. (Like MapValue, but requires the key node to be a scalar — the form the migration transforms + the core loader's legacy-shape detection both use.)

func FirstNonEmpty

func FirstNonEmpty(vals ...string) string

FirstNonEmpty returns the first non-empty string in vals, or "".

func FirstYAMLVersionLine

func FirstYAMLVersionLine(data []byte) string

FirstYAMLVersionLine extracts the value of the first top-level `version:` line.

func HostCharlyIsNewer added in v0.2026186.739

func HostCharlyIsNewer(hostVer, venueVerOut string) bool

HostCharlyIsNewer reports whether the host charly (hostVer) is STRICTLY newer than a venue's charly (venueVerOut = raw `charly version` stdout). The single CalVer arbiter (R3): a venue version that is unparseable/absent → host wins (treated as older); both parse → strict CalVer compare (host newer → true, venue equal-or-newer → false, never downgraded); host unparseable → false (never clobber a venue charly on an unprovable claim).

func InputStr added in v0.2026187.747

func InputStr(c *spec.Op, key string) string

InputStr reads a string field from the step's desugared plugin input map — the per-verb fields live there since the schema-compaction cutover (the `<word>: <input>` sugar desugars to Plugin/PluginInput; core #Op carries no per-verb fields).

func IsGitSubmoduleDir

func IsGitSubmoduleDir(p, root string) bool

IsGitSubmoduleDir reports whether p (≠ root) contains a .git entry (a nested submodule/repo boundary).

func ListVmSshAliases added in v0.2026186.739

func ListVmSshAliases(home string) ([]string, error)

ListVmSshAliases returns the alias names in the managed fragment, sorted.

func ManagedBlockBody

func ManagedBlockBody(shell ShellKind, home string) string

ManagedBlockBody returns the shell-specific loop that sources the env.d directory under a home. POSIX-sh for bash/zsh, fish syntax for fish.

func ManagedBody added in v0.2026186.739

func ManagedBody(text string) string

ManagedBody returns just the contents between the begin/end fence markers (the global, untagged managed block) in text. Returns "" when the markers are absent.

func MapValue

func MapValue(m *yaml.Node, key string) *yaml.Node

MapValue returns the value node for key in a YAML mapping node, or nil.

func MappingChild added in v0.2026186.1019

func MappingChild(m *yaml.Node, key string) *yaml.Node

MappingChild looks up a key in a mapping node. Returns the value node or nil if missing. yaml mapping nodes store [key, value, key, value, …].

func MappingRoot added in v0.2026186.124

func MappingRoot(n *yaml.Node) *yaml.Node

MappingRoot unwraps a YAML document node to its top-level mapping node (or nil). Shared by charly core (via the mappingRoot alias) and the out-of-module candy/plugin-migrate engine (R3).

func MarkersForTag

func MarkersForTag(marker string) (begin, end string)

MarkersForTag returns the begin/end fence pair for a marker tag. Empty tag → the global-block fence; non-empty → a per-candy fence so multiple candies coexist.

func MigrateCandidateYAMLFiles

func MigrateCandidateYAMLFiles(dir string, treeSubdirs []string) []string

MigrateCandidateYAMLFiles is the ONE candidate-file scanner the multi-document doc-migration steps share AND the core loader's legacy-vocab rejection scan uses: every `.yml`/`.yaml` under each of treeSubdirs (walked recursively, skipping nested git submodules + any `testdata` dir) plus the root-level YAML siblings in dir. Sorted, deduplicated.

func NodeShapedValue

func NodeShapedValue(val *yaml.Node) bool

NodeShapedValue reports whether a mapping node carries a reserved kind word as a key (i.e. it is a name-first node-form value).

func OpUnifyCandidateFiles

func OpUnifyCandidateFiles(dir string) []string

OpUnifyCandidateFiles is the candidate-file set the op/plan-unify migrators AND the core loader's legacy-test-vocab rejection scan walk (candy/ + box/ trees + root siblings).

func ParseTaskMode

func ParseTaskMode(mode string, def uint32) uint32

ParseTaskMode parses a candy task mode string ("0644","0o755") into a uint32 file mode, falling back to def when empty/unparseable.

func PosArtifact

func PosArtifact(c *spec.Op) []string

func PosCommandFields

func PosCommandFields(c *spec.Op) []string

PosCommandFields splits the input `command` into argv slots, prefixed with `--` so kong does not treat embedded -flags as its own (libvirt:guest/exec). For shell metachars use `command: "sh -c '<full>'"`.

func PosKeyNameSplit

func PosKeyNameSplit(c *spec.Op) []string

PosKeyNameSplit splits the input `key` on whitespace (libvirt send-key: "ctrl alt F2" → 3 slots).

func PosLibvirtQmp

func PosLibvirtQmp(c *spec.Op) []string

PosLibvirtQmp emits a QMP method name + optional JSON args (input text = method, input = JSON).

func PosTarget

func PosTarget(c *spec.Op) []string

func PosText

func PosText(c *spec.Op) []string

func RemoveImagesByReference added in v0.2026186.831

func RemoveImagesByReference(engineBin, reference string)

RemoveImagesByReference best-effort removes every local image whose repository BASENAME exactly equals `reference` (e.g. "<deploy>-overlay") via `<engineBin> images … | rmi`. Silent on error (image cleanup is best-effort). engineBin is the resolved engine binary (the host resolves "podman"/"docker"/"auto" and passes the concrete binary — kit does no detection).

The `--filter reference=<reference>` glob is NOT trusted alone: podman lists EVERY repo:tag of any matched image ID, so a base image or a cross-deploy image that shares content (same image ID as the overlay) leaks into the output. rmi'ing those blindly would destroy the base + unrelated deploys' images. So the emitted repo is re-checked in Go and only the EXACT `<reference>` repo is removed.

func RemoveSshConfigInclude added in v0.2026186.739

func RemoveSshConfigInclude(home string) error

RemoveSshConfigInclude removes the managed Include line from ~/.ssh/config. Idempotent.

func RemoveVmSshStanza added in v0.2026186.739

func RemoveVmSshStanza(home string, alias string) (remaining int, err error)

RemoveVmSshStanza drops the named alias and returns the remaining count (0 → the caller also removes the Include). Idempotent.

func RenderDownloadScript

func RenderDownloadScript(op *spec.Op, candyVars map[string]string) string

RenderDownloadScript emits a shell snippet that fetches op.Download to a temp file, optionally extracts it into op.To, then cleans up — honoring the same flags the container build path respects (extract format, strip_components, include, mode, env). candyVars are exported alongside op.Env so a vars: key referenced in the URL resolves at deploy time.

func RenderEnvdBody

func RenderEnvdBody(candyName string, envVars map[string]string, pathAdd []string) string

RenderEnvdBody produces the deterministic, shell-agnostic POSIX-sh fragment for a candy's env vars + PATH additions. Sorted keys guarantee stable output.

func RenderOpCommand

func RenderOpCommand(op *spec.Op, ctxPath string, candyVars map[string]string) (string, bool)

RenderOpCommand turns an op into a shell command suitable for sudo/user execution. It handles every plugin-renderable verb EXCEPT copy (staged via the executor's PutFile) and the act-`plugin:` verb (whose ProvisionActor shell needs the in-proc registry — package main / RunHostStep render those). Returns (cmd, handled): handled=false means the op is a copy or an act-`plugin:` verb the caller must route elsewhere; handled=true with an empty cmd never occurs (every handled verb yields a body).

func ReplaceOrAppendManagedBlock

func ReplaceOrAppendManagedBlock(existing, body, marker string) string

ReplaceOrAppendManagedBlock replaces the begin/end fence pair's body (tagged with marker, empty for the global block) in existing, appending a fresh block at EOF when absent.

func ReplaceOrPrependManagedBlock added in v0.2026186.739

func ReplaceOrPrependManagedBlock(existing, body, marker string) string

ReplaceOrPrependManagedBlock replaces the marker-tagged managed block in place, or PREPENDS it (with a blank-line separator before the rest of the file) when absent. Used for the ssh-config Include line, which MUST be at the TOP of ~/.ssh/config (outside any Host block) — an appended Include would be gated on the last preceding Host stanza's match (ssh_config(5) lexical scoping).

func ResolvePackageName

func ResolvePackageName(pkg string, packageMap map[string]string, distros []string) string

ResolvePackageName picks the correct package name for the running image's distro: if packageMap has a key matching any of the image's distro tags (first match wins — tags are authored most-specific-first, "fedora:43" before "fedora"), that mapping is used; otherwise the bare pkg name. The single cross-distro name resolver shared by the `package` candy's check + act AND the host's step materializer (R3).

func ScalarNode

func ScalarNode(v string) *yaml.Node

ScalarNode builds a string scalar YAML node.

func SetByDotPath added in v0.2026186.1019

func SetByDotPath(path, dotpath, valueYAML string) error

SetByDotPath edits the file at path, navigating into the YAML structure via dotpath (dot-separated keys) and replacing the leaf value with valueYAML (parsed as YAML so callers can pass scalars, lists, or maps). Comments and key order are preserved.

func ShDoubleQuote

func ShDoubleQuote(v string) string

ShDoubleQuote wraps a string in double quotes for a shell context where variable expansion MUST still happen (e.g. download URLs that template ${BUILD_ARCH}). Escapes the metachars that break out of a double-quoted string, but deliberately does NOT escape `$` so authored ${FOO} / $FOO still expand.

func ShDoubleQuotePath

func ShDoubleQuotePath(v string) string

ShDoubleQuotePath escapes a PATH-list value for use INSIDE double quotes, leaving `$` unescaped so `$PATH` expands at sourcing time.

func ShQuoteArg

func ShQuoteArg(v string) string

ShQuoteArg single-quotes an argument for POSIX shell embedding.

func ShQuoteEnv

func ShQuoteEnv(v string) string

ShQuoteEnv single-quotes a value for POSIX sh (env.d export values). Inside single quotes nothing needs escaping except the single quote itself.

func ShellInitFilePath

func ShellInitFilePath(shell ShellKind, home string) string

ShellInitFilePath returns the init file the managed block lands in for each shell under a home.

func ShellQuote

func ShellQuote(s string) string

ShellQuote wraps s in single quotes for safe interpolation into a shell command (the importable analogue of charly's shellSingleQuote). Embedded single quotes are escaped as '\”.

func SortStrings

func SortStrings(s []string)

SortStrings sorts s in place (ascending). A small insertion-free bubble sort, kept identical to the original package-main helper.

func SshConfigPath added in v0.2026186.739

func SshConfigPath(home string) string

SshConfigPath returns ~/.ssh/config for the user's home dir.

func SshFragmentPath added in v0.2026186.739

func SshFragmentPath(home string) string

SshFragmentPath returns ~/.config/charly/ssh_config for the user's home dir.

func StampDescent added in v0.2026187.1533

func StampDescent(node *spec.Deploy)

StampDescent stamps node.Descent from node.Target and recurses into the whole nested (Children) + peer (Members) subtree, so every node the deploy chain can descend into carries a descriptor. Idempotent — re-stamping writes the same value.

func StripManagedBlock

func StripManagedBlock(existing, marker string) string

StripManagedBlock removes the begin/end fence pair (tagged with marker) and its body.

func TaskShellPreamble

func TaskShellPreamble(candyVars, opEnv map[string]string) string

TaskShellPreamble returns the BUILD_ARCH/ARCH exports plus any candy vars + op env (sorted for deterministic output) so cmd: bodies can reference ${ARCH} / ${MY_CANDY_VAR} at deploy-time the same way they do at build-time.

func TrimPreview

func TrimPreview(s string) string

TrimPreview truncates s to a 200-char preview (trailing "…") for compact check-output display.

func VmSshAlias added in v0.2026186.739

func VmSshAlias(vmName string) string

VmSshAlias returns the canonical alias for a VM deployment name ("charly-" namespaced).

func WaitForCloudInit added in v0.2026186.739

func WaitForCloudInit(ctx context.Context, ssh SSHArgs, poll PollFunc) error

WaitForCloudInit polls `sudo cloud-init status` (as root — charly guests have passwordless sudo) until cloud-init reaches an EXPLICIT terminal status (done/error/disabled) over a SURVIVING ssh connection — that is the deterministic signal that first-boot host-key regen finished (sshd stable) AND the seed-package phase (which holds the distro package lock) completed. Ready ONLY on a terminal token: a blank/transient/"running" read keeps polling, so the deploy's first `pacman -Sy` can't race cloud-final.service. Only meaningful for cloud-image sources; skip for bootc with no cidata ISO.

func WaitForPackageLock added in v0.2026186.739

func WaitForPackageLock(ctx context.Context, ssh SSHArgs, poll PollFunc) error

WaitForPackageLock makes the guest package manager READY for the deploy's own pacman/apt/dnf: (1) block until no boot-time package PROCESS runs (the direct R4 sync primitive on the contended lock, process-based so a stale lock can't hang it forever), then (2) clear a STALE lock FILE that a reaped boot-time install left with no holding process (re-checking no-process inside the command keeps the removal safe).

func WaitForSSH added in v0.2026186.739

func WaitForSSH(ctx context.Context, ssh SSHArgs, poll PollFunc) error

WaitForSSH polls `ssh <alias> true` until sshd answers (BINARY/EDGE readiness: refused→up), under the injected poll's bounds. ConnectTimeout=2 bounds each connect; ServerAlive* bound a connected-then-blackholed session; the injected poll's per-attempt context is the never-hang bound.

func WalkPlans

func WalkPlans(ctx context.Context, exec DeployExecutor, plans []spec.InstallPlanView, opts WalkOpts) ([]spec.ReverseOp, error)

WalkPlans executes every plan's steps on the venue and returns the combined teardown ops (plugin-renderable kinds echo the host-computed view.ReverseOps; host-engine kinds return theirs from RunHostStep). The caller folds them into its DeployReply.

func WrapContainerCommand

func WrapContainerCommand(script string) string

WrapContainerCommand guards an in-container command-check script against stdin-consuming subcommands. The runner delivers in-container scripts to the pod shell over a stdin heredoc ("stdin-attached exec"); without this guard the FIRST subcommand that reads stdin — adb shell, ssh, read, cat — consumes the REST of the heredoc (the not-yet-executed script lines), silently truncating the check to its first command. Wrapping the whole script in a brace group with stdin redirected from /dev/null fixes it generically: the shell reads the entire group before executing it (so the heredoc is fully drained by parse time), then runs every subcommand with stdin tied to /dev/null. The host path (a plain `sh -c` argv) is unaffected.

func WriteVmSshStanza added in v0.2026186.739

func WriteVmSshStanza(home string, s VmSshStanza) error

WriteVmSshStanza adds (or replaces) a Host stanza in the managed fragment. Idempotent.

Types

type CalVer

type CalVer struct {
	Year int // calendar year (e.g. 2026)
	Day  int // day of year, 1-366
	HHMM int // hour*100 + minute, 0-2359
}

CalVer is a parsed YYYY.DDD.HHMM calendar version. The same format that ComputeCalVer (core) emits for image tags is, since the 2026-05 schema-versioning cutover, the schema-version stamp carried by every versioned YAML config. The declarative migration table is ordered by CalVer, and the load-time gate compares a file's CalVer against LatestSchemaVersion.

func LatestSchemaVersion

func LatestSchemaVersion() CalVer

LatestSchemaVersion is the HEAD schema CalVer — every current-format versioned file is stamped to it and the load-time gate requires it. Core exposes it via a thin shim of the same name; the in-core migration engine reads it directly.

func MustCalVer

func MustCalVer(s string) CalVer

MustCalVer parses a compile-time-constant CalVer literal, panicking on a malformed value. Used for the CUE-owned HEAD/floor consts (spec.SchemaVersion / spec.SchemaFloor), so a non-canonical literal that slipped past the strict #CanonCalVer CUE gate still fails fast at process start rather than silently mis-ordering the migration table.

func ParseCalVer

func ParseCalVer(s string) (CalVer, bool)

ParseCalVer parses the CANONICAL CalVer string "YYYY.DDD.HHMM" — exactly a 4-digit year, a 3-digit zero-padded day-of-year, and a 4-digit zero-padded HHMM, separated by dots. It is EXTREMELY STRICT and has NO backward compatibility: every component must be the exact width, pure ASCII digits (no sign, no inner whitespace), within range (day 1-366, hour 0-23, minute 0-59). Anything else — the legacy integer "4", a non-padded "2026.45.830", an empty string, junk — returns ok=false. (Surrounding whitespace, a transport artifact of e.g. a `charly version` trailing newline, is trimmed before the format check.)

A false result is exactly what the schema gate and migration runner treat as "older than every real CalVer", so a non-canonical config flows into `charly migrate` and is re-stamped canonical — one clean migration forward.

Because the canonical form is fixed-width zero-padded, a plain alphanumeric (lexicographic) sort of CalVer strings is chronological (see CalVer.Less).

func SchemaFloor

func SchemaFloor() CalVer

SchemaFloor is the oldest schema CalVer `charly migrate` can migrate FROM. A config below it (or with a non-CalVer version) is unmigratable — the engine refuses it with an actionable "predates the supported floor" error rather than blind-stamping a stale body to HEAD.

func (CalVer) Less

func (c CalVer) Less(o CalVer) bool

Less reports whether c is chronologically before o. Because the canonical string form is fixed-width zero-padded, chronological order IS lexicographic order, so this is a plain string comparison.

func (CalVer) String

func (c CalVer) String() string

String renders the canonical CalVer "YYYY.DDD.HHMM" — 4-digit year, 3-digit zero-padded day, 4-digit zero-padded HHMM. This is the ONLY form ParseCalVer accepts, so String∘Parse is the identity and a plain alphanumeric sort of these strings is chronological.

type CharlyInstallStrategy added in v0.2026186.739

type CharlyInstallStrategy string

CharlyInstallStrategy is the resolved charly_install.strategy: "auto" | "scp" | "skip".

const (
	CharlyInstallAuto CharlyInstallStrategy = "auto"
	CharlyInstallScp  CharlyInstallStrategy = "scp"
	CharlyInstallSkip CharlyInstallStrategy = "skip"
)

func ResolveCharlyInstallStrategy added in v0.2026186.739

func ResolveCharlyInstallStrategy(raw string) CharlyInstallStrategy

ResolveCharlyInstallStrategy applies the default to a raw charly_install.strategy string.

type CheckContext

type CheckContext interface {
	// Exec runs commands on the venue (in-container under ModeLive, in a disposable
	// container under ModeBox, or host-side depending on the executor).
	Exec() Executor
	// Mode is the run mode (Live vs Box).
	Mode() RunMode
	// HTTPDo issues an HTTP request from the CHARLY HOST's network namespace, applying
	// the per-request TLS / redirect / CA policy in req, and returns the status, body, and
	// response headers. It REPLACES the former HTTPClient() *http.Client leg: an
	// *http.Client cannot cross a process boundary, so out-of-process the REQUEST crosses
	// (CheckContextService.HTTPDo) and the host dials; in-process the host builds the client
	// and dials directly. The transport-level error is returned as err (a non-2xx is NOT an
	// error — the caller matches resp.Status).
	HTTPDo(ctx context.Context, req HTTPRequest) (HTTPResponse, error)
	// ResolveEndpoint resolves the check target's venue (container / VM / ssh / local) and
	// returns a host-reachable "host:port" address for an in-venue TCP port, opening (and
	// host-side tracking, for teardown after this verb's Invoke) any ssh -L forward a VM/ssh
	// venue needs. An endpoint verb (cdp/vnc/spice/…) declares its in-venue port and dials
	// the returned addr — REPLACING the per-verb host preresolvers: the host owns the
	// venue/podman/go-libvirt machinery the out-of-process plugin lacks. Empty addr with a
	// nil error means "no live venue" (box-mode / no-box) — the verb's own no-endpoint skip
	// then fires; a resolution failure is returned as err.
	ResolveEndpoint(ctx context.Context, port int) (addr string, err error)
	// ResolveGraphicsEndpoint resolves a VM's <graphics type='<kind>'> listener (kind =
	// "vnc" | "spice") to a dialable endpoint, opening (and host-side tracking, for teardown
	// after this verb's Invoke) any ssh -L forward + socket->TCP bridge the venue needs. A
	// graphics verb (vnc/spice) calls it instead of the removed per-verb host preresolver;
	// the host owns the go-libvirt resolution, tunnel, bridge, and credential-store password.
	// GraphicsEndpoint.Skip=true means the deployment declares no graphics device of that kind
	// (an N/A skip). A zero GraphicsEndpoint with a nil error means no live VM context.
	ResolveGraphicsEndpoint(ctx context.Context, kind string) (GraphicsEndpoint, error)
	// DialTimeout is the per-dial ceiling for host-side TCP reachability probes.
	DialTimeout() time.Duration
	// Box / Instance are the deployment's image + instance names (empty under ModeBox).
	Box() string
	Instance() string
	// Distros is the image's distro tag list (e.g. ["fedora:43","fedora"]) for
	// distro-specific package-name resolution.
	Distros() []string
	// AddBackground registers a host-side background process PID with the active plan run
	// so plan teardown reaps it (SIGTERM). A no-op when the engine has no scenario context
	// (a bare-Op run) or pid<=0. Used by a verb that fire-and-forgets a host process
	// (the `command` verb's background path).
	AddBackground(pid int)
}

CheckContext is the live check-engine surface a host-coupled verb's RunVerb consumes. charly's *Runner implements it; a candy reaches the running deployment through it without importing charly's package main.

type CheckVerbProvider

type CheckVerbProvider interface {
	Reserved() string
	RunVerb(ctx context.Context, cc CheckContext, op *spec.Op) Result
}

CheckVerbProvider is the typed in-process contract a host-coupled check-verb candy implements. Reserved() is the verb word; RunVerb runs the probe against the live CheckContext and returns a Result. The authored plugin_input rides op.PluginInput (decode it into the candy's CUE-generated params struct).

type DeployExecutor

type DeployExecutor interface {
	// Venue returns the host executor's stable venue identifier.
	Venue(ctx context.Context) (string, error)
	// RunSystem runs a root (sudo) script on the venue; optsJSON is a marshalled EmitOpts (nil ok).
	RunSystem(ctx context.Context, script string, optsJSON []byte) error
	// RunUser runs an unprivileged script on the venue.
	RunUser(ctx context.Context, script string, optsJSON []byte) error
	// PutFile places content at a path on the venue (ownerRoot → root:root). Binary-safe.
	PutFile(ctx context.Context, remotePath string, content []byte, mode uint32, ownerRoot bool) error
	// GetFile reads a venue file back to the host (asRoot reads via sudo).
	GetFile(ctx context.Context, path string, asRoot bool) ([]byte, error)
	// RunCapture runs a command on the venue, returning stdout/stderr/exit separately.
	RunCapture(ctx context.Context, script string) (stdout, stderr string, exit int, err error)
	// RunHostStep drives a HOST-ENGINE step on the host engine + applies onto the venue,
	// returning the step's recorded reverse ops.
	RunHostStep(ctx context.Context, step spec.InstallStepView, optsJSON []byte) ([]spec.ReverseOp, error)
}

DeployExecutor is the reverse-channel surface WalkPlans drives. charly's plugin SDK *Executor satisfies it structurally (identical method set), so a plugin passes its sdk.Executor straight through — kit need not import the SDK (no cycle).

type Executor

type Executor interface {
	// RunCapture runs a shell command/script on the venue, returning stdout,
	// stderr, the exit code, and any execution error (NOT a non-zero exit — that
	// is reported via the exit code). No root escalation; callers add sudo.
	RunCapture(ctx context.Context, script string) (stdout, stderr string, exit int, err error)
	// Kind classifies the venue: "host" | "container" | "image" | "vm".
	Kind() string
}

Executor is the subset of charly's DeployExecutor a check verb needs: run one command/script on the venue and capture stdout/stderr/exit separately. charly's DeployExecutor satisfies this structurally (RunCapture + Kind have identical signatures), so *Runner.Exec is passed straight through.

type GraphicsEndpoint added in v0.2026187.2153

type GraphicsEndpoint struct {
	Addr        string
	Socket      string
	Password    string
	Skip        bool
	SkipMessage string
}

GraphicsEndpoint is the resolved, dialable VM graphics endpoint a vnc/spice verb gets from CheckContext.ResolveGraphicsEndpoint. Exactly one of Addr / Socket is set (the host bridges a UNIX socket to TCP for a TCP-only client, or forwards a remote listener, before returning). Password is the resolved ticket ("" = no auth). Skip=true (with SkipMessage) means the deployment declares no graphics device of that kind — an N/A skip, not a failure.

type HTTPRequest

type HTTPRequest struct {
	Method            string
	URL               string
	Body              []byte
	Headers           map[string]string
	Timeout           string
	AllowInsecure     bool
	NoFollowRedirects bool
	CAPEM             []byte
}

HTTPRequest is the host-vantage HTTP request a check verb hands cc.HTTPDo. It carries the FULL request plus the per-request policy the host needs to build the client: Timeout is a Go duration string ("" = the engine's base timeout); CAPEM is the resolved CA PEM bytes (a candy reads its authored ca_file host-side and ships the bytes, so the host server needs no filesystem access). Both placements (in-proc + the CheckContextService RPC) consume the SAME struct.

type HTTPResponse

type HTTPResponse struct {
	Status     int
	Body       []byte
	HeaderBlob string
}

HTTPResponse is the result of cc.HTTPDo: the status code, the response body, and the response headers as a pre-formatted "Key: value\n" blob (the host formats once — R3 — preserving multi-value headers the matcher pipeline consumes directly). A transport-level failure is returned as the HTTPDo error, not here.

type MethodSpec

type MethodSpec struct {
	// Path is the `charly check <verb> <method...>` subcommand path.
	Path []string
	// Required names the plugin-input fields that must be set for this method.
	Required []string
	// PosArgs builds the positional args inserted after the image name, before -i.
	PosArgs func(c *spec.Op) []string
	// Artifact marks a state-dependent capture method (screenshot) whose produced
	// file is validated.
	Artifact bool
	// SkipBox = true means the verb targets a cluster/other non-image target, so the
	// usual image/deploy-name positional is NOT inserted.
	SkipBox bool
}

MethodSpec is one method's nested-CLI dispatch spec: the `charly check <verb> <method...>` subcommand path, the required #Op modifiers, the positional-arg builder, and the artifact / skip-box flags. A plugin's method allowlist is a map[string]MethodSpec; fields are exported so a candy module can author it.

type PollCond added in v0.2026186.739

type PollCond func(ctx context.Context) (ready bool, progress float64, err error)

PollCond is a per-tick readiness probe (ready?, progress, err) — the same shape the host poll primitive expects.

type PollFunc added in v0.2026186.739

type PollFunc func(ctx context.Context, cond PollCond) error

PollFunc drives a PollCond to readiness under the caller's readiness-configured bounds. Core and the vm plugin inject one wrapping vmshared's pollUntil + the resolved remote bounds, so kit never imports the readiness/poll subsystem.

type ProvisionActor

type ProvisionActor interface {
	Reserved() string
	RenderProvisionScript(op *spec.Op, distros []string) (script string, ok bool)
}

ProvisionActor is the OPTIONAL second role of a host-coupled verb candy: the do:act renderer for a state-provision verb (kernel_param/mount/user/unix_group/file/command/ service/package), rendering the shell that ENACTS the op under the live init / package manager. It is reached at install COMPILE+EMIT (a `run: {plugin: <verb>}` step → the build-act RUN in emitTasks, and the local/vm deploy act) AND at runtime act. A candy whose verb type implements this ALONGSIDE CheckVerbProvider is registered as a multi-role provider (the host adapter then also satisfies the package-main ProvisionActor). op is the spec.Op (the verb's plugin_input rides op.PluginInput); distros is the image's distro tag list for package-name resolution. Returns (script, ok); ok=false means "no act form for this op" (the host skips/errors per its act path). This is the SHELL-string act role — a verb that instead lowers into a typed InstallPlan step (service/package) additionally needs the kit step contract.

type Result

type Result struct {
	Status        Status
	Message       string
	CapturedValue string // value stashed under `capture:` (recorded only on PASS)
}

Result is a host-coupled verb's verdict. charly converts it to its internal CheckResult (stamping the Op/Verb/timing) at the dispatch boundary.

func Fail

func Fail(msg string) Result

func Failf

func Failf(format string, a ...any) Result

func Pass

func Pass(msg string) Result

Pass / Fail / Skip are the verdict constructors a verb returns; the *f variants take a printf format (mirror charly's passf/failf/skipf).

func Passf

func Passf(format string, a ...any) Result

func Skip

func Skip(msg string) Result

func Skipf

func Skipf(format string, a ...any) Result

type RunMode

type RunMode int

RunMode mirrors charly's RunMode: the mode a check runs under.

const (
	// ModeLive — `charly check live`, against a running container/VM (in-container probes).
	ModeLive RunMode = iota
	// ModeBox — `charly check box`, against a disposable build container.
	ModeBox
)

func (RunMode) String

func (m RunMode) String() string

String renders the mode as "box" / "live" (mirrors charly's runModeName).

type SSHArgs added in v0.2026186.739

type SSHArgs struct {
	User           string
	Host           string
	Port           int
	Args           []string
	ConnectTimeout int
}

SSHArgs is the host-surface ssh/scp coordinates for a managed VM alias.

func (SSHArgs) BaseArgs added in v0.2026186.739

func (a SSHArgs) BaseArgs() []string

BaseArgs builds the common ssh invocation prefix: only the per-call ergonomics (LogLevel, ConnectTimeout) + optional Port + pass-through Args + the "user@host"-or-"host" destination (ssh(1) reads ~/.ssh/config + ssh-agent for keys/host-key checking/identity).

func (SSHArgs) ScpBaseArgs added in v0.2026186.739

func (a SSHArgs) ScpBaseArgs() []string

ScpBaseArgs builds the scp invocation prefix (scp's `-P` is uppercase vs ssh's lowercase `-p`).

type ServicePackagedDesc

type ServicePackagedDesc struct {
	Unit   string
	Enable bool
}

ServicePackagedDesc is the candy-decodable construction input for a service-packaged step: the host materializer adds the op-resolved scope + candy name and keeps the load-bearing Reverse() (disable / restore-enabled / remove-dropin) in package main.

type ShellKind

type ShellKind string

ShellKind classifies the venue's login shell.

const (
	ShellBash ShellKind = "bash"
	ShellZsh  ShellKind = "zsh"
	ShellFish ShellKind = "fish"
)

func DetectShellFromPath

func DetectShellFromPath(shellPath string) ShellKind

DetectShellFromPath maps a $SHELL path (or shell base name) to a ShellKind. Unknown / empty shells default to bash — the POSIX-safest choice.

type Status

type Status int

Status is a check verdict (mirrors charly's CheckStatus ordering).

const (
	StatusPass Status = iota
	StatusFail
	StatusSkip
)

type StepDescriptor

type StepDescriptor struct {
	ServicePackaged *ServicePackagedDesc
	SystemPackages  *SystemPackagesDesc
}

StepDescriptor is the candy-decodable construction input for a TYPED install-plan step (the build/deploy install timeline). Exactly one variant is non-nil; the host materializer rebuilds the real package-main InstallStep from it (computing the package-main-only inputs — scope from op.RunAs+img, candy name — and keeping the load-bearing Reverse() in package main, so the candy never imports an IR type).

type StepKindName

type StepKindName string

StepKindName names the TYPED install-plan step a step-providing verb lowers into. The host maps it to its internal StepKind enum; kept a string so the kit need not import charly's package main.

const (
	// StepKindServicePackaged — the `service` verb (enable a packaged unit; load-bearing reversals).
	StepKindServicePackaged StepKindName = "service-packaged"
	// StepKindSystemPackages — the `package` verb (install system packages).
	StepKindSystemPackages StepKindName = "system-packages"
)

type StepProvider

type StepProvider interface {
	StepKind() StepKindName
	ConstructStepDescriptor(op *spec.Op) StepDescriptor
}

StepProvider is the OPTIONAL third role of a host-coupled verb candy: a verb whose build/deploy ACT lowers into a TYPED install-plan step (service → service-packaged, package → system-packages) rather than a shell (ProvisionActor) or a generic OpStep. StepKind names the target step (static); ConstructStepDescriptor returns the candy-decodable construction inputs for one op. The host wraps a candy implementing this in an adapter that satisfies package-main's TypedStepProvider, materializing the descriptor into the real IR step.

type SystemPackagesDesc

type SystemPackagesDesc struct {
	Package    string
	PackageMap map[string]string
}

SystemPackagesDesc is the candy-decodable construction input for a system-packages step (the `package` verb): the authored package name + per-distro map. The host materializer resolves the cross-distro name (ResolvePackageName against the image's tags), sets the image format + PhaseInstall, and builds the SystemPackagesStep.

type VmSshStanza added in v0.2026186.739

type VmSshStanza struct {
	Alias          string // ssh-config Host alias (e.g. "charly-arch-vm"); unique within the fragment
	Hostname       string // IP/DNS ssh connects to ("127.0.0.1" for user-mode networking)
	Port           int    // host-side port forwarded to the guest's :22
	User           string // guest account ssh logs in as
	IdentityFile   string // absolute private-key path
	KnownHostsFile string // absolute per-VM known_hosts path
}

VmSshStanza captures the fields to render one ssh-config Host stanza for a VM.

type WalkOpts

type WalkOpts struct {
	// Shell overrides the detected venue login shell for the managed-block finalizer.
	Shell ShellKind
	// Home overrides the detected venue home. When empty WalkPlans probes `$HOME`.
	Home string
}

WalkOpts tunes the walk. All fields optional — WalkPlans probes the venue for the shell + home it needs (the env.d managed-block finalizer) when they are not supplied.

Jump to

Keyboard shortcuts

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