Remove Bootstrap from Env

This commit is contained in:
Brian Picciano 2022-10-27 00:23:39 +02:00
parent 08f47bd514
commit b23a4cafa6
12 changed files with 253 additions and 242 deletions

View File

@ -25,6 +25,18 @@ const (
hostNamePath = "hostname" 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 is used for accessing all information contained within a
// bootstrap.tgz file. // bootstrap.tgz file.
type Bootstrap struct { type Bootstrap struct {

View File

@ -148,7 +148,7 @@ var subCmdAdminCreateNetwork = subCmd{
return fmt.Errorf("creating nebula cert for host: %w", err) return fmt.Errorf("creating nebula cert for host: %w", err)
} }
env.Bootstrap = bootstrap.Bootstrap{ hostBootstrap := bootstrap.Bootstrap{
AdminCreationParams: adminCreationParams, AdminCreationParams: adminCreationParams,
Hosts: map[string]bootstrap.Host{ Hosts: map[string]bootstrap.Host{
*hostName: bootstrap.Host{ *hostName: bootstrap.Host{
@ -165,16 +165,16 @@ var subCmdAdminCreateNetwork = subCmd{
GarageGlobalBucketS3APICredentials: garage.NewS3APICredentials(), 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) 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 { if err != nil {
return fmt.Errorf("generating nebula config: %w", err) return fmt.Errorf("generating nebula config: %w", err)
} }
garagePmuxProcConfigs, err := garagePmuxProcConfigs(env, daemonConfig) garagePmuxProcConfigs, err := garagePmuxProcConfigs(env, hostBootstrap, daemonConfig)
if err != nil { if err != nil {
return fmt.Errorf("generating garage configs: %w", err) 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") 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) return fmt.Errorf("waiting for garage to start up: %w", err)
} }
fmt.Fprintln(os.Stderr, "applying initial garage layout") 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) return fmt.Errorf("applying initial garage layout: %w", err)
} }
fmt.Fprintln(os.Stderr, "initializing garage shared global bucket") 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 { 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?") 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{ err = admin.Admin{
CreationParams: adminCreationParams, CreationParams: adminCreationParams,
NebulaCACert: nebulaCACert, NebulaCACert: nebulaCACert,
GarageRPCSecret: env.Bootstrap.GarageRPCSecret, GarageRPCSecret: hostBootstrap.GarageRPCSecret,
GarageGlobalBucketS3APICredentials: env.Bootstrap.GarageGlobalBucketS3APICredentials, GarageGlobalBucketS3APICredentials: hostBootstrap.GarageGlobalBucketS3APICredentials,
GarageAdminBucketS3APICredentials: garage.NewS3APICredentials(), GarageAdminBucketS3APICredentials: garage.NewS3APICredentials(),
}.WriteTo(os.Stdout) }.WriteTo(os.Stdout)
@ -271,17 +271,22 @@ var subCmdAdminMakeBootstrap = subCmd{
env := subCmdCtx.env env := subCmdCtx.env
hostBootstrap, err := loadHostBootstrap(env.DataDirPath)
if err != nil {
return fmt.Errorf("loading host bootstrap: %w", err)
}
adm, err := readAdmin(*adminPath) adm, err := readAdmin(*adminPath)
if err != nil { if err != nil {
return fmt.Errorf("reading admin.tgz with --admin-path of %q: %w", *adminPath, err) 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` // NOTE this isn't _technically_ required, but if the `hosts add`
// command for this host has been run recently then it might not have // 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 // made it into the bootstrap file yet, and so won't be in
// `env.Bootstrap`. // `hostBootstrap`.
hosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, client) hosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, client)
if err != nil { if err != nil {
return fmt.Errorf("retrieving host info from garage: %w", err) 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) return fmt.Errorf("creating new nebula host key/cert: %w", err)
} }
newBootstrap := bootstrap.Bootstrap{ newHostBootstrap := bootstrap.Bootstrap{
AdminCreationParams: adm.CreationParams, AdminCreationParams: adm.CreationParams,
Hosts: hosts, Hosts: hosts,
@ -315,7 +320,7 @@ var subCmdAdminMakeBootstrap = subCmd{
GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials, GarageGlobalBucketS3APICredentials: adm.GarageGlobalBucketS3APICredentials,
} }
return newBootstrap.WriteTo(os.Stdout) return newHostBootstrap.WriteTo(os.Stdout)
}, },
} }

View File

@ -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)
}

View File

@ -5,6 +5,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io/fs"
"os" "os"
"sync" "sync"
"time" "time"
@ -38,71 +39,76 @@ import (
// creates a new bootstrap file using available information from the network. If // 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 // the new bootstrap file is different than the existing one, the existing one
// is overwritten, env's bootstrap is reloaded, true is returned. // is overwritten and true is returned.
func reloadBootstrap(env crypticnet.Env, s3Client garage.S3APIClient) (crypticnet.Env, bool, error) { func reloadBootstrap(
env crypticnet.Env,
hostBootstrap bootstrap.Bootstrap,
s3Client garage.S3APIClient,
) (
bootstrap.Bootstrap, bool, error,
) {
newHosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, s3Client) newHosts, err := bootstrap.GetGarageBootstrapHosts(env.Context, s3Client)
if err != nil { 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) newHostsHash, err := bootstrap.HostsHash(newHosts)
if err != nil { 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 { 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) { if bytes.Equal(newHostsHash, currHostsHash) {
return crypticnet.Env{}, false, nil return hostBootstrap, false, nil
} }
buf := new(bytes.Buffer) newHostBootstrap := hostBootstrap.WithHosts(newHosts)
if err := env.Bootstrap.WithHosts(newHosts).WriteTo(buf); err != nil {
return crypticnet.Env{}, false, fmt.Errorf("writing new bootstrap file to buffer: %w", err) 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 newHostBootstrap, true, nil
return crypticnet.Env{}, false, fmt.Errorf("copying new bootstrap file to data dir: %w", err)
}
return env, true, nil
} }
// runs a single pmux process of daemon, returning only once the env.Context has // 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 // 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 // until the spawned pmux has returned, and returns a copy of hostBootstrap with
// boostrap info. // updated boostrap info.
func runDaemonPmuxOnce( 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) 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 // create s3Client anew on every loop, in case the topology has
// changed and we should be connecting to a different garage // changed and we should be connecting to a different garage
// endpoint. // endpoint.
s3Client := env.Bootstrap.GlobalBucketS3APIClient() s3Client := hostBootstrap.GlobalBucketS3APIClient()
nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env, daemonConfig) nebulaPmuxProcConfig, err := nebulaPmuxProcConfig(env, hostBootstrap, daemonConfig)
if err != nil { 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 { 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 { 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{ pmuxConfig := pmuxlib.Config{
@ -133,12 +139,12 @@ func runDaemonPmuxOnce(
go func() { go func() {
defer wg.Done() 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) fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err)
return return
} }
thisHost := env.Bootstrap.ThisHost() thisHost := hostBootstrap.ThisHost()
err := doOnce(ctx, func(ctx context.Context) error { err := doOnce(ctx, func(ctx context.Context) error {
fmt.Fprintln(os.Stderr, "updating host info in garage") fmt.Fprintln(os.Stderr, "updating host info in garage")
@ -155,14 +161,14 @@ func runDaemonPmuxOnce(
go func() { go func() {
defer wg.Done() 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) fmt.Fprintf(os.Stderr, "aborted waiting for garage instances to be accessible: %v\n", err)
return return
} }
err := doOnce(ctx, func(ctx context.Context) error { err := doOnce(ctx, func(ctx context.Context) error {
fmt.Fprintln(os.Stderr, "applying garage layout") fmt.Fprintln(os.Stderr, "applying garage layout")
return garageApplyLayout(ctx, env, daemonConfig) return garageApplyLayout(ctx, hostBootstrap, daemonConfig)
}) })
if err != nil { if err != nil {
@ -178,7 +184,7 @@ func runDaemonPmuxOnce(
select { select {
case <-doneCh: case <-doneCh:
return crypticnet.Env{}, env.Context.Err() return bootstrap.Bootstrap{}, env.Context.Err()
case <-ticker.C: case <-ticker.C:
@ -189,12 +195,12 @@ func runDaemonPmuxOnce(
err error err error
) )
if env, changed, err = reloadBootstrap(env, s3Client); err != nil { if hostBootstrap, changed, err = reloadBootstrap(env, hostBootstrap, s3Client); err != nil {
return crypticnet.Env{}, fmt.Errorf("reloading bootstrap: %w", err) return bootstrap.Bootstrap{}, fmt.Errorf("reloading bootstrap: %w", err)
} else if changed { } else if changed {
fmt.Fprintln(os.Stderr, "bootstrap info has changed, restarting all processes") 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 var (
// there and reload the bootstrap info bootstrapDataDirPath = bootstrap.DataDirPath(env.DataDirPath)
if env.BootstrapPath != env.DataDirBootstrapPath() { 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 err error
// found. In this case we require the user to provide one on the )
// command-line.
if path == "" {
if *bootstrapPath == "" { tryLoadBootstrap := func(path string) bool {
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()
if err != nil { 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 // up-to-date possible bootstrap. This updated bootstrap will later get
// updated in garage using bootstrap.PutGarageBoostrapHost, so other // updated in garage using bootstrap.PutGarageBoostrapHost, so other
// hosts will see it as well. // 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) return fmt.Errorf("merging daemon config into bootstrap data: %w", err)
} }
for { for {
env, err = runDaemonPmuxOnce(env, daemonConfig) hostBootstrap, err = runDaemonPmuxOnce(env, hostBootstrap, daemonConfig)
if errors.Is(err, context.Canceled) { if errors.Is(err, context.Canceled) {
return nil return nil

View File

@ -1,48 +1,22 @@
package main package main
import ( import (
"bytes"
"context" "context"
crypticnet "cryptic-net" crypticnet "cryptic-net"
"cryptic-net/bootstrap" "cryptic-net/bootstrap"
"cryptic-net/daemon" "cryptic-net/daemon"
"fmt" "fmt"
"io"
"os"
"path/filepath"
"time" "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( 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 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 := writeBootstrapToDataDir(env.DataDirPath, hostBootstrap); err != nil {
if err := env.Bootstrap.WithHosts(env.Bootstrap.Hosts).WriteTo(buf); err != nil { return bootstrap.Bootstrap{}, fmt.Errorf("writing bootstrap file: %w", err)
return crypticnet.Env{}, fmt.Errorf("writing new bootstrap file to buffer: %w", err)
} }
return copyBootstrapToDataDirAndReload(env, buf) return hostBootstrap, nil
} }
func doOnce(ctx context.Context, fn func(context.Context) error) error { func doOnce(ctx context.Context, fn func(context.Context) error) error {

View File

@ -13,15 +13,17 @@ import (
) )
func dnsmasqPmuxProcConfig( func dnsmasqPmuxProcConfig(
env crypticnet.Env, daemonConfig daemon.Config, env crypticnet.Env,
hostBootstrap bootstrap.Bootstrap,
daemonConfig daemon.Config,
) ( ) (
pmuxlib.ProcessConfig, error, pmuxlib.ProcessConfig, error,
) { ) {
confPath := filepath.Join(env.RuntimeDirPath, "dnsmasq.conf") confPath := filepath.Join(env.RuntimeDirPath, "dnsmasq.conf")
hostsSlice := make([]bootstrap.Host, 0, len(env.Bootstrap.Hosts)) hostsSlice := make([]bootstrap.Host, 0, len(hostBootstrap.Hosts))
for _, host := range env.Bootstrap.Hosts { for _, host := range hostBootstrap.Hosts {
hostsSlice = append(hostsSlice, host) hostsSlice = append(hostsSlice, host)
} }
@ -31,8 +33,8 @@ func dnsmasqPmuxProcConfig(
confData := dnsmasq.ConfData{ confData := dnsmasq.ConfData{
Resolvers: daemonConfig.DNS.Resolvers, Resolvers: daemonConfig.DNS.Resolvers,
Domain: env.Bootstrap.AdminCreationParams.Domain, Domain: hostBootstrap.AdminCreationParams.Domain,
IP: env.Bootstrap.ThisHost().Nebula.IP, IP: hostBootstrap.ThisHost().Nebula.IP,
Hosts: hostsSlice, Hosts: hostsSlice,
} }

View File

@ -30,16 +30,21 @@ var subCmdGarageMC = subCmd{
env := subCmdCtx.env 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 == "" || *keySecret == "" {
if *keyID == "" { if *keyID == "" {
*keyID = env.Bootstrap.GarageGlobalBucketS3APICredentials.ID *keyID = hostBootstrap.GarageGlobalBucketS3APICredentials.ID
} }
if *keySecret == "" { if *keySecret == "" {
*keyID = env.Bootstrap.GarageGlobalBucketS3APICredentials.Secret *keyID = hostBootstrap.GarageGlobalBucketS3APICredentials.Secret
} }
} }
@ -85,13 +90,18 @@ var subCmdGarageCLI = subCmd{
env := subCmdCtx.env env := subCmdCtx.env
hostBootstrap, err := loadHostBootstrap(env.DataDirPath)
if err != nil {
return fmt.Errorf("loading host bootstrap: %w", err)
}
var ( var (
binPath = "garage" binPath = "garage"
args = append([]string{"garage"}, subCmdCtx.args...) args = append([]string{"garage"}, subCmdCtx.args...)
cliEnv = append( cliEnv = append(
os.Environ(), os.Environ(),
"GARAGE_RPC_HOST="+env.Bootstrap.ChooseGaragePeer().RPCAddr(), "GARAGE_RPC_HOST="+hostBootstrap.ChooseGaragePeer().RPCAddr(),
"GARAGE_RPC_SECRET="+env.Bootstrap.GarageRPCSecret, "GARAGE_RPC_SECRET="+hostBootstrap.GarageRPCSecret,
) )
) )

View File

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
crypticnet "cryptic-net" crypticnet "cryptic-net"
"cryptic-net/bootstrap"
"cryptic-net/daemon" "cryptic-net/daemon"
"cryptic-net/garage" "cryptic-net/garage"
"fmt" "fmt"
@ -17,22 +18,24 @@ import (
// newGarageAdminClient will return an AdminClient for a local garage instance, // newGarageAdminClient will return an AdminClient for a local garage instance,
// or it will _panic_ if there is no local instance configured. // or it will _panic_ if there is no local instance configured.
func newGarageAdminClient( func newGarageAdminClient(
env crypticnet.Env, daemonConfig daemon.Config, hostBootstrap bootstrap.Bootstrap, daemonConfig daemon.Config,
) *garage.AdminClient { ) *garage.AdminClient {
thisHost := env.Bootstrap.ThisHost() thisHost := hostBootstrap.ThisHost()
return garage.NewAdminClient( return garage.NewAdminClient(
net.JoinHostPort( net.JoinHostPort(
thisHost.Nebula.IP, thisHost.Nebula.IP,
strconv.Itoa(daemonConfig.Storage.Allocations[0].AdminPort), strconv.Itoa(daemonConfig.Storage.Allocations[0].AdminPort),
), ),
env.Bootstrap.GarageAdminToken, hostBootstrap.GarageAdminToken,
) )
} }
func waitForGarageAndNebula( func waitForGarageAndNebula(
ctx context.Context, env crypticnet.Env, daemonConfig daemon.Config, ctx context.Context,
hostBootstrap bootstrap.Bootstrap,
daemonConfig daemon.Config,
) error { ) error {
allocs := daemonConfig.Storage.Allocations allocs := daemonConfig.Storage.Allocations
@ -40,19 +43,19 @@ func waitForGarageAndNebula(
// if this host doesn't have any allocations specified then fall back to // if this host doesn't have any allocations specified then fall back to
// waiting for nebula // waiting for nebula
if len(allocs) == 0 { if len(allocs) == 0 {
return waitForNebula(ctx, env) return waitForNebula(ctx, hostBootstrap)
} }
for _, alloc := range allocs { for _, alloc := range allocs {
adminAddr := net.JoinHostPort( adminAddr := net.JoinHostPort(
env.Bootstrap.ThisHost().Nebula.IP, hostBootstrap.ThisHost().Nebula.IP,
strconv.Itoa(alloc.AdminPort), strconv.Itoa(alloc.AdminPort),
) )
adminClient := garage.NewAdminClient( adminClient := garage.NewAdminClient(
adminAddr, adminAddr,
env.Bootstrap.GarageAdminToken, hostBootstrap.GarageAdminToken,
) )
if err := adminClient.Wait(ctx); err != nil { if err := adminClient.Wait(ctx); err != nil {
@ -66,6 +69,7 @@ func waitForGarageAndNebula(
func garageWriteChildConfig( func garageWriteChildConfig(
env crypticnet.Env, env crypticnet.Env,
hostBootstrap bootstrap.Bootstrap,
alloc daemon.ConfigStorageAllocation, alloc daemon.ConfigStorageAllocation,
) ( ) (
string, error, string, error,
@ -75,7 +79,7 @@ func garageWriteChildConfig(
return "", fmt.Errorf("making directory %q: %w", alloc.MetaPath, err) return "", fmt.Errorf("making directory %q: %w", alloc.MetaPath, err)
} }
thisHost := env.Bootstrap.ThisHost() thisHost := hostBootstrap.ThisHost()
peer := garage.Peer{ peer := garage.Peer{
IP: thisHost.Nebula.IP, IP: thisHost.Nebula.IP,
@ -103,14 +107,14 @@ func garageWriteChildConfig(
MetaPath: alloc.MetaPath, MetaPath: alloc.MetaPath,
DataPath: alloc.DataPath, DataPath: alloc.DataPath,
RPCSecret: env.Bootstrap.GarageRPCSecret, RPCSecret: hostBootstrap.GarageRPCSecret,
AdminToken: env.Bootstrap.GarageAdminToken, AdminToken: hostBootstrap.GarageAdminToken,
RPCAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)), RPCAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)),
APIAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.S3APIPort)), APIAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.S3APIPort)),
AdminAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.AdminPort)), AdminAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.AdminPort)),
BootstrapPeers: env.Bootstrap.GarageRPCPeerAddrs(), BootstrapPeers: hostBootstrap.GarageRPCPeerAddrs(),
}) })
if err != nil { if err != nil {
@ -121,7 +125,9 @@ func garageWriteChildConfig(
} }
func garagePmuxProcConfigs( func garagePmuxProcConfigs(
env crypticnet.Env, daemonConfig daemon.Config, env crypticnet.Env,
hostBootstrap bootstrap.Bootstrap,
daemonConfig daemon.Config,
) ( ) (
[]pmuxlib.ProcessConfig, error, []pmuxlib.ProcessConfig, error,
) { ) {
@ -130,7 +136,7 @@ func garagePmuxProcConfigs(
for _, alloc := range daemonConfig.Storage.Allocations { for _, alloc := range daemonConfig.Storage.Allocations {
childConfigPath, err := garageWriteChildConfig(env, alloc) childConfigPath, err := garageWriteChildConfig(env, hostBootstrap, alloc)
if err != nil { if err != nil {
return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err) return nil, fmt.Errorf("writing child config file for alloc %+v: %w", alloc, err)
@ -141,7 +147,7 @@ func garagePmuxProcConfigs(
Cmd: "garage", Cmd: "garage",
Args: []string{"-c", childConfigPath, "server"}, Args: []string{"-c", childConfigPath, "server"},
StartAfterFunc: func(ctx context.Context) error { StartAfterFunc: func(ctx context.Context) error {
return waitForNebula(ctx, env) return waitForNebula(ctx, hostBootstrap)
}, },
}) })
} }
@ -150,12 +156,14 @@ func garagePmuxProcConfigs(
} }
func garageInitializeGlobalBucket( func garageInitializeGlobalBucket(
ctx context.Context, env crypticnet.Env, daemonConfig daemon.Config, ctx context.Context,
hostBootstrap bootstrap.Bootstrap,
daemonConfig daemon.Config,
) error { ) error {
var ( var (
adminClient = newGarageAdminClient(env, daemonConfig) adminClient = newGarageAdminClient(hostBootstrap, daemonConfig)
globalBucketCreds = env.Bootstrap.GarageGlobalBucketS3APICredentials globalBucketCreds = hostBootstrap.GarageGlobalBucketS3APICredentials
) )
// first attempt to import the key // first attempt to import the key
@ -210,12 +218,14 @@ func garageInitializeGlobalBucket(
} }
func garageApplyLayout( func garageApplyLayout(
ctx context.Context, env crypticnet.Env, daemonConfig daemon.Config, ctx context.Context,
hostBootstrap bootstrap.Bootstrap,
daemonConfig daemon.Config,
) error { ) error {
var ( var (
adminClient = newGarageAdminClient(env, daemonConfig) adminClient = newGarageAdminClient(hostBootstrap, daemonConfig)
thisHost = env.Bootstrap.ThisHost() thisHost = hostBootstrap.ThisHost()
hostName = thisHost.Name hostName = thisHost.Name
ip = thisHost.Nebula.IP ip = thisHost.Nebula.IP
allocs = daemonConfig.Storage.Allocations allocs = daemonConfig.Storage.Allocations

View File

@ -60,7 +60,13 @@ var subCmdHostsAdd = subCmd{
// TODO validate that the IP is in the correct CIDR // TODO validate that the IP is in the correct CIDR
env := subCmdCtx.env 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{ host := bootstrap.Host{
Name: *name, Name: *name,
@ -81,7 +87,12 @@ var subCmdHostsList = subCmd{
env := subCmdCtx.env 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) hostsMap, err := bootstrap.GetGarageBootstrapHosts(env.Context, client)
if err != nil { if err != nil {
@ -121,7 +132,13 @@ var subCmdHostsDelete = subCmd{
} }
env := subCmdCtx.env 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) return bootstrap.RemoveGarageBootstrapHost(env.Context, client, *name)
}, },

View File

@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"os" "os"
crypticnet "cryptic-net" crypticnet "cryptic-net"
@ -14,13 +13,9 @@ import (
func main() { func main() {
env, err := crypticnet.NewEnv(true) env := crypticnet.NewEnv()
if err != nil { err := subCmdCtx{
panic(fmt.Sprintf("loading environment: %v", err))
}
err = subCmdCtx{
args: os.Args[1:], args: os.Args[1:],
env: env, env: env,
}.doSubCmd( }.doSubCmd(

View File

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
crypticnet "cryptic-net" crypticnet "cryptic-net"
"cryptic-net/bootstrap"
"cryptic-net/daemon" "cryptic-net/daemon"
"cryptic-net/yamlutil" "cryptic-net/yamlutil"
"fmt" "fmt"
@ -16,9 +17,9 @@ import (
// this by attempting to create a UDP connection which has the nebula IP set as // 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 // its source. If this succeeds we can assume that at the very least the nebula
// interface has been initialized. // 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) ip := net.ParseIP(ipStr)
lUdpAddr := &net.UDPAddr{IP: ip, Port: 0} lUdpAddr := &net.UDPAddr{IP: ip, Port: 0}
@ -35,7 +36,9 @@ func waitForNebula(ctx context.Context, env crypticnet.Env) error {
} }
func nebulaPmuxProcConfig( func nebulaPmuxProcConfig(
env crypticnet.Env, daemonConfig daemon.Config, env crypticnet.Env,
hostBootstrap bootstrap.Bootstrap,
daemonConfig daemon.Config,
) ( ) (
pmuxlib.ProcessConfig, error, pmuxlib.ProcessConfig, error,
) { ) {
@ -45,7 +48,7 @@ func nebulaPmuxProcConfig(
staticHostMap = map[string][]string{} staticHostMap = map[string][]string{}
) )
for _, host := range env.Bootstrap.Hosts { for _, host := range hostBootstrap.Hosts {
if host.Nebula.PublicAddr == "" { if host.Nebula.PublicAddr == "" {
continue continue
@ -57,9 +60,9 @@ func nebulaPmuxProcConfig(
config := map[string]interface{}{ config := map[string]interface{}{
"pki": map[string]string{ "pki": map[string]string{
"ca": env.Bootstrap.NebulaHostCert.CACert, "ca": hostBootstrap.NebulaHostCert.CACert,
"cert": env.Bootstrap.NebulaHostCert.HostCert, "cert": hostBootstrap.NebulaHostCert.HostCert,
"key": env.Bootstrap.NebulaHostCert.HostKey, "key": hostBootstrap.NebulaHostCert.HostKey,
}, },
"static_host_map": staticHostMap, "static_host_map": staticHostMap,
"punchy": map[string]bool{ "punchy": map[string]bool{

View File

@ -2,10 +2,7 @@ package crypticnet
import ( import (
"context" "context"
"cryptic-net/bootstrap"
"errors"
"fmt" "fmt"
"io/fs"
"os" "os"
"os/signal" "os/signal"
"path/filepath" "path/filepath"
@ -22,11 +19,6 @@ type Env struct {
AppDirPath string AppDirPath string
RuntimeDirPath string RuntimeDirPath string
DataDirPath 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 { func getAppDirPath() string {
@ -38,11 +30,7 @@ func getAppDirPath() string {
} }
// NewEnv calculates an Env instance based on the APPDIR and XDG envvars. // NewEnv calculates an Env instance based on the APPDIR and XDG envvars.
// func NewEnv() Env {
// 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) {
runtimeDirPath := filepath.Join(xdg.RuntimeDir, "cryptic-net") runtimeDirPath := filepath.Join(xdg.RuntimeDir, "cryptic-net")
appDirPath := getAppDirPath() appDirPath := getAppDirPath()
@ -53,79 +41,8 @@ func NewEnv(bootstrapOptional bool) (Env, error) {
DataDirPath: filepath.Join(xdg.DataHome, "cryptic-net"), 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 var cancel context.CancelFunc
e.Context, cancel = context.WithCancel(context.Background()) env.Context, cancel = context.WithCancel(context.Background())
signalCh := make(chan os.Signal, 2) signalCh := make(chan os.Signal, 2)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
@ -142,5 +59,5 @@ func (e Env) init(bootstrapOptional bool) (Env, error) {
os.Exit(1) os.Exit(1)
}() }()
return e.initBootstrap(bootstrapOptional) return env
} }