2022-10-16 14:39:05 +00:00
|
|
|
// Package bootstrap deals with the parsing and creation of bootstrap.tgz files.
|
|
|
|
// It also contains some helpers which rely on bootstrap data.
|
2021-04-20 21:31:37 +00:00
|
|
|
package bootstrap
|
|
|
|
|
|
|
|
import (
|
2022-10-16 15:05:05 +00:00
|
|
|
"cryptic-net/admin"
|
2022-10-11 19:24:53 +00:00
|
|
|
"cryptic-net/garage"
|
2022-10-16 14:39:05 +00:00
|
|
|
"cryptic-net/nebula"
|
2021-04-20 21:31:37 +00:00
|
|
|
"cryptic-net/tarutil"
|
2022-10-15 14:28:03 +00:00
|
|
|
"cryptic-net/yamlutil"
|
2022-10-15 16:41:07 +00:00
|
|
|
"crypto/sha512"
|
2021-04-20 21:31:37 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/fs"
|
2022-10-15 14:28:03 +00:00
|
|
|
"os"
|
2022-10-15 16:41:07 +00:00
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
"gopkg.in/yaml.v3"
|
2021-04-20 21:31:37 +00:00
|
|
|
)
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
// Paths within the bootstrap FS which for general data.
|
|
|
|
const (
|
2022-10-16 15:05:05 +00:00
|
|
|
adminCreationParamsPath = "admin/creation-params.yml"
|
|
|
|
hostNamePath = "hostname"
|
2022-10-15 14:28:03 +00:00
|
|
|
)
|
2021-04-20 21:31:37 +00:00
|
|
|
|
2022-10-26 22:23:39 +00:00
|
|
|
// 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")
|
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
// Bootstrap is used for accessing all information contained within a
|
|
|
|
// bootstrap.tgz file.
|
|
|
|
type Bootstrap struct {
|
2022-10-16 15:05:05 +00:00
|
|
|
AdminCreationParams admin.CreationParams
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
Hosts map[string]Host
|
|
|
|
HostName string
|
|
|
|
|
2022-10-29 19:11:40 +00:00
|
|
|
NebulaHostCredentials nebula.HostCredentials
|
2022-10-15 14:28:03 +00:00
|
|
|
|
|
|
|
GarageRPCSecret string
|
2022-10-16 19:22:58 +00:00
|
|
|
GarageAdminToken string
|
2022-10-15 14:28:03 +00:00
|
|
|
GarageGlobalBucketS3APICredentials garage.S3APICredentials
|
|
|
|
}
|
2021-04-20 21:31:37 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
// 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) {
|
2021-04-20 21:31:37 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
var (
|
|
|
|
b Bootstrap
|
|
|
|
err error
|
|
|
|
)
|
2021-04-20 21:31:37 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
if b.Hosts, err = loadHosts(bootstrapFS); err != nil {
|
|
|
|
return Bootstrap{}, fmt.Errorf("loading hosts info from fs: %w", err)
|
2021-04-20 21:31:37 +00:00
|
|
|
}
|
|
|
|
|
2022-10-16 15:05:05 +00:00
|
|
|
filesToLoadAsYAML := []struct {
|
|
|
|
into interface{}
|
|
|
|
path string
|
|
|
|
}{
|
|
|
|
{&b.AdminCreationParams, adminCreationParamsPath},
|
|
|
|
{&b.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, f := range filesToLoadAsYAML {
|
|
|
|
if err := yamlutil.LoadYamlFSFile(f.into, bootstrapFS, f.path); err != nil {
|
|
|
|
return Bootstrap{}, fmt.Errorf("loading %q from fs: %w", f.path, err)
|
|
|
|
}
|
2022-10-15 14:28:03 +00:00
|
|
|
}
|
2022-10-11 19:24:53 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
filesToLoadAsString := []struct {
|
|
|
|
into *string
|
|
|
|
path string
|
2022-10-11 19:24:53 +00:00
|
|
|
}{
|
2022-10-15 16:41:07 +00:00
|
|
|
{&b.HostName, hostNamePath},
|
2022-10-29 19:11:40 +00:00
|
|
|
{&b.NebulaHostCredentials.CACertPEM, nebulaCertsCACertPath},
|
|
|
|
{&b.NebulaHostCredentials.HostCertPEM, nebulaCertsHostCertPath},
|
|
|
|
{&b.NebulaHostCredentials.HostKeyPEM, nebulaCertsHostKeyPath},
|
2022-10-15 16:41:07 +00:00
|
|
|
{&b.GarageRPCSecret, garageRPCSecretPath},
|
2022-10-16 19:22:58 +00:00
|
|
|
{&b.GarageAdminToken, garageAdminTokenPath},
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
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)
|
|
|
|
}
|
2022-10-11 19:24:53 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
return b, nil
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
// FromReader reads a bootstrap.tgz file from the given io.Reader.
|
|
|
|
func FromReader(r io.Reader) (Bootstrap, error) {
|
2022-10-11 19:24:53 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
fs, err := tarutil.FSFromReader(r)
|
2022-10-11 19:24:53 +00:00
|
|
|
if err != nil {
|
2022-10-15 14:28:03 +00:00
|
|
|
return Bootstrap{}, fmt.Errorf("reading bootstrap.tgz: %w", err)
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
return FromFS(fs)
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
// FromFile reads a bootstrap.tgz from a file at the given path.
|
|
|
|
func FromFile(path string) (Bootstrap, error) {
|
2022-10-11 19:24:53 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
f, err := os.Open(path)
|
2022-10-11 19:24:53 +00:00
|
|
|
if err != nil {
|
2022-10-15 14:28:03 +00:00
|
|
|
return Bootstrap{}, fmt.Errorf("opening file: %w", err)
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
2022-10-15 14:28:03 +00:00
|
|
|
defer f.Close()
|
2022-10-11 19:24:53 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
return FromReader(f)
|
|
|
|
}
|
2022-10-11 19:24:53 +00:00
|
|
|
|
2022-10-15 16:41:07 +00:00
|
|
|
// WriteTo writes the Bootstrap as a new bootstrap.tgz to the given io.Writer.
|
|
|
|
func (b Bootstrap) WriteTo(into io.Writer) error {
|
|
|
|
|
|
|
|
w := tarutil.NewTGZWriter(into)
|
|
|
|
|
2022-10-16 15:05:05 +00:00
|
|
|
for _, host := range b.Hosts {
|
|
|
|
|
|
|
|
hostB, err := yaml.Marshal(host)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("yaml encoding host %#v: %w", host, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
path := filepath.Join(hostsDirPath, host.Name+".yml")
|
|
|
|
|
|
|
|
w.WriteFileBytes(path, hostB)
|
|
|
|
}
|
|
|
|
|
|
|
|
filesToWriteAsYAML := []struct {
|
|
|
|
value interface{}
|
|
|
|
path string
|
|
|
|
}{
|
|
|
|
{b.AdminCreationParams, adminCreationParamsPath},
|
|
|
|
{b.GarageGlobalBucketS3APICredentials, garageGlobalBucketKeyYmlPath},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, f := range filesToWriteAsYAML {
|
|
|
|
|
|
|
|
b, err := yaml.Marshal(f.value)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("yaml encoding data for %q: %w", f.path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteFileBytes(f.path, b)
|
|
|
|
}
|
|
|
|
|
2022-10-15 16:41:07 +00:00
|
|
|
filesToWriteAsString := []struct {
|
|
|
|
value string
|
|
|
|
path string
|
|
|
|
}{
|
|
|
|
{b.HostName, hostNamePath},
|
2022-10-29 19:11:40 +00:00
|
|
|
{b.NebulaHostCredentials.CACertPEM, nebulaCertsCACertPath},
|
|
|
|
{b.NebulaHostCredentials.HostCertPEM, nebulaCertsHostCertPath},
|
|
|
|
{b.NebulaHostCredentials.HostKeyPEM, nebulaCertsHostKeyPath},
|
2022-10-15 16:41:07 +00:00
|
|
|
{b.GarageRPCSecret, garageRPCSecretPath},
|
2022-10-16 19:22:58 +00:00
|
|
|
{b.GarageAdminToken, garageAdminTokenPath},
|
2022-10-15 16:41:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, f := range filesToWriteAsString {
|
|
|
|
w.WriteFileBytes(f.path, []byte(f.value))
|
|
|
|
}
|
|
|
|
|
|
|
|
return w.Close()
|
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
// 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 := b.Hosts[b.HostName]
|
|
|
|
if !ok {
|
|
|
|
panic(fmt.Sprintf("hostname %q not defined in bootstrap's hosts", b.HostName))
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
return host
|
2022-10-11 19:24:53 +00:00
|
|
|
}
|
2022-10-15 16:41:07 +00:00
|
|
|
|
|
|
|
// Hash returns a deterministic hash of the given hosts map.
|
|
|
|
func HostsHash(hostsMap map[string]Host) ([]byte, error) {
|
|
|
|
|
|
|
|
hosts := make([]Host, 0, len(hostsMap))
|
|
|
|
for _, host := range hostsMap {
|
|
|
|
hosts = append(hosts, host)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(hosts, func(i, j int) bool { return hosts[i].Name < hosts[j].Name })
|
|
|
|
|
|
|
|
h := sha512.New()
|
|
|
|
|
|
|
|
if err := yaml.NewEncoder(h).Encode(hosts); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return h.Sum(nil), nil
|
|
|
|
}
|