163 lines
4.7 KiB
Go
163 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
|
|
"isle/bootstrap"
|
|
"isle/daemon"
|
|
|
|
"dev.mediocregopher.com/mediocre-go-lib.git/mctx"
|
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
|
)
|
|
|
|
var subCmdDaemon = subCmd{
|
|
name: "daemon",
|
|
descr: "Runs the isle daemon (Default if no sub-command given)",
|
|
do: func(subCmdCtx subCmdCtx) error {
|
|
|
|
flags := subCmdCtx.flagSet(false)
|
|
|
|
daemonConfigPath := flags.StringP(
|
|
"config-path", "c", "",
|
|
"Optional path to a daemon.yml file to load configuration from.",
|
|
)
|
|
|
|
dumpConfig := flags.Bool(
|
|
"dump-config", false,
|
|
"Write the default configuration file to stdout and exit.",
|
|
)
|
|
|
|
bootstrapPath := flags.StringP(
|
|
"bootstrap-path", "b", "",
|
|
`Path to a bootstrap.json file. This only needs to be provided the first time the daemon is started, after that it is ignored. If the isle binary has a bootstrap built into it then this argument is always optional.`,
|
|
)
|
|
|
|
logLevelStr := flags.StringP(
|
|
"log-level", "l", "info",
|
|
`Maximum log level which should be output. Values can be "debug", "info", "warn", "error", "fatal". Does not apply to sub-processes`,
|
|
)
|
|
|
|
if err := flags.Parse(subCmdCtx.args); err != nil {
|
|
return fmt.Errorf("parsing flags: %w", err)
|
|
}
|
|
|
|
ctx := subCmdCtx.ctx
|
|
|
|
if *dumpConfig {
|
|
return daemon.CopyDefaultConfig(os.Stdout, envAppDirPath)
|
|
}
|
|
|
|
logLevel := mlog.LevelFromString(*logLevelStr)
|
|
if logLevel == nil {
|
|
return fmt.Errorf("couldn't parse log level %q", *logLevelStr)
|
|
}
|
|
|
|
logger := subCmdCtx.logger.WithMaxLevel(logLevel.Int())
|
|
|
|
runtimeDirCleanup, err := setupAndLockRuntimeDir(ctx, logger)
|
|
if err != nil {
|
|
return fmt.Errorf("setting up runtime directory: %w", err)
|
|
}
|
|
defer runtimeDirCleanup()
|
|
|
|
var (
|
|
bootstrapStateDirPath = bootstrap.StateDirPath(daemonEnvVars.StateDirPath)
|
|
bootstrapAppDirPath = bootstrap.AppDirPath(envAppDirPath)
|
|
|
|
hostBootstrapPath string
|
|
hostBootstrap bootstrap.Bootstrap
|
|
)
|
|
|
|
tryLoadBootstrap := func(path string) bool {
|
|
ctx := mctx.Annotate(ctx, "bootstrapFilePath", path)
|
|
|
|
if err != nil {
|
|
return false
|
|
|
|
} else if hostBootstrap, err = bootstrap.FromFile(path); errors.Is(err, fs.ErrNotExist) {
|
|
logger.WarnString(ctx, "bootstrap file not found")
|
|
err = nil
|
|
return false
|
|
|
|
} else if err != nil {
|
|
err = fmt.Errorf("parsing bootstrap.json at %q: %w", path, err)
|
|
return false
|
|
}
|
|
|
|
logger.Info(ctx, "bootstrap file found")
|
|
|
|
hostBootstrapPath = path
|
|
return true
|
|
}
|
|
|
|
switch {
|
|
case tryLoadBootstrap(bootstrapStateDirPath):
|
|
case *bootstrapPath != "" && tryLoadBootstrap(*bootstrapPath):
|
|
case tryLoadBootstrap(bootstrapAppDirPath):
|
|
case err != nil:
|
|
return fmt.Errorf("attempting to load bootstrap.json file: %w", err)
|
|
default:
|
|
return errors.New("No bootstrap.json file could be found, and one is not provided with --bootstrap-path")
|
|
}
|
|
|
|
if hostBootstrapPath != bootstrapStateDirPath {
|
|
|
|
// If the bootstrap file is not being stored in the data dir, copy
|
|
// it there, so it can be loaded from there next time.
|
|
if err := writeBootstrapToStateDir(hostBootstrap); err != nil {
|
|
return fmt.Errorf("writing bootstrap.json to data dir: %w", err)
|
|
}
|
|
}
|
|
|
|
daemonConfig, err := daemon.LoadConfig(envAppDirPath, *daemonConfigPath)
|
|
if err != nil {
|
|
return fmt.Errorf("loading daemon config: %w", err)
|
|
}
|
|
|
|
// we update this Host's data using whatever configuration has been
|
|
// provided by the daemon config. This way the daemon has the most
|
|
// up-to-date possible bootstrap. This updated bootstrap will later get
|
|
// updated in garage as a background daemon task, so other hosts will
|
|
// see it as well.
|
|
if hostBootstrap, err = coalesceDaemonConfigAndBootstrap(hostBootstrap, daemonConfig); err != nil {
|
|
return fmt.Errorf("merging daemon config into bootstrap data: %w", err)
|
|
}
|
|
|
|
daemonInst := daemon.NewDaemon(
|
|
logger, daemonConfig, envBinDirPath, hostBootstrap, nil,
|
|
)
|
|
defer func() {
|
|
logger.Info(ctx, "Stopping child processes")
|
|
if err := daemonInst.Shutdown(); err != nil {
|
|
logger.Error(ctx, "Shutting down daemon cleanly failed, there may be orphaned child processes", err)
|
|
}
|
|
logger.Info(ctx, "Child processes successfully stopped")
|
|
}()
|
|
|
|
{
|
|
logger := logger.WithNamespace("http")
|
|
httpSrv, err := newHTTPServer(
|
|
ctx, logger, daemon.NewRPC(daemonInst),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("starting HTTP server: %w", err)
|
|
}
|
|
defer func() {
|
|
// see comment in daemonInst shutdown logic regarding background
|
|
// context.
|
|
logger.Info(ctx, "Shutting down HTTP socket")
|
|
if err := httpSrv.Shutdown(context.Background()); err != nil {
|
|
logger.Error(ctx, "Failed to cleanly shutdown http server", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
<-ctx.Done()
|
|
return nil
|
|
},
|
|
}
|