// kubesolo-cloudinit is a lightweight cloud-init parser for KubeSolo OS. // // It reads a YAML configuration file and applies hostname, network, and // KubeSolo settings during the init sequence. Designed to run as a static // binary on BusyBox-based systems. // // Usage: // // kubesolo-cloudinit apply // kubesolo-cloudinit validate // kubesolo-cloudinit dump package main import ( "encoding/json" "fmt" "log/slog" "os" cloudinit "github.com/portainer/kubesolo-os/cloud-init" ) const ( defaultConfigPath = "/mnt/data/etc-kubesolo/cloud-init.yaml" persistDataDir = "/mnt/data" configDir = "/etc/kubesolo" ) func main() { // Set up structured logging to stderr (captured by init) slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelInfo, }))) if len(os.Args) < 2 { usage() os.Exit(1) } cmd := os.Args[1] // Determine config path configPath := defaultConfigPath if len(os.Args) >= 3 { configPath = os.Args[2] } switch cmd { case "apply": if err := cmdApply(configPath); err != nil { slog.Error("cloud-init apply failed", "error", err) os.Exit(1) } case "validate": if err := cmdValidate(configPath); err != nil { fmt.Fprintf(os.Stderr, "validation failed: %s\n", err) os.Exit(1) } fmt.Println("OK") case "dump": if err := cmdDump(configPath); err != nil { fmt.Fprintf(os.Stderr, "error: %s\n", err) os.Exit(1) } default: fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd) usage() os.Exit(1) } } func cmdApply(configPath string) error { slog.Info("applying cloud-init", "config", configPath) cfg, err := cloudinit.Parse(configPath) if err != nil { return err } // 1. Apply hostname if err := cloudinit.ApplyHostname(cfg); err != nil { return fmt.Errorf("hostname: %w", err) } // 2. Apply network configuration if err := cloudinit.ApplyNetwork(cfg); err != nil { return fmt.Errorf("network: %w", err) } // 3. Apply KubeSolo settings if err := cloudinit.ApplyKubeSolo(cfg, configDir); err != nil { return fmt.Errorf("kubesolo config: %w", err) } // 4. Apply Portainer Edge Agent manifest (if enabled) if err := cloudinit.ApplyPortainer(cfg, "/var/lib/kubesolo/server/manifests"); err != nil { return fmt.Errorf("portainer edge agent: %w", err) } // 5. Save persistent configs for next boot if err := cloudinit.SaveHostname(cfg, persistDataDir+"/etc-kubesolo"); err != nil { slog.Warn("failed to save hostname", "error", err) } if err := cloudinit.SaveNetworkConfig(cfg, persistDataDir+"/network"); err != nil { slog.Warn("failed to save network config", "error", err) } slog.Info("cloud-init applied successfully") return nil } func cmdValidate(configPath string) error { _, err := cloudinit.Parse(configPath) return err } func cmdDump(configPath string) error { cfg, err := cloudinit.Parse(configPath) if err != nil { return err } enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(cfg) } func usage() { fmt.Fprintf(os.Stderr, `Usage: kubesolo-cloudinit [config.yaml] Commands: apply Parse and apply cloud-init configuration validate Check config file for errors dump Parse and print config as JSON If config path is omitted, defaults to %s `, defaultConfigPath) }