184 lines
4.6 KiB
Go
184 lines
4.6 KiB
Go
|
package daecommon
|
||
|
|
||
|
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
|
||
|
}
|