Move common daemon types and values into daecommon

This commit is contained in:
Brian Picciano 2024-09-07 15:11:04 +02:00
parent ef86c1bbd1
commit a840d0e701
19 changed files with 424 additions and 383 deletions

View File

@ -6,6 +6,7 @@ import (
"os"
"isle/daemon"
"isle/daemon/daecommon"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
)
@ -41,7 +42,7 @@ var subCmdDaemon = subCmd{
}
if *dumpConfig {
return daemon.CopyDefaultConfig(os.Stdout, envAppDirPath)
return daecommon.CopyDefaultConfig(os.Stdout, envAppDirPath)
}
logLevel := mlog.LevelFromString(*logLevelStr)
@ -55,7 +56,7 @@ var subCmdDaemon = subCmd{
// required linux capabilities are set.
// TODO check that the tun module is loaded (for nebula).
daemonConfig, err := daemon.LoadConfig(envAppDirPath, *daemonConfigPath)
daemonConfig, err := daecommon.LoadConfig(envAppDirPath, *daemonConfigPath)
if err != nil {
return fmt.Errorf("loading daemon config: %w", err)
}

View File

@ -2,12 +2,13 @@ package main
import (
"context"
"isle/daemon"
"os"
"os/signal"
"path/filepath"
"syscall"
"isle/daemon/daecommon"
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
)
@ -21,7 +22,7 @@ func getAppDirPath() string {
}
var (
daemonEnvVars = daemon.GetEnvVars()
daemonEnvVars = daecommon.GetEnvVars()
envAppDirPath = getAppDirPath()
envBinDirPath = filepath.Join(envAppDirPath, "bin")
)

View File

@ -130,7 +130,9 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
}
daemonRPC := daemon.RPCFromClient(
jsonrpc2.NewUnixHTTPClient(daemon.HTTPSocketPath(), daemonHTTPRPCPath),
jsonrpc2.NewUnixHTTPClient(
daemon.HTTPSocketPath(), daemonHTTPRPCPath,
),
)
err := subCmd.do(subCmdCtx{

View File

@ -10,6 +10,7 @@ import (
"reflect"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/garage/garagesrv"
"isle/jsonutil"
"isle/secrets"
@ -42,7 +43,7 @@ func writeBootstrapToStateDir(
}
func coalesceDaemonConfigAndBootstrap(
daemonConfig Config, hostBootstrap bootstrap.Bootstrap,
daemonConfig daecommon.Config, hostBootstrap bootstrap.Bootstrap,
) (
bootstrap.Bootstrap, error,
) {
@ -88,7 +89,7 @@ type bootstrapDiff struct {
}
func calcBootstrapDiff(
daemonConfig Config,
daemonConfig daecommon.Config,
prevBootstrap, nextBootstrap bootstrap.Bootstrap,
) (
diff bootstrapDiff, err error,

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/dnsmasq"
"path/filepath"
"sort"
@ -13,7 +14,7 @@ import (
)
func dnsmasqConfig(
daemonConfig Config, hostBootstrap bootstrap.Bootstrap,
daemonConfig daecommon.Config, hostBootstrap bootstrap.Bootstrap,
) dnsmasq.ConfData {
hostsSlice := make([]dnsmasq.ConfDataHost, 0, len(hostBootstrap.Hosts))
for _, host := range hostBootstrap.Hosts {
@ -37,7 +38,7 @@ func dnsmasqConfig(
func dnsmasqWriteConfig(
runtimeDirPath string,
daemonConfig Config,
daemonConfig daecommon.Config,
hostBootstrap bootstrap.Bootstrap,
) (
string, error,
@ -57,7 +58,7 @@ func dnsmasqWriteConfig(
func dnsmasqPmuxProcConfig(
logger *mlog.Logger,
runtimeDirPath, binDirPath string,
daemonConfig Config,
daemonConfig daecommon.Config,
hostBootstrap bootstrap.Bootstrap,
) (
pmuxlib.ProcessConfig, error,

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/garage"
"isle/garage/garagesrv"
"net"
@ -23,7 +24,7 @@ func garageAdminClientLogger(logger *mlog.Logger) *mlog.Logger {
// or it will _panic_ if there is no local instance configured.
func newGarageAdminClient(
logger *mlog.Logger,
daemonConfig Config,
daemonConfig daecommon.Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap,
) *garage.AdminClient {
@ -43,7 +44,7 @@ func newGarageAdminClient(
func waitForGarage(
ctx context.Context,
logger *mlog.Logger,
daemonConfig Config,
daemonConfig daecommon.Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap,
) error {
@ -88,7 +89,7 @@ func waitForGarage(
// This assumes that coalesceDaemonConfigAndBootstrap has already been called.
func bootstrapGarageHostForAlloc(
host bootstrap.Host,
alloc ConfigStorageAllocation,
alloc daecommon.ConfigStorageAllocation,
) bootstrap.GarageHostInstance {
for _, inst := range host.Garage.Instances {
@ -103,7 +104,7 @@ func bootstrapGarageHostForAlloc(
func garageWriteChildConfig(
rpcSecret, runtimeDirPath, adminToken string,
hostBootstrap bootstrap.Bootstrap,
alloc ConfigStorageAllocation,
alloc daecommon.ConfigStorageAllocation,
) (
string, error,
) {
@ -147,7 +148,7 @@ func garagePmuxProcConfigs(
ctx context.Context,
logger *mlog.Logger,
rpcSecret, runtimeDirPath, binDirPath string,
daemonConfig Config,
daemonConfig daecommon.Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap,
) (
@ -188,7 +189,7 @@ func garagePmuxProcConfigs(
func garageApplyLayout(
ctx context.Context,
logger *mlog.Logger,
daemonConfig Config,
daemonConfig daecommon.Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap,
) error {

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/yamlutil"
"net"
"path/filepath"
@ -47,7 +48,7 @@ func waitForNebula(
}
func nebulaConfig(
daemonConfig Config,
daemonConfig daecommon.Config,
hostBootstrap bootstrap.Bootstrap,
) (
map[string]any, error,
@ -136,7 +137,7 @@ func nebulaConfig(
func nebulaWriteConfig(
runtimeDirPath string,
daemonConfig Config,
daemonConfig daecommon.Config,
hostBootstrap bootstrap.Bootstrap,
) (
string, error,
@ -157,7 +158,7 @@ func nebulaWriteConfig(
func nebulaPmuxProcConfig(
runtimeDirPath, binDirPath string,
daemonConfig Config,
daemonConfig daecommon.Config,
hostBootstrap bootstrap.Bootstrap,
) (
pmuxlib.ProcessConfig, error,

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"isle/bootstrap"
"isle/daemon/daecommon"
"code.betamike.com/micropelago/pmux/pmuxlib"
)
@ -11,7 +12,7 @@ import (
func (c *Children) newPmuxConfig(
ctx context.Context,
garageRPCSecret, binDirPath string,
daemonConfig Config,
daemonConfig daecommon.Config,
garageAdminToken string,
hostBootstrap bootstrap.Bootstrap,
) (
@ -67,7 +68,7 @@ func (c *Children) newPmuxConfig(
func (c *Children) postPmuxInit(
ctx context.Context,
daemonConfig Config,
daemonConfig daecommon.Config,
garageAdminToken string,
hostBootstrap bootstrap.Bootstrap,
) error {

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/secrets"
"code.betamike.com/micropelago/pmux/pmuxlib"
@ -18,7 +19,7 @@ import (
// - garage (0 or more, depending on configured storage allocations)
type Children struct {
logger *mlog.Logger
daemonConfig Config
daemonConfig daecommon.Config
opts Opts
pmux *pmuxlib.Pmux
@ -31,7 +32,7 @@ func NewChildren(
logger *mlog.Logger,
binDirPath string,
secretsStore secrets.Store,
daemonConfig Config,
daemonConfig daecommon.Config,
garageAdminToken string,
hostBootstrap bootstrap.Bootstrap,
opts *Opts,
@ -41,7 +42,7 @@ func NewChildren(
opts = opts.withDefaults()
logger.Info(ctx, "Loading secrets")
garageRPCSecret, err := getGarageRPCSecret(ctx, secretsStore)
garageRPCSecret, err := daecommon.GetGarageRPCSecret(ctx, secretsStore)
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
return nil, fmt.Errorf("loading garage RPC secret: %w", err)
}

View File

@ -1,183 +1,81 @@
package daemon
import (
"errors"
"fmt"
"io"
"isle/yamlutil"
"io/fs"
"os"
"path/filepath"
"strconv"
"github.com/imdario/mergo"
"gopkg.in/yaml.v3"
"slices"
"strings"
"sync"
)
func defaultConfigPath(appDirPath string) string {
return filepath.Join(appDirPath, "etc", "daemon.yml")
func getDefaultHTTPSocketDirPath() string {
path, err := firstExistingDir(
"/tmp",
// TODO it's possible the daemon process can't actually write to these
"/run",
"/var/run",
"/dev/shm",
)
if err != nil {
panic(fmt.Sprintf("Failed to find directory for HTTP socket: %v", err))
}
type ConfigTun struct {
Device string `yaml:"device"`
return path
}
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",
// HTTPSocketPath returns the path to the daemon's HTTP socket which is used for
// RPC and other functionality.
var HTTPSocketPath = sync.OnceValue(func() string {
return envOr(
"ISLE_DAEMON_HTTP_SOCKET_PATH",
func() string {
return filepath.Join(
getDefaultHTTPSocketDirPath(), "isle-daemon.sock",
)
},
)
})
////////////////////////////////////////////////////////////////////////////////
// Jigs
func envOr(name string, fallback func() string) string {
if v := os.Getenv(name); v != "" {
return v
}
return fallback()
}
c.VPN.Firewall.Inbound = append(
c.VPN.Firewall.Inbound,
firewallGarageInbound...,
func firstExistingDir(paths ...string) (string, error) {
var errs []error
for _, path := range paths {
stat, err := os.Stat(path)
switch {
case errors.Is(err, fs.ErrExist):
continue
case err != nil:
errs = append(
errs, fmt.Errorf("checking if path %q exists: %w", path, err),
)
}
// 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)
case !stat.IsDir():
errs = append(
errs, fmt.Errorf("path %q exists but is not a directory", path),
)
default:
return path, nil
}
}
fullDaemonB, err := yaml.Marshal(fullDaemon)
if err != nil {
return Config{}, fmt.Errorf("yaml marshaling: %w", err)
err := fmt.Errorf(
"no directory found at any of the following paths: %s",
strings.Join(paths, ", "),
)
if len(errs) > 0 {
err = errors.Join(slices.Insert(errs, 0, 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
return "", err
}

View File

@ -0,0 +1,183 @@
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
}

View File

@ -0,0 +1,3 @@
// Package daecommon holds types and functionality which are required the daemon
// package and other of its subpackages.
package daecommon

View File

@ -0,0 +1,45 @@
package daecommon
import (
"os"
"path/filepath"
"sync"
"github.com/adrg/xdg"
)
// EnvVars are variables which are derived based on the environment which the
// process is running in.
type EnvVars struct {
RuntimeDirPath string
StateDirPath string
}
// GetEnvVars will return the EnvVars of the current processes, as determined by
// the process's environment.
var GetEnvVars = sync.OnceValue(func() (v EnvVars) {
// RUNTIME_DIRECTORY/STATE_DIRECTORY are used by the systemd service in
// conjunction with the RuntimeDirectory/StateDirectory directives.
v.RuntimeDirPath = envOr(
"RUNTIME_DIRECTORY",
func() string { return filepath.Join(xdg.RuntimeDir, "isle") },
)
v.StateDirPath = envOr(
"STATE_DIRECTORY",
func() string { return filepath.Join(xdg.StateHome, "isle") },
)
return
})
////////////////////////////////////////////////////////////////////////////////
// Jigs
func envOr(name string, fallback func() string) string {
if v := os.Getenv(name); v != "" {
return v
}
return fallback()
}

View File

@ -0,0 +1,51 @@
package daecommon
import (
"fmt"
"isle/garage"
"isle/nebula"
"isle/secrets"
)
const (
secretsNSNebula = "nebula"
secretsNSGarage = "garage"
)
////////////////////////////////////////////////////////////////////////////////
// Nebula-related secrets
// IDs and Get/Set functions for nebula-related secrets.
var (
NebulaCASigningPrivateKeySecretID = secrets.NewID(secretsNSNebula, "ca-signing-private-key")
GetNebulaCASigningPrivateKey, SetNebulaCASigningPrivateKey = secrets.GetSetFunctions[nebula.SigningPrivateKey](
NebulaCASigningPrivateKeySecretID,
)
)
////////////////////////////////////////////////////////////////////////////////
// Garage-related secrets
func garageS3APIBucketCredentialsSecretID(credsName string) secrets.ID {
return secrets.NewID(
secretsNSGarage, fmt.Sprintf("s3-api-bucket-credentials-%s", credsName),
)
}
// IDs and Get/Set functions for garage-related secrets.
var (
GarageRPCSecretSecretID = secrets.NewID(secretsNSGarage, "rpc-secret")
GarageS3APIGlobalBucketCredentialsSecretID = garageS3APIBucketCredentialsSecretID(
garage.GlobalBucketS3APICredentialsName,
)
GetGarageRPCSecret, SetGarageRPCSecret = secrets.GetSetFunctions[string](
GarageRPCSecretSecretID,
)
GetGarageS3APIGlobalBucketCredentials,
SetGarageS3APIGlobalBucketCredentials = secrets.GetSetFunctions[garage.S3APICredentials](
GarageS3APIGlobalBucketCredentialsSecretID,
)
)

View File

@ -12,6 +12,7 @@ import (
"io"
"io/fs"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/jsonutil"
"isle/nebula"
"isle/secrets"
@ -33,7 +34,8 @@ type Opts struct {
// will be directed to.
Stdout, Stderr io.Writer
EnvVars EnvVars // Defaults to that returned by GetEnvVars.
// Defaults to that returned by daecommon.GetEnvVars.
EnvVars daecommon.EnvVars
}
func (o *Opts) withDefaults() *Opts {
@ -49,8 +51,8 @@ func (o *Opts) withDefaults() *Opts {
o.Stderr = os.Stderr
}
if o.EnvVars == (EnvVars{}) {
o.EnvVars = GetEnvVars()
if o.EnvVars == (daecommon.EnvVars{}) {
o.EnvVars = daecommon.GetEnvVars()
}
return o
@ -83,7 +85,7 @@ var _ RPC = (*Daemon)(nil)
// canceled.
type Daemon struct {
logger *mlog.Logger
daemonConfig Config
daemonConfig daecommon.Config
envBinDirPath string
opts *Opts
@ -103,7 +105,7 @@ type Daemon struct {
func NewDaemon(
ctx context.Context,
logger *mlog.Logger,
daemonConfig Config,
daemonConfig daecommon.Config,
envBinDirPath string,
opts *Opts,
) (
@ -121,7 +123,7 @@ func NewDaemon(
bootstrapFilePath = bootstrap.StateDirPath(d.opts.EnvVars.StateDirPath)
)
if err := d.opts.EnvVars.init(); err != nil {
if err := createDirs(d.opts.EnvVars); err != nil {
return nil, fmt.Errorf("initializing daemon directories: %w", err)
}
@ -151,6 +153,27 @@ func NewDaemon(
return d, nil
}
func createDirs(e daecommon.EnvVars) error {
var errs []error
if err := mkDir(e.RuntimeDirPath); err != nil {
errs = append(errs, fmt.Errorf(
"creating runtime directory %q: %w",
e.RuntimeDirPath,
err,
))
}
if err := mkDir(e.StateDirPath); err != nil {
errs = append(errs, fmt.Errorf(
"creating state directory %q: %w",
e.StateDirPath,
err,
))
}
return errors.Join(errs...)
}
// initialize must be called with d.l write lock held.
func (d *Daemon) initialize(
ctx context.Context, currBootstrap bootstrap.Bootstrap,
@ -312,7 +335,9 @@ func (d *Daemon) postInit(ctx context.Context) error {
//
// TODO this is pretty hacky, but there doesn't seem to be a better way to
// manage it at the moment.
_, err := getGarageS3APIGlobalBucketCredentials(ctx, d.secretsStore)
_, err := daecommon.GetGarageS3APIGlobalBucketCredentials(
ctx, d.secretsStore,
)
if errors.Is(err, secrets.ErrNotFound) {
d.logger.Info(ctx, "Initializing garage shared global bucket")
garageGlobalBucketCreds, err := garageInitializeGlobalBucket(
@ -326,7 +351,7 @@ func (d *Daemon) postInit(ctx context.Context) error {
return fmt.Errorf("initializing global bucket: %w", err)
}
err = setGarageS3APIGlobalBucketCredentials(
err = daecommon.SetGarageS3APIGlobalBucketCredentials(
ctx, d.secretsStore, garageGlobalBucketCreds,
)
if err != nil {
@ -406,12 +431,14 @@ func (d *Daemon) CreateNetwork(
garageRPCSecret = randStr(32)
)
err = setGarageRPCSecret(ctx, d.secretsStore, garageRPCSecret)
err = daecommon.SetGarageRPCSecret(ctx, d.secretsStore, garageRPCSecret)
if err != nil {
return fmt.Errorf("setting garage RPC secret: %w", err)
}
err = setNebulaCASigningPrivateKey(ctx, d.secretsStore, nebulaCACreds.SigningPrivateKey)
err = daecommon.SetNebulaCASigningPrivateKey(
ctx, d.secretsStore, nebulaCACreds.SigningPrivateKey,
)
if err != nil {
return fmt.Errorf("setting nebula CA signing key secret: %w", err)
}
@ -679,7 +706,7 @@ func (d *Daemon) CreateHost(
}
// TODO if the ip is given, check that it's not already in use.
caSigningPrivateKey, err := getNebulaCASigningPrivateKey(
caSigningPrivateKey, err := daecommon.GetNebulaCASigningPrivateKey(
ctx, d.secretsStore,
)
if err != nil {
@ -701,12 +728,14 @@ func (d *Daemon) CreateHost(
}
secretsIDs := []secrets.ID{
garageRPCSecretSecretID,
garageS3APIGlobalBucketCredentialsSecretID,
daecommon.GarageRPCSecretSecretID,
daecommon.GarageS3APIGlobalBucketCredentialsSecretID,
}
if opts.CanCreateHosts {
secretsIDs = append(secretsIDs, nebulaCASigningPrivateKeySecretID)
secretsIDs = append(
secretsIDs, daecommon.NebulaCASigningPrivateKeySecretID,
)
}
if joiningBootstrap.Secrets, err = secrets.Export(
@ -760,7 +789,7 @@ func (d *Daemon) CreateNebulaCertificate(
}
ip := host.IP()
caSigningPrivateKey, err := getNebulaCASigningPrivateKey(
caSigningPrivateKey, err := daecommon.GetNebulaCASigningPrivateKey(
ctx, d.secretsStore,
)
if err != nil {

View File

@ -1,130 +0,0 @@
package daemon
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"github.com/adrg/xdg"
)
// EnvVars are variables which are derived based on the environment which the
// process is running in.
type EnvVars struct {
RuntimeDirPath string
StateDirPath string
}
func (e EnvVars) init() error {
var errs []error
if err := mkDir(e.RuntimeDirPath); err != nil {
errs = append(errs, fmt.Errorf(
"creating runtime directory %q: %w",
e.RuntimeDirPath,
err,
))
}
if err := mkDir(e.StateDirPath); err != nil {
errs = append(errs, fmt.Errorf(
"creating state directory %q: %w",
e.StateDirPath,
err,
))
}
return errors.Join(errs...)
}
func getDefaultHTTPSocketDirPath() string {
path, err := firstExistingDir(
"/tmp",
// TODO it's possible the daemon process can't actually write to these
"/run",
"/var/run",
"/dev/shm",
)
if err != nil {
panic(fmt.Sprintf("Failed to find directory for HTTP socket: %v", err))
}
return path
}
// HTTPSocketPath returns the path to the daemon's HTTP socket which is used for
// RPC and other functionality.
var HTTPSocketPath = sync.OnceValue(func() string {
return envOr(
"ISLE_DAEMON_HTTP_SOCKET_PATH",
func() string {
return filepath.Join(
getDefaultHTTPSocketDirPath(), "isle-daemon.sock",
)
},
)
})
// GetEnvVars will return the EnvVars of the current processes, as determined by
// the process's environment.
var GetEnvVars = sync.OnceValue(func() (v EnvVars) {
// RUNTIME_DIRECTORY/STATE_DIRECTORY are used by the systemd service in
// conjunction with the RuntimeDirectory/StateDirectory directives.
v.RuntimeDirPath = envOr(
"RUNTIME_DIRECTORY",
func() string { return filepath.Join(xdg.RuntimeDir, "isle") },
)
v.StateDirPath = envOr(
"STATE_DIRECTORY",
func() string { return filepath.Join(xdg.StateHome, "isle") },
)
return
})
////////////////////////////////////////////////////////////////////////////////
// Jigs
func envOr(name string, fallback func() string) string {
if v := os.Getenv(name); v != "" {
return v
}
return fallback()
}
func firstExistingDir(paths ...string) (string, error) {
var errs []error
for _, path := range paths {
stat, err := os.Stat(path)
switch {
case errors.Is(err, fs.ErrExist):
continue
case err != nil:
errs = append(
errs, fmt.Errorf("checking if path %q exists: %w", path, err),
)
case !stat.IsDir():
errs = append(
errs, fmt.Errorf("path %q exists but is not a directory", path),
)
default:
return path, nil
}
}
err := fmt.Errorf(
"no directory found at any of the following paths: %s",
strings.Join(paths, ", "),
)
if len(errs) > 0 {
err = errors.Join(slices.Insert(errs, 0, err)...)
}
return "", err
}

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/garage"
"isle/secrets"
)
@ -24,12 +25,14 @@ func (d *Daemon) getGarageClientParams(
) (
GarageClientParams, error,
) {
creds, err := getGarageS3APIGlobalBucketCredentials(ctx, d.secretsStore)
creds, err := daecommon.GetGarageS3APIGlobalBucketCredentials(
ctx, d.secretsStore,
)
if err != nil {
return GarageClientParams{}, fmt.Errorf("getting garage global bucket creds: %w", err)
}
rpcSecret, err := getGarageRPCSecret(ctx, d.secretsStore)
rpcSecret, err := daecommon.GetGarageRPCSecret(ctx, d.secretsStore)
if err != nil && !errors.Is(err, secrets.ErrNotFound) {
return GarageClientParams{}, fmt.Errorf("getting garage rpc secret: %w", err)
}

View File

@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/garage"
"isle/nebula"
"path/filepath"
@ -23,7 +24,7 @@ const (
func garageInitializeGlobalBucket(
ctx context.Context,
logger *mlog.Logger,
daemonConfig Config,
daemonConfig daecommon.Config,
adminToken string,
hostBootstrap bootstrap.Bootstrap,
) (

View File

@ -1,52 +0,0 @@
package daemon
import (
"fmt"
"isle/garage"
"isle/nebula"
"isle/secrets"
)
const (
secretsNSNebula = "nebula"
secretsNSGarage = "garage"
)
////////////////////////////////////////////////////////////////////////////////
// Nebula-related secrets
var (
nebulaCASigningPrivateKeySecretID = secrets.NewID(secretsNSNebula, "ca-signing-private-key")
)
var getNebulaCASigningPrivateKey, setNebulaCASigningPrivateKey = secrets.GetSetFunctions[nebula.SigningPrivateKey](
nebulaCASigningPrivateKeySecretID,
)
////////////////////////////////////////////////////////////////////////////////
// Garage-related secrets
func garageS3APIBucketCredentialsSecretID(credsName string) secrets.ID {
return secrets.NewID(
secretsNSGarage, fmt.Sprintf("s3-api-bucket-credentials-%s", credsName),
)
}
var (
garageRPCSecretSecretID = secrets.NewID(secretsNSGarage, "rpc-secret")
garageS3APIGlobalBucketCredentialsSecretID = garageS3APIBucketCredentialsSecretID(
garage.GlobalBucketS3APICredentialsName,
)
)
// Get/Set functions for garage-related secrets.
var (
getGarageRPCSecret, setGarageRPCSecret = secrets.GetSetFunctions[string](
garageRPCSecretSecretID,
)
getGarageS3APIGlobalBucketCredentials,
setGarageS3APIGlobalBucketCredentials = secrets.GetSetFunctions[garage.S3APICredentials](
garageS3APIGlobalBucketCredentialsSecretID,
)
)