Documentation
¶
Overview ¶
Package profile loads and selects modules from a profile.
Index ¶
- Constants
- func DecodeModuleTOML(path string, data []byte, cfg *ModuleConfig) error
- func DecodeModuleTOMLFile(path string, cfg *ModuleConfig) error
- func ModuleDir(root, layer, id string) string
- func ParseModuleFilter(args []string) []string
- type Config
- type Dotfile
- type HookCommand
- type Hooks
- type Module
- type ModuleConfig
- type ModulesConfig
- type MountSpec
- type Packages
- type Profile
- type ShareSpec
- type Skip
- type SmbSpec
- type When
Constants ¶
const ( ScopeUser = "user" ScopeSystem = "system" )
Scope values for ModuleConfig.Scope. Scope is module-level: it decides whether the module's dotfiles are applied as the invoking user (into ~/) or with root privileges (into /etc and other system paths).
const FilterReason = "module filter"
FilterReason is the Skip.Reason stamped on modules excluded by the positional module filter.
Variables ¶
This section is empty.
Functions ¶
func DecodeModuleTOML ¶ added in v0.21.0
func DecodeModuleTOML(path string, data []byte, cfg *ModuleConfig) error
DecodeModuleTOML strictly decodes module.toml contents into cfg: any key not in the ModuleConfig schema is an error naming the file, line, and key — typos like `preset = [...]` under packages must fail loudly at load, never decode silently. path is used for error messages only. The schema is the ModuleConfig struct tree (toml tags); docs/product/profile-layout.md is its human-readable counterpart. Every production module.toml read goes through one of these two functions.
func DecodeModuleTOMLFile ¶ added in v0.21.0
func DecodeModuleTOMLFile(path string, cfg *ModuleConfig) error
DecodeModuleTOMLFile strictly decodes a module.toml into cfg. It is DecodeModuleTOML over the file's contents; see its contract.
func ParseModuleFilter ¶ added in v0.4.0
ParseModuleFilter normalizes positional module-filter args: each arg is split on commas, whitespace is trimmed, empties are dropped, and duplicates collapse keeping first-seen order. Empty input returns nil.
Types ¶
type Config ¶
type Config struct {
Modules ModulesConfig `toml:"modules"`
// Colors overrides the colored-output palette per role (see
// internal/palette). Validated at load: unknown roles and malformed
// SGR values are errors naming the file.
Colors map[string]string `toml:"colors"`
}
Config is the top-level dotdrift.toml configuration.
type Dotfile ¶
type Dotfile struct {
Source string `toml:"source"`
Mode string `toml:"mode"`
Line string `toml:"line"` // edit: ensure exact line exists
Block string `toml:"block"` // edit: marker-delimited block
Comment string `toml:"comment"` // edit: comment prefix for a block (mise default: #)
Template string `toml:"template"` // edit: engine name (e.g. "tera"); requires source
}
Dotfile describes a single managed path. Whole-file entries use Source+Mode (symlink, symlink-each, copy, template). Edit entries are partial edits to a file something else owns, keyed by "<file-path>/<edit-id>" in the [dotfiles] map: a `line` ensures an exact line exists, a `block` wraps content in mise marker delimiters, and a `source`+`template` renders a block via an engine (e.g. "tera"). The syntax mirrors mise's edit-entry vocabulary exactly (see https://mise.jdx.dev/dotfiles.html, "Edit entries").
func (Dotfile) IsEdit ¶ added in v0.17.0
IsEdit reports whether the entry is a partial edit (line/block/template, or mode = "edit" with a source file) rather than a whole-file entry. An empty line/block/template and a mode other than "edit" means the entry is whole-file (an ensure-empty-line edit is nonsense; mise is the backstop).
type HookCommand ¶ added in v0.12.0
HookCommand is a single pre/post hook shell command. It runs from the profile root with the DOTDRIFT_* facts in the environment. Optional = true makes a non-zero exit non-fatal: the failure is logged at warn and the apply step continues, so a flaky/best-effort hook cannot abort the run.
Two TOML spellings decode into the same value: the legacy string array (`pre = ["echo hi"]`, all required) and the structured table array (`[[hooks.pre]] command = "..." optional = true`). UnmarshalText makes the string form work without a custom Hooks decoder.
func (*HookCommand) UnmarshalTOML ¶ added in v0.12.0
func (h *HookCommand) UnmarshalTOML(v any) error
UnmarshalTOML accepts both spellings. The legacy form feeds each array element as a string; the structured form feeds a map with command/optional.
type Hooks ¶ added in v0.2.0
type Hooks struct {
Pre []HookCommand `toml:"pre"`
Post []HookCommand `toml:"post"`
}
Hooks declares pre/post apply shell commands for a module. Unlike packages/tools/dotfiles, hooks are ordered sequences: layers merge by appending base → host → user (see internal/resolve).
type Module ¶
type Module struct {
ID string
App string
Path string
Config ModuleConfig
}
Module is a discovered module with its resolved identity and path.
type ModuleConfig ¶
type ModuleConfig struct {
ID string `toml:"id"`
App string `toml:"app"`
Description string `toml:"description"`
Disabled bool `toml:"disabled"`
Scope string `toml:"scope"`
When When `toml:"when"`
Packages Packages `toml:"packages"`
Tools map[string]string `toml:"tools"`
Dotfiles map[string]Dotfile `toml:"dotfiles"`
Hooks Hooks `toml:"hooks"`
Mounts map[string]MountSpec `toml:"mounts"`
Smb SmbSpec `toml:"smb"`
}
ModuleConfig is the base module.toml configuration.
func LoadModuleConfig ¶
func LoadModuleConfig(dir string) (*ModuleConfig, error)
LoadModuleConfig reads a module.toml from the given directory. It returns the parsed config and the resolved module path, or nil if no module.toml exists.
func (ModuleConfig) ScopeOrDefault ¶ added in v0.2.0
func (c ModuleConfig) ScopeOrDefault() string
ScopeOrDefault returns the module's dotfile scope, defaulting to user when the key is omitted. Validity is not checked here — resolve rejects unknown values loudly.
type ModulesConfig ¶
type ModulesConfig struct {
Disable []string `toml:"disable"`
}
ModulesConfig holds the [modules] table.
type MountSpec ¶ added in v0.2.0
type MountSpec struct {
Source string `toml:"source"`
Destination string `toml:"destination"`
Type string `toml:"type"`
Options []string `toml:"options"`
StartAt string `toml:"startat"`
State string `toml:"state"`
}
MountSpec describes a single filesystem attachment declared in module.toml under [mounts.<name>]. Resolve validates structure only (non-empty source/destination/type, known state); Type is never checked against any registry — the registry lives outside resolve and evolves independently.
type Profile ¶
Profile is the loaded set of modules and selection state.
func Load ¶
Load reads a profile directory, unions dotdrift.toml layers, discovers modules, and runs selection against the provided facts.
func (*Profile) LimitTo ¶ added in v0.4.0
LimitTo restricts the selection to the listed module ids. An empty list is a no-op. Every id must name a discovered module, and the filter never resurrects modules skipped for their own reason (disabled, when filter): naming one is an error carrying that reason. Selected modules not in ids move to Skipped with reason "module filter", preserving order.
type ShareSpec ¶ added in v0.2.0
type ShareSpec struct {
}
ShareSpec describes one Samba share declared under [smb.shares.<name>]. A share's path may coincide with a mount's destination, but shares and mounts are declared independently — no derivation exists between them.
type SmbSpec ¶ added in v0.2.0
type SmbSpec struct {
Group string `toml:"group"`
Users []string `toml:"users"`
Avahi *bool `toml:"avahi"`
}
SmbSpec is the [smb] table of a module.toml. Avahi is a *bool so an unset key (nil) is distinguishable from an explicit false; downstream consumers treat nil as the default (avahi enabled). Layers merge the scalar fields by replacement-when-set and Shares whole-entry by name (see internal/resolve).
type When ¶
type When struct {
Hosts []string `toml:"hosts"`
Users []string `toml:"users"`
OS []string `toml:"os"`
GPU string `toml:"gpu"`
Kernel string `toml:"kernel"`
Packages []string `toml:"packages"`
Tools []string `toml:"tools"`
And []When `toml:"and"`
Or []When `toml:"or"`
Not *When `toml:"not"`
}
When is a boolean expression over system facts. Leaf fields — Hosts, Users, OS, GPU, Kernel, Packages, Tools — AND together within one node (empty/omitted leaves are ignored); Kernel holds one "<op> <version>" constraint ("<", "<=", ">", ">=", "==", "!=") compared numerically per dotted segment against the running kernel release, and Packages/Tools list installed system packages / mise-managed tools (both probed lazily at load — see probes.go). Three combinators build larger expressions, each recursively a When again, nested arbitrarily deep:
and = [ <when>, ... ] — all sub-expressions must match (grouping)
or = [ <when>, ... ] — at least one sub-expression must match
not = { <when> } — the sub-expression must NOT match
A node matches when its leaves match AND every and-group matches AND at least one or-element matches (when or is non-empty) AND the not target does not match. So the top level stays plain AND of fields (historical behavior) until a combinator appears — e.g. `kernel = ">= 7"` beside `not = { packages = ["p"] }` reads "kernel >= 7 AND p not installed". NOT over several leaves negates their conjunction (De Morgan: not {a, b} = not-a OR not-b). Malformed kernel constraints are load-time errors at every depth; an empty not/or/and (nothing to evaluate, or an or-element that would vacuously match) is a load-time error naming the module — never a silent always/never-select footgun.