Simplify socket file path selection, only use /tmp

This commit is contained in:
Brian Picciano 2024-12-16 14:59:11 +01:00
parent 4151fe8f17
commit 73af69fa04
15 changed files with 232 additions and 167 deletions

View File

@ -6,6 +6,7 @@ import (
"fmt"
"io/fs"
"isle/daemon"
"isle/toolkit"
"net"
"net/http"
"os"
@ -14,14 +15,25 @@ import (
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
)
const daemonHTTPRPCPath = "/rpc/v0.json"
const (
daemonHTTPSocketDefaultPath = "/tmp/isle-daemon.sock"
daemonHTTPSocketPathEnvVar = "ISLE_DAEMON_HTTP_SOCKET_PATH"
daemonHTTPRPCPath = "/rpc/v0.json"
)
func httpSocketPath() string {
return toolkit.EnvOr(
daemonHTTPSocketPathEnvVar,
func() string { return daemonHTTPSocketDefaultPath },
)
}
func newHTTPServer(
ctx context.Context, logger *mlog.Logger, daemonInst *daemon.Daemon,
) (
*http.Server, error,
) {
socketPath := daemon.HTTPSocketPath()
socketPath := httpSocketPath()
ctx = mctx.Annotate(ctx, "socketPath", socketPath)
if err := os.Remove(socketPath); errors.Is(err, fs.ErrNotExist) {

View File

@ -52,7 +52,13 @@ var subCmdGarageMC = subCmd{
return fmt.Errorf("parsing flags: %w", err)
}
clientParams, err := ctx.daemonRPC.GetGarageClientParams(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
clientParams, err := daemonRPC.GetGarageClientParams(ctx)
if err != nil {
return fmt.Errorf("calling GetGarageClientParams: %w", err)
}
@ -124,7 +130,13 @@ var subCmdGarageCLI = subCmd{
return fmt.Errorf("parsing flags: %w", err)
}
clientParams, err := ctx.daemonRPC.GetGarageClientParams(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
clientParams, err := daemonRPC.GetGarageClientParams(ctx)
if err != nil {
return fmt.Errorf("calling GetGarageClientParams: %w", err)
}

View File

@ -40,7 +40,13 @@ var subCmdHostCreate = subCmd{
return errors.New("--hostname is required")
}
res, err := ctx.daemonRPC.CreateHost(
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
res, err := daemonRPC.CreateHost(
ctx, hostName.V, network.CreateHostOpts{
IP: ip.V,
CanCreateHosts: *canCreateHosts,
@ -63,7 +69,13 @@ var subCmdHostList = subCmd{
return nil, fmt.Errorf("parsing flags: %w", err)
}
currBoostrap, err := ctx.daemonRPC.GetBootstrap(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return nil, fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
currBoostrap, err := daemonRPC.GetBootstrap(ctx)
if err != nil {
return nil, fmt.Errorf("calling GetBootstrap: %w", err)
}
@ -131,7 +143,13 @@ var subCmdHostRemove = subCmd{
return errors.New("--hostname is required")
}
if err := ctx.daemonRPC.RemoveHost(ctx, hostName.V); err != nil {
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
if err := daemonRPC.RemoveHost(ctx, hostName.V); err != nil {
return fmt.Errorf("calling RemoveHost: %w", err)
}

View File

@ -3,8 +3,6 @@ package main
import (
"context"
"fmt"
"isle/daemon"
"isle/daemon/jsonrpc2"
"isle/toolkit"
"os"
"os/signal"
@ -66,10 +64,9 @@ var rootCmd = subCmd{
func doRootCmd(
ctx context.Context,
logger *mlog.Logger,
daemonRPC daemon.RPC,
opts *subCmdCtxOpts,
) error {
subCmdCtx := newSubCmdCtx(ctx, logger, daemonRPC, rootCmd, opts)
subCmdCtx := newSubCmdCtx(ctx, logger, rootCmd, opts)
return subCmdCtx.subCmd.do(subCmdCtx)
}
@ -95,19 +92,7 @@ func main() {
logger.FatalString(ctx, "second signal received, force quitting, there may be zombie children left behind, good luck!")
}()
httpClient, baseURL := toolkit.NewUnixHTTPClient(
logger.WithNamespace("http-client"),
daemon.HTTPSocketPath(),
)
defer httpClient.Close()
baseURL.Path = daemonHTTPRPCPath
daemonRPC := daemon.RPCFromClient(
jsonrpc2.NewHTTPClient(httpClient, baseURL.String()),
)
if err := doRootCmd(ctx, logger, daemonRPC, nil); err != nil {
if err := doRootCmd(ctx, logger, nil); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}

View File

@ -58,10 +58,11 @@ func (h *runHarness) run(t *testing.T, args ...string) error {
jsonrpc2.NewHTTPClient(httpClient, h.daemonRPCServer.URL),
)
return doRootCmd(h.ctx, h.logger, daemonRPCClient, &subCmdCtxOpts{
return doRootCmd(h.ctx, h.logger, &subCmdCtxOpts{
args: args,
stdout: h.stdout,
changeStager: h.changeStager,
daemonRPC: daemonRPCClient,
})
}

View File

@ -58,7 +58,13 @@ var subCmdNetworkCreate = subCmd{
return errors.New("--name, --domain, --ip-net, and --hostname are required")
}
err = ctx.daemonRPC.CreateNetwork(
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
err = daemonRPC.CreateNetwork(
ctx, *name, *domain, ipNet.V, hostName.V,
)
if err != nil {
@ -95,7 +101,13 @@ var subCmdNetworkJoin = subCmd{
)
}
return ctx.daemonRPC.JoinNetwork(ctx, newBootstrap)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
return daemonRPC.JoinNetwork(ctx, newBootstrap)
},
}
@ -110,7 +122,13 @@ var subCmdNetworkList = subCmd{
return nil, fmt.Errorf("parsing flags: %w", err)
}
networkCreationParams, err := ctx.daemonRPC.GetNetworks(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return nil, fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
networkCreationParams, err := daemonRPC.GetNetworks(ctx)
if err != nil {
return nil, fmt.Errorf("calling GetNetworks: %w", err)
}
@ -127,10 +145,7 @@ var subCmdNetworkList = subCmd{
Lighthouses []lighthouseView `yaml:"lighthouses"`
}
var (
daemonRPC = ctx.daemonRPC
networkViews = make([]networkView, len(networkCreationParams))
)
networkViews := make([]networkView, len(networkCreationParams))
for i, creationParams := range networkCreationParams {
ctx := daemon.WithNetwork(ctx, creationParams.ID)
@ -191,7 +206,13 @@ var subCmdNetworkGetConfig = subCmd{
return nil, fmt.Errorf("parsing flags: %w", err)
}
return ctx.daemonRPC.GetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return nil, fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
return daemonRPC.GetConfig(ctx)
}),
}

View File

@ -98,14 +98,20 @@ var subCmdStorageAllocationAdd = subCmd{
)
}
config, err := ctx.daemonRPC.GetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
config, err := daemonRPC.GetConfig(ctx)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
config.Storage.Allocations = append(config.Storage.Allocations, alloc)
if err := ctx.daemonRPC.SetConfig(ctx, config); err != nil {
if err := daemonRPC.SetConfig(ctx, config); err != nil {
return fmt.Errorf("updating the network config: %w", err)
}
@ -122,7 +128,13 @@ var subCmdStorageAllocationList = subCmd{
return nil, fmt.Errorf("parsing flags: %w", err)
}
config, err := ctx.daemonRPC.GetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return nil, fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
config, err := daemonRPC.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("getting network config: %w", err)
}
@ -152,7 +164,13 @@ var subCmdStorageAllocationRemove = subCmd{
return errors.New("At least one --index must be specified")
}
config, err := ctx.daemonRPC.GetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
config, err := daemonRPC.GetConfig(ctx)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
@ -186,7 +204,7 @@ var subCmdStorageAllocationRemove = subCmd{
)
config.Storage.Allocations = newAllocs
if err := ctx.daemonRPC.SetConfig(ctx, config); err != nil {
if err := daemonRPC.SetConfig(ctx, config); err != nil {
return fmt.Errorf("updating the network config: %w", err)
}

View File

@ -6,7 +6,9 @@ import (
"fmt"
"io"
"isle/daemon"
"isle/daemon/jsonrpc2"
"isle/jsonutil"
"isle/toolkit"
"os"
"strings"
@ -37,6 +39,7 @@ type subCmdCtxOpts struct {
subCmdNames []string // names of subCmds so far, including this one
stdout io.Writer
changeStager *changeStager
daemonRPC daemon.RPC
}
func (o *subCmdCtxOpts) withDefaults() *subCmdCtxOpts {
@ -58,10 +61,9 @@ func (o *subCmdCtxOpts) withDefaults() *subCmdCtxOpts {
// subCmdCtx contains all information available to a subCmd's do method.
type subCmdCtx struct {
context.Context
logger *mlog.Logger
daemonRPC daemon.RPC
subCmd subCmd // the subCmd itself
opts *subCmdCtxOpts
logger *mlog.Logger
subCmd subCmd // the subCmd itself
opts *subCmdCtxOpts
flags *pflag.FlagSet
}
@ -69,22 +71,55 @@ type subCmdCtx struct {
func newSubCmdCtx(
ctx context.Context,
logger *mlog.Logger,
daemonRPC daemon.RPC,
subCmd subCmd,
opts *subCmdCtxOpts,
) subCmdCtx {
opts = opts.withDefaults()
return subCmdCtx{
Context: ctx,
logger: logger,
daemonRPC: daemonRPC,
subCmd: subCmd,
opts: opts,
flags: pflag.NewFlagSet(subCmd.name, pflag.ExitOnError),
Context: ctx,
logger: logger,
subCmd: subCmd,
opts: opts,
flags: pflag.NewFlagSet(subCmd.name, pflag.ExitOnError),
}
}
type daemonRPCCloser struct {
daemon.RPC
Close func() error
}
func (ctx subCmdCtx) newDaemonRPC() (*daemonRPCCloser, error) {
if ctx.opts.daemonRPC != nil {
return &daemonRPCCloser{
ctx.opts.daemonRPC, func() error { return nil },
}, nil
}
socketPath := httpSocketPath()
if stat, err := os.Stat(socketPath); err != nil {
return nil, fmt.Errorf("checking http socket file: %w", err)
} else if stat.Mode().Type() != os.ModeSocket {
return nil, fmt.Errorf("%q exists but is not a socket", socketPath)
}
httpClient, baseURL := toolkit.NewUnixHTTPClient(
ctx.logger.WithNamespace("http-client"),
socketPath,
)
baseURL.Path = daemonHTTPRPCPath
daemonRPC := daemon.RPCFromClient(
jsonrpc2.NewHTTPClient(httpClient, baseURL.String()),
)
return &daemonRPCCloser{
daemonRPC, func() error { return httpClient.Close() },
}, nil
}
func (ctx subCmdCtx) getChangeStager() *changeStager {
if ctx.opts.changeStager != nil {
return ctx.opts.changeStager
@ -258,7 +293,7 @@ func (ctx subCmdCtx) doSubCmd(subCmds ...subCmd) error {
nextSubCmdCtxOpts.subCmdNames = append(ctx.opts.subCmdNames, subCmdName)
nextSubCmdCtx := newSubCmdCtx(
ctx.Context, ctx.logger, ctx.daemonRPC, subCmd, &nextSubCmdCtxOpts,
ctx.Context, ctx.logger, subCmd, &nextSubCmdCtxOpts,
)
if err := subCmd.do(nextSubCmdCtx); err != nil {

View File

@ -43,7 +43,13 @@ var subCmdVPNCreateCert = subCmd{
return fmt.Errorf("unmarshaling public key as PEM: %w", err)
}
res, err := ctx.daemonRPC.CreateNebulaCertificate(
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
res, err := daemonRPC.CreateNebulaCertificate(
ctx, hostName.V, hostPub,
)
if err != nil {

View File

@ -3,6 +3,7 @@ package main
import (
"errors"
"fmt"
"isle/daemon"
"isle/daemon/daecommon"
"slices"
"strings"
@ -14,8 +15,12 @@ const vpnFirewallConfigChangeStagerName = "vpn-firewall-config"
// vpnFirewallGetConfigWithStaged returns the network config along with any
// staged firewall configuration changes, if there are any.
func vpnFirewallGetConfig(ctx subCmdCtx) (daecommon.NetworkConfig, error) {
config, err := ctx.daemonRPC.GetConfig(ctx)
func vpnFirewallGetConfig(
ctx subCmdCtx, daemonRPC daemon.RPC,
) (
daecommon.NetworkConfig, error,
) {
config, err := daemonRPC.GetConfig(ctx)
if err != nil {
return daecommon.NetworkConfig{}, err
}
@ -110,7 +115,13 @@ var subCmdVPNFirewallAdd = subCmd{
rule.Host = "any"
}
config, err := vpnFirewallGetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
config, err := vpnFirewallGetConfig(ctx, daemonRPC)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
@ -151,14 +162,20 @@ var subCmdVPNFirewallCommit = subCmd{
return errors.New("no changes staged, use 'add' or 'remove' to stage changes")
}
config, err := ctx.daemonRPC.GetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
config, err := daemonRPC.GetConfig(ctx)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
config.VPN.Firewall = firewallConfig
return ctx.daemonRPC.SetConfig(ctx, config)
return daemonRPC.SetConfig(ctx, config)
},
}
@ -192,6 +209,12 @@ var subCmdVPNFirewallRemove = subCmd{
return fmt.Errorf("invalid --from value %q: %w", *from, err)
}
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
indexSet := map[int]struct{}{}
for _, index := range *indexes {
if index < 0 {
@ -200,7 +223,7 @@ var subCmdVPNFirewallRemove = subCmd{
indexSet[index] = struct{}{}
}
config, err := vpnFirewallGetConfig(ctx)
config, err := vpnFirewallGetConfig(ctx, daemonRPC)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
@ -309,7 +332,13 @@ var subCmdVPNFirewallShow = subCmd{
}
if !foundStaged {
config, err := ctx.daemonRPC.GetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return nil, fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
config, err := daemonRPC.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("getting network config: %w", err)
}

View File

@ -15,7 +15,13 @@ var subCmdVPNPublicAddressGet = subCmd{
return nil, fmt.Errorf("parsing flags: %w", err)
}
config, err := ctx.daemonRPC.GetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return nil, fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
config, err := daemonRPC.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("getting network config: %w", err)
}
@ -49,14 +55,20 @@ var subCmdVPNPublicAddressSet = subCmd{
return fmt.Errorf("invalid --public-addr: %w", err)
}
config, err := ctx.daemonRPC.GetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
config, err := daemonRPC.GetConfig(ctx)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
config.VPN.PublicAddr = *publicAddr
return ctx.daemonRPC.SetConfig(ctx, config)
return daemonRPC.SetConfig(ctx, config)
},
}
@ -69,14 +81,20 @@ var subCmdVPNPublicAddressUnset = subCmd{
return fmt.Errorf("parsing flags: %w", err)
}
config, err := ctx.daemonRPC.GetConfig(ctx)
daemonRPC, err := ctx.newDaemonRPC()
if err != nil {
return fmt.Errorf("creating daemon RPC client: %w", err)
}
defer daemonRPC.Close()
config, err := daemonRPC.GetConfig(ctx)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
config.VPN.PublicAddr = ""
return ctx.daemonRPC.SetConfig(ctx, config)
return daemonRPC.SetConfig(ctx, config)
},
}

View File

@ -2,46 +2,12 @@ package daemon
import (
"context"
"errors"
"fmt"
"io/fs"
"isle/bootstrap"
"isle/daemon/daecommon"
"isle/daemon/network"
"os"
"path/filepath"
"slices"
"strings"
"sync"
)
func getDefaultHTTPSocketDirPath() string {
path, err := firstExistingDir(
"/tmp",
"/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",
)
},
)
})
func pickNetworkConfig(
networkConfigs map[string]daecommon.NetworkConfig,
creationParams bootstrap.CreationParams,
@ -88,43 +54,3 @@ func validateConfig(
return daemonConfig.Validate()
}
////////////////////////////////////////////////////////////////////////////////
// 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

@ -2,7 +2,6 @@ package daecommon
import (
"isle/toolkit"
"os"
"path/filepath"
"sync"
@ -25,12 +24,12 @@ var GetEnvVars = sync.OnceValue(func() EnvVars {
var (
res EnvVars
stateDirPath = envOr(
stateDirPath = toolkit.EnvOr(
"STATE_DIRECTORY",
func() string { return filepath.Join(xdg.StateHome, "isle") },
)
runtimeDirPath = envOr(
runtimeDirPath = toolkit.EnvOr(
"RUNTIME_DIRECTORY",
func() string { return filepath.Join(xdg.RuntimeDir, "isle") },
)
@ -46,13 +45,3 @@ var GetEnvVars = sync.OnceValue(func() EnvVars {
return res
})
////////////////////////////////////////////////////////////////////////////////
// Jigs
func envOr(name string, fallback func() string) string {
if v := os.Getenv(name); v != "" {
return v
}
return fallback()
}

12
go/toolkit/env.go Normal file
View File

@ -0,0 +1,12 @@
package toolkit
import "os"
// EnvOr returns the value of the given environment variable, or the return of
// the given callback if the environment variable isn't set.
func EnvOr(name string, fallback func() string) string {
if v := os.Getenv(name); v != "" {
return v
}
return fallback()
}

View File

@ -1,17 +0,0 @@
---
type: task
---
# Audit HTTP Socket Directory Search Options
We are currently searching these directories for the first writeable one, within
which to create the socket file:
- /tmp
- /run
- /var/run
- /dev/shm
It would be ideal to not default to `/tmp`, but it seems that some of the others
aren't necessarily available always, or aren't readable/writeable by the `isle`
user.