package config import ( "fmt" "metis/pkg/inventory" ) // NodeConfig represents boot-time configuration to inject. type NodeConfig struct { Hostname string `json:"hostname"` IP string `json:"ip"` K3s K3sConfig `json:"k3s"` SSHUser string `json:"ssh_user,omitempty"` SSHKeys []string `json:"ssh_keys,omitempty"` Labels map[string]string `json:"labels,omitempty"` Taints []string `json:"taints,omitempty"` Fstab []FstabEntry `json:"fstab,omitempty"` USBScratch *USBScratchConfig `json:"usb_scratch,omitempty"` Secrets map[string]string `json:"secrets,omitempty"` // optional key/values for local agent use } // K3sConfig includes role and token/url. type K3sConfig struct { Role string `json:"role"` Version string `json:"version,omitempty"` URL string `json:"url,omitempty"` Token string `json:"token,omitempty"` Args []string `json:"args,omitempty"` Labels map[string]string `json:"labels,omitempty"` Taints []string `json:"taints,omitempty"` } // FstabEntry for Longhorn or other mounts. type FstabEntry struct { Source string `json:"source,omitempty"` UUID string `json:"uuid,omitempty"` Label string `json:"label,omitempty"` Mountpoint string `json:"mountpoint"` FS string `json:"fs"` Options string `json:"options"` } // USBScratchConfig describes a recovery USB disk and its bind mounts. type USBScratchConfig struct { Mountpoint string `json:"mountpoint"` UUID string `json:"uuid,omitempty"` Label string `json:"label,omitempty"` FS string `json:"fs,omitempty"` BindTargets []string `json:"bind_targets,omitempty"` } // Build creates a NodeConfig from inventory. func Build(inv *inventory.Inventory, nodeName string) (*NodeConfig, error) { n, cls, err := inv.FindNode(nodeName) if err != nil { return nil, err } labels := map[string]string{} for k, v := range cls.DefaultLabels { labels[k] = v } for k, v := range n.Labels { labels[k] = v } taints := append([]string{}, cls.DefaultTaints...) taints = append(taints, n.Taints...) k3sVersion := cls.K3sVersion if n.K3sVersion != "" { k3sVersion = n.K3sVersion } cfg := &NodeConfig{ Hostname: n.Hostname, IP: n.IP, SSHUser: n.SSHUser, SSHKeys: n.SSHAuthorized, Labels: labels, Taints: taints, K3s: K3sConfig{ Role: n.K3sRole, Version: k3sVersion, URL: n.K3sURL, Token: n.K3sToken, Labels: labels, Taints: taints, }, } fstab := []FstabEntry{} for _, d := range n.LonghornDisks { fs := d.FS if fs == "" { fs = "ext4" } fstab = append(fstab, FstabEntry{ UUID: d.UUID, Mountpoint: d.Mountpoint, FS: fs, Options: "defaults,nofail", }) } if n.USBScratch != nil { scratch := USBScratchConfig{ Mountpoint: n.USBScratch.Mountpoint, UUID: n.USBScratch.UUID, Label: n.USBScratch.Label, FS: n.USBScratch.FS, BindTargets: append([]string{}, n.USBScratch.BindTargets...), } if scratch.FS == "" { scratch.FS = "ext4" } cfg.USBScratch = &scratch fstab = append(fstab, FstabEntry{ UUID: scratch.UUID, Label: scratch.Label, Mountpoint: scratch.Mountpoint, FS: scratch.FS, Options: "defaults,nofail", }) for _, target := range scratch.BindTargets { fstab = append(fstab, FstabEntry{ Source: scratch.Mountpoint, Mountpoint: target, FS: "none", Options: "bind,nofail", }) } } cfg.Fstab = fstab if cfg.Hostname == "" || cfg.IP == "" { return nil, fmt.Errorf("hostname/ip required for node %s", nodeName) } return cfg, nil }