~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
86 lines · 2.5 KB Raw History
package bootstrap

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"syscall"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/devinstance"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/hostsvc"
)

const (
	ProfileStandIns  = "stand-ins"
	StandInStopGrace = 5 * time.Second
)

var standInRole = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,23}$`)

func StandInUnit(l devinstance.Layout, role string) string {
	return l.Names().StandIn(role)
}

func ReadStandIns(path string) ([]devinstance.StandIn, error) {
	b, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	var s []devinstance.StandIn
	if err := json.Unmarshal(b, &s); err != nil {
		return nil, fmt.Errorf("bootstrap: stand-ins %s: %w", path, err)
	}
	if len(s) == 0 {
		return nil, fmt.Errorf("bootstrap: stand-ins %s names no unit", path)
	}
	seen := map[string]bool{}
	for _, u := range s {
		if !standInRole.MatchString(u.Role) || seen[u.Role] || u.Role == "core" {
			return nil, fmt.Errorf("bootstrap: stand-in role %q: want a unique lower-case name of at most 24, not core", u.Role)
		}
		seen[u.Role] = true
		if len(u.Exec) == 0 || !filepath.IsAbs(u.Exec[0]) {
			return nil, fmt.Errorf("bootstrap: stand-in %s: exec must start with an absolute path (never PATH)", u.Role)
		}
	}
	return s, nil
}

const StandInMemoryMax = 1 << 30

func (c Config) standInSpec(s devinstance.StandIn) hostsvc.UnitSpec {
	l := c.Layout
	log := filepath.Join(l.Logs, s.Role+".log")
	return hostsvc.UnitSpec{Name: StandInUnit(l, s.Role), Description: "stand-in " + s.Role + " of " + l.ID, Exec: s.Exec,
		Env: s.Env, WorkDir: l.StateDir, Slice: l.KcpSlice(), Restart: hostsvc.RestartNo, KillSignal: syscall.SIGTERM,
		StopTimeout: StandInStopGrace, Limits: hostsvc.Limits{MemoryMax: StandInMemoryMax}, Output: hostsvc.Output{StdoutFile: log, StderrFile: log}}
}

func ensureStandIns(ctx context.Context, m hostsvc.Manager, cfg Config, rec *devinstance.Record) error {
	cfg.silent = map[string]bool{}
	for _, s := range rec.StandIns {
		cfg.silent[s.Role] = !s.Listens
	}
	if err := os.MkdirAll(cfg.Layout.Logs, 0o700); err != nil {
		return err
	}
	for _, s := range rec.StandIns {
		u, _, err := ensureUnit(ctx, m, cfg, rec, s.Role, cfg.standInSpec(s), nil)
		if err != nil {
			return fmt.Errorf("bootstrap: stand-in %s: %w", s.Role, err)
		}
		if s.Listens {
			u.Endpoint = "tcp://127.0.0.1:" + strconv.Itoa(u.Port)
			cfg.onListen(s.Role, u.Port)
		}
		rec.SetUnit(u)
	}
	rec.Ready = true
	rec.Updated = time.Now().UTC()
	return nil
}