package daemon import ( "fmt" "io" "isle/yamlutil" "os" "path/filepath" "strconv" "github.com/imdario/mergo" "gopkg.in/yaml.v3" ) func defaultConfigPath(appDirPath string) string { return filepath.Join(appDirPath, "etc", "daemon.yml") } type ConfigTun struct { Device string `yaml:"device"` } type ConfigFirewall struct { Conntrack ConfigConntrack `yaml:"conntrack"` Outbound []ConfigFirewallRule `yaml:"outbound"` Inbound []ConfigFirewallRule `yaml:"inbound"` } type ConfigConntrack struct { TCPTimeout string `yaml:"tcp_timeout"` UDPTimeout string `yaml:"udp_timeout"` DefaultTimeout string `yaml:"default_timeout"` MaxConnections int `yaml:"max_connections"` } type ConfigFirewallRule struct { Port string `yaml:"port,omitempty"` Code string `yaml:"code,omitempty"` Proto string `yaml:"proto,omitempty"` Host string `yaml:"host,omitempty"` Group string `yaml:"group,omitempty"` Groups []string `yaml:"groups,omitempty"` CIDR string `yaml:"cidr,omitempty"` CASha string `yaml:"ca_sha,omitempty"` CAName string `yaml:"ca_name,omitempty"` } // ConfigStorageAllocation describes the structure of each storage allocation // within the daemon config file. type ConfigStorageAllocation struct { DataPath string `yaml:"data_path"` MetaPath string `yaml:"meta_path"` Capacity int `yaml:"capacity"` S3APIPort int `yaml:"s3_api_port"` RPCPort int `yaml:"rpc_port"` AdminPort int `yaml:"admin_port"` // Zone is a secret option which makes it easier to test garage bugs, but // which we don't want users to otherwise know about. Zone string `yaml:"zone"` } // Config describes the structure of the daemon config file. type Config struct { DNS struct { Resolvers []string `yaml:"resolvers"` } `yaml:"dns"` VPN struct { PublicAddr string `yaml:"public_addr"` Firewall ConfigFirewall `yaml:"firewall"` Tun ConfigTun `yaml:"tun"` } `yaml:"vpn"` Storage struct { Allocations []ConfigStorageAllocation } `yaml:"storage"` } func (c *Config) fillDefaults() { var firewallGarageInbound []ConfigFirewallRule for i := range c.Storage.Allocations { if c.Storage.Allocations[i].RPCPort == 0 { c.Storage.Allocations[i].RPCPort = 3900 + (i * 10) } if c.Storage.Allocations[i].S3APIPort == 0 { c.Storage.Allocations[i].S3APIPort = 3901 + (i * 10) } if c.Storage.Allocations[i].AdminPort == 0 { c.Storage.Allocations[i].AdminPort = 3902 + (i * 10) } alloc := c.Storage.Allocations[i] firewallGarageInbound = append( firewallGarageInbound, ConfigFirewallRule{ Port: strconv.Itoa(alloc.S3APIPort), Proto: "tcp", Host: "any", }, ConfigFirewallRule{ Port: strconv.Itoa(alloc.RPCPort), Proto: "tcp", Host: "any", }, ) } c.VPN.Firewall.Inbound = append( c.VPN.Firewall.Inbound, firewallGarageInbound..., ) } // CopyDefaultConfig copies the daemon config file embedded in the AppDir into // the given io.Writer. func CopyDefaultConfig(into io.Writer, appDirPath string) error { defaultConfigPath := defaultConfigPath(appDirPath) f, err := os.Open(defaultConfigPath) if err != nil { return fmt.Errorf("opening daemon config at %q: %w", defaultConfigPath, err) } defer f.Close() if _, err := io.Copy(into, f); err != nil { return fmt.Errorf("copying daemon config from %q: %w", defaultConfigPath, err) } return nil } // LoadConfig loads the daemon config from userConfigPath, merges it with // the default found in the appDirPath, and returns the result. // // If userConfigPath is not given then the default is loaded and returned. func LoadConfig( appDirPath, userConfigPath string, ) ( Config, error, ) { defaultConfigPath := defaultConfigPath(appDirPath) var fullDaemon map[string]interface{} if err := yamlutil.LoadYamlFile(&fullDaemon, defaultConfigPath); err != nil { return Config{}, fmt.Errorf("parsing default daemon config file: %w", err) } if userConfigPath != "" { var daemonConfig map[string]interface{} if err := yamlutil.LoadYamlFile(&daemonConfig, userConfigPath); err != nil { return Config{}, fmt.Errorf("parsing %q: %w", userConfigPath, err) } err := mergo.Merge(&fullDaemon, daemonConfig, mergo.WithOverride) if err != nil { return Config{}, fmt.Errorf("merging contents of file %q: %w", userConfigPath, err) } } fullDaemonB, err := yaml.Marshal(fullDaemon) if err != nil { return Config{}, fmt.Errorf("yaml marshaling: %w", err) } var config Config if err := yaml.Unmarshal(fullDaemonB, &config); err != nil { return Config{}, fmt.Errorf("yaml unmarshaling back into Config struct: %w", err) } config.fillDefaults() return config, nil }