836e69735d
Host types have been moved within the `bootstrap` package. Refactored how boostrap FS is interacted with. There is now a `Bootstrap` struct which has pre-loaded all data within the bootstrap FS. This helps centralize the logic around reading the data (though not yet completely). Choosing of the garage node to interact with no longer occurs in like three different places. It occurs at the environment level now, and is aided by the new `garage.Peer` type.
227 lines
5.6 KiB
Go
227 lines
5.6 KiB
Go
package crypticnet
|
|
|
|
import (
|
|
"context"
|
|
"cryptic-net/bootstrap"
|
|
"cryptic-net/yamlutil"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"sync"
|
|
"syscall"
|
|
|
|
"github.com/adrg/xdg"
|
|
)
|
|
|
|
// Names of various environment variables which get set by the entrypoint.
|
|
const (
|
|
DaemonYmlPathEnvVar = "_DAEMON_YML_PATH"
|
|
BootstrapPathEnvVar = "_BOOTSTRAP_PATH"
|
|
RuntimeDirPathEnvVar = "_RUNTIME_DIR_PATH"
|
|
DataDirPathEnvVar = "_DATA_DIR_PATH"
|
|
)
|
|
|
|
// Env contains the values of environment variables, as well as other entities
|
|
// which are useful across all processes.
|
|
type Env struct {
|
|
Context context.Context
|
|
|
|
AppDirPath string
|
|
DaemonYmlPath string
|
|
RuntimeDirPath 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
|
|
|
|
thisDaemon DaemonYml
|
|
thisDaemonOnce sync.Once
|
|
}
|
|
|
|
func getAppDirPath() string {
|
|
appDirPath := os.Getenv("APPDIR")
|
|
if appDirPath == "" {
|
|
appDirPath = "."
|
|
}
|
|
return appDirPath
|
|
}
|
|
|
|
// NewEnv calculates an Env instance based on the APPDIR and XDG envvars.
|
|
//
|
|
// 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")
|
|
appDirPath := getAppDirPath()
|
|
|
|
env := &Env{
|
|
AppDirPath: appDirPath,
|
|
DaemonYmlPath: filepath.Join(runtimeDirPath, "daemon.yml"),
|
|
RuntimeDirPath: runtimeDirPath,
|
|
DataDirPath: filepath.Join(xdg.DataHome, "cryptic-net"),
|
|
}
|
|
|
|
return env, env.init(bootstrapOptional)
|
|
}
|
|
|
|
// ReadEnv reads an Env from the process's environment variables, rather than
|
|
// calculating like NewEnv does.
|
|
func ReadEnv() (*Env, error) {
|
|
|
|
var err error
|
|
|
|
readEnv := func(key string) string {
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
val := os.Getenv(key)
|
|
|
|
if val == "" {
|
|
err = fmt.Errorf("envvar %q not set", key)
|
|
}
|
|
|
|
return val
|
|
}
|
|
|
|
env := &Env{
|
|
AppDirPath: getAppDirPath(),
|
|
DaemonYmlPath: readEnv(DaemonYmlPathEnvVar),
|
|
RuntimeDirPath: readEnv(RuntimeDirPathEnvVar),
|
|
DataDirPath: readEnv(DataDirPathEnvVar),
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return env, env.init(false)
|
|
}
|
|
|
|
// 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 sets BootstrapPath to the given value, and loads BootstrapFS
|
|
// and all derived fields based on that.
|
|
func (e *Env) LoadBootstrap(path string) error {
|
|
|
|
var err error
|
|
|
|
if e.Bootstrap, err = bootstrap.FromFile(path); err != nil {
|
|
return fmt.Errorf("parsing bootstrap.tgz at %q: %w", path, err)
|
|
}
|
|
|
|
e.BootstrapPath = path
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Env) initBootstrap(bootstrapOptional bool) 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 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 fmt.Errorf("determining if %q exists: %w", bootstrapPath, err)
|
|
|
|
} else if !exists && !bootstrapOptional {
|
|
return fmt.Errorf("boostrap file not found at %q", bootstrapPath)
|
|
|
|
} else if exists {
|
|
return e.LoadBootstrap(bootstrapPath)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (e *Env) init(bootstrapOptional bool) error {
|
|
|
|
var cancel context.CancelFunc
|
|
e.Context, cancel = context.WithCancel(context.Background())
|
|
|
|
signalCh := make(chan os.Signal, 2)
|
|
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
sig := <-signalCh
|
|
cancel()
|
|
fmt.Fprintf(os.Stderr, "got signal %v, will exit gracefully\n", sig)
|
|
|
|
sig = <-signalCh
|
|
fmt.Fprintf(os.Stderr, "second interrupt signal %v received, force quitting, there may be zombie children left behind, good luck!\n", sig)
|
|
|
|
os.Stderr.Sync()
|
|
os.Exit(1)
|
|
}()
|
|
|
|
if err := e.initBootstrap(bootstrapOptional); err != nil {
|
|
return fmt.Errorf("initializing bootstrap data: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 {
|
|
return map[string]string{
|
|
DaemonYmlPathEnvVar: e.DaemonYmlPath,
|
|
BootstrapPathEnvVar: e.BootstrapPath,
|
|
RuntimeDirPathEnvVar: e.RuntimeDirPath,
|
|
DataDirPathEnvVar: e.DataDirPath,
|
|
}
|
|
}
|
|
|
|
// ThisDaemon returns the DaemonYml (loaded from DaemonYmlPath) for the
|
|
// currently running process.
|
|
func (e *Env) ThisDaemon() DaemonYml {
|
|
e.thisDaemonOnce.Do(func() {
|
|
if err := yamlutil.LoadYamlFile(&e.thisDaemon, e.DaemonYmlPath); err != nil {
|
|
panic(err)
|
|
}
|
|
})
|
|
return e.thisDaemon
|
|
}
|
|
|
|
// BinPath returns the absolute path to a binary in the AppDir.
|
|
func (e *Env) BinPath(name string) string {
|
|
return filepath.Join(e.AppDirPath, "bin", name)
|
|
}
|