2021-04-20 21:31:37 +00:00
|
|
|
package crypticnet
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-10-15 14:28:03 +00:00
|
|
|
"cryptic-net/bootstrap"
|
2021-04-20 21:31:37 +00:00
|
|
|
"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
|
2022-10-15 14:28:03 +00:00
|
|
|
Bootstrap bootstrap.Bootstrap
|
2021-04-20 21:31:37 +00:00
|
|
|
|
|
|
|
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 {
|
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
var err error
|
2021-04-20 21:31:37 +00:00
|
|
|
|
2022-10-15 14:28:03 +00:00
|
|
|
if e.Bootstrap, err = bootstrap.FromFile(path); err != nil {
|
|
|
|
return fmt.Errorf("parsing bootstrap.tgz at %q: %w", path, err)
|
2021-04-20 21:31:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|