~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
147 lines · 3.8 KB Raw History
package install

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"strings"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/bootstrap"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/bootstrap/table"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/hostcontract"
)

type Enabler interface {
	Enable(ctx context.Context, units []string) error
}

func BootstrapApps(role hostcontract.Role, prefix string, handles []string) []table.App {
	if role == hostcontract.RoleGateway {
		g := table.Gateway()
		g.Prefix = prefix
		return []table.App{g}
	}
	var out []table.App
	if role == hostcontract.RoleRequester {
		for _, n := range handles {
			r := table.Requester(n, RequesterKey(n))
			r.Prefix = prefix
			out = append(out, r)
		}
		return out
	}
	for _, h := range handles {
		b := table.Bidder(h, HandleID(h))
		b.Prefix = prefix
		out = append(out, b)
	}
	return out
}

func BootableUnits(role hostcontract.Role, prefix string, handles []string) ([]string, error) {
	var out []string
	for _, a := range BootstrapApps(role, prefix, handles) {
		entries, err := bootstrap.Bootable(a)
		if err != nil {
			return nil, err
		}
		for _, e := range entries {
			out = append(out, e.Unit)
		}
	}
	return out, nil
}

func (in *installer) bootstrapFiles(dir string) ([]string, bool, error) {
	var names []string
	changed := false
	uid, gid := in.acct.UID, in.acct.GID
	front := in.configString("kcp-front-address")
	for _, a := range BootstrapApps(in.o.Role, in.o.UnitPrefix, in.instances()) {
		p := bootstrap.InstallParams(a, in.o.StateDir)
		frontURL := ""
		if front != "" && a.Role == table.RoleGateway {
			p.FrontAddress = front
			if !strings.HasSuffix(front, ":0") {
				frontURL = "https://" + front
			}
		}
		units, err := bootstrap.Installed(p, frontURL)
		if err != nil {
			return nil, false, err
		}
		for _, d := range append(in.accountParents(p.Dir), p.Dir, p.Run, filepath.Join(p.Dir, "bin")) {
			if err := in.ensureDir(d, 0o700, uid, gid, false); err != nil {
				return nil, false, err
			}
		}
		for _, u := range units {
			full := filepath.Join(dir, u.Path)
			if strings.Contains(u.Path, "/") {
				if err := in.ensureDir(filepath.Dir(full), 0o755, uid, gid, false); err != nil {
					return nil, false, err
				}
			}
			c, err := in.ensureFile(full, u.File.Unit, 0o644, uid, gid)
			if err != nil {
				return nil, false, err
			}
			in.rec.addUnitFile(full)
			if c {
				in.noteWritten(u.Path)
			}
			changed = changed || c
			names = append(names, u.Path)
			if u.File.EnvPath == "" || u.File.Env == nil {
				continue
			}
			if u.Entry.Kind == table.KindSlot {
				if _, err := os.Stat(in.path(u.File.EnvPath)); err == nil {
					continue
				}
			}
			ec, err := in.ensureFile(u.File.EnvPath, u.File.Env, 0o600, uid, gid)
			if err != nil {
				return nil, false, err
			}
			changed = changed || ec
		}
	}
	return names, changed, nil
}

func (in *installer) configString(key string) string {
	b, err := os.ReadFile(in.path(filepath.Join(in.o.StateDir, "config.json")))
	if err != nil {
		return ""
	}
	cfg := map[string]any{}
	if json.Unmarshal(b, &cfg) != nil {
		return ""
	}
	s, _ := cfg[key].(string)
	return s
}

func (in *installer) enableBootstrap(ctx context.Context, um UserManager) error {
	units, err := BootableUnits(in.o.Role, in.o.UnitPrefix, in.instances())
	if err != nil || len(units) == 0 {
		return err
	}
	e, ok := um.(Enabler)
	if !ok {
		return nil
	}
	return in.change("enable "+strings.Join(units, " ")+" as "+in.o.Account+" (started at boot, before the core)", func() error {
		return e.Enable(ctx, units)
	})
}

func (in *installer) accountParents(dir string) []string {
	var out []string
	for d := filepath.Dir(dir); d != in.o.StateDir && within(d, in.o.StateDir); d = filepath.Dir(d) {
		out = append([]string{d}, out...)
	}
	return out
}