config

package
v0.0.0-...-443b9db Latest Latest
Warning

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

Go to latest
Published: May 7, 2026 License: AGPL-3.0 Imports: 18 Imported by: 0

Documentation

Overview

Package config contains the YAML contract consumed by linuxctl.

Index

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

func Validate(l *Linux) error

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

func ValidateEnv(e *Env) error

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

type BundleExpander func(name string) (map[string]string, error)

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

func LoadEnv(path string, r *Resolver) (*Env, error)

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

func (e *Env) NodeHostnames() ([]string, error)

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 Generator

type Generator interface {
	Generate(spec string) (string, error)
}

Generator is the hook for ${gen:…} — e.g. random passwords. Nil = unsupported.

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

type HTTPVaultReader struct {
	Addr  string
	Token string
	// contains filtered or unexported fields
}

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.

func (*HTTPVaultReader) Read

func (r *HTTPVaultReader) Read(expr string) (string, error)

Read implements VaultReader.

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

type HostSpec struct {
	Selector Selector `yaml:"selector"`
	Spec     Spec     `yaml:"spec"`
}

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):

  1. Legacy selector array — populates `Hosts []HostSpec`: hosts: - selector: { role: [db] } spec: { packages: { install: [chrony] } }

  2. 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

func LoadLinux(path string) (*Linux, error)

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

func LoadLinuxWithResolver(path string, r *Resolver) (*Linux, error)

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

func (l *Linux) EffectiveSpec(hostname string) *Spec

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

func (l *Linux) UnmarshalYAML(node *yaml.Node) error

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"`
	Share            string   `yaml:"share,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).

func (*Resolver) Resolve

func (r *Resolver) Resolve(input string) (string, error)

Resolve substitutes every placeholder found in input. Unknown schemes or missing backends return an error.

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

type VaultReader interface {
	Read(path string) (string, error)
}

VaultReader is the hook an outer caller provides to resolve ${vault:…}. The default Resolver leaves vault placeholders untouched if Reader is nil.

Jump to

Keyboard shortcuts

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