// Package children manages the creation, lifetime, and shutdown of child // processes created by the daemon. package children import ( "context" "errors" "fmt" "io" "os" "code.betamike.com/micropelago/pmux/pmuxlib" "dev.mediocregopher.com/mediocre-go-lib.git/mlog" "isle/bootstrap" "isle/daemon/daecommon" "isle/secrets" "isle/toolkit" ) // Opts are optional parameters which can be passed in when initializing a new // Children instance. A nil Opts is equivalent to a zero value. type Opts struct { // Stdout and Stderr are what the associated outputs from child processes // will be directed to. Stdout, Stderr io.Writer } func (o *Opts) withDefaults() *Opts { if o == nil { o = new(Opts) } if o.Stdout == nil { o.Stdout = os.Stdout } if o.Stderr == nil { o.Stderr = os.Stderr } return o } // Children manages all child processes of a network. Child processes are // comprised of: // - nebula // - dnsmasq // - garage (0 or more, depending on configured storage allocations) type Children struct { logger *mlog.Logger runtimeDir toolkit.Dir opts Opts pmux *pmuxlib.Pmux } // New initializes and returns a Children instance. If initialization fails an // error is returned. func New( ctx context.Context, logger *mlog.Logger, binDirPath string, secretsStore secrets.Store, networkConfig daecommon.NetworkConfig, runtimeDir toolkit.Dir, garageAdminToken string, hostBootstrap bootstrap.Bootstrap, opts *Opts, ) ( *Children, error, ) { opts = opts.withDefaults() logger.Info(ctx, "Loading secrets") garageRPCSecret, err := daecommon.GetGarageRPCSecret(ctx, secretsStore) if err != nil && !errors.Is(err, secrets.ErrNotFound) { return nil, fmt.Errorf("loading garage RPC secret: %w", err) } c := &Children{ logger: logger, runtimeDir: runtimeDir, opts: *opts, } pmuxConfig, err := c.newPmuxConfig( ctx, garageRPCSecret, binDirPath, networkConfig, garageAdminToken, hostBootstrap, ) if err != nil { return nil, fmt.Errorf("generating pmux config: %w", err) } c.pmux = pmuxlib.NewPmux(pmuxConfig, c.opts.Stdout, c.opts.Stderr) initErr := c.postPmuxInit( ctx, networkConfig, garageAdminToken, hostBootstrap, ) if initErr != nil { logger.Warn(ctx, "failed to initialize Children, shutting down child processes", err) c.Shutdown() return nil, initErr } return c, nil } // TODO block until process has been confirmed to have come back up // successfully. func (c *Children) reloadDNSMasq( networkConfig daecommon.NetworkConfig, hostBootstrap bootstrap.Bootstrap, ) error { _, err := dnsmasqWriteConfig( c.runtimeDir.Path, networkConfig, hostBootstrap, ) if err != nil { return fmt.Errorf("writing new dnsmasq config: %w", err) } c.pmux.Restart("dnsmasq") return nil } func (c *Children) reloadNebula( ctx context.Context, networkConfig daecommon.NetworkConfig, hostBootstrap bootstrap.Bootstrap, ) error { _, err := nebulaWriteConfig( c.runtimeDir.Path, networkConfig, hostBootstrap, ) if err != nil { return fmt.Errorf("writing a new nebula config: %w", err) } c.pmux.Restart("nebula") c.logger.Info(ctx, "Waiting for nebula VPN to come online") if err := waitForNebula(ctx, c.logger, hostBootstrap); err != nil { return fmt.Errorf("waiting for nebula to start: %w", err) } return nil } // Reload applies a ReloadDiff to the Children, using the given bootstrap which // must be the same one which was passed to CalculateReloadDiff. func (c *Children) Reload( ctx context.Context, newNetworkConfig daecommon.NetworkConfig, newBootstrap bootstrap.Bootstrap, diff ReloadDiff, ) error { var errs []error if diff.DNSChanged { c.logger.Info(ctx, "Reloading dnsmasq to account for bootstrap changes") if err := c.reloadDNSMasq(newNetworkConfig, newBootstrap); err != nil { errs = append(errs, fmt.Errorf("reloading dnsmasq: %w", err)) } } if diff.NebulaChanged { c.logger.Info(ctx, "Reloading nebula to account for bootstrap changes") err := c.reloadNebula(ctx, newNetworkConfig, newBootstrap) if err != nil { errs = append(errs, fmt.Errorf("reloading nebula: %w", err)) } } return errors.Join(errs...) } // Shutdown blocks until all child processes have gracefully shut themselves // down. func (c *Children) Shutdown() { c.pmux.Stop() }