package cloudinit import ( "fmt" "log/slog" "os" "path/filepath" "strings" ) // DefaultUpdateConfPath is where the update agent expects to find its config. // Kept in sync with update/pkg/config.DefaultPath. const DefaultUpdateConfPath = "/etc/kubesolo/update.conf" // ApplyUpdates writes /etc/kubesolo/update.conf from the cloud-init // updates: block. Called once per boot; idempotent (overwrites any existing // file with the cloud-init values). // // If the updates: block is empty (all fields blank), the file is not // written — preserves any hand-edited update.conf on systems that aren't // managed via cloud-init. func ApplyUpdates(cfg *Config, confPath string) error { if confPath == "" { confPath = DefaultUpdateConfPath } u := cfg.Updates if u.Server == "" && u.Channel == "" && u.MaintenanceWindow == "" && u.PubKey == "" { // Nothing to write — leave any existing file alone. return nil } if err := os.MkdirAll(filepath.Dir(confPath), 0o755); err != nil { return fmt.Errorf("creating dir for %s: %w", confPath, err) } var sb strings.Builder sb.WriteString("# Generated by KubeSolo OS cloud-init — edit this file or the\n") sb.WriteString("# cloud-init source YAML; subsequent first-boots will regenerate it.\n") if u.Server != "" { fmt.Fprintf(&sb, "server = %s\n", u.Server) } if u.Channel != "" { fmt.Fprintf(&sb, "channel = %s\n", u.Channel) } if u.MaintenanceWindow != "" { fmt.Fprintf(&sb, "maintenance_window = %s\n", u.MaintenanceWindow) } if u.PubKey != "" { fmt.Fprintf(&sb, "pubkey = %s\n", u.PubKey) } if err := os.WriteFile(confPath, []byte(sb.String()), 0o644); err != nil { return fmt.Errorf("writing %s: %w", confPath, err) } slog.Info("wrote update.conf", "path", confPath) return nil }