// Package bootstrap deals with the parsing and creation of bootstrap.yml files. // It also contains some helpers which rely on bootstrap data. package bootstrap import ( "isle/admin" "isle/garage" "isle/nebula" "crypto/sha512" "fmt" "io" "os" "path/filepath" "sort" "gopkg.in/yaml.v3" ) // 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.yml") } // 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.yml") } // Bootstrap is used for accessing all information contained within a // bootstrap.yml file. type Bootstrap struct { AdminCreationParams admin.CreationParams `yaml:"admin_creation_params"` Hosts map[string]Host `yaml:"hosts"` HostName string `yaml:"hostname"` Nebula struct { CAPublicCredentials nebula.CAPublicCredentials `yaml:"ca_public_credentials"` HostCredentials nebula.HostCredentials `yaml:"host_credentials"` SignedPublicCredentials string `yaml:"signed_public_credentials"` } `yaml:"nebula"` Garage struct { RPCSecret string `yaml:"rpc_secret"` AdminToken string `yaml:"admin_token"` GlobalBucketS3APICredentials garage.S3APICredentials `yaml:"global_bucket_s3_api_credentials"` } `yaml:"garage"` } // FromReader reads a bootstrap.yml file from the given io.Reader. func FromReader(r io.Reader) (Bootstrap, error) { var b Bootstrap err := yaml.NewDecoder(r).Decode(&b) return b, err } // FromFile reads a bootstrap.yml from a file at the given path. func FromFile(path string) (Bootstrap, error) { f, err := os.Open(path) if err != nil { return Bootstrap{}, fmt.Errorf("opening file: %w", err) } defer f.Close() return FromReader(f) } // WriteTo writes the Bootstrap as a new bootstrap.yml to the given io.Writer. func (b Bootstrap) WriteTo(into io.Writer) error { return yaml.NewEncoder(into).Encode(b) } // 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)) } return host } // 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 }