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

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"maps"
	"net/url"
	"os"
	"path/filepath"
	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/app/compose"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/app/marketbidder"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/app/marketnetwork"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/appcli"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/bootstrap"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/bootstrap/table"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/buildstamp"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/clicfg"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/computevm"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/devinstance"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/devsup"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/guestimage"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/guestimagerelease"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/hostsvc"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/kit/fsx"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/progress"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/stack/bidder"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/stack/request"
	"github.com/johnandersen777/socialweb-computer-kcp/internal/vmruntime"
)

const bidderIdentityWait = 5 * time.Minute

var bidderOptions = slices.Concat([]string{backendOpt, "profile", "network", "name", "instance", "state-dir", "fg", "ready-timeout",
	"network-timeout", "market-network-file", "bidder-policy", "bidder-policy-args", "bidder-operator-signin", "bidder-options", "kcp-path", "kine-path", "bao-path", "openbao-plugin-dir", "asset-cache", "unit-prefix", "kcp-front-address", "guest-image-release-base", "no-guest-lan-udp", "guest-host-udp", "compute-kinds"},
	devVMOptions)

type bidderRun struct {
	a              *app
	vals           clicfg.Values
	backend        hostsvc.Backend
	profile        string
	handle         string
	network        string
	policy         string
	options        []v1alpha1.ApplicationOption
	value          *v1alpha1.MarketNetworkValue
	ingress        *v1alpha1.IngressConfig
	comms          *v1alpha1.MarketComms
	guestNetwork   *v1alpha1.GuestNetworkOptions
	pdsURL         string
	identityMode   v1alpha1.MarketIdentityMode
	password       string
	readyTimeout   time.Duration
	networkTimeout time.Duration
	computeKinds   []string
}

type bidderSummary struct {
	Profile   string                          `json:"profile"`
	Handle    string                          `json:"handle,omitempty"`
	Network   string                          `json:"network,omitempty"`
	Name      string                          `json:"name,omitempty"`
	Instance  string                          `json:"instance,omitempty"`
	KcpURL    string                          `json:"kcpUrl,omitempty"`
	Did       string                          `json:"did,omitempty"`
	Phase     v1alpha1.ApplicationPhase       `json:"phase,omitempty"`
	Message   string                          `json:"message,omitempty"`
	Listeners []v1alpha1.ApplicationListener  `json:"listeners,omitempty"`
	Comps     []v1alpha1.ApplicationComponent `json:"components,omitempty"`
	Seconds   float64                         `json:"seconds"`
	Identity  *v1alpha1.MarketIdentityStatus  `json:"identity,omitempty"`
}

func (r *bidderRun) log(msg string, args ...any) {
	r.a.log.Info(msg, append([]any{"verb", "run bidder", "profile", r.profile, "utc", time.Now().UTC().Format(time.RFC3339)}, args...)...)
}

func (r *bidderRun) guestNet() *v1alpha1.GuestNetworkOptions {
	if r.guestNetwork == nil {
		r.guestNetwork = &v1alpha1.GuestNetworkOptions{}
	}
	return r.guestNetwork
}

func hostUDPValue(v string) (*bool, error) {
	switch strings.ToLower(strings.TrimSpace(v)) {
	case "", "auto":
		return nil, nil
	case "true":
		on := true
		return &on, nil
	case "false":
		off := false
		return &off, nil
	}
	return nil, fmt.Errorf("%q: want true, false or auto", v)
}

func guestNetworkFlags(vals clicfg.Values) (*v1alpha1.GuestNetworkOptions, error) {
	host, err := hostUDPValue(vals.String("guest-host-udp"))
	if err != nil {
		return nil, fmt.Errorf("--guest-host-udp %w", err)
	}
	lan := !vals.Bool("no-guest-lan-udp")
	return &v1alpha1.GuestNetworkOptions{LANUDP: &lan, HostUDP: host}, nil
}

func (r *bidderRun) passthrough(args []string) error {
	parsed, err := compose.ParseAppArgs(marketbidder.BidderApp, args)
	if err != nil {
		return err
	}
	var nv v1alpha1.MarketNetworkValue
	nvSet := false
	for _, p := range parsed {
		if r.network != "" && strings.HasPrefix(p.To, compose.MapToNetwork) {
			return fmt.Errorf("--%s maps to %s, which the market network sets for its child applications", p.Name, p.To)
		}
		switch {
		case p.To == compose.MapToOptions && p.Name == "atproto-oauth":
			r.identityMode = v1alpha1.MarketIdentityModeOAuthBrowser
		case p.To == compose.MapToOptions && p.Name == "atproto-oauth-qr":
			r.identityMode = v1alpha1.MarketIdentityModeOAuthQr
		case p.To == compose.MapToOptions:
			r.options = append(r.options, v1alpha1.ApplicationOption{Name: p.Name, Value: p.Value})
		case p.To == "spec.policy":
			r.policy = p.Value
		case p.To == "spec.guestNetwork.lanUDP":
			on, err := strconv.ParseBool(p.Value)
			if err != nil {
				return fmt.Errorf("--%s %q: want true or false", p.Name, p.Value)
			}
			r.guestNet().LANUDP = &on
		case p.To == "spec.guestNetwork.hostUDP":
			on, err := hostUDPValue(p.Value)
			if err != nil {
				return fmt.Errorf("--%s: %w", p.Name, err)
			}
			r.guestNet().HostUDP = on
		case p.To == "spec.identity.mode":
			r.identityMode = v1alpha1.MarketIdentityMode(p.Value)
		case p.To == "spec.identity.pdsUrl":
			r.pdsURL = p.Value
		case p.To == compose.MapToState && p.Name == "atproto-password":
			r.password = p.Value
		case p.To == "spec.identity.handle":
			if r.handle != "" && r.handle != p.Value {
				return fmt.Errorf("--atproto-handle %s and --handle %s name two accounts", p.Value, r.handle)
			}
			r.handle = p.Value
		case strings.HasPrefix(p.To, "spec.firecracker"):
			return marketbidder.ErrFirecrackerInPod
		case strings.HasPrefix(p.To, "spec.ingress"):
			var applied bool
			var err error
			if r.ingress, applied, err = applyIngressOption(r.ingress, p.Name, p.Value); err != nil {
				return err
			}
			if !applied {
				return fmt.Errorf("--%s maps to %s, which run bidder does not take", p.Name, p.To)
			}
		case strings.HasPrefix(p.To, "spec.comms"):
			var applied bool
			var err error
			if r.comms, applied, err = applyCommsOption(r.comms, p.Name, p.Value); err != nil {
				return err
			}
			if !applied {
				return fmt.Errorf("--%s maps to %s, which run bidder does not take", p.Name, p.To)
			}
		case strings.HasPrefix(p.To, compose.MapToNetwork):
			nvSet = true
			switch p.To {
			case "network.plcDirectoryUrl":
				nv.PLCDirectoryURL = p.Value
			case "network.ingressProxyHost":
				nv.IngressProxyHost = p.Value
			case "network.qrOrigin":
				nv.QrOrigin = p.Value
			case "network.guestTlsUrl":
				port := strings.TrimSpace(p.Value)
				if u, err := url.Parse(port); err == nil && u.Port() != "" {
					port = u.Port()
				}
				if _, err := strconv.Atoi(port); err != nil {
					return fmt.Errorf("--%s %q: want the dispatcher's TLS port", p.Name, p.Value)
				}
				nv.Hosts = append(nv.Hosts, v1alpha1.NetworkHost{Name: v1alpha1.NetworkDispatcherHost, Address: "127.0.0.1:" + port})
			case "network.hosts":
				for _, h := range splitList(p.Value) {
					name, addr, ok := strings.Cut(h, "=")
					if !ok {
						return fmt.Errorf("--network-hosts %q: want name=address,...", p.Value)
					}
					nv.Hosts = append(nv.Hosts, v1alpha1.NetworkHost{Name: name, Address: addr})
				}
			}
		default:
			return fmt.Errorf("--%s maps to %s, which run bidder does not take", p.Name, p.To)
		}
	}
	if nvSet {
		r.value = &nv
	}
	return nil
}

func (r *bidderRun) configOptions(words string) error {
	parsed, err := compose.ParseAppArgs(marketbidder.BidderApp, strings.Fields(words))
	if err != nil {
		return err
	}
	for _, p := range parsed {
		if p.To != compose.MapToOptions {
			return fmt.Errorf("--%s maps to %s; bidder-options carries only the options entries of %s", p.Name, p.To, compose.OptionMapPath)
		}
		if !slices.ContainsFunc(r.options, func(o v1alpha1.ApplicationOption) bool { return o.Name == p.Name }) {
			r.options = append(r.options, v1alpha1.ApplicationOption{Name: p.Name, Value: p.Value})
		}
	}
	return nil
}

func runBidder(ctx context.Context, a *app, vals clicfg.Values, rest []string) int {
	pos, pass := splitWords(rest)
	if len(pos) > 0 {
		return a.fail(exitRefused, "run bidder: unexpected %q; hono-bidder options go after --", strings.Join(pos, " "))
	}
	backend, err := hostsvc.ParseBackend(vals.String(backendOpt))
	if err != nil {
		return a.fail(exitRefused, "run bidder: %v", err)
	}
	r := &bidderRun{a: a, vals: vals, backend: backend, profile: vals.String("profile"), handle: strings.ToLower(vals.String("handle")),
		network: vals.String("network")}
	if r.profile == "" {
		r.profile = marketbidder.BidderProfileProdQr
		if r.network != "" || vals.String("market-network-file") != "" {
			r.profile = marketbidder.BidderProfileNetwork
		}
	}
	if !slices.Contains(marketbidder.BidderProfiles, r.profile) {
		return a.fail(exitRefused, "run bidder: --profile %q: want one of %s", r.profile, strings.Join(marketbidder.BidderProfiles, ", "))
	}
	if r.readyTimeout, err = time.ParseDuration(vals.String("ready-timeout")); err != nil {
		return a.fail(exitRefused, "run bidder: ready-timeout: %v", err)
	}
	if r.readyTimeout < 0 {
		return a.fail(exitRefused, "run bidder: ready-timeout: %s is negative", r.readyTimeout)
	}
	if r.networkTimeout, err = time.ParseDuration(vals.String("network-timeout")); err != nil {
		return a.fail(exitRefused, "run bidder: network-timeout: %v", err)
	}
	if err := r.passthrough(pass); err != nil {
		return a.fail(exitRefused, "run bidder: %v", err)
	}
	if err := r.configOptions(vals.String("bidder-options")); err != nil {
		return a.fail(exitRefused, "run bidder: bidder-options: %v", err)
	}
	flags, err := guestNetworkFlags(vals)
	if err != nil {
		return a.fail(exitRefused, "run bidder: %v", err)
	}
	r.guestNetwork = r.guestNetwork.WithDefaults(flags)
	if r.password != "" {
		if err := os.Setenv(compose.EnvPdsAccountPassword, r.password); err != nil {
			return a.fail(exitRefused, "run bidder: --atproto-password: %v", err)
		}
	}
	if r.computeKinds, err = computeKindsFlag(vals); err != nil {
		return a.fail(exitRefused, "run bidder: %v", err)
	}
	if len(r.computeKinds) == 0 {
		r.computeKinds = []string{string(v1alpha1.ComputeKindVM)}
	}
	if r.policy == "" {
		r.policy = vals.String("bidder-policy")
	}
	if args := vals.String("bidder-policy-args"); args != "" && !slices.ContainsFunc(r.options, func(o v1alpha1.ApplicationOption) bool { return o.Name == "policy-args" }) {
		r.options = append(r.options, v1alpha1.ApplicationOption{Name: "policy-args", Value: args})
	}
	if account := vals.String("bidder-operator-signin"); account != "" && !slices.ContainsFunc(r.options, func(o v1alpha1.ApplicationOption) bool { return o.Name == compose.OperatorSignInOption }) {
		r.options = append(r.options, v1alpha1.ApplicationOption{Name: compose.OperatorSignInOption, Value: account})
	}
	switch {
	case r.network != "" && r.profile != marketbidder.BidderProfileNetwork:
		return a.fail(exitRefused, "run bidder: --network places the bidder in a market network, whose profile is %s, not %s", marketbidder.BidderProfileNetwork, r.profile)
	case r.network == "" && r.profile == marketbidder.BidderProfileNetwork && r.vals.String("market-network-file") == "":
		return a.fail(exitRefused, "run bidder: --profile %s needs --network <market network instance> (dev up --profile market-network), or --market-network-file for an installed bidder account", r.profile)
	case r.network != "" && r.vals.String("market-network-file") != "":
		return a.fail(exitRefused, "run bidder: --network places the bidder in the market network, which hands it the network value; --market-network-file is for a bidder outside it")
	case r.network != "" && backend != hostsvc.BackendProc:
		return a.fail(exitRefused, "run bidder: --network: the market network places its children under its own devsup; pass --host-backend=proc")
	case r.network == "" && r.handle == "":
		return a.fail(exitRefused, "run bidder: --handle is required: it names the bidder's account and owns its ComputeVMs")
	}
	if r.network == "" && backend == hostsvc.BackendProc && r.servesVM() {
		if err := a.vmHostPreflight(ctx, r.vals); err != nil {
			return a.fail(exitRefused, "run bidder: %v", err)
		}
	}
	if !r.servesVM() {
		return r.runDenoOnly(ctx, backend)
	}
	if r.vals.String("guest-image-source") == "" {
		if backend == hostsvc.BackendSystemdUser && cachedGuestImage(r.vals.String("state-dir")) {
			r.a.log.Info("the pinned guest image is already in the store, so no source is asked of the published artifacts")
		} else {
			src, err := publishedGuestImageSource(ctx, r.vals.String("guest-image-release-base"))
			if err != nil {
				return a.fail(exitRefused, "run bidder: %v", err)
			}
			r.vals = r.vals.With("guest-image-source", src)
		}
	}
	if r.handle != "" {
		if err := bidder.CheckHandle(r.handle); err != nil {
			return a.fail(exitRefused, "run bidder: %v", err)
		}
	}
	if p := r.vals.String("market-network-file"); p != "" {
		if err := r.readMarketNetwork(p); err != nil {
			return a.fail(exitRefused, "run bidder: --market-network-file: %v", err)
		}
	}
	switch {
	case r.network != "":
		return r.inNetwork(ctx)
	case backend == hostsvc.BackendSystemdUser:
		return r.installedCore(ctx)
	}
	return r.standalone(ctx)
}

func (r *bidderRun) readMarketNetwork(path string) error {
	if r.profile != marketbidder.BidderProfileNetwork {
		return fmt.Errorf("it names a market network, whose profile is %s, not %s", marketbidder.BidderProfileNetwork, r.profile)
	}
	if r.value != nil {
		return errors.New("it and the --network-* options both name the network value")
	}
	b, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	var st v1alpha1.MarketNetworkStatus
	if err := json.Unmarshal(b, &st); err != nil {
		return fmt.Errorf("%s is not a MarketNetwork status: %w", path, err)
	}
	if st.Network == nil || st.BidderPdsURL == "" {
		return fmt.Errorf("%s names no network value or bidderPdsUrl (dev status --instance <n> | jq .network.status, once the network is Ready)", path)
	}
	if strings.TrimSpace(st.Network.CABundle) == "" {
		return fmt.Errorf("%s names no network CA bundle", path)
	}
	r.value, r.pdsURL = st.Network, st.BidderPdsURL
	return nil
}

func cachedGuestImage(stateDir string) bool {
	if stateDir == "" {
		return false
	}
	pin, err := guestimage.PinFor(vmruntime.ImageTarget())
	if err != nil || pin.Valid() != nil {
		return false
	}
	dir := guestimage.Store{Root: filepath.Join(stateDir, "images")}.Dir(pin)
	for _, name := range []string{guestimage.ManifestName, guestimage.KernelName, guestimage.RootfsName} {
		st, err := os.Stat(filepath.Join(dir, name))
		if err != nil || !st.Mode().IsRegular() || st.Size() != pin.Files[name].Bytes {
			return false
		}
	}
	return true
}

func publishedGuestImageSource(ctx context.Context, base string) (string, error) {
	version, commit := buildstamp.Identity()
	target := vmruntime.ImageTarget()
	pin, err := guestimage.PinFor(target)
	if err != nil {
		return "", err
	}
	src, err := guestimagerelease.Source(ctx, guestimagerelease.Options{
		Base:           base,
		Target:         target,
		Version:        version,
		Commit:         commit,
		ManifestSHA256: pin.Files[guestimage.ManifestName].SHA256,
	})
	if err != nil {
		return "", fmt.Errorf("%w (pass --guest-image-source file:///<dir>, or point --guest-image-release-base at the published artifacts)", err)
	}
	return src, nil
}

func (r *bidderRun) kcpProvider() *v1alpha1.KcpProvider {
	return &v1alpha1.KcpProvider{Handle: r.handle, GuestImageSource: r.vals.String("guest-image-source"),
		VmHostBackend: r.vals.String("vm-host-backend")}
}

func (r *bidderRun) spec() (v1alpha1.MarketBidderSpec, error) {
	var kcp *v1alpha1.KcpProvider
	if r.servesVM() {
		kcp = r.kcpProvider()
	}
	return bidder.Spec(bidder.SpecInput{Profile: r.profile, Handle: r.handle, Policy: r.policy, Network: r.value, PdsUrl: r.pdsURL,
		Mode: r.identityMode, Kcp: kcp, Ingress: r.ingress, Comms: r.comms, Options: r.options, GuestNetwork: r.guestNetwork,
		ComputeKinds: r.computeKinds})
}

func computeKindsFlag(vals clicfg.Values) ([]string, error) {
	var kinds []string
	for _, k := range splitList(vals.String("compute-kinds")) {
		switch v1alpha1.ComputeKind(k) {
		case v1alpha1.ComputeKindVM, v1alpha1.ComputeKindDeno:
		default:
			return nil, fmt.Errorf("--compute-kinds %q: want vm or deno", k)
		}
		if !slices.Contains(kinds, k) {
			kinds = append(kinds, k)
		}
	}
	return kinds, nil
}

func (r *bidderRun) servesVM() bool {
	for _, k := range r.computeKinds {
		if k == string(v1alpha1.ComputeKindVM) {
			return true
		}
	}
	return false
}

func (r *bidderRun) runDenoOnly(ctx context.Context, backend hostsvc.Backend) int {
	if backend != hostsvc.BackendProc {
		return r.a.fail(exitRefused, "run bidder: --compute-kinds without vm runs in the foreground under devsup; pass --host-backend=proc (or --fg)")
	}
	if !r.vals.Bool("fg") {
		return r.a.fail(exitRefused, "run bidder: --compute-kinds without vm runs in the foreground; pass --fg so the sandbox units die with this process")
	}
	return r.standalone(ctx)
}

func (r *bidderRun) marketNetwork() (devinstance.Layout, devinstance.Record, error) {
	l, err := r.a.layoutOf(r.network, r.vals)
	if err != nil {
		return l, devinstance.Record{}, err
	}
	var rec devinstance.Record
	if err := fsx.ReadJSON(l.Record, &rec); err != nil {
		return l, rec, fmt.Errorf("market network %s has no instance record (dev up --profile market-network first): %w", r.network, err)
	}
	if rec.Profile != bootstrap.ProfileMarketNetwork || rec.AdminKubeconfig == "" {
		return l, rec, fmt.Errorf("instance %s is not a market network (profile %s)", r.network, rec.Profile)
	}
	return l, rec, nil
}

func (r *bidderRun) inNetwork(ctx context.Context) int {
	started := time.Now()
	_, netRec, err := r.marketNetwork()
	if err != nil {
		return r.a.fail(exitRefused, "run bidder: %v", err)
	}
	name := r.vals.String("name")
	if name == "" && r.handle != "" {
		name = bidder.AppName(r.handle)
	}
	if name == "" {
		name = "bidder-" + strings.ToLower(time.Now().UTC().Format("150405"))
	}
	if err := request.CheckName(name); err != nil {
		return r.a.fail(exitRefused, "run bidder: --name: %v", err)
	}
	app, err := marketnetwork.NetworkBidderApp(name, nil, marketnetwork.BidderVMDefaults{GuestImageSource: r.vals.String("guest-image-source"),
		VmHostBackend: r.vals.String("vm-host-backend"), Handle: r.handle, ComputeKinds: r.computeKinds})
	if err != nil {
		return r.a.fail(exitRefused, "run bidder: %v", err)
	}
	app.Profile, app.Options, app.GuestNetwork = r.profile, r.options, r.guestNetwork
	if r.policy != "" {
		app.Policy = r.policy
	}
	cur, found, err := request.NetworkApp(ctx, netRec.AdminKubeconfig, name)
	switch {
	case err != nil:
		return r.a.fail(exitFailed, "run bidder: %v", err)
	case !found:
		if err := request.AddToNetwork(ctx, netRec.AdminKubeconfig, app); err != nil {
			return r.a.fail(exitFailed, "run bidder: %v", err)
		}
		r.log("the market network places the bidder in its workspace of the network's kcp", "network", r.network, "name", name, "computeVMHandle", marketnetwork.KcpHandle(app))
	case cur.Message == marketnetwork.StoppedMessage:
		if err := request.SetStopped(ctx, netRec.AdminKubeconfig, name, false); err != nil {
			return r.a.fail(exitFailed, "run bidder: %v", err)
		}
		r.log("the market network starts the bidder's kcp again from its kept state", "name", name)
	default:
		r.log("the market network already runs this bidder; waiting for it to be Ready", "name", name, "phase", string(cur.Phase))
	}
	st, err := request.WaitNetworkApp(ctx, netRec.AdminKubeconfig, name, r.networkTimeout, func(a v1alpha1.NetworkAppStatus) bool {
		return a.Phase == v1alpha1.ApplicationPhaseReady && a.Did != ""
	}, func(a v1alpha1.NetworkAppStatus) {
		r.log("network child", "name", name, "phase", string(a.Phase), "message", a.Message)
	})
	sum := bidderSummary{Profile: r.profile, Handle: name + "." + marketnetwork.BidderAccountDomain, Network: r.network, Name: name, Instance: st.Instance,
		KcpURL: st.KcpURL, Did: st.Did, Phase: st.Phase, Message: st.Message, Listeners: st.Listeners, Seconds: time.Since(started).Seconds()}
	r.a.printJSON(sum)
	if err != nil {
		return r.a.fail(exitFailed, "run bidder: %v", err)
	}
	if !r.vals.Bool("fg") {
		return exitOK
	}
	r.log("run bidder --fg: the bidder lives as long as this process; Ctrl-C removes it from the market network", "name", name)
	<-ctx.Done()
	rctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), networkDownTimeout)
	defer cancel()
	if err := request.RemoveFromNetwork(rctx, netRec.AdminKubeconfig, name); err != nil {
		return r.a.fail(exitFailed, "run bidder: removing %s from the market network: %v", name, err)
	}
	return exitOK
}

func (r *bidderRun) standalone(ctx context.Context) int {
	started := time.Now()
	spec, err := r.spec()
	if err != nil {
		return r.a.fail(exitRefused, "run bidder: %v", err)
	}
	instance := r.vals.String("instance")
	if instance == "" {
		instance = bidder.InstanceID(r.handle)
	}
	vmHandle := r.handle
	if !r.servesVM() {
		vmHandle = ""
	}
	vals := r.vals.With("instance", instance).With("profile", bootstrap.ProfileQuick).With("handle", vmHandle).With(appRoleOpt, table.RoleBidder)
	up, code := bringUp(ctx, r.a, vals, false)
	if code != exitOK {
		return code
	}
	code = r.serve(ctx, up, spec, started)
	if !r.vals.Bool("fg") {
		return code
	}
	if ctx.Err() != nil {
		code = exitOK
	}
	dctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), fgDownTimeout)
	defer cancel()
	d := downInstance(dctx, r.a, up.layout, true, false)
	r.a.printJSON(d)
	if len(d.Remaining) > 0 {
		return exitFailed
	}
	return code
}

func (r *bidderRun) serve(ctx context.Context, up upReport, spec v1alpha1.MarketBidderSpec, started time.Time) int {
	k, err := bidder.OpenKcp(up.AdminKubeconfig)
	if err != nil {
		return r.a.fail(exitFailed, "run bidder: %v", err)
	}
	deadline := time.Now().Add(3 * time.Minute)
	delay := 2 * time.Second
	var applyErr error
	for {
		if applyErr = k.ApplyRoot(ctx, spec); applyErr == nil {
			break
		}
		left := time.Until(deadline)
		if left <= 0 {
			return r.a.fail(exitFailed, "run bidder: applying the MarketBidder root object: %v", applyErr)
		}
		again := time.NewTimer(min(delay, left))
		select {
		case <-ctx.Done():
			again.Stop()
			return r.a.fail(exitFailed, "run bidder: applying the MarketBidder root object: %v", ctx.Err())
		case <-again.C:
		}
		delay = min(2*delay, bidder.RootRetryMax)
	}
	r.log("the MarketBidder root object is applied into the handle's own kcp", "instance", up.Instance, "kcp", up.KcpURL)
	sum := bidderSummary{Profile: r.profile, Handle: r.handle, Instance: up.Instance, KcpURL: up.KcpURL}
	id, err := r.signIn(ctx, k, spec)
	if id != nil {
		s := id.Status
		sum.Identity = &s
	}
	if err != nil {
		r.a.printJSON(sum)
		return r.a.fail(exitFailed, "run bidder: %v", err)
	}
	o, err := k.WaitReady(ctx, r.networkTimeout, func(st v1alpha1.ApplicationStatus) {
		r.log("bidder", "phase", string(st.Phase), "message", st.Message)
	})
	if o != nil {
		sum.Did, sum.Phase, sum.Message, sum.Listeners, sum.Comps = o.Status.Did, o.Status.Phase, o.Status.Message, o.Status.Listeners, o.Status.Components
	}
	sum.Seconds = time.Since(started).Seconds()
	r.a.printJSON(sum)
	if err != nil {
		return r.a.fail(exitFailed, "run bidder: %v", err)
	}
	if !r.vals.Bool("fg") {
		return exitOK
	}
	r.log("run bidder --fg: the bidder lives as long as this process; Ctrl-C stops its instance and keeps its state", "instance", up.Instance)
	<-ctx.Done()
	return exitOK
}

func (r *bidderRun) signIn(ctx context.Context, k *bidder.Kcp, spec v1alpha1.MarketBidderSpec) (*v1alpha1.MarketIdentity, error) {
	if err := k.WaitIdentityObject(ctx, r.networkTimeout); err != nil {
		return nil, err
	}
	ids, err := k.Identities()
	if err != nil {
		return nil, err
	}
	var in io.Reader
	if fi, err := os.Stdin.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 {
		in = os.Stdin
	}
	wctx, cancel := ctx, context.CancelFunc(func() {})
	if !r.vals.Bool("fg") {
		wctx, cancel = context.WithTimeout(ctx, bidderIdentityWait)
	} else {
		r.log("run bidder --fg: waiting for the bidder identity without a deadline (a QR sign-in, or the association of a new DID); Ctrl-C stops", "identity", marketbidder.BidderIdentity)
	}
	defer cancel()
	id, err := appcli.IdentityRenderer{Client: ids, Watch: k.AwaitIdentity, Out: r.a.stderr, In: in}.WaitReady(wctx, marketbidder.BidderIdentity)
	if err != nil {
		return id, err
	}
	if err := bidder.CheckSignIn(spec, id); err != nil {
		return id, fmt.Errorf("the sign-in is refused; the core removes the session and shows a new QR: %w", err)
	}
	return id, nil
}

func (r *bidderRun) installedCore(ctx context.Context) int {
	const verb = "run bidder"
	state := r.vals.String("state-dir")
	if !filepath.IsAbs(state) {
		return r.a.fail(exitRefused, "%s: host-backend=systemd-user: state-dir %q is not absolute (install bidder writes it into config.json)", verb, state)
	}
	if err := bidder.CheckAccount(r.backend, r.handle, "/"); err != nil {
		return r.a.fail(exitRefused, "%s: %v", verb, err)
	}
	spec, err := r.spec()
	if err != nil {
		return r.a.fail(exitRefused, "%s: %v", verb, err)
	}
	prefix := r.vals.String("unit-prefix")
	exes := map[string]devinstance.Executable{}
	for _, x := range []struct{ name, option string }{{bidder.ExeKcp, "kcp-path"}, {bidder.ExeKine, "kine-path"}, {bidder.ExeBao, "bao-path"}} {
		e, err := r.a.optionalExe(ctx, r.vals, x.name, x.option)
		if err != nil {
			return r.a.fail(exitRefused, "%s: %v", verb, err)
		}
		exes[x.name] = e
	}
	plugins, err := r.a.openBaoPluginExes(r.vals)
	if err != nil {
		return r.a.fail(exitRefused, "%s: %v", verb, err)
	}
	maps.Copy(exes, plugins)
	downloads, err := r.a.guestDownloads(r.vals)
	if err != nil {
		return r.a.fail(exitRefused, "%s: the guest downloads asset %s: %v", verb, marketbidder.GuestDownloadsExecutable, err)
	}
	if downloads.Path != "" {
		exes[marketbidder.GuestDownloadsExecutable] = downloads
	}
	exe, err := selfExe()
	if err != nil {
		return r.a.fail(exitFailed, "%s: %v", verb, err)
	}
	exes[bidder.ExeCore] = devinstance.Executable{Path: exe}
	grace, err := vmStopGrace(r.vals)
	if err != nil {
		return r.a.fail(exitRefused, "%s: %v", verb, err)
	}
	runtimeDir := r.vals.String("vm-runtime-dir")
	if !filepath.IsAbs(runtimeDir) {
		return r.a.fail(exitRefused, "%s: vm-runtime-dir %q must be absolute (the account's XDG_RUNTIME_DIR)", verb, runtimeDir)
	}
	vm := devinstance.VMRuntime{Handle: r.handle, Backend: hostsvc.BackendSystemdUser, ImageSource: r.vals.String("guest-image-source"),
		StopGrace: grace.String(), RuntimeDir: runtimeDir, Executables: map[string]string{}}
	var vexe vmruntime.Executables
	for _, x := range []struct {
		dst          *string
		name, option string
	}{{&vexe.Stage, "swc-userns-stage", "userns-stage-path"}, {&vexe.Pasta, "pasta", "pasta-path"},
		{&vexe.VMExec, "socialweb-vmexec", "vmexec-path"}, {&vexe.Firecracker, "firecracker", "firecracker-path"}} {
		p, err := r.a.vmExe(r.vals, x.name, x.option)
		if err != nil {
			return r.a.fail(exitRefused, "%s: %v", verb, err)
		}
		*x.dst, vm.Executables[x.name] = p, p
	}
	m, err := hostsvc.Open(ctx, hostsvc.Config{Backend: hostsvc.BackendSystemdUser, UnitPrefix: prefix})
	if err != nil {
		return r.a.fail(exitFailed, "%s: the systemd-user backend: %v", verb, err)
	}
	defer m.Close()
	rt, err := vmruntime.ForHost(vmruntime.Config{Manager: m, StateDir: state, RuntimeDir: runtimeDir, Exec: vexe,
		ImageSource: vm.ImageSource, StopGrace: grace, Binaries: installedBinaries(state),
		ImageOut: r.a.stderr, ImageTTY: progress.IsTTYWriter(r.a.stderr)})
	if err != nil {
		return r.a.fail(exitFailed, "%s: the VM runtime: %v", verb, err)
	}
	unit, _ := r.a.env.LookupEnv(devsup.EnvUnit)
	cfg := bidder.CoreConfig{StateDir: state, Prefix: prefix, Handle: r.handle, Executables: exes, Spec: spec, VM: vm,
		ComputeVM: computevm.Config{Handle: r.handle, Runtime: rt, Manager: m, Log: r.a.log}, FrontAddress: r.vals.String("kcp-front-address"),
		ReadyTimeout: r.readyTimeout, Unit: unit, Log: r.a.log}
	if err := bidder.RunCore(ctx, cfg, m); err != nil {
		if errors.Is(err, context.Canceled) {
			return exitOK
		}
		return r.a.fail(exitFailed, "%s: %v", verb, err)
	}
	return exitOK
}