diff --git a/go-workspace/src/bootstrap/bootstrap.go b/go-workspace/src/bootstrap/bootstrap.go index 3481cb3..11cb80e 100644 --- a/go-workspace/src/bootstrap/bootstrap.go +++ b/go-workspace/src/bootstrap/bootstrap.go @@ -2,130 +2,129 @@ package bootstrap import ( - "context" - crypticnet "cryptic-net" "cryptic-net/garage" - "cryptic-net/nebula" "cryptic-net/tarutil" + "cryptic-net/yamlutil" "fmt" "io" "io/fs" + "os" + "strings" ) -// GetHashFromFS returns the hash of the contents of the given bootstrap file. -// It may return nil if the bootstrap file doesn't have a hash. -func GetHashFromFS(bootstrapFS fs.FS) ([]byte, error) { +// Paths within the bootstrap FS which for general data. +const ( + HostNamePath = "hostname" +) - b, err := fs.ReadFile(bootstrapFS, tarutil.HashBinPath) +// Bootstrap is used for accessing all information contained within a +// bootstrap.tgz file. +// +// An instance of Bootstrap is read-only, the creator sub-package should be used +// to create new instances. +type Bootstrap struct { + Hosts map[string]Host + HostName string - if err != nil { - return nil, fmt.Errorf("reading file %q from bootstrap fs: %w", tarutil.HashBinPath, err) + NebulaCertsCACert string + NebulaCertsHostCert string + NebulaCertsHostKey string + + GarageRPCSecret string + GarageGlobalBucketS3APICredentials garage.S3APICredentials + + // Hash is a determinstic hash of the contents of the bootstrap file. This + // will be populated when parsing a Bootstrap from a bootstrap.tgz, but will + // be ignored when creating a new bootstrap.tgz. + Hash []byte + + // DEPRECATED do not use + FS fs.FS +} + +// FromFS loads a Boostrap instance from the given fs.FS, which presumably +// represents the file structure of a bootstrap.tgz file. +func FromFS(bootstrapFS fs.FS) (Bootstrap, error) { + + var ( + b Bootstrap + err error + ) + + b.FS = bootstrapFS + + if b.Hosts, err = loadHosts(bootstrapFS); err != nil { + return Bootstrap{}, fmt.Errorf("loading hosts info from fs: %w", err) + } + + if err = yamlutil.LoadYamlFSFile( + &b.GarageGlobalBucketS3APICredentials, + bootstrapFS, + GarageGlobalBucketKeyYmlPath, + ); err != nil { + return Bootstrap{}, fmt.Errorf("loading %q from fs: %w", b.GarageGlobalBucketS3APICredentials, err) + } + + filesToLoadAsString := []struct { + into *string + path string + }{ + {&b.HostName, HostNamePath}, + {&b.NebulaCertsCACert, NebulaCertsCACertPath}, + {&b.NebulaCertsHostCert, NebulaCertsHostCertPath}, + {&b.NebulaCertsHostKey, NebulaCertsHostKeyPath}, + {&b.GarageRPCSecret, GarageRPCSecretPath}, + } + + for _, f := range filesToLoadAsString { + body, err := fs.ReadFile(bootstrapFS, f.path) + if err != nil { + return Bootstrap{}, fmt.Errorf("loading %q from fs: %w", f.path, err) + } + *f.into = string(body) + } + + // TODO confirm if this is necessary + b.GarageRPCSecret = strings.TrimSpace(b.GarageRPCSecret) + + if b.Hash, err = fs.ReadFile(bootstrapFS, tarutil.HashBinPath); err != nil { + return Bootstrap{}, fmt.Errorf("loading %q from fs: %w", tarutil.HashBinPath, err) } return b, nil } -// GetHashFromReader reads the given tgz file as an fs.FS, and passes that to -// GetHashFromFS. -func GetHashFromReader(r io.Reader) ([]byte, error) { +// FromReader reads a bootstrap.tgz file from the given io.Reader. +func FromReader(r io.Reader) (Bootstrap, error) { - bootstrapFS, err := tarutil.FSFromReader(r) + fs, err := tarutil.FSFromReader(r) if err != nil { - return nil, fmt.Errorf("reading tar fs from reader: %w", err) + return Bootstrap{}, fmt.Errorf("reading bootstrap.tgz: %w", err) } - return GetHashFromFS(bootstrapFS) + return FromFS(fs) } -func newBootstrap( - ctx context.Context, - into io.Writer, - hostname provider, - nebulaCerts provider, - nebulaHosts provider, - garageRPCSecret provider, - garageGlobalBucketKey provider, - garageHosts provider, -) error { +// FromFile reads a bootstrap.tgz from a file at the given path. +func FromFile(path string) (Bootstrap, error) { - pairs := []struct { - path string - provider provider - }{ - {"hostname", hostname}, - {"nebula/certs", nebulaCerts}, - {"nebula/hosts", nebulaHosts}, - {"garage/rpc-secret.txt", garageRPCSecret}, - {"garage/global-bucket-key.yml", garageGlobalBucketKey}, - {"garage/hosts", garageHosts}, - } - - w := tarutil.NewTGZWriter(into) - - for _, pair := range pairs { - - if err := pair.provider(ctx, w, pair.path); err != nil { - return fmt.Errorf("populating %q in new bootstrap: %w", pair.path, err) - } - } - - return w.Close() -} - -// NewForThisHost generates a new bootstrap file for the current host, based on -// the existing environment as well as data in garage. -func NewForThisHost(env *crypticnet.Env, into io.Writer) error { - - client, err := garage.GlobalBucketAPIClient(env) + f, err := os.Open(path) if err != nil { - return fmt.Errorf("creating client for global bucket: %w", err) + return Bootstrap{}, fmt.Errorf("opening file: %w", err) } + defer f.Close() - return newBootstrap( - env.Context, - into, - provideFromFS(env.BootstrapFS), // hostname - provideDirFromFS(env.BootstrapFS), // nebulaCerts - provideDirFromGarage(client), // nebulaHosts - provideFromFS(env.BootstrapFS), // garageRPCSecret - provideFromFS(env.BootstrapFS), // garageGlobalBucketKey - provideDirFromGarage(client), // garageHosts - ) + return FromReader(f) } -// NewForHost generates a new bootstrap file for an arbitrary host, based on the -// given admin file's FS and data in garage. -func NewForHost(env *crypticnet.Env, adminFS fs.FS, name string, into io.Writer) error { +// ThisHost is a shortcut for b.Hosts[b.HostName], but will panic if the +// HostName isn't found in the Hosts map. +func (b Bootstrap) ThisHost() Host { - host, ok := env.Hosts[name] + host, ok := b.Hosts[b.HostName] if !ok { - return fmt.Errorf("unknown host %q, make sure host entry has been created", name) + panic(fmt.Sprintf("hostname %q not defined in bootstrap's hosts", b.HostName)) } - client, err := garage.GlobalBucketAPIClient(env) - if err != nil { - return fmt.Errorf("creating client for global bucket: %w", err) - } - - nebulaHostCert, err := nebula.NewHostCert(adminFS, host.Nebula) - if err != nil { - return fmt.Errorf("creating new nebula host key/cert: %w", err) - } - - nebulaHostCertDir := map[string][]byte{ - "ca.crt": nebulaHostCert.CACert, - "host.key": nebulaHostCert.HostKey, - "host.crt": nebulaHostCert.HostCert, - } - - return newBootstrap( - env.Context, - into, - provideFromBytes([]byte(name)), // hostname - provideDirFromMap(nebulaHostCertDir), // nebulaCerts - provideDirFromGarage(client), // nebulaHosts - provideFromFS(adminFS), // garageRPCSecret - provideFromFS(adminFS), // garageGlobalBucketKey - provideDirFromGarage(client), // garageHosts - ) + return host } diff --git a/go-workspace/src/bootstrap/creator/creator.go b/go-workspace/src/bootstrap/creator/creator.go new file mode 100644 index 0000000..6807818 --- /dev/null +++ b/go-workspace/src/bootstrap/creator/creator.go @@ -0,0 +1,110 @@ +// Package creator is responsible for creating bootstrap files. It exists +// separately from the main bootstrap package in order to prevent import loops +// due to its use of crypticnet.Env. +package creator + +import ( + "context" + crypticnet "cryptic-net" + "cryptic-net/bootstrap" + "cryptic-net/nebula" + "cryptic-net/tarutil" + "fmt" + "io" + "io/fs" +) + +func newBootstrap( + ctx context.Context, + into io.Writer, + hostname provider, + nebulaCertsCACert provider, + nebulaCertsHostCert provider, + nebulaCertsHostKey provider, + nebulaHosts provider, + garageRPCSecret provider, + garageGlobalBucketKey provider, + garageHosts provider, +) error { + + pairs := []struct { + path string + provider provider + }{ + {bootstrap.HostNamePath, hostname}, + {bootstrap.NebulaCertsCACertPath, nebulaCertsCACert}, + {bootstrap.NebulaCertsHostCertPath, nebulaCertsHostCert}, + {bootstrap.NebulaCertsHostKeyPath, nebulaCertsHostKey}, + {bootstrap.NebulaHostsDirPath, nebulaHosts}, + {bootstrap.GarageRPCSecretPath, garageRPCSecret}, + {bootstrap.GarageGlobalBucketKeyYmlPath, garageGlobalBucketKey}, + {bootstrap.GarageHostsDirPath, garageHosts}, + } + + w := tarutil.NewTGZWriter(into) + + for _, pair := range pairs { + + if err := pair.provider(ctx, w, pair.path); err != nil { + return fmt.Errorf("populating %q in new bootstrap: %w", pair.path, err) + } + } + + return w.Close() +} + +// NewForThisHost generates a new bootstrap file for the current host, based on +// the existing environment as well as data in garage. +func NewForThisHost(env *crypticnet.Env, into io.Writer) error { + + client, err := env.GlobalBucketS3APIClient() + if err != nil { + return fmt.Errorf("creating client for global bucket: %w", err) + } + + return newBootstrap( + env.Context, + into, + provideFromFS(env.Bootstrap.FS), // hostname + provideFromFS(env.Bootstrap.FS), // nebulaCertsCACert + provideFromFS(env.Bootstrap.FS), // nebulaCertsHostCert + provideFromFS(env.Bootstrap.FS), // nebulaCertsHostKey + provideDirFromGarage(client), // nebulaHosts + provideFromFS(env.Bootstrap.FS), // garageRPCSecret + provideFromFS(env.Bootstrap.FS), // garageGlobalBucketKey + provideDirFromGarage(client), // garageHosts + ) +} + +// NewForHost generates a new bootstrap file for an arbitrary host, based on the +// given admin file's FS and data in garage. +func NewForHost(env *crypticnet.Env, adminFS fs.FS, name string, into io.Writer) error { + + host, ok := env.Bootstrap.Hosts[name] + if !ok { + return fmt.Errorf("unknown host %q, make sure host entry has been created", name) + } + + client, err := env.GlobalBucketS3APIClient() + if err != nil { + return fmt.Errorf("creating client for global bucket: %w", err) + } + + nebulaHostCert, err := nebula.NewHostCert(adminFS, host.Nebula) + if err != nil { + return fmt.Errorf("creating new nebula host key/cert: %w", err) + } + + return newBootstrap( + env.Context, + into, + provideFromBytes([]byte(name)), // hostname + provideFromBytes(nebulaHostCert.CACert), // nebulaCertsCACert + provideFromBytes(nebulaHostCert.HostCert), // nebulaCertsHostCert + provideFromBytes(nebulaHostCert.HostKey), // nebulaCertsHostKey + provideDirFromGarage(client), // nebulaHosts + provideFromFS(adminFS), // garageRPCSecret + provideFromFS(adminFS), // garageGlobalBucketKey + provideDirFromGarage(client), // garageHosts + ) +} diff --git a/go-workspace/src/bootstrap/provider.go b/go-workspace/src/bootstrap/creator/provider.go similarity index 87% rename from go-workspace/src/bootstrap/provider.go rename to go-workspace/src/bootstrap/creator/provider.go index f7c49f3..4361172 100644 --- a/go-workspace/src/bootstrap/provider.go +++ b/go-workspace/src/bootstrap/creator/provider.go @@ -1,4 +1,4 @@ -package bootstrap +package creator import ( "context" @@ -6,7 +6,6 @@ import ( "cryptic-net/tarutil" "fmt" "io/fs" - "path/filepath" "github.com/minio/minio-go/v7" ) @@ -69,23 +68,6 @@ func provideDirFromFS(srcFS fs.FS) provider { } } -func provideDirFromMap(m map[string][]byte) provider { - - return func( - ctx context.Context, - w *tarutil.TGZWriter, - dirPath string, - ) error { - - for filePath, body := range m { - filePath := filepath.Join(dirPath, filePath) - w.WriteFileBytes(filePath, body) - } - - return nil - } -} - // TODO it'd be great if we could wrap a minio.Client into an fs.FS. That would // get rid of a weird dependency in this package, and clean up this code a ton. func provideDirFromGarage(client *minio.Client) provider { diff --git a/go-workspace/src/bootstrap/garage.go b/go-workspace/src/bootstrap/garage.go new file mode 100644 index 0000000..1be0e00 --- /dev/null +++ b/go-workspace/src/bootstrap/garage.go @@ -0,0 +1,48 @@ +package bootstrap + +import ( + "cryptic-net/garage" +) + +// Paths within the bootstrap FS related to garage. +const ( + GarageGlobalBucketKeyYmlPath = "garage/global-bucket-key.yml" + GarageRPCSecretPath = "garage/rpc-secret.txt" + GarageHostsDirPath = "garage/hosts" +) + +// GaragePeers returns a Peer for each known garage instance in the network. +func (b Bootstrap) GaragePeers() []garage.Peer { + + var peers []garage.Peer + + for _, host := range b.Hosts { + + if host.Garage == nil { + continue + } + + for _, instance := range host.Garage.Instances { + + peer := garage.Peer{ + IP: host.Nebula.IP, + RPCPort: instance.RPCPort, + S3APIPort: instance.S3APIPort, + } + + peers = append(peers, peer) + } + } + + return peers +} + +// GarageRPCPeerAddrs returns the full RPC peer address for each known garage +// instance in the network. +func (b Bootstrap) GarageRPCPeerAddrs() []string { + var addrs []string + for _, peer := range b.GaragePeers() { + addrs = append(addrs, peer.RPCPeerAddr()) + } + return addrs +} diff --git a/go-workspace/src/hosts.go b/go-workspace/src/bootstrap/hosts.go similarity index 79% rename from go-workspace/src/hosts.go rename to go-workspace/src/bootstrap/hosts.go index 67392ad..1fbe79e 100644 --- a/go-workspace/src/hosts.go +++ b/go-workspace/src/bootstrap/hosts.go @@ -1,4 +1,4 @@ -package crypticnet +package bootstrap import ( "errors" @@ -19,9 +19,9 @@ type NebulaHost struct { // GarageHostInstance describes a single garage instance running on a host. type GarageHostInstance struct { - APIPort int `yaml:"api_port"` - RPCPort int `yaml:"rpc_port"` - WebPort int `yaml:"web_port"` + RPCPort int `yaml:"rpc_port"` + S3APIPort int `yaml:"s3_api_port"` + WebPort int `yaml:"web_port"` } // GarageHost describes the contents of a `./garage/hosts/.yml` file. @@ -37,8 +37,7 @@ type Host struct { Garage *GarageHost } -// LostHosts returns a mapping of hostnames to Host objects for each host. -func LoadHosts(bootstrapFS fs.FS) (map[string]Host, error) { +func loadHosts(bootstrapFS fs.FS) (map[string]Host, error) { hosts := map[string]Host{} @@ -52,9 +51,11 @@ func LoadHosts(bootstrapFS fs.FS) (map[string]Host, error) { } { - nebulaHostFiles, err := fs.Glob(bootstrapFS, "nebula/hosts/*.yml") + globPath := filepath.Join(NebulaHostsDirPath, "*.yml") + + nebulaHostFiles, err := fs.Glob(bootstrapFS, globPath) if err != nil { - return nil, fmt.Errorf("listing nebula host files: %w", err) + return nil, fmt.Errorf("listing nebula host files at %q in fs: %w", globPath, err) } for _, nebulaHostPath := range nebulaHostFiles { @@ -76,7 +77,7 @@ func LoadHosts(bootstrapFS fs.FS) (map[string]Host, error) { for hostName, host := range hosts { - garageHostPath := filepath.Join("garage/hosts", hostName+".yml") + garageHostPath := filepath.Join(GarageHostsDirPath, hostName+".yml") var garageHost GarageHost if err := readAsYaml(&garageHost, garageHostPath); errors.Is(err, fs.ErrNotExist) { diff --git a/go-workspace/src/bootstrap/nebula.go b/go-workspace/src/bootstrap/nebula.go new file mode 100644 index 0000000..f2888a5 --- /dev/null +++ b/go-workspace/src/bootstrap/nebula.go @@ -0,0 +1,10 @@ +package bootstrap + +// Paths within the bootstrap FS related to nebula. +const ( + NebulaHostsDirPath = "nebula/hosts" + + NebulaCertsCACertPath = "nebula/certs/ca.crt" + NebulaCertsHostCertPath = "nebula/certs/host.crt" + NebulaCertsHostKeyPath = "nebula/certs/host.key" +) diff --git a/go-workspace/src/cmd/entrypoint/daemon.go b/go-workspace/src/cmd/entrypoint/daemon.go index 0a7d4b8..d6ab91d 100644 --- a/go-workspace/src/cmd/entrypoint/daemon.go +++ b/go-workspace/src/cmd/entrypoint/daemon.go @@ -14,6 +14,7 @@ import ( crypticnet "cryptic-net" "cryptic-net/bootstrap" + bootstrap_creator "cryptic-net/bootstrap/creator" "cryptic-net/yamlutil" "github.com/cryptic-io/pmux/pmuxlib" @@ -111,21 +112,16 @@ func reloadBootstrap(env *crypticnet.Env) (bool, error) { buf := new(bytes.Buffer) - if err := bootstrap.NewForThisHost(env, buf); err != nil { + if err := bootstrap_creator.NewForThisHost(env, buf); err != nil { return false, fmt.Errorf("generating new bootstrap from env: %w", err) } - newHash, err := bootstrap.GetHashFromReader(bytes.NewReader(buf.Bytes())) + newBootstrap, err := bootstrap.FromReader(bytes.NewReader(buf.Bytes())) if err != nil { - return false, fmt.Errorf("reading hash from new bootstrap file: %w", err) + return false, fmt.Errorf("parsing bootstrap which was just created: %w", err) } - currHash, err := bootstrap.GetHashFromFS(env.BootstrapFS) - if err != nil { - return false, fmt.Errorf("reading hash from existing bootstrap fs: %w", err) - } - - if bytes.Equal(newHash, currHash) { + if bytes.Equal(newBootstrap.Hash, env.Bootstrap.Hash) { return false, nil } @@ -136,12 +132,12 @@ func reloadBootstrap(env *crypticnet.Env) (bool, error) { return true, nil } -// runs a single pmux process for daemon, returning only once the env.Context +// runs a single pmux process ofor 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. func runDaemonPmuxOnce(env *crypticnet.Env) error { - thisHost := env.ThisHost() + thisHost := env.Bootstrap.ThisHost() fmt.Fprintf(os.Stderr, "host name is %q, ip is %q\n", thisHost.Name, thisHost.Nebula.IP) pmuxProcConfigs := []pmuxlib.ProcessConfig{ @@ -157,7 +153,7 @@ func runDaemonPmuxOnce(env *crypticnet.Env) error { Cmd: "bash", Args: []string{ "wait-for-ip", - env.ThisHost().Nebula.IP, + thisHost.Nebula.IP, "bash", "dnsmasq-entrypoint", }, @@ -170,7 +166,7 @@ func runDaemonPmuxOnce(env *crypticnet.Env) error { Cmd: "bash", Args: []string{ "wait-for-ip", - env.ThisHost().Nebula.IP, + thisHost.Nebula.IP, "cryptic-net-main", "garage-entrypoint", }, diff --git a/go-workspace/src/cmd/entrypoint/garage.go b/go-workspace/src/cmd/entrypoint/garage.go index 4084dbd..1365d8a 100644 --- a/go-workspace/src/cmd/entrypoint/garage.go +++ b/go-workspace/src/cmd/entrypoint/garage.go @@ -2,31 +2,10 @@ package entrypoint import ( "fmt" - "io/fs" - "log" "os" - "strings" "syscall" - - crypticnet "cryptic-net" - "cryptic-net/garage" ) -func getGaragePeer(env *crypticnet.Env) (string, error) { - - if allocs := env.ThisDaemon().Storage.Allocations; len(allocs) > 0 { - return garage.GeneratePeerAddr(env.ThisHost().Nebula.IP, allocs[0].RPCPort) - } - - bootstrapPeers, err := garage.BootstrapPeerAddrs(env.Hosts) - - if err != nil { - return "", err - } - - return bootstrapPeers[0], nil -} - var subCmdGarageMC = subCmd{ name: "mc", descr: "Runs the mc (minio-client) binary. The cryptic-net garage can be accessed under the `garage` alias", @@ -51,21 +30,16 @@ var subCmdGarageMC = subCmd{ env := subCmdCtx.env - apiAddr := garage.APIAddr(env) + s3APIAddr := env.ChooseGaragePeer().S3APIAddr() if *keyID == "" || *keySecret == "" { - globalBucketCreds, err := garage.GlobalBucketAPICredentials(env) - if err != nil { - return fmt.Errorf("loading global bucket credentials: %w", err) - } - if *keyID == "" { - *keyID = globalBucketCreds.ID + *keyID = env.Bootstrap.GarageGlobalBucketS3APICredentials.ID } if *keySecret == "" { - *keySecret = globalBucketCreds.Secret + *keyID = env.Bootstrap.GarageGlobalBucketS3APICredentials.Secret } } @@ -83,7 +57,7 @@ var subCmdGarageMC = subCmd{ os.Environ(), fmt.Sprintf( "MC_HOST_garage=http://%s:%s@%s", - *keyID, *keySecret, apiAddr, + *keyID, *keySecret, s3APIAddr, ), // The garage docs say this is necessary, though nothing bad @@ -111,27 +85,13 @@ var subCmdGarageCLI = subCmd{ env := subCmdCtx.env - peerAddr, err := getGaragePeer(env) - - if err != nil { - return fmt.Errorf("picking peer to communicate with: %w", err) - } - - rpcSecretB, err := fs.ReadFile(env.BootstrapFS, "garage/rpc-secret.txt") - - if err != nil { - log.Fatalf("reading garage rpc secret bootstrap fs: %v", err) - } - - rpcSecret := strings.TrimSpace(string(rpcSecretB)) - var ( binPath = env.BinPath("garage") args = append([]string{"garage"}, subCmdCtx.args...) cliEnv = append( os.Environ(), - "GARAGE_RPC_HOST="+peerAddr, - "GARAGE_RPC_SECRET="+rpcSecret, + "GARAGE_RPC_HOST="+env.ChooseGaragePeer().RPCAddr(), + "GARAGE_RPC_SECRET="+env.Bootstrap.GarageRPCSecret, ) ) diff --git a/go-workspace/src/cmd/entrypoint/hosts.go b/go-workspace/src/cmd/entrypoint/hosts.go index 45ab1db..4c80a16 100644 --- a/go-workspace/src/cmd/entrypoint/hosts.go +++ b/go-workspace/src/cmd/entrypoint/hosts.go @@ -2,8 +2,8 @@ package entrypoint import ( "bytes" - crypticnet "cryptic-net" "cryptic-net/bootstrap" + bootstrap_creator "cryptic-net/bootstrap/creator" "cryptic-net/garage" "cryptic-net/tarutil" "errors" @@ -73,12 +73,12 @@ var subCmdHostsAdd = subCmd{ env := subCmdCtx.env - client, err := garage.GlobalBucketAPIClient(env) + client, err := env.GlobalBucketS3APIClient() if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } - nebulaHost := crypticnet.NebulaHost{ + nebulaHost := bootstrap.NebulaHost{ Name: *name, IP: *ip, } @@ -113,7 +113,7 @@ var subCmdHostsList = subCmd{ env := subCmdCtx.env - client, err := garage.GlobalBucketAPIClient(env) + client, err := env.GlobalBucketS3APIClient() if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } @@ -147,7 +147,7 @@ var subCmdHostsList = subCmd{ return fmt.Errorf("retrieving object %q from global bucket: %w", objInfo.Key, err) } - var nebulaHost crypticnet.NebulaHost + var nebulaHost bootstrap.NebulaHost err = yaml.NewDecoder(obj).Decode(&nebulaHost) obj.Close() @@ -191,7 +191,7 @@ var subCmdHostsDelete = subCmd{ filePath := nebulaHostPath(*name) - client, err := garage.GlobalBucketAPIClient(env) + client, err := env.GlobalBucketS3APIClient() if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } @@ -263,7 +263,7 @@ var subCmdHostsMakeBootstrap = subCmd{ return fmt.Errorf("reading admin.tgz with --admin-path of %q: %w", *adminPath, err) } - return bootstrap.NewForHost(subCmdCtx.env, adminFS, *name, os.Stdout) + return bootstrap_creator.NewForHost(subCmdCtx.env, adminFS, *name, os.Stdout) }, } diff --git a/go-workspace/src/cmd/garage-entrypoint/main.go b/go-workspace/src/cmd/garage-entrypoint/main.go index 232eadf..995354d 100644 --- a/go-workspace/src/cmd/garage-entrypoint/main.go +++ b/go-workspace/src/cmd/garage-entrypoint/main.go @@ -2,13 +2,11 @@ package garage_entrypoint import ( "fmt" - "io/fs" "log" "net" "os" "path/filepath" "strconv" - "strings" "time" crypticnet "cryptic-net" @@ -19,24 +17,23 @@ import ( func writeChildConf( env *crypticnet.Env, - bootstrapPeers []string, alloc crypticnet.DaemonYmlStorageAllocation, - rpcSecret string, ) (string, error) { if err := os.MkdirAll(alloc.MetaPath, 0750); err != nil { return "", fmt.Errorf("making directory %q: %w", alloc.MetaPath, err) } - pubKey, privKey, err := garage.GeneratePeerKey(env.ThisHost().Nebula.IP, alloc.RPCPort) + thisHost := env.Bootstrap.ThisHost() - if err != nil { - return "", fmt.Errorf( - "generating node key with input %q,%d: %w", - env.ThisHost().Nebula.IP, alloc.RPCPort, err, - ) + peer := garage.Peer{ + IP: thisHost.Nebula.IP, + RPCPort: alloc.RPCPort, + S3APIPort: alloc.S3APIPort, } + pubKey, privKey := peer.RPCPeerKey() + nodeKeyPath := filepath.Join(alloc.MetaPath, "node_key") nodeKeyPubPath := filepath.Join(alloc.MetaPath, "node_keypub") @@ -51,17 +48,17 @@ func writeChildConf( env.RuntimeDirPath, fmt.Sprintf("garage-%d.toml", alloc.RPCPort), ) - err = garage.WriteGarageTomlFile(garageTomlPath, garage.GarageTomlData{ + err := garage.WriteGarageTomlFile(garageTomlPath, garage.GarageTomlData{ MetaPath: alloc.MetaPath, DataPath: alloc.DataPath, - RPCSecret: rpcSecret, + RPCSecret: env.Bootstrap.GarageRPCSecret, - RPCAddr: net.JoinHostPort(env.ThisHost().Nebula.IP, strconv.Itoa(alloc.RPCPort)), - APIAddr: net.JoinHostPort(env.ThisHost().Nebula.IP, strconv.Itoa(alloc.APIPort)), - WebAddr: net.JoinHostPort(env.ThisHost().Nebula.IP, strconv.Itoa(alloc.WebPort)), + RPCAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)), + APIAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.S3APIPort)), + WebAddr: net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.WebPort)), - BootstrapPeers: bootstrapPeers, + BootstrapPeers: env.Bootstrap.GarageRPCPeerAddrs(), }) if err != nil { @@ -73,13 +70,15 @@ func writeChildConf( func waitForArgs(env *crypticnet.Env, bin string, binArgs ...string) []string { + thisHost := env.Bootstrap.ThisHost() + var args []string for _, alloc := range env.ThisDaemon().Storage.Allocations { args = append( args, "wait-for", - net.JoinHostPort(env.ThisHost().Nebula.IP, strconv.Itoa(alloc.RPCPort)), + net.JoinHostPort(thisHost.Nebula.IP, strconv.Itoa(alloc.RPCPort)), "--", ) } @@ -98,25 +97,11 @@ func Main() { log.Fatalf("reading envvars: %v", err) } - bootstrapPeers, err := garage.BootstrapPeerAddrs(env.Hosts) - - if err != nil { - log.Fatalf("generating set of bootstrap peers: %v", err) - } - - rpcSecretB, err := fs.ReadFile(env.BootstrapFS, "garage/rpc-secret.txt") - - if err != nil { - log.Fatalf("reading garage rpc secret bootstrap fs: %v", err) - } - - rpcSecret := strings.TrimSpace(string(rpcSecretB)) - var pmuxProcConfigs []pmuxlib.ProcessConfig for _, alloc := range env.ThisDaemon().Storage.Allocations { - childConfPath, err := writeChildConf(env, bootstrapPeers, alloc, rpcSecret) + childConfPath, err := writeChildConf(env, alloc) if err != nil { log.Fatalf("writing child config file for alloc %+v: %v", alloc, err) diff --git a/go-workspace/src/cmd/garage-layout-diff/main.go b/go-workspace/src/cmd/garage-layout-diff/main.go index bdd05b0..0f90812 100644 --- a/go-workspace/src/cmd/garage-layout-diff/main.go +++ b/go-workspace/src/cmd/garage-layout-diff/main.go @@ -148,29 +148,30 @@ func readCurrNodes(r io.Reader) (clusterNodes, int, error) { return currNodes, version, nil } -func readExpNodes(env *crypticnet.Env) (clusterNodes, error) { +func readExpNodes(env *crypticnet.Env) clusterNodes { + + thisHost := env.Bootstrap.ThisHost() var expNodes clusterNodes for _, alloc := range env.ThisDaemon().Storage.Allocations { - id, err := garage.GeneratePeerID(env.ThisHost().Nebula.IP, alloc.RPCPort) - - if err != nil { - return nil, fmt.Errorf( - "generating peer id for ip:%q port:%d: %w", - env.ThisHost().Nebula.IP, alloc.RPCPort, err, - ) + peer := garage.Peer{ + IP: thisHost.Nebula.IP, + RPCPort: alloc.RPCPort, + S3APIPort: alloc.S3APIPort, } + id := peer.RPCPeerID() + expNodes = append(expNodes, clusterNode{ ID: id, - Zone: env.ThisHost().Name, + Zone: env.Bootstrap.HostName, Capacity: alloc.Capacity / 100, }) } - return expNodes, nil + return expNodes } // NOTE: The id formatting for currNodes and expNodes is different; expNodes has @@ -232,18 +233,14 @@ func Main() { for _, node := range currNodes { - if env.ThisHost().Name != node.Zone { + if env.Bootstrap.HostName != node.Zone { continue } thisCurrNodes = append(thisCurrNodes, node) } - expNodes, err := readExpNodes(env) - - if err != nil { - panic(fmt.Errorf("reading expected layout from environment: %w", err)) - } + expNodes := readExpNodes(env) lines := diff(thisCurrNodes, expNodes) diff --git a/go-workspace/src/cmd/garage-peer-keygen/main.go b/go-workspace/src/cmd/garage-peer-keygen/main.go index 436255b..0d038a5 100644 --- a/go-workspace/src/cmd/garage-peer-keygen/main.go +++ b/go-workspace/src/cmd/garage-peer-keygen/main.go @@ -42,12 +42,13 @@ func Main() { panic("The arguments -ip, -port, and -danger are required") } - pubKey, privKey, err := garage.GeneratePeerKey(*ip, *port) - - if err != nil { - panic(fmt.Errorf("GeneratePeerKey returned: %w", err)) + peer := garage.Peer{ + IP: *ip, + RPCPort: *port, } + pubKey, privKey := peer.RPCPeerKey() + fmt.Fprintln(os.Stdout, hex.EncodeToString(pubKey)) if *outPub != "" { diff --git a/go-workspace/src/cmd/garage-update-global-bucket/main.go b/go-workspace/src/cmd/garage-update-global-bucket/main.go index f0abb8e..85da6f2 100644 --- a/go-workspace/src/cmd/garage-update-global-bucket/main.go +++ b/go-workspace/src/cmd/garage-update-global-bucket/main.go @@ -3,6 +3,7 @@ package garage_update_global_bucket import ( "bytes" crypticnet "cryptic-net" + "cryptic-net/bootstrap" "cryptic-net/garage" "fmt" "log" @@ -16,12 +17,15 @@ func updateGlobalBucket(env *crypticnet.Env) error { ctx := env.Context - client, err := garage.GlobalBucketAPIClient(env) + client, err := env.GlobalBucketS3APIClient() if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } - filePath := filepath.Join("garage/hosts", env.ThisHost().Name+".yml") + filePath := filepath.Join( + "garage/hosts", + env.Bootstrap.HostName+".yml", + ) daemon := env.ThisDaemon() @@ -41,14 +45,14 @@ func updateGlobalBucket(env *crypticnet.Env) error { return nil } - var garageHost crypticnet.GarageHost + var garageHost bootstrap.GarageHost for _, alloc := range daemon.Storage.Allocations { - garageHostInstance := crypticnet.GarageHostInstance{ - APIPort: alloc.APIPort, - RPCPort: alloc.RPCPort, - WebPort: alloc.WebPort, + garageHostInstance := bootstrap.GarageHostInstance{ + RPCPort: alloc.RPCPort, + S3APIPort: alloc.S3APIPort, + WebPort: alloc.WebPort, } garageHost.Instances = append(garageHost.Instances, garageHostInstance) diff --git a/go-workspace/src/cmd/nebula-entrypoint/main.go b/go-workspace/src/cmd/nebula-entrypoint/main.go index 039fbbe..bb434d5 100644 --- a/go-workspace/src/cmd/nebula-entrypoint/main.go +++ b/go-workspace/src/cmd/nebula-entrypoint/main.go @@ -2,8 +2,6 @@ package nebula_entrypoint import ( "cryptic-net/yamlutil" - "fmt" - "io/fs" "log" "net" "path/filepath" @@ -27,7 +25,7 @@ func Main() { staticHostMap = map[string][]string{} ) - for _, host := range env.Hosts { + for _, host := range env.Bootstrap.Hosts { if host.Nebula.PublicAddr == "" { continue @@ -37,26 +35,11 @@ func Main() { staticHostMap[host.Nebula.IP] = []string{host.Nebula.PublicAddr} } - readCertFile := func(name string) string { - if err != nil { - return "" - } - - path := filepath.Join("nebula", "certs", name) - - var b []byte - if b, err = fs.ReadFile(env.BootstrapFS, path); err != nil { - err = fmt.Errorf("reading %q from bootstrap fs: %w", path, err) - } - - return string(b) - } - config := map[string]interface{}{ "pki": map[string]string{ - "ca": readCertFile("ca.crt"), - "cert": readCertFile("host.crt"), - "key": readCertFile("host.key"), + "ca": env.Bootstrap.NebulaCertsCACert, + "cert": env.Bootstrap.NebulaCertsHostCert, + "key": env.Bootstrap.NebulaCertsHostKey, }, "static_host_map": staticHostMap, "punchy": map[string]bool{ @@ -110,7 +93,7 @@ func Main() { firewallInbound = append( firewallInbound, crypticnet.ConfigFirewallRule{ - Port: strconv.Itoa(alloc.APIPort), + Port: strconv.Itoa(alloc.S3APIPort), Proto: "tcp", Host: "any", }, diff --git a/go-workspace/src/cmd/nebula-update-global-bucket/main.go b/go-workspace/src/cmd/nebula-update-global-bucket/main.go index 4d32227..555f2be 100644 --- a/go-workspace/src/cmd/nebula-update-global-bucket/main.go +++ b/go-workspace/src/cmd/nebula-update-global-bucket/main.go @@ -16,14 +16,14 @@ func updateGlobalBucket(env *crypticnet.Env) error { ctx := env.Context - client, err := garage.GlobalBucketAPIClient(env) + client, err := env.GlobalBucketS3APIClient() if err != nil { return fmt.Errorf("creating client for global bucket: %w", err) } daemon := env.ThisDaemon() - host := env.ThisHost() + host := env.Bootstrap.ThisHost() host.Nebula.Name = host.Name host.Nebula.PublicAddr = daemon.VPN.PublicAddr diff --git a/go-workspace/src/daemon_yml.go b/go-workspace/src/daemon_yml.go index c2a7f16..62f3fdb 100644 --- a/go-workspace/src/daemon_yml.go +++ b/go-workspace/src/daemon_yml.go @@ -28,12 +28,12 @@ type ConfigFirewallRule struct { // DaemonYmlStorageAllocation describes the structure of each storage allocation // within the daemon.yml file. type DaemonYmlStorageAllocation struct { - DataPath string `yaml:"data_path"` - MetaPath string `yaml:"meta_path"` - Capacity int `yaml:"capacity"` - APIPort int `yaml:"api_port"` - RPCPort int `yaml:"rpc_port"` - WebPort int `yaml:"web_port"` + DataPath string `yaml:"data_path"` + MetaPath string `yaml:"meta_path"` + Capacity int `yaml:"capacity"` + S3APIPort int `yaml:"api_port"` // TODO fix field name here + RPCPort int `yaml:"rpc_port"` + WebPort int `yaml:"web_port"` } // DaemonYml describes the structure of the daemon.yml file. diff --git a/go-workspace/src/env.go b/go-workspace/src/env.go index 77f5e8a..606fc6a 100644 --- a/go-workspace/src/env.go +++ b/go-workspace/src/env.go @@ -2,7 +2,7 @@ package crypticnet import ( "context" - "cryptic-net/tarutil" + "cryptic-net/bootstrap" "cryptic-net/yamlutil" "errors" "fmt" @@ -37,9 +37,7 @@ type Env struct { // If NewEnv is called with bootstrapOptional, and a bootstrap file is not // found, then these fields will not be set. BootstrapPath string - BootstrapFS fs.FS - Hosts map[string]Host - HostName string + Bootstrap bootstrap.Bootstrap thisDaemon DaemonYml thisDaemonOnce sync.Once @@ -118,32 +116,13 @@ func (e *Env) DataDirBootstrapPath() string { // and all derived fields based on that. func (e *Env) LoadBootstrap(path string) error { - var ( - err error + var err error - // load all values into temp variables before setting the fields on Env, - // so we don't leave it in an inconsistent state. - bootstrapFS fs.FS - hosts map[string]Host - hostNameB []byte - ) - - if bootstrapFS, err = tarutil.FSFromTGZFile(path); err != nil { - return fmt.Errorf("reading bootstrap file at %q: %w", e.BootstrapPath, err) - } - - if hosts, err = LoadHosts(bootstrapFS); err != nil { - return fmt.Errorf("loading hosts info from bootstrap fs: %w", err) - } - - if hostNameB, err = fs.ReadFile(bootstrapFS, "hostname"); err != nil { - return fmt.Errorf("loading hostname from bootstrap fs: %w", err) + if e.Bootstrap, err = bootstrap.FromFile(path); err != nil { + return fmt.Errorf("parsing bootstrap.tgz at %q: %w", path, err) } e.BootstrapPath = path - e.BootstrapFS = bootstrapFS - e.Hosts = hosts - e.HostName = string(hostNameB) return nil } @@ -219,11 +198,6 @@ func (e *Env) init(bootstrapOptional bool) error { return nil } -// ThisHost is a shortcut for returning env.Hosts[env.HostName]. -func (e *Env) ThisHost() Host { - return e.Hosts[e.HostName] -} - // ToMap returns the Env as a map of key/value strings. If this map is set into // a process's environment, then that process can read it back using ReadEnv. func (e *Env) ToMap() map[string]string { diff --git a/go-workspace/src/garage.go b/go-workspace/src/garage.go new file mode 100644 index 0000000..779b800 --- /dev/null +++ b/go-workspace/src/garage.go @@ -0,0 +1,42 @@ +package crypticnet + +import ( + "cryptic-net/garage" + "fmt" +) + +// ChooseGaragePeer returns a Peer for a garage instance from the network. It +// will prefer a garage instance on this particular host, if there is one, but +// will otherwise return a random endpoint. +func (env *Env) ChooseGaragePeer() garage.Peer { + + if allocs := env.ThisDaemon().Storage.Allocations; len(allocs) > 0 { + + return garage.Peer{ + IP: env.Bootstrap.ThisHost().Nebula.IP, + RPCPort: allocs[0].RPCPort, + S3APIPort: allocs[0].S3APIPort, + } + } + + for _, peer := range env.Bootstrap.GaragePeers() { + return peer + } + + panic("no garage instances configured") +} + +// GlobalBucketS3APIClient returns an S3 client pre-configured with access to +// the global bucket. +func (env *Env) GlobalBucketS3APIClient() (garage.S3APIClient, error) { + + addr := env.ChooseGaragePeer().S3APIAddr() + creds := env.Bootstrap.GarageGlobalBucketS3APICredentials + + client, err := garage.NewS3APIClient(addr, creds) + if err != nil { + return nil, fmt.Errorf("connecting to garage S3 API At %q: %w", addr, err) + } + + return client, err +} diff --git a/go-workspace/src/garage/client.go b/go-workspace/src/garage/client.go index b706765..b9befc8 100644 --- a/go-workspace/src/garage/client.go +++ b/go-workspace/src/garage/client.go @@ -1,12 +1,7 @@ package garage import ( - crypticnet "cryptic-net" - "cryptic-net/yamlutil" "errors" - "fmt" - "net" - "strconv" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" @@ -19,74 +14,21 @@ func IsKeyNotFound(err error) bool { return errors.As(err, &mErr) && mErr.Code == "NoSuchKey" } -// APICredentials describe data fields necessary for authenticating with a -// garage api endpoint. -type APICredentials struct { +// S3APIClient is a client used to interact with garage's S3 API. +type S3APIClient = *minio.Client + +// S3APICredentials describe data fields necessary for authenticating with a +// garage S3 API endpoint. +type S3APICredentials struct { ID string `yaml:"id"` Secret string `yaml:"secret"` } -// GlobalBucketAPICredentials returns APICredentials for the global bucket. -func GlobalBucketAPICredentials(env *crypticnet.Env) (APICredentials, error) { - - const path = "garage/priv/global-bucket-key.yml" - - var creds APICredentials - if err := yamlutil.LoadYamlFSFile(&creds, env.BootstrapFS, path); err != nil { - return APICredentials{}, fmt.Errorf("loading %q from bootstrap fs: %w", path, err) - } - - return creds, nil -} - -// APIAddr returns the network address of a garage api endpoint in the network. -// It will prefer an endpoint on this particular host, if there is one, but will -// otherwise return a random endpoint. -func APIAddr(env *crypticnet.Env) string { - - if allocs := env.ThisDaemon().Storage.Allocations; len(allocs) > 0 { - - return net.JoinHostPort( - env.ThisHost().Nebula.IP, - strconv.Itoa(allocs[0].APIPort), - ) - - } - - for _, host := range env.Hosts { - - if host.Garage == nil || len(host.Garage.Instances) == 0 { - continue - } - - return net.JoinHostPort( - host.Nebula.IP, - strconv.Itoa(host.Garage.Instances[0].APIPort), - ) - } - - panic("no garage instances configured") -} - -// APIClient returns a minio client configured to use the given garage API +// NewS3APIClient returns a minio client configured to use the given garage S3 API // endpoint. -func APIClient(addr string, creds APICredentials) (*minio.Client, error) { +func NewS3APIClient(addr string, creds S3APICredentials) (S3APIClient, error) { return minio.New(addr, &minio.Options{ Creds: credentials.NewStaticV4(creds.ID, creds.Secret, ""), Region: Region, }) } - -// GlobalBucketAPIClient returns a minio client pre-configured with access to -// the global bucket. -func GlobalBucketAPIClient(env *crypticnet.Env) (*minio.Client, error) { - - creds, err := GlobalBucketAPICredentials(env) - if err != nil { - return nil, fmt.Errorf("loading global bucket credentials: %w", err) - } - - addr := APIAddr(env) - - return APIClient(addr, creds) -} diff --git a/go-workspace/src/garage/garage.go b/go-workspace/src/garage/garage.go index 02fb5b7..79f3c4b 100644 --- a/go-workspace/src/garage/garage.go +++ b/go-workspace/src/garage/garage.go @@ -2,15 +2,6 @@ // setting up garage configs, processes, and deployments. package garage -import ( - crypticnet "cryptic-net" - "crypto/ed25519" - "encoding/hex" - "fmt" - "net" - "strconv" -) - const ( // Region is the region which garage is configured with. @@ -20,81 +11,3 @@ const ( // accessible to all hosts in the network. GlobalBucket = "cryptic-net-global" ) - -// GeneratePeerKey deterministically generates a public/private keys which can -// be used as a garage node key. -// -// DANGER: This function will deterministically produce public/private keys -// given some arbitrary input. This is NEVER what you want. It's only being used -// in cryptic-net for a very specific purpose for which I think it's ok and is -// very necessary, and people are probably _still_ going to yell at me. -// -func GeneratePeerKey(ip string, port int) (pubKey, privKey []byte, err error) { - - input := []byte(net.JoinHostPort(ip, strconv.Itoa(port))) - - // Append the length of the input to the input, so that the input "foo" - // doesn't generate the same key as the input "foofoo". - input = strconv.AppendInt(input, int64(len(input)), 10) - - return ed25519.GenerateKey(NewInfiniteReader(input)) -} - -// GeneratePeerID generates the peer id for the given peer. -// -// DANGER: See warning on GenerateNodeKey. -func GeneratePeerID(ip string, port int) (string, error) { - - peerNodeKeyPub, _, err := GeneratePeerKey(ip, port) - - if err != nil { - return "", err - } - - return hex.EncodeToString(peerNodeKeyPub), nil -} - -// GeneratePeerAddr generates the peer address (e.g. "id@ip:port") for the -// given peer. -// -// DANGER: See warning on GenerateNodeKey. -func GeneratePeerAddr(ip string, port int) (string, error) { - - id, err := GeneratePeerID(ip, port) - - if err != nil { - return "", fmt.Errorf("generating peer id: %w", err) - } - - return fmt.Sprintf("%s@%s", id, net.JoinHostPort(ip, strconv.Itoa(port))), nil -} - -// BootstrapPeerAddrs generates the list of bootstrap peer strings based on the -// bootstrap hosts. -func BootstrapPeerAddrs(hosts map[string]crypticnet.Host) ([]string, error) { - - var peers []string - - for _, host := range hosts { - - if host.Garage == nil { - continue - } - - for _, instance := range host.Garage.Instances { - - peer, err := GeneratePeerAddr(host.Nebula.IP, instance.RPCPort) - - if err != nil { - return nil, fmt.Errorf( - "generating peer address with input %q,%d: %w", - host.Nebula.IP, instance.RPCPort, err, - ) - } - - peers = append(peers, peer) - } - } - - return peers, nil -} diff --git a/go-workspace/src/garage/peer.go b/go-workspace/src/garage/peer.go new file mode 100644 index 0000000..17b671a --- /dev/null +++ b/go-workspace/src/garage/peer.go @@ -0,0 +1,66 @@ +package garage + +import ( + "crypto/ed25519" + "encoding/hex" + "fmt" + "net" + "strconv" +) + +// Peer describes all information necessary to connect to a given garage node. +type Peer struct { + IP string + RPCPort int + S3APIPort int +} + +// RPCPeerKey deterministically generates a public/private keys which can +// be used as a garage node key. +// +// DANGER: This function will deterministically produce public/private keys +// given some arbitrary input. This is NEVER what you want. It's only being used +// in cryptic-net for a very specific purpose for which I think it's ok and is +// very necessary, and people are probably _still_ going to yell at me. +// +func (p Peer) RPCPeerKey() (pubKey, privKey []byte) { + input := []byte(net.JoinHostPort(p.IP, strconv.Itoa(p.RPCPort))) + + // Append the length of the input to the input, so that the input "foo" + // doesn't generate the same key as the input "foofoo". + input = strconv.AppendInt(input, int64(len(input)), 10) + + pubKey, privKey, err := ed25519.GenerateKey(NewInfiniteReader(input)) + if err != nil { + panic(err) + } + + return pubKey, privKey +} + +// RPCPeerID returns the peer ID of the garage node for use in communicating +// over RPC. +// +// DANGER: See warning on RPCPeerKey. +func (p Peer) RPCPeerID() string { + pubKey, _ := p.RPCPeerKey() + return hex.EncodeToString(pubKey) +} + +// RPCAddr returns the address of the peer's RPC port. +func (p Peer) RPCAddr() string { + return net.JoinHostPort(p.IP, strconv.Itoa(p.RPCPort)) +} + +// RPCPeerAddr returns the full peer address (e.g. "id@ip:port") of the garage +// node for use in communicating over RPC. +// +// DANGER: See warning on RPCPeerKey. +func (p Peer) RPCPeerAddr() string { + return fmt.Sprintf("%s@%s", p.RPCPeerID(), p.RPCAddr()) +} + +// S3APIAddr returns the address of the peer's S3 API port. +func (p Peer) S3APIAddr() string { + return net.JoinHostPort(p.IP, strconv.Itoa(p.S3APIPort)) +} diff --git a/go-workspace/src/nebula/nebula.go b/go-workspace/src/nebula/nebula.go index 00f60ae..55cef5d 100644 --- a/go-workspace/src/nebula/nebula.go +++ b/go-workspace/src/nebula/nebula.go @@ -3,7 +3,7 @@ package nebula import ( - crypticnet "cryptic-net" + "cryptic-net/bootstrap" "crypto/ed25519" "crypto/rand" "fmt" @@ -43,7 +43,7 @@ type CACert struct { // NewHostCert generates a new key/cert for a nebula host using the CA key // which will be found in the adminFS. func NewHostCert( - adminFS fs.FS, host crypticnet.NebulaHost, + adminFS fs.FS, host bootstrap.NebulaHost, ) ( HostCert, error, ) { diff --git a/go-workspace/src/tarutil/tarutil.go b/go-workspace/src/tarutil/tarutil.go index 2f6a5cb..1b30e59 100644 --- a/go-workspace/src/tarutil/tarutil.go +++ b/go-workspace/src/tarutil/tarutil.go @@ -7,12 +7,11 @@ import ( "fmt" "io" "io/fs" - "os" "github.com/nlepage/go-tarfs" ) -// FSFromTGZFile returns a FS instance which will read the contents of a tgz +// FSFromReader returns a FS instance which will read the contents of a tgz // file from the given Reader. func FSFromReader(r io.Reader) (fs.FS, error) { gf, err := gzip.NewReader(r) @@ -23,15 +22,3 @@ func FSFromReader(r io.Reader) (fs.FS, error) { return tarfs.New(gf) } - -// FSFromTGZFile returns a FS instance which will read the contents of a tgz -// file. -func FSFromTGZFile(path string) (fs.FS, error) { - f, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("opening file: %w", err) - } - defer f.Close() - - return FSFromReader(f) -}