package main import ( "fmt" "os" "os/exec" "path" "strings" "text/template" "github.com/hjson/hjson-go/v4" "github.com/ghodss/yaml" "dario.cat/mergo" ) type Config struct { System SystemConfig Networking NetworkingConfig Services map[string]ServiceConfig } type SystemConfig struct { Base string Packages []string } type NetworkingConfig struct { } type ServiceConfig struct { Enable string Config map[string]interface{} ExtraConfig string Src string Ports []string Volumes []string Proxy string Provider string Packages []string ConfigFiles []string } func main() { var err error var config Config // Initialize structs for scripted configurations ScriptedInit() // Read user config file { file, err := os.ReadFile("goolinux.json") if err != nil { fmt.Println("Cannot read user config file") fmt.Println(err) return } err = hjson.Unmarshal(file, &config) if err != nil { fmt.Println(err) return } } // Read system "base" config file if it exists if config.System.Base != "" { var systemConfig Config fp := "services/" + config.System.Base + ".json" file, err := os.ReadFile(fp) if err != nil { fmt.Println("Cannot read system base config file") fmt.Println(err) return } err = hjson.Unmarshal(file, &systemConfig) if err != nil { fmt.Println(err) return } // Merge base system config layer mergo.Merge(&config, systemConfig, mergo.WithOverride) } // Parse user services for name, service := range config.Services { s := ServiceConfig{ Config: make(map[string]interface{}), } // If no provider specified, try system if service.Provider == "" { service.Provider = "system" } // Read service config file fp := fmt.Sprintf("services/%s/%s.json", name, service.Provider) c, err := os.ReadFile(fp) if err != nil { switch err := err.(type) { case *os.PathError: // No system config to merge // Rely solely on the user config fmt.Println(" Warning: No system config for service: " + name) continue default: fmt.Println("Error reading service config: ", err) return } } { // Parse service config err = hjson.Unmarshal(c, &s) if err != nil { fmt.Println("Error parsing config file") fmt.Println(err) return } mergo.Merge(&s, service, mergo.WithOverride) /* // Initialize the config map if it wasn't already if s.Config == map[string]interface{} { s.Config = make(map[string]interface{}) } */ config.Services[name] = s // Add service packages to the global list config.System.Packages = append(config.System.Packages, s.Packages...) // If a proxy definition exists, append it if s.Proxy != "" { ports := strings.Split(s.Ports[0], ":") if len(ports) == 0 { fmt.Println("Error: No port defined for proxy to", s.Proxy) return } err = AddProxy(s.Proxy, ports[0]) if err != nil { fmt.Println(err.Error()) return } } } } // Handle any special case (scripted) configs { // Expand the proxy list to Services.proxy.Config.scripted_proxy config.Services["proxy"].Config["scripted_proxy"] = GetProxy() } // Generate service config templates for name, s := range config.Services { for _, f := range s.ConfigFiles { f := strings.Split(f, ":") // Read service template file fp := fmt.Sprintf("services/%s/%s", name, f[0]) t, err := os.ReadFile(fp) if err != nil { fmt.Println("No template for service: ", err) return } tmpl, err := template.New(name).Parse(string(t)) if err != nil { fmt.Println("Error reading template") fmt.Println(err) return } err = os.MkdirAll("/config" + path.Dir(f[1]), 0555) if err != nil { fmt.Println("Error creating config directory.") fmt.Println(err) return } fd, err := os.Create("/config" + f[1]) defer fd.Close() if err != nil { fmt.Println(err) fmt.Println("No permission to create config file") return } fd.Chmod(0444) err = tmpl.Execute(fd, s) if err != nil { fmt.Println(err) return } } } // Convert service configs from JSON to YAML fmt.Println("Converting docker service configs to YAML") servicesPath := "/config/services/docker/" dirents, err := os.ReadDir(servicesPath) for _, d := range dirents { if d.IsDir() == true { continue } if strings.HasSuffix(d.Name(), ".json") != true { continue } f := servicesPath + d.Name() j, err := os.ReadFile(f) if err != nil { fmt.Println(err) return } y, err := yaml.JSONToYAML(j) if err != nil { fmt.Println(err) return } fd, _ := os.Create(strings.TrimSuffix(f, ".json") + ".yaml") defer fd.Close() fd.Chmod(0444) fd.Write(y) } // Parse package list // Add extra packages needed // ... // Install packages fmt.Println("Installing packages") var installString = []string{"add", "--no-interactive", "--no-progress"} var testArgs, args []string var out []byte testArgs = append(installString, "-s") testArgs = append(testArgs, config.System.Packages...) out, err = exec.Command("/sbin/apk", testArgs...).CombinedOutput() if err != nil { fmt.Println("===") fmt.Println(err) fmt.Println(string(out)) fmt.Println("===") fmt.Println("Error preparing packages") return } args = append(installString, config.System.Packages...) out, err = exec.Command("/sbin/apk", args...).CombinedOutput() if err != nil { fmt.Println("===") fmt.Println(err) fmt.Println(string(out)) fmt.Println("===") fmt.Println("Error installing packages") return } // (Re)start services // Handle docker services fmt.Println("Starting docker services") servicesPath = "/config/services/docker/" dirents, err = os.ReadDir(servicesPath) for _, d := range dirents { if d.IsDir() == true { continue } if strings.HasSuffix(d.Name(), ".yaml") != true { continue } f := servicesPath + d.Name() fmt.Println(" Starting:", d.Name()) var cmd = "/usr/bin/podman-compose" var args = []string{"-f", f, "up", "-d"} out, err := exec.Command(cmd, args...).CombinedOutput() if err != nil { fmt.Println("===") fmt.Println(err) fmt.Println(string(out)) fmt.Println("===") fmt.Println("Error starting service:" + d.Name()) return } } }