goo/main.go

273 lines
5.8 KiB
Go

package main
import (
"fmt"
"os"
"os/exec"
"path"
"slices"
"strings"
"text/template"
"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
SystemPackages []string `json:"system_packages"`
}
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() {
// Initialize structs for scripted configurations
ScriptedInit()
// Read user config file
config, err := ParseConfig("goolinux.json")
if err != nil {
fmt.Println(err)
return
}
// Read system "base" config file if it exists
if config.System.Base != "" {
fp := "services/" + config.System.Base + ".json"
systemConfig, err := ParseConfig(fp)
if err != nil {
fmt.Println(err)
return
}
// Merge base system config layer
mergo.Merge(&config, systemConfig)
}
// Parse user services
for name, service := range config.Services {
// 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)
s, err := ParseServiceConfig(fp)
if err != nil {
switch err {
case ErrFileNotFound:
// 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
mergo.Merge(&s, ServiceConfig{
Config: make(map[string]interface{}),
})
mergo.Merge(&s, service, mergo.WithOverride)
config.Services[name] = s
// Scripted actions for the service
// 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 != "" {
port, err := s.GetHostPort()
if err != nil {
fmt.Println(err.Error())
fmt.Println("Error in service", name)
return
}
err = AddProxy(s.Proxy, port)
if err != nil {
fmt.Println(err.Error())
fmt.Println("Error adding proxy for service", name)
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
{
var err error
err = os.MkdirAll("/config" + "/etc/apk/", 0555)
if err != nil {
fmt.Println("Error creating config directory.")
fmt.Println(err)
return
}
slices.Sort(config.System.Packages)
slices.Sort(config.System.SystemPackages)
config.System.Packages = append(
config.System.SystemPackages,
config.System.Packages...)
config.System.Packages = slices.Compact(
config.System.Packages)
p := strings.Join(config.System.Packages, "\n")
err = os.WriteFile("/config/etc/apk/world",[]byte(p), 0444)
if err != nil {
fmt.Println("Error creating package list")
fmt.Println(err)
return
}
}
// Install and remove packages
{
fmt.Println("Installing packages")
args := []string{"add", "-s", "-u", "-l"}
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 updating 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
}
}
}