Help us shape the future of Go! Take the 2026 Go Developer Survey

config

package
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Oct 17, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SeverityError   = "error"
	SeverityWarning = "warning"
)

Severity ranks a validation finding. Errors mean the config is wrong and something will not work; warnings mean it parses but probably does not do what the user intended.

Variables

View Source
var ErrCredentialWouldMove = fmt.Errorf("that change would send a saved credential to a new address")

ErrCredentialWouldMove is returned when a patch points a stored credential at an address it was not given for.

The reason it is worth refusing rather than warning: a saved password is sent to whatever answers at the address in the file, and changing that address is a one-field edit. Anyone who can reach the write endpoints — or who has taken the token from someone who can — could point a server at a machine of theirs and collect the password on the next refresh. Proxmox is the same shape: the token travels in a header to whatever host the endpoint names.

Sending the credential again with the change is the whole fix. Somebody who knows the password can move the server; somebody who only has the config cannot take it with them.

View Source
var ErrFlowStyle = errors.New("that section is written on one line, which homebutler cannot edit in place")

ErrFlowStyle is returned for a section written on one line. Editing text at a position cannot change one value inside `alerts: {cpu: 90}` without rewriting the line, and rewriting it would lose whatever else it holds.

View Source
var ErrStale = errors.New("the config file changed on disk since it was read")

ErrStale is returned when the file changed after the revision being saved against was taken.

Functions

func CheckWakeTarget added in v0.34.0

func CheckWakeTarget(name, mac string) error

CheckWakeTarget reports why a target cannot be saved, in the words config validate uses for the same mistake.

It is one function rather than a rule written twice because a save and a `config validate` that disagreed about what a MAC looks like would be two answers to one question — and the disagreement would only surface after the packet had gone nowhere. Wake-on-LAN is fire and forget: nothing replies, so a typed address shows up as a machine that simply did not turn on, which is exactly the case a form has to catch before it writes.

func FormatValidation added in v0.20.0

func FormatValidation(r *ValidationResult) string

FormatValidation renders a validation result for a terminal.

func PermissionProblem added in v0.28.0

func PermissionProblem(path string, cfg *Config) (os.FileMode, bool)

PermissionProblem reports whether a config file is more open than one holding plaintext secrets should be, and the permissions it found.

One decision, three renderings: Load refuses, config validate reports it as an error, and doctor surfaces it before either has to. #47 and #65 were both about the version of this where each caller kept its own copy of the rule.

func RegisterAlertRulesCheck added in v1.2.0

func RegisterAlertRulesCheck(check func(node *yaml.Node) []Finding)

RegisterAlertRulesCheck is called by the alerts package at init.

func Resolve added in v0.2.0

func Resolve(explicit string) string

Resolve finds the config file path. See ResolveWithSource for the priority.

func Save added in v0.33.0

func Save(path string, rev Revision, patch Patch) error

Save applies patch to the config file at path.

The file is edited rather than regenerated: it is written by a person, and keeps their comments, their key order, the keys this version of homebutler does not recognise, and their indentation. Only the values the patch names are touched.

#154 calls it as:

rev, _ := config.ReadRevision(path)
err := config.Save(path, rev, config.Patch{Alerts: &config.AlertsPatch{CPU: &v}})

Types

type AlertConfig

type AlertConfig struct {
	CPU    float64 `yaml:"cpu"`
	Memory float64 `yaml:"memory"`
	Disk   float64 `yaml:"disk"`
	// Ignore names containers whose stopping and starting is intended, such
	// as one a cron job stops every night. The watcher reports neither their
	// going down nor their recovery (#338). Every other container is still
	// watched, as before.
	Ignore []string `yaml:"ignore,omitempty"`
}

type AlertsPatch added in v0.33.0

type AlertsPatch struct {
	CPU    *float64
	Memory *float64
	Disk   *float64
}

AlertsPatch changes alert thresholds. A nil field is not written.

type BackupConfig added in v0.24.0

type BackupConfig struct {
	Dir       string                 `yaml:"dir,omitempty"`
	Retention backup.RetentionConfig `yaml:"retention,omitempty"`
}

BackupConfig groups everything about where backups go and how many are kept.

`backup: dir:` is the spelling docs/backup.md has documented all along, and the schema only ever had the top-level `backup_dir`. A config copied from the documentation parsed without complaint and then wrote to the home directory, so an operator pointing backups at a NAS got neither the destination they asked for nor any indication they had not. Both spellings are read; the nested one wins.

type Config

type Config struct {
	Path      string                `yaml:"-"` // resolved config file path (not serialized)
	Servers   []ServerConfig        `yaml:"servers"`
	Proxmox   []ProxmoxConfig       `yaml:"proxmox,omitempty"`
	Wake      []WakeTarget          `yaml:"wake,omitempty"`
	Alerts    AlertConfig           `yaml:"alerts"`
	Notify    notify.ProviderConfig `yaml:"notify,omitempty"`
	Watch     WatchRuntimeConfig    `yaml:"watch,omitempty"`
	Backup    BackupConfig          `yaml:"backup,omitempty"`
	BackupDir string                `yaml:"backup_dir,omitempty"`
	Web       WebConfig             `yaml:"web,omitempty"`
}

func Load

func Load(path string) (*Config, error)

func LoadForDiagnosis added in v0.28.0

func LoadForDiagnosis(path string) (*Config, error)

LoadForDiagnosis parses a config without refusing it for open permissions.

Only doctor should call this. The command whose job is to explain what is wrong with a machine cannot be the one command that will not start because something is wrong — a user whose config is 0644 currently meets a refusal they read as homebutler being broken, from every command including the one that would have named the problem.

func (*Config) FindProxmox added in v0.22.1

func (c *Config) FindProxmox(name string) *ProxmoxConfig

FindProxmox returns the Proxmox endpoint config by name, or nil if absent.

func (*Config) FindServer added in v0.2.0

func (c *Config) FindServer(name string) *ServerConfig

FindServer returns the server config by name, or nil if not found.

func (*Config) FindWakeTarget

func (c *Config) FindWakeTarget(name string) *WakeTarget

func (*Config) ResolveBackupDir added in v0.9.0

func (c *Config) ResolveBackupDir() string

ResolveBackupDir returns the backup directory from config or the default ~/.homebutler/backups/.

func (*Config) ResolveBackupRetention added in v0.24.0

func (c *Config) ResolveBackupRetention() backup.RetentionConfig

ResolveBackupRetention returns the configured retention policy, which is unlimited unless the operator asked for something narrower.

func (*Config) SelectProxmox added in v0.22.1

func (c *Config) SelectProxmox(name string) (*ProxmoxConfig, error)

SelectProxmox returns the named endpoint, or the sole configured endpoint.

type Finding added in v0.20.0

type Finding struct {
	Severity string `json:"severity"`
	Field    string `json:"field,omitempty"`
	Message  string `json:"message"`
	Hint     string `json:"hint,omitempty"`
}

Finding is one problem found in the config.

type NotifyPatch added in v0.33.0

type NotifyPatch struct {
	Values  map[string]string
	Secrets map[string]*Secret
	Remove  bool
}

Patch is the set of settings to change.

It is a patch of the fields being changed rather than a whole document, and that is a decision about secrets rather than a convenience. /api/config redacts a password to bullets, so a caller that sent back what it was given would write those bullets into the file as the password. A field nobody set is not written, so a secret nobody is changing cannot be damaged by a save that was about something else. NotifyPatch changes one notification channel.

Values carries the keys that are not credentials; a key that is absent is left as it is. Secrets carries the ones that are, because those have three states rather than two. Remove deletes the whole block.

type Patch added in v0.33.0

type Patch struct {
	Alerts *AlertsPatch
	// Notify is keyed by channel name, as internal/notify spells it.
	Notify  map[string]*NotifyPatch
	Wake    []WakePatch
	Servers []ServerPatch
	Proxmox []ProxmoxPatch
}

func (Patch) IsEmpty added in v0.33.0

func (p Patch) IsEmpty() bool

IsEmpty reports whether the patch would change nothing.

func (Patch) Validate added in v0.33.0

func (p Patch) Validate() error

Validate reports what is wrong with the patch itself, before any file is read. A channel or key homebutler does not have is a caller mistake rather than a config problem, and saying so here keeps it out of the file.

type ProxmoxConfig added in v0.22.1

type ProxmoxConfig struct {
	Name        string `yaml:"name"`
	Host        string `yaml:"host"`
	Port        int    `yaml:"port,omitempty"`
	TokenID     string `yaml:"token_id"`
	Token       string `yaml:"token,omitempty" json:"-" secret:"true"`
	TokenFile   string `yaml:"token_file,omitempty"`
	Fingerprint string `yaml:"fingerprint,omitempty"`
	CAFile      string `yaml:"ca_file,omitempty"`
	Insecure    bool   `yaml:"insecure,omitempty"`
	Timeout     string `yaml:"timeout,omitempty"`

	// ActionTokenID/ActionToken/ActionTokenFile configure an optional second
	// credential used only for guest power actions (start/reboot/shutdown).
	// The credential above stays read-only; if no action credential is
	// configured, guest actions are unavailable rather than falling back to
	// the read credential.
	ActionTokenID   string `yaml:"action_token_id,omitempty"`
	ActionToken     string `yaml:"action_token,omitempty" json:"-" secret:"true"`
	ActionTokenFile string `yaml:"action_token_file,omitempty"`

	// Guests names guests `watch start` expects to stay running on this
	// endpoint. A guest not listed here is observational only: watch never
	// alerts on it, deliberately stopped or not.
	Guests []proxmox.ExpectedGuest `yaml:"guests,omitempty"`
}

ProxmoxConfig describes one Proxmox VE API endpoint. It is separate from ServerConfig because Proxmox uses its HTTP API rather than SSH.

func (ProxmoxConfig) APIPort added in v0.22.1

func (p ProxmoxConfig) APIPort() int

APIPort returns the configured API port or Proxmox's default HTTPS port.

func (ProxmoxConfig) ActionTokenFilePath added in v0.27.0

func (p ProxmoxConfig) ActionTokenFilePath() string

ActionTokenFilePath expands a leading ~/ in the configured action token file path.

func (ProxmoxConfig) ActionTokenValue added in v0.27.0

func (p ProxmoxConfig) ActionTokenValue() (string, error)

ActionTokenValue returns the action API token from the configured file or inline value, mirroring TokenValue's file-wins precedence.

func (ProxmoxConfig) HasActionCredential added in v0.27.0

func (p ProxmoxConfig) HasActionCredential() bool

HasActionCredential reports whether an action credential is configured for guest power actions. Guest actions are unavailable when this is false; there is no fallback to the read credential.

func (ProxmoxConfig) ResolveCredential added in v0.27.0

func (p ProxmoxConfig) ResolveCredential(action bool) (tokenID, token string, err error)

ResolveCredential returns the token ID and token value for either the read or action credential, wrapping resolution failures the same way across every caller. Guest actions never fall back to the read credential: when action is true and no action credential is configured, this fails before any token is read.

func (ProxmoxConfig) TimeoutDuration added in v0.22.1

func (p ProxmoxConfig) TimeoutDuration() time.Duration

TimeoutDuration returns the configured timeout or the default 10 seconds. Validation reports malformed values before a client is constructed.

func (ProxmoxConfig) TokenFilePath added in v0.22.1

func (p ProxmoxConfig) TokenFilePath() string

TokenFilePath expands a leading ~/ in the configured token file path.

func (ProxmoxConfig) TokenValue added in v0.22.1

func (p ProxmoxConfig) TokenValue() (string, error)

TokenValue returns the API token from the configured file or inline value. Validation rejects configuring both sources, but the file takes precedence here to keep token_file the preferred source for direct callers.

type ProxmoxPatch added in v0.35.0

type ProxmoxPatch struct {
	Name          string
	Rename        string
	Host          *string
	Port          *int
	TokenID       *string
	Token         *Secret
	ActionTokenID *string
	ActionToken   *Secret
	Remove        bool
}

ProxmoxPatch changes one Proxmox endpoint. The read credential and the one that performs guest actions are separate on purpose, and stay separate here.

The *_file paths are absent for the same reason KeyFile is.

type Revision added in v0.33.0

type Revision struct {
	// contains filtered or unexported fields
}

Revision identifies the config file as it was when it was read.

Someone with the dashboard open and an editor in another window is the ordinary case, not an edge one: without this, saving a threshold from the browser silently discards whatever they wrote in vim. A save carries the revision it was made against and is refused if the file has moved on.

func ParseRevision added in v0.33.0

func ParseRevision(s string) (Revision, error)

ParseRevision reads back what String wrote. An empty string is the revision of a file that was not there, which is how creating one is expressed.

func ReadRevision added in v0.33.0

func ReadRevision(path string) (Revision, error)

ReadRevision returns the revision of the file at path. A file that does not exist has a revision too, so that creating one can also be refused if something else created it first.

func (Revision) Exists added in v0.33.0

func (r Revision) Exists() bool

Exists reports whether the file was there when the revision was taken.

func (Revision) String added in v0.33.0

func (r Revision) String() string

String is the form that crosses to a browser and comes back with the save it was made against.

type Secret added in v0.33.0

type Secret struct {
	// contains filtered or unexported fields
}

Secret is a credential inside a patch, which has three states rather than two: absent, replaced, or cleared.

An empty string cannot mean "clear". A form submitted with the token box left blank sends an empty string, and reading that as "delete the token" destroys a working configuration because someone changed a threshold on the same page. Clearing is its own call.

func ClearSecret added in v0.33.0

func ClearSecret() *Secret

ClearSecret removes the stored value. Explicit, and never the result of an empty input.

func SetSecret added in v0.33.0

func SetSecret(value string) *Secret

SetSecret replaces the stored value.

type Section added in v0.20.0

type Section struct {
	Name    string `json:"name"`
	Present bool   `json:"present"`
	Summary string `json:"summary"`
}

Section reports what homebutler made of one top-level config key.

This is the half of the output that answers "why is my notify config being ignored?". A key the file never set reads as "not set", and a key that was set but did not match the schema shows up in Findings instead of here — so the two together account for every line of the file.

type ServerConfig

type ServerConfig struct {
	Name     string `yaml:"name"`
	Host     string `yaml:"host"`
	Local    bool   `yaml:"local,omitempty"`
	User     string `yaml:"user,omitempty"`
	Port     int    `yaml:"port,omitempty"`
	KeyFile  string `yaml:"key,omitempty"`
	Password string `yaml:"password,omitempty" json:"-" secret:"true"`
	AuthMode string `yaml:"auth,omitempty"` // "key" (default) or "password"
	BinPath  string `yaml:"bin,omitempty"`  // remote homebutler path (default: homebutler)
}

func (*ServerConfig) SSHBinPath added in v0.2.0

func (s *ServerConfig) SSHBinPath() string

SSHBinPath returns the remote homebutler binary path.

func (*ServerConfig) SSHPort added in v0.2.0

func (s *ServerConfig) SSHPort() int

SSHPort returns the configured port or default 22.

func (*ServerConfig) SSHUser added in v0.2.0

func (s *ServerConfig) SSHUser() string

SSHUser returns the configured user or default "root".

func (*ServerConfig) UseKeyAuth added in v0.2.0

func (s *ServerConfig) UseKeyAuth() bool

UseKeyAuth returns true if key-based auth should be used (default).

type ServerPatch added in v0.35.0

type ServerPatch struct {
	Name     string
	Rename   string
	Host     *string
	Port     *int
	User     *string
	AuthMode *string
	Password *Secret
	Remove   bool
}

ServerPatch changes one server, addressed by the name it has now. A field left nil is not written, so a save about the port cannot disturb the user.

KeyFile is deliberately absent. It names a path on the machine homebutler runs on, and a browser is the wrong place to be choosing one; the CLI still edits it.

type Source added in v0.20.0

type Source string

Source identifies which resolution rule produced the config path.

const (
	SourceFlag Source = "flag" // --config
	SourceEnv  Source = "env"  // $HOMEBUTLER_CONFIG
	SourceXDG  Source = "xdg"  // ~/.config/homebutler/config.yaml
	SourceCwd  Source = "cwd"  // ./homebutler.yaml
	SourceNone Source = "none" // nothing found; built-in defaults apply
)

func ResolveWithSource added in v0.20.0

func ResolveWithSource(explicit string) (string, Source)

ResolveWithSource finds the config file path using the following priority:

  1. Explicit path (--config flag)
  2. $HOMEBUTLER_CONFIG environment variable
  3. ~/.config/homebutler/config.yaml (XDG standard)
  4. ./homebutler.yaml (current directory)

Returns an empty path and SourceNone if no config file is found (defaults will be used). Note that the first two rules do not check that the file exists: a path the user named explicitly is returned as-is, so that a mistyped one can be reported rather than silently falling through to the next rule.

func (Source) Describe added in v0.20.0

func (s Source) Describe() string

Describe returns a human-readable form of the rule that produced the path.

type ValidationResult added in v0.20.0

type ValidationResult struct {
	Path     string    `json:"path"`
	Source   string    `json:"source"`
	Exists   bool      `json:"exists"`
	Sections []Section `json:"sections,omitempty"`
	Findings []Finding `json:"findings,omitempty"`
	Valid    bool      `json:"valid"`
}

ValidationResult is the outcome of checking a config file.

func Validate added in v0.20.0

func Validate(explicit string) *ValidationResult

Validate resolves the config the same way every other command does, then reports what it found without applying it. It never contacts a remote server and never writes anything.

Validate deliberately does not go through Load: Load treats a missing file as "use defaults" so that running without a config works, which also means it cannot distinguish that from a path the user typed wrong.

func (*ValidationResult) Errors added in v0.20.0

func (r *ValidationResult) Errors() int

Errors returns the number of error-severity findings.

func (*ValidationResult) Warnings added in v0.20.0

func (r *ValidationResult) Warnings() int

Warnings returns the number of warning-severity findings.

type WakePatch added in v0.33.0

type WakePatch struct {
	Name      string
	MAC       string
	Broadcast string
	Remove    bool
}

WakePatch adds or removes one Wake-on-LAN target by name.

type WakeTarget

type WakeTarget struct {
	Name      string `yaml:"name"`
	MAC       string `yaml:"mac"`
	Broadcast string `yaml:"ip,omitempty"`
}

type WatchRuntimeConfig added in v0.16.1

type WatchRuntimeConfig struct {
	Notify    watch.NotifySettings  `yaml:"notify,omitempty"`
	Flapping  watch.FlappingConfig  `yaml:"flapping,omitempty"`
	Retention watch.RetentionConfig `yaml:"retention,omitempty"`
}

func (*WatchRuntimeConfig) UnmarshalYAML added in v0.20.0

func (w *WatchRuntimeConfig) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts both spellings of the watch notification settings:

watch:            # canonical
  notify:
    enabled: true

watch:            # flat, as the README documented it
  enabled: true

The flat keys were never part of the schema, so a config copied from the README parsed without complaint and then ran with notifications off — the same symptom as #31, from a different cause. Correcting only the docs would have left those configs silently broken, so both spellings are read.

A file containing both forms gets the nested block; the flat keys are a compatibility path, not an override.

type WebConfig added in v0.39.0

type WebConfig struct {
	// Token authenticates the dashboard's API. It lives here rather than in a
	// flag because --token is visible in ps to every user on the machine, and
	// a supervised dashboard would have to keep it in a unit file that is not
	// ours to protect. The secret tag is what makes the 0600 check apply to a
	// config that holds one.
	Token string `yaml:"token,omitempty" json:"-" secret:"true"`
}

WebConfig holds what the dashboard needs that cannot be passed on a command line.

Jump to

Keyboard shortcuts

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