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
- Variables
- func AcquireFileLock(path string, blocking bool) (release func() error, err error)
- func BuildArchExports() string
- func BuilderCollectContext(word string, in spec.BuilderCollectInput) map[string]any
- func BuilderResolve(word string, in spec.BuilderResolveInput) (spec.BuilderResolveReply, error)
- func BuilderReverse(word string, in spec.BuilderReverseInput) []spec.ReverseOp
- func DecodeInput(in map[string]any, out any)
- func DefaultDeployConfigPath() (string, error)
- func DirExists(path string) bool
- func EnsureCharlyInGuest(ctx context.Context, ssh SSHArgs, charlyBin, hostVer, strategyRaw string) (string, error)
- func EnsureCharlyInVenue(ctx context.Context, ve DeployExecutor, charlyBin, hostVer string) (string, error)
- func EnsureSshConfigInclude(home string) error
- func EnvdDir(home string) string
- func EnvdFilePath(home, candyName string) string
- func FileExists(path string) bool
- func FindMappingValue(m *yaml.Node, key string) *yaml.Node
- func FirstNonEmpty(vals ...string) string
- func FirstYAMLVersionLine(data []byte) string
- func HostCharlyIsNewer(hostVer, venueVerOut string) bool
- func InputStr(c *spec.Op, key string) string
- func IsGitSubmoduleDir(p, root string) bool
- func ListVmSshAliases(home string) ([]string, error)
- func ManagedBlockBody(shell ShellKind, home string) string
- func ManagedBody(text string) string
- func MapValue(m *yaml.Node, key string) *yaml.Node
- func MappingChild(m *yaml.Node, key string) *yaml.Node
- func MappingRoot(n *yaml.Node) *yaml.Node
- func MarkersForTag(marker string) (begin, end string)
- func MigrateCandidateYAMLFiles(dir string, treeSubdirs []string) []string
- func NodeShapedValue(val *yaml.Node) bool
- func OpUnifyCandidateFiles(dir string) []string
- func ParseTaskMode(mode string, def uint32) uint32
- func PosArtifact(c *spec.Op) []string
- func PosCommandFields(c *spec.Op) []string
- func PosKeyNameSplit(c *spec.Op) []string
- func PosLibvirtQmp(c *spec.Op) []string
- func PosTarget(c *spec.Op) []string
- func PosText(c *spec.Op) []string
- func RemoveImagesByReference(engineBin, reference string)
- func RemoveSshConfigInclude(home string) error
- func RemoveVmSshStanza(home string, alias string) (remaining int, err error)
- func RenderDownloadScript(op *spec.Op, candyVars map[string]string) string
- func RenderEnvdBody(candyName string, envVars map[string]string, pathAdd []string) string
- func RenderOpCommand(op *spec.Op, ctxPath string, candyVars map[string]string) (string, bool)
- func ReplaceOrAppendManagedBlock(existing, body, marker string) string
- func ReplaceOrPrependManagedBlock(existing, body, marker string) string
- func ResolvePackageName(pkg string, packageMap map[string]string, distros []string) string
- func ScalarNode(v string) *yaml.Node
- func SetByDotPath(path, dotpath, valueYAML string) error
- func ShDoubleQuote(v string) string
- func ShDoubleQuotePath(v string) string
- func ShQuoteArg(v string) string
- func ShQuoteEnv(v string) string
- func ShellInitFilePath(shell ShellKind, home string) string
- func ShellQuote(s string) string
- func SortStrings(s []string)
- func SshConfigPath(home string) string
- func SshFragmentPath(home string) string
- func StampDescent(node *spec.Deploy)
- func StripManagedBlock(existing, marker string) string
- func TaskShellPreamble(candyVars, opEnv map[string]string) string
- func TrimPreview(s string) string
- func VmSshAlias(vmName string) string
- func WaitForCloudInit(ctx context.Context, ssh SSHArgs, poll PollFunc) error
- func WaitForPackageLock(ctx context.Context, ssh SSHArgs, poll PollFunc) error
- func WaitForSSH(ctx context.Context, ssh SSHArgs, poll PollFunc) error
- func WalkPlans(ctx context.Context, exec DeployExecutor, plans []spec.InstallPlanView, ...) ([]spec.ReverseOp, error)
- func WrapContainerCommand(script string) string
- func WriteVmSshStanza(home string, s VmSshStanza) error
- type CalVer
- type CharlyInstallStrategy
- type CheckContext
- type CheckVerbProvider
- type DeployExecutor
- type Executor
- type GraphicsEndpoint
- type HTTPRequest
- type HTTPResponse
- type MethodSpec
- type PollCond
- type PollFunc
- type ProvisionActor
- type Result
- type RunMode
- type SSHArgs
- type ServicePackagedDesc
- type ShellKind
- type Status
- type StepDescriptor
- type StepKindName
- type StepProvider
- type SystemPackagesDesc
- type VmSshStanza
- type WalkOpts
Constants ¶
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`.
const ( KwRun spec.StepKeyword = "run" KwCheck spec.StepKeyword = "check" KwAgentRun spec.StepKeyword = "agent-run" KwAgentCheck spec.StepKeyword = "agent-check" KwInclude spec.StepKeyword = "include" )
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.
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 ¶
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 ¶
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 ¶
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
DefaultDeployConfigPath returns the per-host deploy overlay file (~/.config/charly/charly.yml), honoring the DeployConfigEnv override.
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
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 EnvdFilePath ¶
EnvdFilePath returns the env file path for a given candy under a home.
func FileExists ¶
FileExists reports whether path exists and is a regular (non-dir) file.
func FindMappingValue ¶
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 ¶
FirstNonEmpty returns the first non-empty string in vals, or "".
func FirstYAMLVersionLine ¶
FirstYAMLVersionLine extracts the value of the first top-level `version:` line.
func HostCharlyIsNewer ¶ added in v0.2026186.739
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
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 ¶
IsGitSubmoduleDir reports whether p (≠ root) contains a .git entry (a nested submodule/repo boundary).
func ListVmSshAliases ¶ added in v0.2026186.739
ListVmSshAliases returns the alias names in the managed fragment, sorted.
func ManagedBlockBody ¶
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
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 MappingChild ¶ added in v0.2026186.1019
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
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ParseTaskMode parses a candy task mode string ("0644","0o755") into a uint32 file mode, falling back to def when empty/unparseable.
func PosArtifact ¶
func PosCommandFields ¶
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 ¶
PosKeyNameSplit splits the input `key` on whitespace (libvirt send-key: "ctrl alt F2" → 3 slots).
func PosLibvirtQmp ¶
PosLibvirtQmp emits a QMP method name + optional JSON args (input text = method, input = JSON).
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
RemoveSshConfigInclude removes the managed Include line from ~/.ssh/config. Idempotent.
func RemoveVmSshStanza ¶ added in v0.2026186.739
RemoveVmSshStanza drops the named alias and returns the remaining count (0 → the caller also removes the Include). Idempotent.
func RenderDownloadScript ¶
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 ¶
RenderEnvdBody produces the deterministic, shell-agnostic POSIX-sh fragment for a candy's env vars + PATH additions. Sorted keys guarantee stable output.
func RenderOpCommand ¶
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 ¶
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
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 ¶
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 SetByDotPath ¶ added in v0.2026186.1019
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 ¶
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 ¶
ShDoubleQuotePath escapes a PATH-list value for use INSIDE double quotes, leaving `$` unescaped so `$PATH` expands at sourcing time.
func ShQuoteArg ¶
ShQuoteArg single-quotes an argument for POSIX shell embedding.
func ShQuoteEnv ¶
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 ¶
ShellInitFilePath returns the init file the managed block lands in for each shell under a home.
func ShellQuote ¶
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
SshConfigPath returns ~/.ssh/config for the user's home dir.
func SshFragmentPath ¶ added in v0.2026186.739
SshFragmentPath returns ~/.config/charly/ssh_config for the user's home dir.
func StampDescent ¶ added in v0.2026187.1533
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 ¶
StripManagedBlock removes the begin/end fence pair (tagged with marker) and its body.
func TaskShellPreamble ¶
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 ¶
TrimPreview truncates s to a 200-char preview (trailing "…") for compact check-output display.
func VmSshAlias ¶ added in v0.2026186.739
VmSshAlias returns the canonical alias for a VM deployment name ("charly-" namespaced).
func WaitForCloudInit ¶ added in v0.2026186.739
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
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
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 ¶
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 ¶
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 ¶
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 ¶
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.
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 ¶
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
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
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.
type SSHArgs ¶ added in v0.2026186.739
SSHArgs is the host-surface ssh/scp coordinates for a managed VM alias.
func (SSHArgs) BaseArgs ¶ added in v0.2026186.739
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
ScpBaseArgs builds the scp invocation prefix (scp's `-P` is uppercase vs ssh's lowercase `-p`).
type ServicePackagedDesc ¶
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.
func DetectShellFromPath ¶
DetectShellFromPath maps a $SHELL path (or shell base name) to a ShellKind. Unknown / empty shells default to bash — the POSIX-safest choice.
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 ¶
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.