diff --git a/entrypoint/src/bootstrap/bootstrap.go b/entrypoint/src/bootstrap/bootstrap.go index 1e75327..aa22b91 100644 --- a/entrypoint/src/bootstrap/bootstrap.go +++ b/entrypoint/src/bootstrap/bootstrap.go @@ -25,6 +25,18 @@ const ( hostNamePath = "hostname" ) +// DataDirPath returns the path within the user's data directory where the +// bootstrap file is stored. +func DataDirPath(dataDirPath string) string { + return filepath.Join(dataDirPath, "bootstrap.tgz") +} + +// AppDirPath returns the path within the AppDir where an embedded bootstrap +// file might be found. +func AppDirPath(appDirPath string) string { + return filepath.Join(appDirPath, "share/bootstrap.tgz") +} + // Bootstrap is used for accessing all information contained within a // bootstrap.tgz file. type Bootstrap struct { diff --git a/entrypoint/src/cmd/entrypoint/admin.go b/entrypoint/src/cmd/entrypoint/admin.go index 3ab05aa..1cce0e2 100644 --- a/entrypoint/src/cmd/entrypoint/admin.go +++ b/entrypoint/src/cmd/entrypoint/admin.go @@ -148,7 +148,7 @@ var subCmdAdminCreateNetwork = subCmd{ return fmt.Errorf("creating nebula cert for host: %w", err) } - env.Bootstrap = bootstrap.Bootstrap{ + hostBootstrap := bootstrap.Bootstrap{ AdminCreationParams: adminCreationParams, Hosts: map[string]bootstrap.Host{ *hostName: bootstrap.Host{ @@ -165,16 +165,16 @@ var subCmdAdminCreateNetwork = subCmd{ GarageGlobalBucketS3APICredentials: garage.NewS3APICredentials(), } - if env, err = mergeDaemonConfigIntoBootstrap(env, daemonConfig); err != nil { + if hostBootstrap, err = mergeDaemonConfigIntoBootstrap(env, hostBootstrap, daemonConfig); err != nil { return fmt.Errorf("merging daemon config into bootstrap data: %w", err) } - nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env, daemonConfig) + nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env, hostBootstrap, daemonConfig) if err != nil { return fmt.Errorf("generating nebula config: %w", err) } - garagePmuxProcConfigs, err := garagePmuxProcConfigs(env, daemonConfig) + garagePmuxProcConfigs, err := garagePmuxProcConfigs(env, hostBootstrap, daemonConfig) if err != nil { return fmt.Errorf("generating garage configs: %w", err) } @@ -206,17 +206,17 @@ var subCmdAdminCreateNetwork = subCmd{ }() fmt.Fprintln(os.Stderr, "waiting for garage instances to come online") - if err := waitForGarageAndNebula(ctx, env, daemonConfig); err != nil { + if err := waitForGarageAndNebula(ctx, hostBootstrap, daemonConfig); err != nil { return fmt.Errorf("waiting for garage to start up: %w", err) } fmt.Fprintln(os.Stderr, "applying initial garage layout") - if err := garageApplyLayout(ctx, env, daemonConfig); err != nil { + if err := garageApplyLayout(ctx, hostBootstrap, daemonConfig); err != nil { return fmt.Errorf("applying initial garage layout: %w", err) } fmt.Fprintln(os.Stderr, "initializing garage shared global bucket") - err = garageInitializeGlobalBucket(ctx, env, daemonConfig) + err = garageInitializeGlobalBucket(ctx, hostBootstrap, daemonConfig) if cErr := (garage.AdminClientError{}); errors.As(err, &cErr) && cErr.StatusCode == 409 { return fmt.Errorf("shared global bucket has already been created, are the storage allocations from a previously initialized cryptic-net being used?") @@ -230,8 +230,8 @@ var subCmdAdminCreateNetwork = subCmd{ err = admin.Admin{ CreationParams: adminCreationParams, NebulaCACert: nebulaCACert, - GarageRPCSecret: env.Bootstrap.GarageRPCSecret, - GarageGlobalBucketS3APICredentials: env.Bootstrap.GarageGlobalBucketS3APICredentials, + GarageRPCSecret: hostBootstrap.GarageRPCSecret, + GarageGlobalBucketS3APICredentials: hostBootstrap.GarageGlobalBucketS3APICredentials, GarageAdminBucketS3APICredentials: garage.NewS3APICredentials(), }.WriteTo(os.Stdout) @@ -271,17 +271,22 @@ var subCmdAdminMakeBootstrap = subCmd{ env := subCmdCtx.env + hostBootstrap, err := loadHostBootstrap(env.DataDirPath) + if err != nil { + return fmt.Errorf("loading host bootstrap: %w", err) + } + adm, err := readAdmin(*adminPath) if err != nil { return fmt.Errorf("reading admin.tgz with --admin-path of %q: %w", *adminPath, err) } - client := env.Bootstrap.GlobalBucketS3APIClient() + client := hostBootstrap.GlobalBucketS3APIClient() // NOTE this isn't _technically_ required, but if the `hosts add` // command for this host has been run recently then it might not have // made it into the bootstrap file yet, and so won't be in - // `env.Bootstrap`. + // `hostBootstrap`. hosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, client) if err != nil { return fmt.Errorf("retrieving host info from garage: %w", err) @@ -302,7 +307,7 @@ var subCmdAdminMakeBootstrap = subCmd{ return fmt.Errorf("creating new nebula host key/cert: %w", err) } - newBootstrap := bootstrap.Bootstrap{ + newHostBootstrap := bootstrap.Bootstrap{ AdminCreationParams: adm.CreationParams, Hosts: hosts, @@ -315,7 +320,7 @@ var subCmdAdminMakeBootstrap = subCmd{ GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials, } - return newBootstrap.WriteTo(os.Stdout) + return newHostBootstrap.WriteTo(os.Stdout) }, } diff --git a/entrypoint/src/cmd/entrypoint/bootstrap_util.go b/entrypoint/src/cmd/entrypoint/bootstrap_util.go new file mode 100644 index 0000000..7092cf4 --- /dev/null +++ b/entrypoint/src/cmd/entrypoint/bootstrap_util.go @@ -0,0 +1,44 @@ +package main + +import ( + "cryptic-net/bootstrap" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" +) + +func loadHostBootstrap(dataDirPath string) (bootstrap.Bootstrap, error) { + + dataDirPath = bootstrap.DataDirPath(dataDirPath) + + hostBootstrap, err := bootstrap.FromFile(dataDirPath) + if errors.Is(err, fs.ErrNotExist) { + return bootstrap.Bootstrap{}, errors.New("%q not found, has the daemon ever been run?") + + } else if err != nil { + return bootstrap.Bootstrap{}, fmt.Errorf("loading %q: %w", dataDirPath, err) + } + + return hostBootstrap, nil +} + +func writeBootstrapToDataDir(dataDirPath string, hostBootstrap bootstrap.Bootstrap) error { + + path := bootstrap.DataDirPath(dataDirPath) + dirPath := filepath.Dir(path) + + if err := os.MkdirAll(dirPath, 0700); err != nil { + return fmt.Errorf("creating directory %q: %w", dirPath, err) + } + + f, err := os.Create(path) + if err != nil { + return fmt.Errorf("creating file %q: %w", path, err) + } + + defer f.Close() + + return hostBootstrap.WriteTo(f) +} diff --git a/entrypoint/src/cmd/entrypoint/daemon.go b/entrypoint/src/cmd/entrypoint/daemon.go index c6f7b62..0ddc382 100644 --- a/entrypoint/src/cmd/entrypoint/daemon.go +++ b/entrypoint/src/cmd/entrypoint/daemon.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "io/fs" "os" "sync" "time" @@ -38,71 +39,76 @@ import ( // creates a new bootstrap file using available information from the network. If // the new bootstrap file is different than the existing one, the existing one -// is overwritten, env's bootstrap is reloaded, true is returned. -func reloadBootstrap(env crypticnet.Env, s3Client garage.S3APIClient) (crypticnet.Env, bool, error) { +// is overwritten and true is returned. +func reloadBootstrap( + env crypticnet.Env, + hostBootstrap bootstrap.Bootstrap, + s3Client garage.S3APIClient, +) ( + bootstrap.Bootstrap, bool, error, +) { newHosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, s3Client) if err != nil { - return crypticnet.Env{}, false, fmt.Errorf("getting hosts from garage: %w", err) + return bootstrap.Bootstrap{}, false, fmt.Errorf("getting hosts from garage: %w", err) } newHostsHash, err := bootstrap.HostsHash(newHosts) if err != nil { - return crypticnet.Env{}, false, fmt.Errorf("calculating hash of new hosts: %w", err) + return bootstrap.Bootstrap{}, false, fmt.Errorf("calculating hash of new hosts: %w", err) } - currHostsHash, err := bootstrap.HostsHash(env.Bootstrap.Hosts) + currHostsHash, err := bootstrap.HostsHash(hostBootstrap.Hosts) if err != nil { - return crypticnet.Env{}, false, fmt.Errorf("calculating hash of current hosts: %w", err) + return bootstrap.Bootstrap{}, false, fmt.Errorf("calculating hash of current hosts: %w", err) } if bytes.Equal(newHostsHash, currHostsHash) { - return crypticnet.Env{}, false, nil + return hostBootstrap, false, nil } - buf := new(bytes.Buffer) - if err := env.Bootstrap.WithHosts(newHosts).WriteTo(buf); err != nil { - return crypticnet.Env{}, false, fmt.Errorf("writing new bootstrap file to buffer: %w", err) + newHostBootstrap := hostBootstrap.WithHosts(newHosts) + + if err := writeBootstrapToDataDir(env.DataDirPath, newHostBootstrap); err != nil { + return bootstrap.Bootstrap{}, false, fmt.Errorf("writing new bootstrap.tgz to data dir: %w", err) } - if env, err = copyBootstrapToDataDirAndReload(env, buf); err != nil { - return crypticnet.Env{}, false, fmt.Errorf("copying new bootstrap file to data dir: %w", err) - } - - return env, true, nil + return newHostBootstrap, true, nil } // runs a single pmux process of daemon, returning only once the env.Context has // been canceled or bootstrap info has been changed. This will always block -// until the spawned pmux has returned, and returns a copy of Env with updated -// boostrap info. +// until the spawned pmux has returned, and returns a copy of hostBootstrap with +// updated boostrap info. func runDaemonPmuxOnce( - env crypticnet.Env, daemonConfig daemon.Config, + env crypticnet.Env, + hostBootstrap bootstrap.Bootstrap, + daemonConfig daemon.Config, ) ( - crypticnet.Env, error, + bootstrap.Bootstrap, error, ) { - thisHost := env.Bootstrap.ThisHost() + thisHost := hostBootstrap.ThisHost() fmt.Fprintf(os.Stderr, "host name is %q, ip is %q\n", thisHost.Name, thisHost.Nebula.IP) // create s3Client anew on every loop, in case the topology has // changed and we should be connecting to a different garage // endpoint. - s3Client := env.Bootstrap.GlobalBucketS3APIClient() + s3Client := hostBootstrap.GlobalBucketS3APIClient() - nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env, daemonConfig) + nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env, hostBootstrap, daemonConfig) if err != nil { - return crypticnet.Env{}, fmt.Errorf("generating nebula config: %w", err) + return bootstrap.Bootstrap{}, fmt.Errorf("generating nebula config: %w", err) } - dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(env, daemonConfig) + dnsmasqPmuxProcConfig, err := dnsmasqPmuxProcConfig(env, hostBootstrap, daemonConfig) if err != nil { - return crypticnet.Env{}, fmt.Errorf("generating dnsmasq config: %w", err) + return bootstrap.Bootstrap{}, fmt.Errorf("generating dnsmasq config: %w", err) } - garagePmuxProcConfigs, err := garagePmuxProcConfigs(env, daemonConfig) + garagePmuxProcConfigs, err := garagePmuxProcConfigs(env, hostBootstrap, daemonConfig) if err != nil { - return crypticnet.Env{}, fmt.Errorf("generating garage children configs: %w", err) + return bootstrap.Bootstrap{}, fmt.Errorf("generating garage children configs: %w", err) } pmuxConfig := pmuxlib.Config{ @@ -133,12 +139,12 @@ func runDaemonPmuxOnce( go func() { defer wg.Done() - if err := waitForGarageAndNebula(ctx, env, daemonConfig); err != nil { + if err := waitForGarageAndNebula(ctx, hostBootstrap, daemonConfig); err != nil { fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err) return } - thisHost := env.Bootstrap.ThisHost() + thisHost := hostBootstrap.ThisHost() err := doOnce(ctx, func(ctx context.Context) error { fmt.Fprintln(os.Stderr, "updating host info in garage") @@ -155,14 +161,14 @@ func runDaemonPmuxOnce( go func() { defer wg.Done() - if err := waitForGarageAndNebula(ctx, env, daemonConfig); err != nil { + if err := waitForGarageAndNebula(ctx, hostBootstrap, daemonConfig); err != nil { fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err) return } err := doOnce(ctx, func(ctx context.Context) error { fmt.Fprintln(os.Stderr, "applying garage layout") - return garageApplyLayout(ctx, env, daemonConfig) + return garageApplyLayout(ctx, hostBootstrap, daemonConfig) }) if err != nil { @@ -178,7 +184,7 @@ func runDaemonPmuxOnce( select { case <-doneCh: - return crypticnet.Env{}, env.Context.Err() + return bootstrap.Bootstrap{}, env.Context.Err() case <-ticker.C: @@ -189,12 +195,12 @@ func runDaemonPmuxOnce( err error ) - if env, changed, err = reloadBootstrap(env, s3Client); err != nil { - return crypticnet.Env{}, fmt.Errorf("reloading bootstrap: %w", err) + if hostBootstrap, changed, err = reloadBootstrap(env, hostBootstrap, s3Client); err != nil { + return bootstrap.Bootstrap{}, fmt.Errorf("reloading bootstrap: %w", err) } else if changed { fmt.Fprintln(os.Stderr, "bootstrap info has changed, restarting all processes") - return env, nil + return hostBootstrap, nil } } } @@ -253,34 +259,51 @@ var subCmdDaemon = subCmd{ } }() - // If the bootstrap file is not being stored in the data dir, move it - // there and reload the bootstrap info - if env.BootstrapPath != env.DataDirBootstrapPath() { + var ( + bootstrapDataDirPath = bootstrap.DataDirPath(env.DataDirPath) + bootstrapAppDirPath = bootstrap.AppDirPath(env.AppDirPath) - path := env.BootstrapPath + hostBootstrapPath string + hostBootstrap bootstrap.Bootstrap + foundHostBootstrap bool - // If there's no BootstrapPath then no bootstrap file could be - // found. In this case we require the user to provide one on the - // command-line. - if path == "" { + err error + ) - if *bootstrapPath == "" { - return errors.New("No bootstrap.tgz file could be found, and one is not provided with --bootstrap-path") - } - - path = *bootstrapPath - } - - f, err := os.Open(path) - if err != nil { - return fmt.Errorf("opening file %q: %w", env.BootstrapPath, err) - } - - env, err = copyBootstrapToDataDirAndReload(env, f) - f.Close() + tryLoadBootstrap := func(path string) bool { if err != nil { - return fmt.Errorf("copying bootstrap file from %q: %w", path, err) + return false + + } else if hostBootstrap, err = bootstrap.FromFile(path); errors.Is(err, fs.ErrNotExist) { + err = nil + return false + + } else if err != nil { + err = fmt.Errorf("parsing bootstrap.tgz at %q: %w", path, err) + return false + } + + hostBootstrapPath = path + return true + } + + foundHostBootstrap = tryLoadBootstrap(bootstrapDataDirPath) + foundHostBootstrap = !foundHostBootstrap && *bootstrapPath != "" && tryLoadBootstrap(*bootstrapPath) + foundHostBootstrap = !foundHostBootstrap && tryLoadBootstrap(bootstrapAppDirPath) + + if err != nil { + return fmt.Errorf("attempting to load bootstrap.tgz file: %w", err) + + } else if !foundHostBootstrap { + return errors.New("No bootstrap.tgz file could be found, and one is not provided with --bootstrap-path") + + } else if hostBootstrapPath != bootstrapDataDirPath { + + // If the bootstrap file is not being stored in the data dir, copy + // it there, so it can be loaded from there next time. + if err := writeBootstrapToDataDir(env.DataDirPath, hostBootstrap); err != nil { + return fmt.Errorf("writing bootstrap.tgz to data dir: %w", err) } } @@ -294,13 +317,13 @@ var subCmdDaemon = subCmd{ // up-to-date possible bootstrap. This updated bootstrap will later get // updated in garage using bootstrap.PutGarageBoostrapHost, so other // hosts will see it as well. - if env, err = mergeDaemonConfigIntoBootstrap(env, daemonConfig); err != nil { + if hostBootstrap, err = mergeDaemonConfigIntoBootstrap(env, hostBootstrap, daemonConfig); err != nil { return fmt.Errorf("merging daemon config into bootstrap data: %w", err) } for { - env, err = runDaemonPmuxOnce(env, daemonConfig) + hostBootstrap, err = runDaemonPmuxOnce(env, hostBootstrap, daemonConfig) if errors.Is(err, context.Canceled) { return nil diff --git a/entrypoint/src/cmd/entrypoint/daemon_util.go b/entrypoint/src/cmd/entrypoint/daemon_util.go index 5c56c4c..dd5d853 100644 --- a/entrypoint/src/cmd/entrypoint/daemon_util.go +++ b/entrypoint/src/cmd/entrypoint/daemon_util.go @@ -1,48 +1,22 @@ package main import ( - "bytes" "context" crypticnet "cryptic-net" "cryptic-net/bootstrap" "cryptic-net/daemon" "fmt" - "io" - "os" - "path/filepath" "time" ) -func copyBootstrapToDataDirAndReload(env crypticnet.Env, r io.Reader) (crypticnet.Env, error) { - - path := env.DataDirBootstrapPath() - dirPath := filepath.Dir(path) - - if err := os.MkdirAll(dirPath, 0700); err != nil { - return crypticnet.Env{}, fmt.Errorf("creating directory %q: %w", dirPath, err) - } - - f, err := os.Create(path) - if err != nil { - return crypticnet.Env{}, fmt.Errorf("creating file %q: %w", path, err) - } - - _, err = io.Copy(f, r) - f.Close() - - if err != nil { - return crypticnet.Env{}, fmt.Errorf("copying bootstrap file to %q: %w", path, err) - } - - return env.LoadBootstrap(path) -} - func mergeDaemonConfigIntoBootstrap( - env crypticnet.Env, daemonConfig daemon.Config, + env crypticnet.Env, + hostBootstrap bootstrap.Bootstrap, + daemonConfig daemon.Config, ) ( - crypticnet.Env, error, + bootstrap.Bootstrap, error, ) { - host := env.Bootstrap.ThisHost() + host := hostBootstrap.ThisHost() host.Nebula.PublicAddr = daemonConfig.VPN.PublicAddr @@ -60,14 +34,13 @@ func mergeDaemonConfigIntoBootstrap( } } - env.Bootstrap.Hosts[host.Name] = host + hostBootstrap.Hosts[host.Name] = host - buf := new(bytes.Buffer) - if err := env.Bootstrap.WithHosts(env.Bootstrap.Hosts).WriteTo(buf); err != nil { - return crypticnet.Env{}, fmt.Errorf("writing new bootstrap file to buffer: %w", err) + if err := writeBootstrapToDataDir(env.DataDirPath, hostBootstrap); err != nil { + return bootstrap.Bootstrap{}, fmt.Errorf("writing bootstrap file: %w", err) } - return copyBootstrapToDataDirAndReload(env, buf) + return hostBootstrap, nil } func doOnce(ctx context.Context, fn func(context.Context) error) error { diff --git a/entrypoint/src/cmd/entrypoint/dnsmasq_util.go b/entrypoint/src/cmd/entrypoint/dnsmasq_util.go index 80fc7c8..feb7422 100644 --- a/entrypoint/src/cmd/entrypoint/dnsmasq_util.go +++ b/entrypoint/src/cmd/entrypoint/dnsmasq_util.go @@ -13,15 +13,17 @@ import ( ) func dnsmasqPmuxProcConfig( - env crypticnet.Env, daemonConfig daemon.Config, + env crypticnet.Env, + hostBootstrap bootstrap.Bootstrap, + daemonConfig daemon.Config, ) ( pmuxlib.ProcessConfig, error, ) { confPath := filepath.Join(env.RuntimeDirPath, "dnsmasq.conf") - hostsSlice := make([]bootstrap.Host, 0, len(env.Bootstrap.Hosts)) - for _, host := range env.Bootstrap.Hosts { + hostsSlice := make([]bootstrap.Host, 0, len(hostBootstrap.Hosts)) + for _, host := range hostBootstrap.Hosts { hostsSlice = append(hostsSlice, host) } @@ -31,8 +33,8 @@ func dnsmasqPmuxProcConfig( confData := dnsmasq.ConfData{ Resolvers: daemonConfig.DNS.Resolvers, - Domain: env.Bootstrap.AdminCreationParams.Domain, - IP: env.Bootstrap.ThisHost().Nebula.IP, + Domain: hostBootstrap.AdminCreationParams.Domain, + IP: hostBootstrap.ThisHost().Nebula.IP, Hosts: hostsSlice, } diff --git a/entrypoint/src/cmd/entrypoint/garage.go b/entrypoint/src/cmd/entrypoint/garage.go index b3bf3fd..ff4de28 100644 --- a/entrypoint/src/cmd/entrypoint/garage.go +++ b/entrypoint/src/cmd/entrypoint/garage.go @@ -30,16 +30,21 @@ var subCmdGarageMC = subCmd{ env := subCmdCtx.env - s3APIAddr := env.Bootstrap.ChooseGaragePeer().S3APIAddr() + hostBootstrap, err := loadHostBootstrap(env.DataDirPath) + if err != nil { + return fmt.Errorf("loading host bootstrap: %w", err) + } + + s3APIAddr := hostBootstrap.ChooseGaragePeer().S3APIAddr() if *keyID == "" || *keySecret == "" { if *keyID == "" { - *keyID = env.Bootstrap.GarageGlobalBucketS3APICredentials.ID + *keyID = hostBootstrap.GarageGlobalBucketS3APICredentials.ID } if *keySecret == "" { - *keyID = env.Bootstrap.GarageGlobalBucketS3APICredentials.Secret + *keyID = hostBootstrap.GarageGlobalBucketS3APICredentials.Secret } } @@ -85,13 +90,18 @@ var subCmdGarageCLI = subCmd{ env := subCmdCtx.env + hostBootstrap, err := loadHostBootstrap(env.DataDirPath) + if err != nil { + return fmt.Errorf("loading host bootstrap: %w", err) + } + var ( binPath = "garage" args = append([]string{"garage"}, subCmdCtx.args...) cliEnv = append( os.Environ(), - "GARAGE_RPC_HOST="+env.Bootstrap.ChooseGaragePeer().RPCAddr(), - "GARAGE_RPC_SECRET="+env.Bootstrap.GarageRPCSecret, + "GARAGE_RPC_HOST="+hostBootstrap.ChooseGaragePeer().RPCAddr(), + "GARAGE_RPC_SECRET="+hostBootstrap.GarageRPCSecret, ) ) diff --git a/entrypoint/src/cmd/entrypoint/garage_util.go b/entrypoint/src/cmd/entrypoint/garage_util.go index 91f101e..e2fa448 100644 --- a/entrypoint/src/cmd/entrypoint/garage_util.go +++ b/entrypoint/src/cmd/entrypoint/garage_util.go @@ -3,6 +3,7 @@ package main import ( "context" crypticnet "cryptic-net" + "cryptic-net/bootstrap" "cryptic-net/daemon" "cryptic-net/garage" "fmt" @@ -17,22 +18,24 @@ import ( // newGarageAdminClient will return an AdminClient for a local garage instance, // or it will _panic_ if there is no local instance configured. func newGarageAdminClient( - env crypticnet.Env, daemonConfig daemon.Config, + hostBootstrap bootstrap.Bootstrap, daemonConfig daemon.Config, ) *garage.AdminClient { - thisHost := env.Bootstrap.ThisHost() + thisHost := hostBootstrap.ThisHost() return garage.NewAdminClient( net.JoinHostPort( thisHost.Nebula.IP, strconv.Itoa(daemonConfig.Storage.Allocations[0].AdminPort), ), - env.Bootstrap.GarageAdminToken, + hostBootstrap.GarageAdminToken, ) } func waitForGarageAndNebula( - ctx context.Context, env crypticnet.Env, daemonConfig daemon.Config, + ctx context.Context, + hostBootstrap bootstrap.Bootstrap, + daemonConfig daemon.Config, ) error { allocs := daemonConfig.Storage.Allocations @@ -40,19 +43,19 @@ func waitForGarageAndNebula( // if this host doesn't have any allocations specified then fall back to // waiting for nebula if len(allocs) == 0 { - return waitForNebula(ctx, env) + return waitForNebula(ctx, hostBootstrap) } for _, alloc := range allocs { adminAddr := net.JoinHostPort( - env.Bootstrap.ThisHost().Nebula.IP, + hostBootstrap.ThisHost().Nebula.IP, strconv.Itoa(alloc.AdminPort), ) adminClient := garage.NewAdminClient( adminAddr, - env.Bootstrap.GarageAdminToken, + hostBootstrap.GarageAdminToken, ) if err := adminClient.Wait(ctx); err != nil { @@ -66,6 +69,7 @@ func waitForGarageAndNebula( func garageWriteChildConfig( env crypticnet.Env, + hostBootstrap bootstrap.Bootstrap, alloc daemon.ConfigStorageAllocation, ) ( string, error, @@ -75,7 +79,7 @@ func garageWriteChildConfig( return "", fmt.Errorf("making directory %q: %w", alloc.MetaPath, err) } - thisHost := env.Bootstrap.ThisHost() + thisHost := hostBootstrap.ThisHost() peer := garage.Peer{ IP: thisHost.Nebula.IP, @@ -103,14 +107,14 @@ func garageWriteChildConfig( MetaPath: alloc.MetaPath, DataPath: alloc.DataPath, - RPCSecret: env.Bootstrap.GarageRPCSecret, - AdminToken: env.Bootstrap.GarageAdminToken, + RPCSecret: hostBootstrap.GarageRPCSecret, + AdminToken: hostBootstrap.GarageAdminToken, RPCAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)), APIAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.S3APIPort)), AdminAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.AdminPort)), - BootstrapPeers: env.Bootstrap.GarageRPCPeerAddrs(), + BootstrapPeers: hostBootstrap.GarageRPCPeerAddrs(), }) if err != nil { @@ -121,7 +125,9 @@ func garageWriteChildConfig( } func garagePmuxProcConfigs( - env crypticnet.Env, daemonConfig daemon.Config, + env crypticnet.Env, + hostBootstrap bootstrap.Bootstrap, + daemonConfig daemon.Config, ) ( []pmuxlib.ProcessConfig, error, ) { @@ -130,7 +136,7 @@ func garagePmuxProcConfigs( for _, alloc := range daemonConfig.Storage.Allocations { - childConfigPath, err := garageWriteChildConfig(env, alloc) + childConfigPath, err := garageWriteChildConfig(env, hostBootstrap, alloc) if err != nil { return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err) @@ -141,7 +147,7 @@ func garagePmuxProcConfigs( Cmd: "garage", Args: []string{"-c", childConfigPath, "server"}, StartAfterFunc: func(ctx context.Context) error { - return waitForNebula(ctx, env) + return waitForNebula(ctx, hostBootstrap) }, }) } @@ -150,12 +156,14 @@ func garagePmuxProcConfigs( } func garageInitializeGlobalBucket( - ctx context.Context, env crypticnet.Env, daemonConfig daemon.Config, + ctx context.Context, + hostBootstrap bootstrap.Bootstrap, + daemonConfig daemon.Config, ) error { var ( - adminClient = newGarageAdminClient(env, daemonConfig) - globalBucketCreds = env.Bootstrap.GarageGlobalBucketS3APICredentials + adminClient = newGarageAdminClient(hostBootstrap, daemonConfig) + globalBucketCreds = hostBootstrap.GarageGlobalBucketS3APICredentials ) // first attempt to import the key @@ -210,12 +218,14 @@ func garageInitializeGlobalBucket( } func garageApplyLayout( - ctx context.Context, env crypticnet.Env, daemonConfig daemon.Config, + ctx context.Context, + hostBootstrap bootstrap.Bootstrap, + daemonConfig daemon.Config, ) error { var ( - adminClient = newGarageAdminClient(env, daemonConfig) - thisHost = env.Bootstrap.ThisHost() + adminClient = newGarageAdminClient(hostBootstrap, daemonConfig) + thisHost = hostBootstrap.ThisHost() hostName = thisHost.Name ip = thisHost.Nebula.IP allocs = daemonConfig.Storage.Allocations diff --git a/entrypoint/src/cmd/entrypoint/hosts.go b/entrypoint/src/cmd/entrypoint/hosts.go index 6fac6f9..65c1c19 100644 --- a/entrypoint/src/cmd/entrypoint/hosts.go +++ b/entrypoint/src/cmd/entrypoint/hosts.go @@ -60,7 +60,13 @@ var subCmdHostsAdd = subCmd{ // TODO validate that the IP is in the correct CIDR env := subCmdCtx.env - client := env.Bootstrap.GlobalBucketS3APIClient() + + hostBootstrap, err := loadHostBootstrap(env.DataDirPath) + if err != nil { + return fmt.Errorf("loading host bootstrap: %w", err) + } + + client := hostBootstrap.GlobalBucketS3APIClient() host := bootstrap.Host{ Name: *name, @@ -81,7 +87,12 @@ var subCmdHostsList = subCmd{ env := subCmdCtx.env - client := env.Bootstrap.GlobalBucketS3APIClient() + hostBootstrap, err := loadHostBootstrap(env.DataDirPath) + if err != nil { + return fmt.Errorf("loading host bootstrap: %w", err) + } + + client := hostBootstrap.GlobalBucketS3APIClient() hostsMap, err := bootstrap.GetGarageBootstrapHosts(env.Context, client) if err != nil { @@ -121,7 +132,13 @@ var subCmdHostsDelete = subCmd{ } env := subCmdCtx.env - client := env.Bootstrap.GlobalBucketS3APIClient() + + hostBootstrap, err := loadHostBootstrap(env.DataDirPath) + if err != nil { + return fmt.Errorf("loading host bootstrap: %w", err) + } + + client := hostBootstrap.GlobalBucketS3APIClient() return bootstrap.RemoveGarageBootstrapHost(env.Context, client, *name) }, diff --git a/entrypoint/src/cmd/entrypoint/main.go b/entrypoint/src/cmd/entrypoint/main.go index a54f4ce..0d73e67 100644 --- a/entrypoint/src/cmd/entrypoint/main.go +++ b/entrypoint/src/cmd/entrypoint/main.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" crypticnet "cryptic-net" @@ -14,13 +13,9 @@ import ( func main() { - env, err := crypticnet.NewEnv(true) + env := crypticnet.NewEnv() - if err != nil { - panic(fmt.Sprintf("loading environment: %v", err)) - } - - err = subCmdCtx{ + err := subCmdCtx{ args: os.Args[1:], env: env, }.doSubCmd( diff --git a/entrypoint/src/cmd/entrypoint/nebula_util.go b/entrypoint/src/cmd/entrypoint/nebula_util.go index 9dc50c8..dd195e5 100644 --- a/entrypoint/src/cmd/entrypoint/nebula_util.go +++ b/entrypoint/src/cmd/entrypoint/nebula_util.go @@ -3,6 +3,7 @@ package main import ( "context" crypticnet "cryptic-net" + "cryptic-net/bootstrap" "cryptic-net/daemon" "cryptic-net/yamlutil" "fmt" @@ -16,9 +17,9 @@ import ( // this by attempting to create a UDP connection which has the nebula IP set as // its source. If this succeeds we can assume that at the very least the nebula // interface has been initialized. -func waitForNebula(ctx context.Context, env crypticnet.Env) error { +func waitForNebula(ctx context.Context, hostBootstrap bootstrap.Bootstrap) error { - ipStr := env.Bootstrap.ThisHost().Nebula.IP + ipStr := hostBootstrap.ThisHost().Nebula.IP ip := net.ParseIP(ipStr) lUdpAddr := &net.UDPAddr{IP: ip, Port: 0} @@ -35,7 +36,9 @@ func waitForNebula(ctx context.Context, env crypticnet.Env) error { } func nebulaPmuxProcConfig( - env crypticnet.Env, daemonConfig daemon.Config, + env crypticnet.Env, + hostBootstrap bootstrap.Bootstrap, + daemonConfig daemon.Config, ) ( pmuxlib.ProcessConfig, error, ) { @@ -45,7 +48,7 @@ func nebulaPmuxProcConfig( staticHostMap = map[string][]string{} ) - for _, host := range env.Bootstrap.Hosts { + for _, host := range hostBootstrap.Hosts { if host.Nebula.PublicAddr == "" { continue @@ -57,9 +60,9 @@ func nebulaPmuxProcConfig( config := map[string]interface{}{ "pki": map[string]string{ - "ca": env.Bootstrap.NebulaHostCert.CACert, - "cert": env.Bootstrap.NebulaHostCert.HostCert, - "key": env.Bootstrap.NebulaHostCert.HostKey, + "ca": hostBootstrap.NebulaHostCert.CACert, + "cert": hostBootstrap.NebulaHostCert.HostCert, + "key": hostBootstrap.NebulaHostCert.HostKey, }, "static_host_map": staticHostMap, "punchy": map[string]bool{ diff --git a/entrypoint/src/env.go b/entrypoint/src/env.go index e1c4c5b..731d844 100644 --- a/entrypoint/src/env.go +++ b/entrypoint/src/env.go @@ -2,10 +2,7 @@ package crypticnet import ( "context" - "cryptic-net/bootstrap" - "errors" "fmt" - "io/fs" "os" "os/signal" "path/filepath" @@ -22,11 +19,6 @@ type Env struct { AppDirPath string RuntimeDirPath string DataDirPath string - - // If NewEnv is called with bootstrapOptional, and a bootstrap file is not - // found, then these fields will not be set. - BootstrapPath string - Bootstrap bootstrap.Bootstrap } func getAppDirPath() string { @@ -38,11 +30,7 @@ func getAppDirPath() string { } // NewEnv calculates an Env instance based on the APPDIR and XDG envvars. -// -// If bootstrapOptional is true then NewEnv will first check if a bootstrap file -// can be found in the expected places, and if not then it will not populate -// BootstrapFS or any other fields based on it. -func NewEnv(bootstrapOptional bool) (Env, error) { +func NewEnv() Env { runtimeDirPath := filepath.Join(xdg.RuntimeDir, "cryptic-net") appDirPath := getAppDirPath() @@ -53,79 +41,8 @@ func NewEnv(bootstrapOptional bool) (Env, error) { DataDirPath: filepath.Join(xdg.DataHome, "cryptic-net"), } - return env.init(bootstrapOptional) -} - -// DataDirBootstrapPath returns the path to the bootstrap file within the user's -// data dir. If the file does not exist there it will be found in the AppDirPath -// by ReloadBootstrap. -func (e Env) DataDirBootstrapPath() string { - return filepath.Join(e.DataDirPath, "bootstrap.tgz") -} - -// LoadBootstrap loads a Bootstrap from the given path, and returns a copy of -// the Env with that Bootstrap set along with the BootstrapPath (or an error). -func (e Env) LoadBootstrap(path string) (Env, error) { - - var err error - - if e.Bootstrap, err = bootstrap.FromFile(path); err != nil { - return Env{}, fmt.Errorf("parsing bootstrap.tgz at %q: %w", path, err) - } - - e.BootstrapPath = path - - return e, nil -} - -func (e Env) initBootstrap(bootstrapOptional bool) (Env, error) { - - exists := func(path string) (bool, error) { - if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { - return false, nil - } else if err != nil { - return false, fmt.Errorf("stat'ing %q: %w", path, err) - } - return true, nil - } - - // start by checking if a bootstrap can be found in the user's data - // directory. This will only not be the case if daemon has never been - // successfully started. - { - bootstrapPath := e.DataDirBootstrapPath() - - if exists, err := exists(bootstrapPath); err != nil { - return Env{}, fmt.Errorf("determining if %q exists: %w", bootstrapPath, err) - - } else if exists { - return e.LoadBootstrap(bootstrapPath) - } - } - - // fallback to checking within the AppDir for a bootstrap which has been - // embedded into the binary. - { - bootstrapPath := filepath.Join(e.AppDirPath, "share/bootstrap.tgz") - - if exists, err := exists(bootstrapPath); err != nil { - return Env{}, fmt.Errorf("determining if %q exists: %w", bootstrapPath, err) - - } else if !exists && !bootstrapOptional { - return Env{}, fmt.Errorf("boostrap file not found at %q", bootstrapPath) - - } else if exists { - return e.LoadBootstrap(bootstrapPath) - } - } - - return e, nil -} - -func (e Env) init(bootstrapOptional bool) (Env, error) { - var cancel context.CancelFunc - e.Context, cancel = context.WithCancel(context.Background()) + env.Context, cancel = context.WithCancel(context.Background()) signalCh := make(chan os.Signal, 2) signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) @@ -142,5 +59,5 @@ func (e Env) init(bootstrapOptional bool) (Env, error) { os.Exit(1) }() - return e.initBootstrap(bootstrapOptional) + return env }