isle/go-workspace/src/bootstrap/bootstrap.go

132 lines
3.5 KiB
Go
Raw Normal View History

// Package bootstrap deals with the creation of bootstrap files
package bootstrap
import (
"context"
crypticnet "cryptic-net"
"cryptic-net/garage"
"cryptic-net/nebula"
"cryptic-net/tarutil"
"fmt"
"io"
"io/fs"
)
// 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) {
b, err := fs.ReadFile(bootstrapFS, tarutil.HashBinPath)
if err != nil {
return nil, fmt.Errorf("reading file %q from bootstrap 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) {
bootstrapFS, err := tarutil.FSFromReader(r)
if err != nil {
return nil, fmt.Errorf("reading tar fs from reader: %w", err)
}
return GetHashFromFS(bootstrapFS)
}
func newBootstrap(
ctx context.Context,
into io.Writer,
hostname provider,
nebulaCerts provider,
nebulaHosts provider,
garageRPCSecret provider,
garageGlobalBucketKey provider,
garageHosts provider,
) 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)
if err != nil {
return fmt.Errorf("creating client for global bucket: %w", err)
}
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
)
}
// 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.Hosts[name]
if !ok {
return fmt.Errorf("unknown host %q, make sure host entry has been created", name)
}
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
)
}