package agent import ( "github.com/kairos-io/kairos-agent/v2/pkg/config" "github.com/kairos-io/kairos-agent/v2/pkg/constants" "github.com/kairos-io/kairos-sdk/collector" "github.com/mudler/yip/pkg/schema" "os" "strings" "github.com/kairos-io/kairos-agent/v2/internal/kairos" "gopkg.in/yaml.v3" ) type BrandingText struct { InteractiveInstall string `yaml:"interactive-install"` Install string `yaml:"install"` Reset string `yaml:"reset"` Recovery string `yaml:"recovery"` } type WebUI struct { Disable bool `yaml:"disable"` ListenAddress string `yaml:"listen_address"` } func (w WebUI) HasAddress() bool { return w.ListenAddress != "" } type Config struct { Fast bool `yaml:"fast,omitempty"` WebUI WebUI `yaml:"webui"` Branding BrandingText `yaml:"branding"` } func LoadConfig(path ...string) (*Config, error) { if len(path) == 0 { path = append(path, "/etc/kairos/agent.yaml") } cfg := &Config{} for _, p := range path { f, err := os.ReadFile(p) if err == nil { yaml.Unmarshal(f, cfg) //nolint:errcheck } } if cfg.Branding.InteractiveInstall == "" { f, err := os.ReadFile(kairos.BrandingFile("interactive_install_text")) if err == nil { cfg.Branding.InteractiveInstall = string(f) } } if cfg.Branding.Install == "" { f, err := os.ReadFile(kairos.BrandingFile("install_text")) if err == nil { cfg.Branding.Install = string(f) } } if cfg.Branding.Recovery == "" { f, err := os.ReadFile(kairos.BrandingFile("recovery_text")) if err == nil { cfg.Branding.Recovery = string(f) } } if cfg.Branding.Reset == "" { f, err := os.ReadFile(kairos.BrandingFile("reset_text")) if err == nil { cfg.Branding.Reset = string(f) } } return cfg, nil } type ExtraFields struct { Extrafields map[string]any `yaml:",inline,omitempty"` } // NewInteractiveInstallConfig creates a new config from model values func NewInteractiveInstallConfig(m *Model) *config.Config { // Always set the extra fields extras := ExtraFields{m.extraFields} // This is temporal to generate a valid cc file, no need to properly initialize everything cc := &config.Config{ Install: &config.Install{ Device: m.disk, }, } if m.source != "" { cc.Install.Source = m.source } var cloudConfig schema.YipConfig // Only add the user stage if we have any users if m.username != "" { user := schema.User{ Name: m.username, PasswordHash: m.password, Groups: []string{"admin"}, SSHAuthorizedKeys: m.sshKeys, } stage := config.NetworkStage.String() // If we got no ssh keys, we don't need network, do the user as soon as possible if len(m.sshKeys) == 0 { stage = config.InitramfsStage.String() } cloudConfig = schema.YipConfig{Name: "Config generated by the installer", Stages: map[string][]schema.Stage{stage: { { Users: map[string]schema.User{ m.username: user, }, }, }}} } else { // If no users, we need to set this option to skip the user validation and confirm that we want a system with no users. cc.Install.NoUsers = true } // Merge all yamls into one dat, err := config.MergeYAML(cloudConfig, cc, extras) if err != nil { return nil } finalCloudConfig := config.AddHeader("#cloud-config", string(dat)) // Read also any userdata in the system cc, _ = config.ScanNoLogs( collector.Readers(strings.NewReader(finalCloudConfig)), collector.Directories(constants.GetUserConfigDirs()...), collector.MergeBootLine, ) // Generate final config ccString, _ := cc.String() m.log.Logger.Debug().Msgf("Generated cloud config: %s", ccString) return cc }