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

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/internal/assets"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/bootstrap"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/bootstrap/table"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/clicfg"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/devinstance"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/hostsvc"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/hostsvc/treeaudit"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/install"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/kcpdatastore"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/kcpserver"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/versions"
)

type placement struct {
	manager hostsvc.Manager
	devsup  int
	runtime string
}

func (a *app) placement(ctx context.Context, vals clicfg.Values) (placement, error) {
	backend, err := hostsvc.ParseBackend(vals.String(backendOpt))
	if err != nil {
		return placement{}, err
	}
	p := placement{runtime: vals.String("asset-cache")}
	switch backend {
	case hostsvc.BackendSystemdUser:
		if p.runtime == "" && vals.String("state-dir") != "" {
			p.runtime = filepath.Join(vals.String("state-dir"), "runtime")
		}
		if p.runtime == "" {
			return p, fmt.Errorf("the systemd-user backend needs --state-dir or --asset-cache to find the pinned runtime sets (install writes both into the account's config)")
		}
		p.manager, err = hostsvc.Open(ctx, hostsvc.Config{Backend: backend})
		return p, err
	case hostsvc.BackendProc:
		l, err := a.layout(vals)
		if err != nil {
			return p, err
		}
		c, h, err := helloDevsup(ctx, l)
		if err != nil {
			return p, fmt.Errorf("the instance's devsup at %s: %w", l.Socket, err)
		}
		p.manager, p.devsup = c, h.PID
		if p.runtime == "" {
			if p.runtime, err = assets.DevCache(a.env); err != nil {
				return p, err
			}
		}
		return p, nil
	}
	return p, fmt.Errorf("no placement backend %q", backend)
}

func runUnitsList(ctx context.Context, a *app, vals clicfg.Values, rest []string) int {
	p, err := a.placement(ctx, vals)
	if err != nil {
		return a.fail(exitRefused, "units list: %v", err)
	}
	defer p.manager.Close()
	var all []hostsvc.UnitStatus
	seen := map[string]bool{}
	for _, prefix := range []string{"socialweb-vm-", "socialweb-market-network-", "socialweb-bao-", "socialweb-kcp-", "socialweb-hp-", "socialweb-pg-", "socialweb-c-", "socialweb-computer-", "socialweb-probe-"} {
		st, err := p.manager.List(ctx, prefix)
		if err != nil {
			return a.fail(exitFailed, "units list: %v", err)
		}
		for _, s := range st {
			if !seen[s.Name] {
				seen[s.Name] = true
				all = append(all, s)
			}
		}
	}
	sort.Slice(all, func(i, j int) bool { return all[i].Name < all[j].Name })
	if vals.Bool("json") {
		a.printJSON(all)
		return exitOK
	}
	for _, s := range all {
		fmt.Fprintf(a.stdout, "%-56s %-12s %-10s pid %d\n", s.Name, s.State, s.SubState, s.MainPID)
	}
	return exitOK
}

func runUnitsRollKcp(ctx context.Context, a *app, vals clicfg.Values, rest []string) int {
	if len(rest) != 0 {
		return a.fail(exitRefused, "units roll kcp: unexpected arguments %v", rest)
	}
	if err := devinstance.RefuseRoot(os.Geteuid()); err != nil {
		return a.fail(exitRefused, "units roll kcp: %v", err)
	}
	backend, err := hostsvc.ParseBackend(vals.String(backendOpt))
	if err != nil {
		return a.fail(exitRefused, "units roll kcp: %v", err)
	}
	if backend != hostsvc.BackendSystemdUser {
		return a.fail(exitRefused, "units roll kcp: the installed kcp units are user units (--%s %s); a dev instance rolls with dev roll kcp", backendOpt, hostsvc.BackendSystemdUser)
	}
	state := vals.String("state-dir")
	if state == "" || !filepath.IsAbs(state) {
		return a.fail(exitRefused, "units roll kcp: --state-dir must name the installed state root (an absolute path)")
	}
	readyTimeout, err := time.ParseDuration(vals.String("ready-timeout"))
	if err != nil {
		return a.fail(exitRefused, "units roll kcp: ready-timeout: %v", err)
	}
	retention, err := kcpdatastore.RetentionOf(vals.Int("store-backup-keep"), vals.Int("store-backup-keep-days"))
	if err != nil {
		return a.fail(exitRefused, "units roll kcp: %v", err)
	}
	app := table.Gateway()
	if h := vals.String("handle"); h != "" {
		app = table.Bidder(h, install.HandleID(h))
	}
	prefix := vals.String("unit-prefix")
	if prefix != "" {
		if err := hostsvc.ValidatePrefix(prefix); err != nil {
			return a.fail(exitRefused, "units roll kcp: unit-prefix: %v", err)
		}
		app.Prefix = prefix
	}
	compat, err := versions.KcpCompatRecords()
	if err != nil {
		return a.fail(exitFailed, "units roll kcp: %v", err)
	}
	p := bootstrap.InstallParams(app, state)
	lock, err := lockFile(ctx, p.RollLock(), rollLockWait)
	if err != nil {
		return a.fail(exitFailed, "units roll kcp: %v", err)
	}
	defer lock.Close()
	m, err := hostsvc.Open(ctx, hostsvc.Config{Backend: backend, UnitPrefix: prefix})
	if err != nil {
		return a.fail(exitFailed, "units roll kcp: %v", err)
	}
	defer m.Close()
	rep, err := bootstrap.RollInstalled(ctx, m, p, bootstrap.InstalledRoll{Kcp: vals.String("kcp-path"), Kine: vals.String("kine-path"),
		PlannedOutage: vals.Bool("planned-outage"), Retention: retention, Compat: compat, ReadyTimeout: readyTimeout, Log: a.log})
	a.printJSON(rep)
	if errors.Is(err, kcpserver.ErrIncompatiblePair) {
		return a.fail(exitRefused, "units roll kcp: %v", err)
	}
	if err != nil {
		return a.fail(exitFailed, "units roll kcp: %v", err)
	}
	return exitOK
}

func runUnitsAudit(ctx context.Context, a *app, vals clicfg.Values, rest []string) int {
	p, err := a.placement(ctx, vals)
	if err != nil {
		return a.fail(exitRefused, "units audit: %v", err)
	}
	defer p.manager.Close()
	pins, err := versions.PlatformPins(assets.Platform())
	if err != nil {
		return a.fail(exitFailed, "units audit: %v", err)
	}
	pinned, notes := treeaudit.Pinned(p.runtime, pins, assets.TreeDigest)
	for _, n := range notes {
		fmt.Fprintln(a.stderr, "units audit: "+n)
	}
	r, err := treeaudit.Audit(ctx, p.manager, treeaudit.Host{}, p.devsup, pinned)
	if err != nil {
		return a.fail(exitFailed, "units audit: %v", err)
	}
	if vals.Bool("json") {
		a.printJSON(r)
	} else {
		treeaudit.Write(a.stdout, r)
	}
	if !r.Pass() {
		return exitFailed
	}
	return exitOK
}