Move some environment variables into daemon package

This commit is contained in:
Brian Picciano 2024-06-24 14:45:57 +02:00
parent c3609252a5
commit c808fa81b9
12 changed files with 148 additions and 124 deletions

View File

@ -93,12 +93,18 @@ func New(
}, nil }, nil
} }
// FromReader reads a bootstrap file from the given io.Reader. // FromFile reads a bootstrap from a file at the given path. The HostAssigned
func FromReader(r io.Reader) (Bootstrap, error) { // field will automatically be unwrapped.
func FromFile(path string) (Bootstrap, error) {
f, err := os.Open(path)
if err != nil {
return Bootstrap{}, fmt.Errorf("opening file: %w", err)
}
defer f.Close()
var b Bootstrap var b Bootstrap
err := json.NewDecoder(r).Decode(&b) if err := json.NewDecoder(f).Decode(&b); err != nil {
if err != nil {
return Bootstrap{}, fmt.Errorf("decoding json: %w", err) return Bootstrap{}, fmt.Errorf("decoding json: %w", err)
} }
@ -106,19 +112,7 @@ func FromReader(r io.Reader) (Bootstrap, error) {
return Bootstrap{}, fmt.Errorf("unwrapping host assigned: %w", err) return Bootstrap{}, fmt.Errorf("unwrapping host assigned: %w", err)
} }
return b, err return b, nil
}
// FromFile reads a bootstrap from a file at the given path.
func FromFile(path string) (Bootstrap, error) {
f, err := os.Open(path)
if err != nil {
return Bootstrap{}, fmt.Errorf("opening file: %w", err)
}
defer f.Close()
return FromReader(f)
} }
// WriteTo writes the Bootstrap as a new bootstrap to the given io.Writer. // WriteTo writes the Bootstrap as a new bootstrap to the given io.Writer.

View File

@ -171,9 +171,7 @@ var subCmdAdminCreateNetwork = subCmd{
logger.WithNamespace("daemon"), logger.WithNamespace("daemon"),
daemonConfig, daemonConfig,
hostBootstrap, hostBootstrap,
envRuntimeDirPath,
envBinDirPath, envBinDirPath,
envStateDirPath,
&daemon.Opts{ &daemon.Opts{
// SkipHostBootstrapPush is required, because the global bucket // SkipHostBootstrapPush is required, because the global bucket
// hasn't actually been initialized yet, so there's nowhere to // hasn't actually been initialized yet, so there's nowhere to

View File

@ -11,7 +11,7 @@ import (
func loadHostBootstrap() (bootstrap.Bootstrap, error) { func loadHostBootstrap() (bootstrap.Bootstrap, error) {
stateDirPath := bootstrap.StateDirPath(envStateDirPath) stateDirPath := bootstrap.StateDirPath(daemonEnvVars.StateDirPath)
hostBootstrap, err := bootstrap.FromFile(stateDirPath) hostBootstrap, err := bootstrap.FromFile(stateDirPath)
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
@ -29,7 +29,7 @@ func loadHostBootstrap() (bootstrap.Bootstrap, error) {
func writeBootstrapToStateDir(hostBootstrap bootstrap.Bootstrap) error { func writeBootstrapToStateDir(hostBootstrap bootstrap.Bootstrap) error {
path := bootstrap.StateDirPath(envStateDirPath) path := bootstrap.StateDirPath(daemonEnvVars.StateDirPath)
dirPath := filepath.Dir(path) dirPath := filepath.Dir(path)
if err := os.MkdirAll(dirPath, 0700); err != nil { if err := os.MkdirAll(dirPath, 0700); err != nil {

View File

@ -98,9 +98,7 @@ func runDaemonPmuxOnce(
logger.WithNamespace("daemon"), logger.WithNamespace("daemon"),
daemonConfig, daemonConfig,
hostBootstrap, hostBootstrap,
envRuntimeDirPath,
envBinDirPath, envBinDirPath,
envStateDirPath,
nil, nil,
) )
if err != nil { if err != nil {
@ -214,7 +212,7 @@ var subCmdDaemon = subCmd{
defer runtimeDirCleanup() defer runtimeDirCleanup()
var ( var (
bootstrapStateDirPath = bootstrap.StateDirPath(envStateDirPath) bootstrapStateDirPath = bootstrap.StateDirPath(daemonEnvVars.StateDirPath)
bootstrapAppDirPath = bootstrap.AppDirPath(envAppDirPath) bootstrapAppDirPath = bootstrap.AppDirPath(envAppDirPath)
hostBootstrapPath string hostBootstrapPath string

View File

@ -66,10 +66,12 @@ func newHTTPServer(
) ( ) (
*http.Server, error, *http.Server, error,
) { ) {
l, err := net.Listen("unix", envSocketPath) l, err := net.Listen("unix", daemonEnvVars.HTTPSocketPath)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"failed to listen on socket %q: %w", envSocketPath, err, "failed to listen on socket %q: %w",
daemonEnvVars.HTTPSocketPath,
err,
) )
} }

View File

@ -13,7 +13,7 @@ import (
// order to prevent it from doing so. // order to prevent it from doing so.
func initMCConfigDir() (string, error) { func initMCConfigDir() (string, error) {
var ( var (
path = filepath.Join(envStateDirPath, "mc") path = filepath.Join(daemonEnvVars.StateDirPath, "mc")
sharePath = filepath.Join(path, "share") sharePath = filepath.Join(path, "share")
configJSONPath = filepath.Join(path, "config.json") configJSONPath = filepath.Join(path, "config.json")
) )

View File

@ -1,47 +0,0 @@
package main
import (
"errors"
"fmt"
"io/fs"
"os"
"slices"
"strings"
)
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

@ -2,7 +2,7 @@ package main
import ( import (
"context" "context"
"fmt" "isle/daemon"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
@ -10,14 +10,8 @@ import (
"dev.mediocregopher.com/mediocre-go-lib.git/mctx" "dev.mediocregopher.com/mediocre-go-lib.git/mctx"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog" "dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"github.com/adrg/xdg"
) )
// The purpose of this binary is to act as the entrypoint of the isle
// process. It processes the command-line arguments which are passed in, and
// then passes execution along to an appropriate binary housed in AppDir/bin
// (usually a bash script, which is more versatile than a go program).
func getAppDirPath() string { func getAppDirPath() string {
appDirPath := os.Getenv("APPDIR") appDirPath := os.Getenv("APPDIR")
if appDirPath == "" { if appDirPath == "" {
@ -26,39 +20,10 @@ func getAppDirPath() string {
return appDirPath return appDirPath
} }
func getRPCSocketDirPath() string {
path, err := firstExistingDir(
"/run",
"/var/run",
"/tmp",
"/dev/shm",
)
if err != nil {
panic(fmt.Sprintf("Failed to find directory for RPC socket: %v", err))
}
return path
}
// RUNTIME_DIRECTORY/STATE_DIRECTORY are used by the systemd service in
// conjunction with the RuntimeDirectory/StateDirectory directives.
var ( var (
daemonEnvVars = daemon.GetEnvVars()
envAppDirPath = getAppDirPath() envAppDirPath = getAppDirPath()
envRuntimeDirPath = envOr(
"RUNTIME_DIRECTORY",
func() string { return filepath.Join(xdg.RuntimeDir, "isle") },
)
envStateDirPath = envOr(
"STATE_DIRECTORY",
func() string { return filepath.Join(xdg.StateHome, "isle") },
)
envBinDirPath = filepath.Join(envAppDirPath, "bin") envBinDirPath = filepath.Join(envAppDirPath, "bin")
envSocketPath = envOr(
"ISLE_SOCKET_PATH",
func() string {
return filepath.Join(getRPCSocketDirPath(), "isle-daemon.sock")
},
)
) )
func binPath(name string) string { func binPath(name string) string {

View File

@ -16,7 +16,7 @@ import (
var errDaemonNotRunning = errors.New("no isle daemon process running") var errDaemonNotRunning = errors.New("no isle daemon process running")
func lockFilePath() string { func lockFilePath() string {
return filepath.Join(envRuntimeDirPath, "lock") return filepath.Join(daemonEnvVars.RuntimeDirPath, "lock")
} }
func writeLock() error { func writeLock() error {
@ -49,11 +49,11 @@ func writeLock() error {
// returns a cleanup function which will clean up the created runtime directory. // returns a cleanup function which will clean up the created runtime directory.
func setupAndLockRuntimeDir(ctx context.Context, logger *mlog.Logger) (func(), error) { func setupAndLockRuntimeDir(ctx context.Context, logger *mlog.Logger) (func(), error) {
ctx = mctx.Annotate(ctx, "runtimeDirPath", envRuntimeDirPath) ctx = mctx.Annotate(ctx, "runtimeDirPath", daemonEnvVars.RuntimeDirPath)
logger.Info(ctx, "will use runtime directory for temporary state") logger.Info(ctx, "will use runtime directory for temporary state")
if err := os.MkdirAll(envRuntimeDirPath, 0700); err != nil { if err := os.MkdirAll(daemonEnvVars.RuntimeDirPath, 0700); err != nil {
return nil, fmt.Errorf("creating directory %q: %w", envRuntimeDirPath, err) return nil, fmt.Errorf("creating directory %q: %w", daemonEnvVars.RuntimeDirPath, err)
} else if err := writeLock(); err != nil { } else if err := writeLock(); err != nil {
return nil, err return nil, err
@ -61,7 +61,7 @@ func setupAndLockRuntimeDir(ctx context.Context, logger *mlog.Logger) (func(), e
return func() { return func() {
logger.Info(ctx, "cleaning up runtime directory") logger.Info(ctx, "cleaning up runtime directory")
if err := os.RemoveAll(envRuntimeDirPath); err != nil { if err := os.RemoveAll(daemonEnvVars.RuntimeDirPath); err != nil {
logger.Error(ctx, "removing temporary directory", err) logger.Error(ctx, "removing temporary directory", err)
} }
}, nil }, nil

View File

@ -110,7 +110,7 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
} }
daemonRCPClient := jsonrpc2.NewUnixHTTPClient( daemonRCPClient := jsonrpc2.NewUnixHTTPClient(
envSocketPath, daemonHTTPRPCPath, daemonEnvVars.HTTPSocketPath, daemonHTTPRPCPath,
) )
err := subCmd.do(subCmdCtx{ err := subCmd.do(subCmdCtx{

View File

@ -18,7 +18,7 @@ type daemon struct {
logger *mlog.Logger logger *mlog.Logger
config Config config Config
hostBootstrap bootstrap.Bootstrap hostBootstrap bootstrap.Bootstrap
stateDirPath string binDirPath string
opts Opts opts Opts
pmuxCancelFn context.CancelFunc pmuxCancelFn context.CancelFunc
@ -56,6 +56,8 @@ type Opts struct {
// Stdout and Stderr are what the associated outputs from child processes // Stdout and Stderr are what the associated outputs from child processes
// will be directed to. // will be directed to.
Stdout, Stderr io.Writer Stdout, Stderr io.Writer
EnvVars EnvVars // Defaults to that returned by GetEnvVars.
} }
func (o *Opts) withDefaults() *Opts { func (o *Opts) withDefaults() *Opts {
@ -71,6 +73,10 @@ func (o *Opts) withDefaults() *Opts {
o.Stderr = os.Stderr o.Stderr = os.Stderr
} }
if o.EnvVars == (EnvVars{}) {
o.EnvVars = GetEnvVars()
}
return o return o
} }
@ -81,7 +87,7 @@ func New(
logger *mlog.Logger, logger *mlog.Logger,
config Config, config Config,
hostBootstrap bootstrap.Bootstrap, hostBootstrap bootstrap.Bootstrap,
runtimeDirPath, binDirPath, stateDirPath string, binDirPath string,
opts *Opts, opts *Opts,
) ( ) (
Daemon, error, Daemon, error,
@ -89,21 +95,27 @@ func New(
opts = opts.withDefaults() opts = opts.withDefaults()
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig( nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(
runtimeDirPath, binDirPath, hostBootstrap, config, opts.EnvVars.RuntimeDirPath,
binDirPath,
hostBootstrap,
config,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("generating nebula config: %w", err) return nil, fmt.Errorf("generating nebula config: %w", err)
} }
dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig( dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(
runtimeDirPath, binDirPath, hostBootstrap, config, opts.EnvVars.RuntimeDirPath,
binDirPath,
hostBootstrap,
config,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("generating dnsmasq config: %w", err) return nil, fmt.Errorf("generating dnsmasq config: %w", err)
} }
garagePmuxProcConfigs, err := garagePmuxProcConfigs( garagePmuxProcConfigs, err := garagePmuxProcConfigs(
runtimeDirPath, binDirPath, hostBootstrap, config, opts.EnvVars.RuntimeDirPath, binDirPath, hostBootstrap, config,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("generating garage children configs: %w", err) return nil, fmt.Errorf("generating garage children configs: %w", err)
@ -125,7 +137,7 @@ func New(
logger: logger, logger: logger,
config: config, config: config,
hostBootstrap: hostBootstrap, hostBootstrap: hostBootstrap,
stateDirPath: stateDirPath, binDirPath: binDirPath,
opts: *opts, opts: *opts,
pmuxCancelFn: pmuxCancelFn, pmuxCancelFn: pmuxCancelFn,
pmuxStoppedCh: make(chan struct{}), pmuxStoppedCh: make(chan struct{}),

102
go/daemon/env.go Normal file
View File

@ -0,0 +1,102 @@
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 // TODO should be private to this package
StateDirPath string // TODO should be private to this package
HTTPSocketPath string
}
func getRPCSocketDirPath() string {
path, err := firstExistingDir(
"/run",
"/var/run",
"/tmp",
"/dev/shm",
)
if err != nil {
panic(fmt.Sprintf("Failed to find directory for RPC socket: %v", err))
}
return path
}
// 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") },
)
v.HTTPSocketPath = envOr(
"ISLE_SOCKET_PATH",
func() string {
return filepath.Join(getRPCSocketDirPath(), "isle-daemon.sock")
},
)
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
}