Documentation
¶
Overview ¶
Package config contains the YAML contract consumed by linuxctl.
Index ¶
- func RegisterBundleExpander(fn BundleExpander)
- func Validate(l *Linux) error
- func ValidateEnv(e *Env) error
- type AdditionalDisk
- type BundleExpander
- type Directory
- type DiskLayout
- type Env
- type EnvSpec
- type Firewall
- type FirewallZone
- type Generator
- type Group
- type HTTPVaultReader
- type Hook
- type Hooks
- type HostEntry
- type HostSpec
- type LimitEntry
- type Linux
- type LogicalVolume
- type Metadata
- type Mount
- type Packages
- type PortRule
- type Ref
- type Resolver
- type RootDisk
- type SELinuxConfig
- type SSHConfig
- type Selector
- type ServiceState
- type Spec
- type SysctlEntry
- type User
- type UsersGroups
- type VaultReader
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RegisterBundleExpander ¶
func RegisterBundleExpander(fn BundleExpander)
RegisterBundleExpander wires an expander. pkg/presets calls this from init.
func Validate ¶
Validate runs go-playground/validator on a decoded Linux manifest plus the cross-field uniqueness / referential checks. An empty manifest is considered valid (scaffold behaviour).
func ValidateEnv ¶
ValidateEnv validates an Env plus its resolved linux layer.
Types ¶
type AdditionalDisk ¶
type AdditionalDisk struct {
Device string `yaml:"device,omitempty"`
Role string `yaml:"role,omitempty"`
Tag string `yaml:"tag,omitempty"`
VGName string `yaml:"vg_name" validate:"required"`
LogicalVolumes []LogicalVolume `yaml:"logical_volumes" validate:"min=1,dive"`
}
AdditionalDisk is a non-root disk resolved either by device path or by role tag (which linuxctl looks up in hypervisor.disks).
type BundleExpander ¶
BundleExpander is an optional hook that pkg/presets wires into the loader to expand `bundle_preset` into per-category *_preset fields. The loader stays free of a pkg/presets import to avoid a cycle; pkg/presets calls RegisterBundleExpander() in its init().
type Directory ¶
type Directory struct {
Path string `yaml:"path" validate:"required,startswith=/"`
Owner string `yaml:"owner,omitempty"`
Group string `yaml:"group,omitempty"`
Mode string `yaml:"mode,omitempty" validate:"omitempty,len=4"`
Recursive bool `yaml:"recursive,omitempty"`
}
Directory is a managed filesystem directory.
type DiskLayout ¶
type DiskLayout struct {
Root *RootDisk `yaml:"root,omitempty"`
Additional []AdditionalDisk `yaml:"additional,omitempty" validate:"dive"`
}
DiskLayout describes root + additional-disk storage.
type Env ¶
type Env struct {
Version string `yaml:"version" validate:"required,eq=1"`
Kind string `yaml:"kind" validate:"required,eq=Env"`
Metadata Metadata `yaml:"metadata" validate:"required"`
Extends string `yaml:"extends,omitempty"`
Spec EnvSpec `yaml:"spec" validate:"required"`
Hooks *Hooks `yaml:"hooks,omitempty"`
}
Env mirrors the top-level env.yaml manifest. linuxctl types the Linux layer in detail; Hypervisor / Networks / Cluster stay opaque because they are owned by proxclt / dbx.
func LoadEnv ¶
LoadEnv reads an env.yaml, resolves $ref on spec.linux (relative to the env file's directory), applies `extends` base merging, resolves secret placeholders through the supplied Resolver, and validates. A nil Resolver skips secret resolution.
func (*Env) NodeHostnames ¶
NodeHostnames returns the sorted hostname keys from an Env's Hypervisor spec.
The Hypervisor map is opaque to linuxctl (owned by proxclt / dbx), so this helper walks the map defensively and tolerates the two shapes seen in the wild:
spec.hypervisor.nodes: # inline form
node1: { ... }
node2: { ... }
spec.hypervisor.Value.Nodes: # $ref-resolved form ($Ref[Hypervisor])
node1: { ... }
node2: { ... }
Returns an error if hypervisor is missing or has no nodes — callers rely on this to surface misconfigured env files instead of silently doing nothing.
type EnvSpec ¶
type EnvSpec struct {
Linux Ref[Linux] `yaml:"linux" validate:"required"`
Hypervisor map[string]any `yaml:"hypervisor,omitempty"`
Networks map[string]any `yaml:"networks,omitempty"`
StorageClasses map[string]any `yaml:"storage_classes,omitempty"`
Cluster map[string]any `yaml:"cluster,omitempty"`
Databases []map[string]any `yaml:"databases,omitempty"`
}
EnvSpec holds the layered subsystems. Only Linux is typed by linuxctl.
type Firewall ¶
type Firewall struct {
Enabled bool `yaml:"enabled,omitempty"`
Zones map[string]FirewallZone `yaml:"zones,omitempty"`
}
Firewall is the firewalld / nftables config block.
type FirewallZone ¶
type FirewallZone struct {
Ports []PortRule `yaml:"ports,omitempty" validate:"dive"`
Sources []string `yaml:"sources,omitempty"`
SourcesFromNetwork string `yaml:"sources_from_network,omitempty"`
}
FirewallZone is a single firewalld zone.
type Group ¶
type Group struct {
Name string `yaml:"name" validate:"required"`
GID int `yaml:"gid,omitempty" validate:"omitempty,gte=1000"`
}
Group is a managed POSIX group.
type HTTPVaultReader ¶
HTTPVaultReader is a config.VaultReader that talks to HashiCorp Vault over HTTP. Reads VAULT_ADDR (default https://vault.int.itunified.io) and VAULT_TOKEN from the environment per ADR-0042. Path syntax in placeholders:
${vault:<path>#<field>} → GET v1/<path>, return data.data[<field>]
${vault:<path>} → GET v1/<path>, return data.data as JSON blob
Used by:
- LoadEnv (ssh_keys, password, etc. inline ${vault:…} placeholders)
- MountManager (CIFS credentials_vault dereference)
func NewHTTPVaultReader ¶
func NewHTTPVaultReader() *HTTPVaultReader
NewHTTPVaultReader returns a VaultReader configured from environment. If VAULT_TOKEN is unset, Read calls will return an error pointing at the missing config — but constructing the reader is harmless.
macOS 26+ Local Network Privacy workaround: On macOS 26.3 (Tahoe), ad-hoc-signed Go binaries are silently denied LAN connect() with EHOSTUNREACH — even when curl/ssh/python all succeed against the same target. The kernel masks the privacy denial as a routing error. To work around this, set:
LINUXCTL_TUNNEL_SSH=user@host[:port] # SSH bastion LINUXCTL_TUNNEL_SSH_KEY=~/.ssh/key # optional
When set, NewHTTPVaultReader spawns `/usr/bin/ssh -L localPort:vaultHost:vaultPort` and rewrites the Vault address to 127.0.0.1:localPort. SSH is notarized + entitled, so it reaches the LAN; localhost is exempt from the privacy gate.
On Linux + CI (no env vars set) this is a no-op — direct HTTPS as before.
See linuxctl#65 / proxctl#70 / itunified-io/infrastructure#576.
type Hook ¶
type Hook struct {
Type string `yaml:"type" validate:"required"`
Params map[string]any `yaml:",inline"`
}
Hook is a single hook definition.
type Hooks ¶
type Hooks struct {
OnApplyStart []Hook `yaml:"on_apply_start,omitempty"`
OnApplySuccess []Hook `yaml:"on_apply_success,omitempty"`
OnApplyFailure []Hook `yaml:"on_apply_failure,omitempty"`
}
Hooks are subset-mirror of proxclt hooks — shape preserved for cross-tool compatibility.
type HostEntry ¶
type HostEntry struct {
IP string `yaml:"ip" validate:"required,ip"`
Names []string `yaml:"names" validate:"required,min=1"`
}
HostEntry is a single /etc/hosts line.
type HostSpec ¶
HostSpec is retained for backward compatibility with the scaffold layout — a manifest may pin per-host selectors and carry a host-scoped Spec override.
type LimitEntry ¶
type LimitEntry struct {
User string `yaml:"user" validate:"required"`
Type string `yaml:"type" validate:"required,oneof=soft hard"`
Item string `yaml:"item" validate:"required"`
Value string `yaml:"value" validate:"required"`
}
LimitEntry is a single /etc/security/limits.d entry.
type Linux ¶
type Linux struct {
Kind string `yaml:"kind" validate:"required,eq=Linux"`
APIVersion string `yaml:"apiVersion,omitempty"`
Hosts []HostSpec `yaml:"-"` // populated via custom UnmarshalYAML
HostsByName map[string]Spec `yaml:"-"` // populated via custom UnmarshalYAML (proxctl-style)
DiskLayout *DiskLayout `yaml:"disk_layout,omitempty"`
UsersGroups *UsersGroups `yaml:"users_groups,omitempty"`
Directories []Directory `yaml:"directories,omitempty" validate:"dive"`
Mounts []Mount `yaml:"mounts,omitempty" validate:"dive"`
Packages *Packages `yaml:"packages,omitempty"`
Sysctl []SysctlEntry `yaml:"sysctl,omitempty" validate:"dive"`
SysctlPreset string `yaml:"sysctl_preset,omitempty"`
Limits []LimitEntry `yaml:"limits,omitempty" validate:"dive"`
LimitsPreset string `yaml:"limits_preset,omitempty"`
// DirectoriesPreset, UsersGroupsPreset, PackagesPreset, BundlePreset:
// plan 033 conventions library. Resolved at config-load time (bundle)
// and per-manager Plan() (individual *_preset fields).
DirectoriesPreset string `yaml:"directories_preset,omitempty"`
UsersGroupsPreset string `yaml:"users_groups_preset,omitempty"`
PackagesPreset string `yaml:"packages_preset,omitempty"`
BundlePreset string `yaml:"bundle_preset,omitempty"`
Firewall *Firewall `yaml:"firewall,omitempty"`
HostsEntries []HostEntry `yaml:"hosts_entries,omitempty" validate:"dive"`
Services []ServiceState `yaml:"services,omitempty" validate:"dive"`
SSHConfig *SSHConfig `yaml:"ssh,omitempty"`
SELinux *SELinuxConfig `yaml:"selinux,omitempty"`
}
Linux is the top-level linux.yaml manifest — the typed Linux layer consumed by the 13 managers.
`hosts:` accepts two shapes via custom UnmarshalYAML (issue #48):
Legacy selector array — populates `Hosts []HostSpec`: hosts: - selector: { role: [db] } spec: { packages: { install: [chrony] } }
proxctl-style map keyed by hostname — populates `HostsByName`: hosts: dbx01: packages: { install: [docker-ce, chrony] }
`EffectiveSpec(hostname)` returns the resolved Spec for a given host: when the hostname matches a `HostsByName` key, the host's spec is layered over the top-level Linux fields; otherwise top-level Linux is returned as-is.
func LoadLinux ¶
LoadLinux reads and decodes a linux.yaml from disk without validation. If the manifest declares `bundle_preset:`, the bundle is expanded into the per-category *_preset fields (user overrides always win). An empty path returns an empty Linux (scaffold behaviour).
NOTE: This entry point does NOT resolve ${vault:…} / ${env:…} / ${file:…} placeholders. Callers that need placeholder expansion (i.e. anything that will reach the user / mount / sshd managers in production) MUST use LoadLinuxWithResolver — otherwise placeholders propagate verbatim into authorized_keys, /etc/sudoers.d, /etc/shadow, mount creds files, etc. (linuxctl#63).
func LoadLinuxWithResolver ¶
LoadLinuxWithResolver is LoadLinux + placeholder expansion. A nil resolver behaves identically to LoadLinux (no expansion). Pass a Vault-backed resolver in production to ensure ${vault:…} entries in users_groups, mounts, and ssh authorized_keys are dereferenced before they reach the host.
func (*Linux) EffectiveSpec ¶
EffectiveSpec returns the resolved Spec for a given hostname. When `HostsByName` contains an entry for the hostname, the host's spec is layered over the top-level Linux fields (host wins on overlap). When no host-specific entry exists, the top-level Linux is returned as-is.
On a nil receiver, returns nil. On an empty hostname or empty map, returns the top-level Linux unchanged.
func (*Linux) UnmarshalYAML ¶
UnmarshalYAML implements custom decoding so the `hosts:` key accepts both the legacy `[]HostSpec` (selector array) and the proxctl-style `map[string]Spec` (hostname-keyed map). See issue #48 + linuxctl#48 for rationale.
type LogicalVolume ¶
type LogicalVolume struct {
Name string `yaml:"name" validate:"required"`
MountPoint string `yaml:"mount_point" validate:"required,startswith=/"`
Size string `yaml:"size" validate:"required"`
FS string `yaml:"fs" validate:"required,oneof=xfs ext4 btrfs"`
}
LogicalVolume describes a single LV carved from the parent VG.
type Metadata ¶
type Metadata struct {
Name string `yaml:"name" validate:"required,hostname_rfc1123"`
Domain string `yaml:"domain,omitempty"`
Tags map[string]string `yaml:"tags,omitempty"`
Description string `yaml:"description,omitempty"`
}
Metadata describes the env identity.
type Mount ¶
type Mount struct {
Type string `yaml:"type" validate:"required,oneof=cifs nfs bind tmpfs"`
Source string `yaml:"source,omitempty"`
Server string `yaml:"server,omitempty"`
MountPoint string `yaml:"mount_point" validate:"required,startswith=/"`
Options []string `yaml:"options,omitempty"`
CredentialsVault string `yaml:"credentials_vault,omitempty"`
Persistent bool `yaml:"persistent,omitempty"`
}
Mount describes a persistent or transient mount.
type Packages ¶
type Packages struct {
Install []string `yaml:"install,omitempty"`
Remove []string `yaml:"remove,omitempty"`
EnabledServices []string `yaml:"enabled_services,omitempty"`
DisabledServices []string `yaml:"disabled_services,omitempty"`
}
Packages describes install / remove / service-state package operations.
type PortRule ¶
type PortRule struct {
Name string `yaml:"name,omitempty"`
Port int `yaml:"port,omitempty"`
Range string `yaml:"port_range,omitempty"`
Proto string `yaml:"proto" validate:"required,oneof=tcp udp"`
}
PortRule is a single accepted port.
type Ref ¶
type Ref[T any] struct { Ref string `yaml:"$ref,omitempty"` Value *T `yaml:"-"` Inline *T `yaml:"-"` }
Ref is either an inline value or a $ref to a file. After loader.Resolve, Value points at the effective value.
type Resolver ¶
type Resolver struct {
Vault VaultReader
Gen Generator
Refs map[string]string // reserved for cross-document refs
BaseDir string // used to resolve relative ${file:…} paths
}
Resolver resolves ${env:…} / ${file:…} / ${vault:…} / ${gen:…} / ${ref:…} placeholders inside string values.
func NewResolver ¶
func NewResolver() *Resolver
NewResolver returns a resolver without external backends (env + file only).
type RootDisk ¶
type RootDisk struct {
Device string `yaml:"device" validate:"required"`
VGName string `yaml:"vg_name,omitempty"`
LogicalVolumes []LogicalVolume `yaml:"logical_volumes,omitempty" validate:"dive"`
}
RootDisk describes the root disk / root VG.
type SELinuxConfig ¶
type SELinuxConfig struct {
Mode string `yaml:"mode,omitempty" validate:"omitempty,oneof=enforcing permissive disabled"`
Booleans map[string]bool `yaml:"booleans,omitempty"`
}
SELinuxConfig captures SELinux mode + boolean overrides.
type SSHConfig ¶
type SSHConfig struct {
AuthorizedKeys map[string][]string `yaml:"authorized_keys,omitempty"`
SSHDConfig map[string]string `yaml:"sshd_config,omitempty"`
}
SSHConfig captures authorized-key drift + sshd_config keys.
type Selector ¶
type Selector struct {
Role []string `yaml:"role,omitempty"`
Distro []string `yaml:"distro,omitempty"`
}
Selector joins against cluster.yaml roles / distros.
type ServiceState ¶
type ServiceState struct {
Name string `yaml:"name" validate:"required"`
Enabled bool `yaml:"enabled,omitempty"`
State string `yaml:"state,omitempty" validate:"omitempty,oneof=running stopped"`
}
ServiceState is a desired systemd service state.
type Spec ¶
type Spec = Linux
Spec is the host-scoped desired state. Alias over Linux so both the top-level manifest and per-host overrides share one schema.
type SysctlEntry ¶
type SysctlEntry struct {
Key string `yaml:"key" validate:"required"`
Value string `yaml:"value" validate:"required"`
}
SysctlEntry is a single sysctl key/value pair.
type User ¶
type User struct {
Name string `yaml:"name" validate:"required"`
UID int `yaml:"uid,omitempty" validate:"omitempty,gte=1000"`
GID string `yaml:"gid,omitempty"`
Groups []string `yaml:"groups,omitempty"`
Home string `yaml:"home,omitempty"`
Shell string `yaml:"shell,omitempty"`
SSHKeys []string `yaml:"ssh_keys,omitempty"`
Password string `yaml:"password,omitempty"`
// Sudo controls whether a /etc/sudoers.d/<name> rule is dropped for the
// user. Values: "" (no rule), "NOPASSWD" (passwordless), "PASSWD"
// (require password). Default empty.
Sudo string `yaml:"sudo,omitempty" validate:"omitempty,oneof=NOPASSWD PASSWD"`
}
User is a managed POSIX user.
type UsersGroups ¶
type UsersGroups struct {
Groups []Group `yaml:"groups,omitempty" validate:"dive"`
Users []User `yaml:"users,omitempty" validate:"dive"`
}
UsersGroups declares managed groups + users.
type VaultReader ¶
VaultReader is the hook an outer caller provides to resolve ${vault:…}. The default Resolver leaves vault placeholders untouched if Reader is nil.