admincore

package
v0.14.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2026 License: MIT Imports: 35 Imported by: 0

Documentation

Overview

Package admincore holds the protocol-agnostic configuration operations outpost exposes — pairing, app CRUD, outbound mounts, built-in toggles, cluster kubeconfig, restart. Both the human-facing admin UI (HTTP + session cookie) and the agent-facing MCP server (HTTP + bearer token) dispatch into the same Server methods here, so validation rules and persistence semantics ship once.

What lives here vs. in the HTTP layer:

  • admincore: validate input, mutate FileConfig under a shared mutex, update the live AppRegistry / OutboundManager, debounce restart.
  • HTTP layer (adminui, mcpapi): authenticate the caller, parse the wire format, translate admincore errors into the protocol's status codes, render the response.

Errors returned by admincore are *APIError when callers need to map them to a transport-level status, plain errors when the operation was unable to even start. HTTP wrappers use RespondError to translate.

Reachability-ledger plumbing for the daemon-side dial path. Wave 3B.1 records-only — every successful sshclient.Dial in dialSSHChain appends one ReachabilityEdge to the JSONL ledger. Wave 3B.2 wires this into the Memberlist gossip layer so peers learn about each other's recent contacts.

Self-PeerID is derived lazily from the SSH host key at the first call and cached for the daemon's lifetime; subsequent appends are just a file write.

SSH-target CRUD + one-shot Exec. These methods back both the `outpost ssh ...` CLI subtree and the `outpost_*_ssh_target` / `outpost_ssh_exec` MCP tools, keeping validation + filesystem access in one place.

Targets are persisted as per-alias JSON files under $XDG_CONFIG_HOME/outpost/ssh/<name>.json (see conf/sshtargets.go). Mutation does NOT trigger admincore's restart-debounce — friendly aliases are pure-cache state.

ExecSSH opens a fresh WS+SSH connection to cloudbox per call. Wave 1 trades the per-call setup latency for simplicity; Wave 2 will add pooling if measurements show it matters.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CloudboxHTTPBase

func CloudboxHTTPBase(fc *conf.FileConfig) string

CloudboxHTTPBase derives the HTTP(S) base URL of cloudbox from the matrix-tunnel pairing fields. Protocols are paired (wss↔https, websocket/ws/tcp↔http). Returns empty when the FileConfig isn't paired yet.

func ValidateApp

func ValidateApp(ac *conf.AppConfig) error

ValidateApp normalizes ac in place (lowercasing scheme, defaulting host, trimming whitespace) and rejects invalid combinations. Returns *APIError so callers can map straight to a transport status code.

Same rules the admin SPA enforces client-side, replicated here as the authoritative gate.

func ValidateOutbound

func ValidateOutbound(p *OutboundParams) error

ValidateOutbound trims, normalizes, and rejects bad combinations on p. After a successful call p.Scheme is one of "" (treated as "http"), "tcp", or "ssh"; required fields per-scheme are non-empty.

Types

type APIError

type APIError struct {
	Status int
	Msg    string
}

APIError carries an HTTP-style status alongside the human message so adminui can map it back to a gin status code and mcpapi can render an MCP-conformant error response.

func AsAPIError

func AsAPIError(err error) *APIError

AsAPIError unwraps err into an *APIError if it is one (directly or via errors.As). Returns nil otherwise. HTTP layers call this to pick the right status code; plain (non-APIError) errors should be treated as 500.

func (*APIError) Error

func (e *APIError) Error() string

func (*APIError) HTTPStatus

func (e *APIError) HTTPStatus() int

HTTPStatus returns the suggested HTTP status code for this error.

type AppHealthView added in v0.10.0

type AppHealthView struct {
	Name       string    `json:"name"`
	Scheme     string    `json:"scheme"`
	Target     string    `json:"target"`
	Reachable  bool      `json:"reachable"`
	RTTms      float64   `json:"rtt_ms"`
	Tier       string    `json:"tier"`
	StatusCode int       `json:"status_code,omitempty"`
	Error      string    `json:"error,omitempty"`
	At         time.Time `json:"at,omitzero"`
}

AppHealthView is one app's reachability measurement (rendered into SafeView).

type AppUpsertParams

type AppUpsertParams struct {
	conf.AppConfig
	URL string `json:"url,omitempty"`
}

AppUpsertParams is the wire shape for adding or updating an app. The URL field is an alternative to the {Scheme, Host, Port, Socket} quartet — when non-empty, it is parsed via conf.AppTargetFromURL and wins over the split fields.

type BackupApplier added in v0.4.2

type BackupApplier interface {
	Apply(cfg *conf.BackupConfig) error
	RunNow(ctx context.Context) ([]backup.Candidate, error)
	History(n int) ([]backup.Candidate, error)
}

BackupApplier is what admincore needs from main.go's backup.Manager without taking on the package import in Deps (admincore stays protocol-agnostic; the backup package is implementation-specific).

type BackupParams added in v0.4.2

type BackupParams struct {
	Enabled    bool     `json:"enabled"`
	Schedule   string   `json:"schedule"`
	Folders    []string `json:"folders"`
	LedgerPath string   `json:"ledger_path,omitempty"`
}

BackupParams is the wire shape the admin UI POSTs. Folder paths are trimmed and absolute-path-normalised before persisting; empty lines are dropped (the UI accepts a textarea so blank lines are common).

type BuiltinView

type BuiltinView struct {
	Enabled   bool   `json:"enabled"`
	Available bool   `json:"available"`
	Target    string `json:"target,omitempty"`
}

BuiltinView is the wire shape for one optional local-daemon proxy (podman/ollama). Enabled reflects the saved config; Available is the live detection result so the SPA can grey out the toggle when the daemon isn't running.

type BuiltinsParams

type BuiltinsParams struct {
	Shell                 *bool    `json:"shell,omitempty"`
	Desktop               *bool    `json:"desktop,omitempty"`
	Clipboard             *bool    `json:"clipboard,omitempty"`
	SSH                   *bool    `json:"ssh,omitempty"`
	SSHAllowLocalForward  *bool    `json:"ssh_allow_local_forward,omitempty"`
	SSHAllowRemoteForward *bool    `json:"ssh_allow_remote_forward,omitempty"`
	SSHAllowAgentForward  *bool    `json:"ssh_allow_agent_forward,omitempty"`
	SSHForwardSockets     []string `json:"ssh_forward_sockets,omitempty"`
	SFTP                  *bool    `json:"sftp,omitempty"`
	// Files builtin (embedded File Browser). Files toggles the mount;
	// FilesAllowWrite flips read-only⇄read-write (all write ops together);
	// FilesScope sets the confined root (nil = leave unchanged, empty
	// string = the OS user's home). FilesAllowWrite is intentionally only
	// settable here on the loopback admin plane — the cloud-facing surface
	// has no path to it, which is what keeps "read-only by default" a real
	// guarantee rather than a default.
	Files           *bool   `json:"files,omitempty"`
	FilesAllowWrite *bool   `json:"files_allow_write,omitempty"`
	FilesScope      *string `json:"files_scope,omitempty"`
	Podman          *bool   `json:"podman,omitempty"`
	Sandbox         *bool   `json:"sandbox,omitempty"`
	Ollama          *bool   `json:"ollama,omitempty"`
	OllamaPool      *bool   `json:"ollama_pool,omitempty"`
	// WarmServing toggles the adaptive, considerate warm-serving plane:
	// keep a small conservative set of models resident (zero cold-start),
	// yielding (unloading) whenever the host is busy with the user's own
	// work and restoring when idle. Default ON for a paired Ollama node.
	// WarmBudgetFrac sets the fraction of usable memory dedicated to warm
	// preload (clamped to (0,1]; default 0.33). nil = leave unchanged.
	WarmServing            *bool           `json:"warm_serving,omitempty"`
	WarmBudgetFrac         *float64        `json:"warm_budget_frac,omitempty"`
	Otel                   *bool           `json:"otel,omitempty"`
	OtelPool               *bool           `json:"otel_pool,omitempty"`
	Ycode                  *bool           `json:"ycode,omitempty"`
	YcodeShare             *bool           `json:"ycode_share,omitempty"`
	YcodeShareRequireLogin *bool           `json:"ycode_share_require_login,omitempty"`
	YcodeShareSurfaces     map[string]bool `json:"ycode_share_surfaces,omitempty"`
	Cluster                *bool           `json:"cluster,omitempty"`
	ClusterAgent           *bool           `json:"cluster_agent,omitempty"`
	ClusterVirtual         []string        `json:"cluster_virtual,omitempty"`
	// UpdateMode is one of "auto" / "manual" / "never" (see
	// conf.UpdateMode* constants). Pointer-string so nil = "leave
	// unchanged"; non-nil with an invalid value is rejected by
	// SetBuiltins with a 400-class APIError.
	UpdateMode *string `json:"update_mode,omitempty"`
	// AutoRollback arms the auto-rollback watchdog's DESTRUCTIVE revert
	// (default off / observe-only). nil = leave unchanged.
	AutoRollback *bool `json:"auto_rollback,omitempty"`
	// Mesh toggles the libp2p mesh data plane (the peer node carrying
	// authenticated, NAT-traversing peer↔peer streams). MeshPort sets its
	// TCP+QUIC listen port (0 = ephemeral). nil = leave unchanged.
	Mesh     *bool `json:"mesh,omitempty"`
	MeshPort *int  `json:"mesh_port,omitempty"`
	// LANInference toggles the same-LAN direct-inference listener: a
	// LAN-reachable reverse proxy to the local inference server, advertised
	// to cloudbox so same-LAN callers reach this host's LLM directly (lower
	// latency, bypassing the relay). LANInferencePort sets its listen port
	// (0 = default 11435). This is a LAN-TRUST endpoint (no per-request
	// auth) — an explicit opt-in. nil = leave unchanged.
	LANInference     *bool `json:"lan_inference,omitempty"`
	LANInferencePort *int  `json:"lan_inference_port,omitempty"`
	// Shard is the Ollama sharding sub-feature: serve a model bigger than one
	// node by splitting it across mesh peers. nil = leave unchanged; *bool=false
	// opts OUT of the zero-config default (on for an owner-registered Ollama
	// node). ShardPeers selects worker hostnames (nil = leave; empty/["auto"] =
	// every same-LAN peer); ShardRole is "auto"/"leader"/"worker".
	Shard      *bool    `json:"shard,omitempty"`
	ShardPeers []string `json:"shard_peers,omitempty"`
	ShardRole  *string  `json:"shard_role,omitempty"`
	// Back-compat convenience for the generic bashy service "loom".
	Loom     *bool `json:"loom,omitempty"`
	LoomPort *int  `json:"loom_port,omitempty"`
	// Back-compat convenience for the generic bashy service "meet" (the
	// web chat room). Like Loom/LoomPort, but with NO mesh service — a
	// personal chat room has no peer consumer. The Command base
	// (["meet","service"]) is pinned in DefaultBashyServices so the
	// supervisor drives `bashy meet service {start,status,stop}`.
	Meet     *bool `json:"meet,omitempty"`
	MeetPort *int  `json:"meet_port,omitempty"`
	// BashyServices replaces the whole generic service set when non-nil.
	BashyServices []conf.BashyService `json:"bashy_services,omitempty"`
	// BashyVersion pins the bashy release the self-heal auto-install fetches
	// when bashy is missing ("" / "latest" = newest; a tag pins it). Takes
	// effect on the next restart. nil = leave unchanged.
	BashyVersion *string `json:"bashy_version,omitempty"`
	// Zot toggles running the Zot OCI registry as a managed external binary on a
	// loopback port, auto-exposed over the mesh as `registry`. ZotPort sets its
	// HTTP port (0 = default 5000). nil = leave unchanged.
	Zot     *bool `json:"zot,omitempty"`
	ZotPort *int  `json:"zot_port,omitempty"`
	// Seaweedfs toggles running SeaweedFS (object/blob store, S3 gateway) as a
	// managed external binary on a loopback port, auto-exposed over the mesh as
	// `s3`. SeaweedfsPort sets its S3 port (0 = default 8333). nil = unchanged.
	Seaweedfs     *bool `json:"seaweedfs,omitempty"`
	SeaweedfsPort *int  `json:"seaweedfs_port,omitempty"`
	// Kopia toggles running the Kopia snapshot-backup repository server as a
	// managed external binary on a loopback port, auto-exposed over the mesh as
	// `backup`. KopiaPort sets its port (0 = default 51515). nil = unchanged.
	Kopia     *bool `json:"kopia,omitempty"`
	KopiaPort *int  `json:"kopia_port,omitempty"`

	// Actrunner toggles running Gitea act_runner (the CI executor) as a managed
	// external binary. Unlike loom/zot it's a CONSUMER: it registers against a
	// Gitea instance and dials OUT. ActrunnerInstance is the Gitea base URL
	// (empty = local loom forge); ActrunnerToken is the registration token;
	// ActrunnerLabels are the executor labels (default "host:host"). nil = unchanged.
	Actrunner         *bool   `json:"actrunner,omitempty"`
	ActrunnerInstance *string `json:"actrunner_instance,omitempty"`
	ActrunnerToken    *string `json:"actrunner_token,omitempty"`
	ActrunnerLabels   *string `json:"actrunner_labels,omitempty"`
	// CloudDOEnabled toggles Digital Ocean provider support; CloudDOToken is the
	// DO API token (exported as DIGITALOCEAN_ACCESS_TOKEN). nil = unchanged.
	CloudDOEnabled *bool   `json:"cloud_do_enabled,omitempty"`
	CloudDOToken   *string `json:"cloud_do_token,omitempty"`
	// ActrunnerSandbox opts the runner into the tier-3 sandbox (container)
	// executor (runs-on: sandbox → OCI container via bashy podman), additive to
	// the host build lane. ActrunnerSandboxImage / ActrunnerDockerHost override
	// the image / DOCKER_HOST (empty docker-host = auto-resolve bashy podman).
	ActrunnerSandbox      *bool   `json:"actrunner_sandbox,omitempty"`
	ActrunnerSandboxImage *string `json:"actrunner_sandbox_image,omitempty"`
	ActrunnerDockerHost   *string `json:"actrunner_docker_host,omitempty"`
}

BuiltinsParams is the partial-update shape for SetBuiltins. Pointer- bool fields mean "leave unchanged when nil"; non-nil fields are written through to the FileConfig.

The set of fields here is broader than the admin SPA currently surfaces — SSHAllowRemoteForward, SSHAllowAgentForward, and SSHForwardSockets exist in FileConfig but the SPA has no toggle for them. MCP / CLI callers can drive them directly.

type BuiltinsResult

type BuiltinsResult struct {
	OK             bool `json:"ok"`
	RestartPending bool `json:"restart_pending"`
}

BuiltinsResult reports what happened. RestartPending is true when the change is one the tunnel / built-in routes need to reload to observe — callers should poll Status until the daemon is back.

type ClusterLLMView added in v0.7.3

type ClusterLLMView struct {
	Configured         bool   `json:"configured"`
	Backend            string `json:"backend,omitempty"`
	State              string `json:"state"`
	Endpoint           string `json:"endpoint,omitempty"`
	Version            string `json:"version,omitempty"`
	HasAPIKey          bool   `json:"has_api_key"`
	MemberCount        int    `json:"member_count,omitempty"`
	AggregateVRAMBytes uint64 `json:"aggregate_vram_bytes,omitempty"`
}

ClusterLLMView is the operator-facing snapshot of the intra-home distributed-inference backend (GPUStack first). State is one of clusterllm's StateUnconfigured / Running / NotReachable. HasAPIKey reflects whether a management key is set (the secret itself is never surfaced); without it AggregateVRAMBytes stays 0 and the cloudbox size filter is inert. MemberCount / AggregateVRAMBytes are the live cluster shape the registry push advertises.

type ClusterView

type ClusterView struct {
	Enabled       bool     `json:"enabled"`
	Agent         bool     `json:"agent"`
	Virtual       []string `json:"virtual,omitempty"`
	APIURL        string   `json:"api_url,omitempty"`
	NodeName      string   `json:"node_name,omitempty"`
	HasToken      bool     `json:"has_token"`
	HasCA         bool     `json:"has_ca"`
	HasNodeToken  bool     `json:"has_node_token,omitempty"`
	HasSTCPSecret bool     `json:"has_stcp_secret,omitempty"`
	K8sAPIPort    int      `json:"k8s_api_port,omitempty"`
	// PodNetworkMode is "overlay" (cloudbox allocated a per-node pod
	// CIDR — the only multi-node-correct mode) or "single-node-fallback"
	// (no CIDR: a fixed range identical on every node, so pod IPs
	// collide the moment a second node joins). Read-only derived state,
	// not a config key — see runtime.ClassifyPodNetwork. PodCIDR is the
	// range that mode actually allocates from.
	PodNetworkMode string `json:"pod_network_mode,omitempty"`
	PodCIDR        string `json:"pod_cidr,omitempty"`
	// Observability fleet-aggregation URLs cloudbox provisioned for
	// this outpost. Empty when the AppStore observability bundle
	// isn't installed; non-empty means ycode is expected to
	// remote_write metrics / push logs / OTLP-export traces here
	// through the tailscale overlay.
	MetricsRemoteURL string `json:"metrics_remote_url,omitempty"`
	LogsRemoteURL    string `json:"logs_remote_url,omitempty"`
	TracesRemoteURL  string `json:"traces_remote_url,omitempty"`
}

ClusterView is the redacted cluster status sent to UI / MCP callers. Token + CA bytes never leave the agent; presence is reported via has_token / has_ca.

type Deps

type Deps struct {
	// ConfigPath is where the persistent FileConfig lives. The Server
	// serializes all read-modify-write sequences against ConfigPath
	// under its own mutex.
	ConfigPath string

	// Apps is the live registry — admincore mutates it directly when
	// the operator adds/removes/toggles custom apps. Concurrent-safe.
	Apps *agent.AppRegistry

	// Outbound manages local mount paths that proxy through cloudbox
	// to remote outposts' apps. Optional — when nil the outbound
	// operations report "not configured" rather than panic.
	Outbound *agent.OutboundManager

	// Restart, when set, is invoked (debounced) after a save that
	// requires the tunnel or built-in routes to reload. Nil during
	// tests; admincore short-circuits ScheduleRestart in that case.
	Restart func()

	// CloudboxBase + CloudboxAccessToken + AgentName feed the outbound-
	// suggestions endpoint and the provisioning relay. CloudboxBase is
	// empty until pairing completes; admincore returns a clear error
	// instead of dialing nothing when the bearer is absent.
	CloudboxBase        string
	CloudboxAccessToken string
	AgentName           string

	// LLMPoolStatus, when set, returns the live pool diagnostic block
	// rendered into SafeView. Nil when the pool service wasn't wired
	// (Ollama off or daemon undetected). Closure rather than a concrete
	// type so admincore doesn't import the ollama package.
	LLMPoolStatus func() LLMPoolStatusView

	// PeerTiers, when set, returns the latest measured peer-locality tiers
	// (the p2p peer-plane probe's ground truth — TP/LAN/WAN per peer).
	// Closure so admincore doesn't import the peerplane package. Nil when
	// the service isn't wired.
	PeerTiers func() []PeerTierView

	// MeshStatus, when set, returns the libp2p mesh host's live status
	// (peer ID, listen addrs, connected-peer count). Closure so admincore
	// doesn't import the mesh package. Nil when the host isn't wired.
	MeshStatus func() *MeshStatusView

	// MeshForward, when set, is the mesh forwarder's operation surface
	// (expose/listen/forwards). Nil when the mesh data plane is off.
	MeshForward MeshForwardOps

	// MeshResolver, when set, queries the cloudbox service registry for the
	// peers exposing a named mesh service (the "who runs <service>" lookup).
	// Closure so admincore doesn't import the peerplane client. Nil when the
	// host isn't paired / mesh is off.
	MeshResolver func(service string) ([]MeshResolvedPeer, error)

	// MeshLinkInfoByHost, when set, returns the live mesh link class
	// ("tp"/"lan"/"wan"/"") AND the LAN label of the DIRECT connection to a
	// paired host — the accurate same-LAN signal that overrides cloudbox's
	// egress-IP location heuristic in PeerStatus, enriched with WHICH LAN the
	// link rides over. Closure so admincore doesn't import the mesh package; it
	// captures the rendezvous's host→peer-id map. Nil when the mesh data plane
	// is off.
	MeshLinkInfoByHost func(host string) MeshLinkInfo

	// ShardTrigger, when set, tells <host> to LEAD a shard for <model> over
	// the mesh (no ssh). Closure so admincore doesn't import the shard /
	// peerplane packages; it captures the shard.Manager + host→peer-id
	// resolution. Nil when sharding / mesh isn't wired.
	ShardTrigger func(ctx context.Context, host, model string) error

	// ShardStatus, when set, returns a node's shard readiness over the mesh:
	// the local node when host == "", otherwise a resolved peer. Returns an
	// opaque value (a shard.StatusReport) the HTTP layers JSON-encode. Nil
	// when sharding / mesh isn't wired.
	ShardStatus func(ctx context.Context, host string) (any, error)

	// ShardLog, when set, returns a node's recent prima-rank shard logs over
	// the mesh: the local node when host == "", otherwise a resolved peer.
	// Closure (captures the shard.Manager + host→peer-id resolution). Nil when
	// sharding / mesh isn't wired.
	ShardLog func(ctx context.Context, host string) (string, error)

	// ClusterRuntimeDown, when set, SYNCHRONOUSLY stops this node's cluster
	// runtime container and — when purge is true — removes its persistent-
	// identity volumes (k3s node-id / tailscale machine key / CNI). LeaveCluster
	// calls it with purge=true so a rejoin gets a fresh overlay identity: a stale
	// machine key for a Headscale node cloudbox already deleted leaves the
	// overlay unable to converge. Closure so admincore doesn't import the runtime
	// package. Nil in tests / when the cluster runtime isn't wired.
	ClusterRuntimeDown func(ctx context.Context, purge bool) error

	// AppHealth, when set, returns the latest per-app reachability
	// measurements (TCP/HTTP probes, no ICMP). Nil when the service
	// isn't wired.
	AppHealth func() []AppHealthView

	// Upgrader + UpgradeLedger feed the Update tab on the admin UI
	// and the corresponding MCP tools. Nil on unpaired hosts (the
	// route falls back to a graceful 404 — see handlers/server.go
	// for the gate). Threaded through admincore so the surface
	// stays uniform across MCP / REST / future CLI.
	Upgrader      *upgrade.Worker
	UpgradeLedger *upgrade.Ledger

	// Backup, when set, is the live scheduler+worker for the folder-
	// watcher backup feature (admincore/backup.go). Optional — when
	// nil, SetBackup still persists the config to FileConfig (so a
	// future restart with the manager wired picks it up) but cannot
	// re-register the scheduler entry live.
	Backup BackupApplier
}

Deps is what main.go threads into admincore.New. Everything here is concurrent-safe (or stateless): the Server doesn't own these values, it borrows them. AppRegistry and OutboundManager are live mutated across goroutines as the SPA / agent flips switches.

type ExecSSHParams added in v0.1.4

type ExecSSHParams struct {
	// Name is the configured target alias (`outpost ssh add <name>`).
	Name string

	// Command is the literal command line to run on the remote host.
	// Quoting / escaping is the caller's responsibility — this is
	// fed verbatim to `ssh.Session.Run`.
	Command string

	// JumpOverride, when non-empty, overrides the target's persisted
	// Via field for this one call (analogous to ssh's `-J <alias>`).
	// Use the empty string to honor the on-disk Via.
	JumpOverride string

	// Timeout caps the remote process's wall-clock runtime. Default
	// 60s; capped at 600s server-side to keep MCP callers from
	// holding the connection forever.
	Timeout time.Duration

	// MaxStdout / MaxStderr cap captured output. Default 1 MiB / 256 KiB.
	MaxStdout int64
	MaxStderr int64

	// Stdin, when non-nil, is fed to the remote process. The MCP
	// surface accepts base64-encoded bytes and constructs an
	// io.Reader here; CLI callers (outpost repair remote-binary,
	// etc.) can pass any io.Reader directly. Closed when copy
	// completes (sshclient does this).
	Stdin io.Reader
}

ExecSSHParams is the input shape for ExecSSH. Defaults match the constraints the MCP tool surfaces.

type ExecSSHResult added in v0.1.4

type ExecSSHResult struct {
	Stdout          []byte `json:"stdout"`
	Stderr          []byte `json:"stderr"`
	ExitCode        int    `json:"exit_code"`
	StdoutTruncated bool   `json:"stdout_truncated,omitempty"`
	StderrTruncated bool   `json:"stderr_truncated,omitempty"`
}

ExecSSHResult is the output shape.

type KubeconfigResult

type KubeconfigResult struct {
	OK             bool        `json:"ok"`
	Cluster        ClusterView `json:"cluster"`
	RestartPending bool        `json:"restart_pending"`
}

KubeconfigResult reports the cluster view after a mutation plus whether the daemon will restart to apply it. Returned from ClearKubeconfig today; previously also from SetKubeconfig (the bring-your-own paste path, removed — outposts only join their owning cloudbox's cluster now; for a different cluster, pair a second outpost against that cloudbox).

type LLMPoolStatusView

type LLMPoolStatusView struct {
	Enabled     bool      `json:"enabled"`
	Running     bool      `json:"running"`
	LastPushAt  time.Time `json:"last_push_at,omitzero"`
	LastModels  int       `json:"last_models"`
	PushCount   int64     `json:"push_count"`
	LastError   string    `json:"last_error,omitempty"`
	MaxParallel int       `json:"max_parallel"`
	InFlight    int       `json:"in_flight"`
	CloudboxURL string    `json:"cloudbox_url,omitempty"`
	OllamaURL   string    `json:"ollama_url,omitempty"`
}

LLMPoolStatusView is the wire shape rendered into SafeView. Kept here (rather than in the ollama package) so the HTTP layers can read it without taking on an ollama dependency.

type MeshConsumeView added in v0.13.3

type MeshConsumeView struct {
	Service   string `json:"service"`
	PeerID    string `json:"peer_id"`
	LocalAddr string `json:"local_addr"`
}

MeshConsumeView is one persistent mesh consume (the dial side).

type MeshForwardOps added in v0.10.0

type MeshForwardOps interface {
	Expose(service, addr string) error
	Unexpose(service string) error
	Listen(peerID, service, localAddr string) (boundAddr string, err error)
	CloseListen(addr string) error
	Forwards() MeshForwardView
}

MeshForwardOps is the mesh forwarder's operation surface. The daemon wires in an adapter over mesh.Forwarder (nil when the mesh data plane is off); admincore stays independent of the mesh package. These drive the loopback-TCP-over-mesh transport: Expose a local service on the worker side, Listen for a (peer, service) on the client/leader side.

type MeshForwardView added in v0.10.0

type MeshForwardView struct {
	Exposed   map[string]string  `json:"exposed"`
	Listeners []MeshListenerView `json:"listeners"`
}

MeshForwardView is the live forwarder state (exposed services + listeners).

type MeshLinkInfo added in v0.12.25

type MeshLinkInfo struct {
	Class string
	LAN   string
}

MeshLinkInfo is the mesh direct-link class plus the LAN label of the path to a paired host, fed by Deps.MeshLinkInfoByHost into PeerStatus's location override. Class is "tp"/"lan"/"wan"/"" (same vocabulary as the old link-class signal); LAN names which local LAN the link uses (e.g. "wired", "10.0.0") and is "" when there's no LAN label.

type MeshListenerView added in v0.10.0

type MeshListenerView struct {
	Addr    string `json:"addr"`
	PeerID  string `json:"peer_id"`
	Service string `json:"service"`
}

MeshListenerView describes one active forward listener.

type MeshPeerConnView added in v0.12.24

type MeshPeerConnView struct {
	ID        string   `json:"id"`
	Direct    bool     `json:"direct"`
	LinkClass string   `json:"link_class"`
	Remote    []string `json:"remote,omitempty"`
}

MeshPeerConnView is the per-connected-peer link detail (which remote address + link class each peer is reached over) for the LOCAL mesh-status debug surface. Raw remote addrs are intentionally surfaced here (owner inspecting their own daemon over loopback) — this is NOT the cross-account peer-status API, which never returns raw IPs.

type MeshResolvedPeer added in v0.10.0

type MeshResolvedPeer struct {
	Host     string   `json:"host"`
	PeerID   string   `json:"peer_id"`
	Services []string `json:"services"`
}

MeshResolvedPeer is one peer from the cloudbox service registry.

type MeshServiceView added in v0.10.0

type MeshServiceView struct {
	Name string `json:"name"`
	Addr string `json:"addr"`
}

MeshServiceView is one persistently-exposed mesh service (the wrap harness).

type MeshStatusView added in v0.10.0

type MeshStatusView struct {
	PeerID         string             `json:"peer_id"`
	ListenAddrs    []string           `json:"listen_addrs,omitempty"`
	ConnectedPeers int                `json:"connected_peers"`
	Peers          []MeshPeerConnView `json:"peers,omitempty"`
}

MeshStatusView is the libp2p mesh host's live status (rendered into SafeView + the status surfaces). Nil/absent when the mesh data plane is off.

type MirrorJobView added in v0.10.0

type MirrorJobView struct {
	Source  string `json:"source"`
	Service string `json:"service"`
	LANOnly bool   `json:"lan_only"`
}

MirrorJobView is one continuous, mobility-aware directory-mirror job.

type MirrorView added in v0.10.0

type MirrorView struct {
	Enabled bool            `json:"enabled"`
	Jobs    []MirrorJobView `json:"jobs"`
}

MirrorView is the mirror feature's read shape.

type NetworkingParams

type NetworkingParams struct {
	// LocalAddr — bind for the matrix-tunnel ingress. Empty to clear.
	// Use *string so callers can distinguish "leave alone" (nil) from
	// "clear to default" (pointer to "").
	LocalAddr *string `json:"local_addr,omitempty"`
	// VNCAddr — upstream for the /desktop bridge.
	VNCAddr *string `json:"vnc_addr,omitempty"`
	// AdminAddr — bind for the admin UI + MCP listener.
	AdminAddr *string `json:"admin_addr,omitempty"`
	// AdminUsers — when non-nil, replaces the entire allowlist. Pass
	// an empty slice to revert to the legacy "anyone with the OS
	// password is admin" mode.
	AdminUsers *[]string `json:"admin_users,omitempty"`

	// DiscoveryEnabled flips the mDNS + HTTP discovery master switch.
	DiscoveryEnabled *bool `json:"discovery_enabled,omitempty"`
	// SSHListenAddr binds the LAN-direct SSH listener. Empty disables.
	SSHListenAddr *string `json:"ssh_listen_addr,omitempty"`
	// DiscoveryHTTPListenAddr binds the /api/v1/discover/* listener.
	DiscoveryHTTPListenAddr *string `json:"discovery_http_listen_addr,omitempty"`
	// PeerTrustPolicy is one of "same-owner" / "same-cloudbox" /
	// "tofu-allow". Validated server-side.
	PeerTrustPolicy *string `json:"peer_trust_policy,omitempty"`

	// ClusterLLMEndpoint is the base URL of an intra-home
	// distributed-inference backend (GPUStack). Empty disables detection.
	// Validated as an http(s) URL. Read once at boot (the detector is
	// built in main.go), so a change restarts like the bind fields.
	ClusterLLMEndpoint *string `json:"cluster_llm_endpoint,omitempty"`
	// ClusterLLMAPIKey is the optional Bearer key for that backend's
	// management API. Empty to clear.
	ClusterLLMAPIKey *string `json:"cluster_llm_api_key,omitempty"`
}

NetworkingParams is the partial-update shape for SetNetworking. All fields are pointers / nil-able so the caller can change one knob without resetting the others. Pass an explicit empty string to clear a field (revert to env / hardcoded default).

type NetworkingResult

type NetworkingResult struct {
	OK             bool `json:"ok"`
	RestartPending bool `json:"restart_pending"`
}

NetworkingResult reports what changed. RestartPending is true whenever any field was modified — the listener bind addresses and the admin-users allowlist all take effect at boot only.

type OutboundParams

type OutboundParams struct {
	Path       string `json:"path"`
	Name       string `json:"name"`
	Host       string `json:"host"`
	User       string `json:"user"`
	Scheme     string `json:"scheme,omitempty"`
	LocalPort  int    `json:"local_port,omitempty"`
	TTLSeconds int64  `json:"ttl_seconds,omitempty"`
}

OutboundParams mirrors the wire payload of POST /api/outbound. Lifted out of adminui so MCP tools can populate the same struct without reaching across packages.

type OutboundSuggestion

type OutboundSuggestion struct {
	Host         string `json:"host"`
	OsUser       string `json:"os_user,omitempty"`
	Name         string `json:"name"`
	Scheme       string `json:"scheme,omitempty"`
	RequireLogin bool   `json:"require_login"`
	IndexPath    string `json:"index_path,omitempty"`
	Title        string `json:"title,omitempty"`
	Online       bool   `json:"online"`
	Shared       bool   `json:"shared,omitempty"`
}

OutboundSuggestion is one row in the "Remote" dropdown — a host + an app on that host (or a synthetic SSH row pointing at the host's built-in /ssh endpoint).

type PairParams

type PairParams struct {
	Server     string `json:"server,omitempty"`
	Code       string `json:"code"`
	Name       string `json:"name"`
	Title      string `json:"title,omitempty"`
	AuthURL    string `json:"auth_url,omitempty"`
	ClientOnly bool   `json:"client_only,omitempty"`
}

PairParams is the wire shape for the portal exchange.

  • Server: portal URL (defaults to https://ai.dhnt.io when empty).
  • Code: one-time pairing code from the portal (required).
  • Name: host name to register (required).
  • Title: optional human-readable subtitle shown in the portal.
  • AuthURL: optional external app-level auth endpoint.
  • ClientOnly: register as a credential-only outpost (no inbound listeners, no matrix tunnel) — see register --client-only.

type PairResult

type PairResult struct {
	OK             bool   `json:"ok"`
	AgentName      string `json:"agent_name"`
	RestartPending bool   `json:"restart_pending"`
}

PairResult reports the new AgentName cloudbox assigned (typically echoing the requested Name) plus the restart signal callers should poll on.

type PeerTierView added in v0.10.0

type PeerTierView struct {
	Host              string    `json:"host"`
	Tier              string    `json:"tier"`
	RTTms             float64   `json:"rtt_ms"`
	Addr              string    `json:"addr,omitempty"`
	EgressSameLANHint bool      `json:"egress_same_lan_hint"`
	At                time.Time `json:"at,omitzero"`
}

PeerTierView is one peer's measured locality (rendered into SafeView + the outpost_peer_tiers MCP tool). Tier is GROUND TRUTH (measured RTT: "tp" <=2ms wired/dedicated, "lan" pipeline, "wan"/"unreached"); EgressSameLANHint is cloudbox's egress-IP guess, surfaced so operators see where the heuristic disagrees with the measurement.

type SSHTargetView added in v0.1.4

type SSHTargetView = conf.SSHTarget

SSHTargetView is the wire shape returned by list / upsert / show. Exactly the on-disk struct; defined as a separate name so we can add presentation-only fields later without breaking the file format.

type SafeView

type SafeView struct {
	AgentName   string `json:"agent_name"`
	ServerAddr  string `json:"server_addr"`
	ServerPort  int    `json:"server_port"`
	CloudboxURL string `json:"cloudbox_url,omitempty"`
	Protocol    string `json:"protocol,omitempty"`
	RemotePort  int    `json:"remote_port"`
	AuthURL     string `json:"auth_url,omitempty"`
	HasToken    bool   `json:"has_token"`
	LocalAddr   string `json:"local_addr,omitempty"`
	VNCAddr     string `json:"vnc_addr,omitempty"`
	AdminAddr   string `json:"admin_addr,omitempty"`
	// Wave 3A discovery + LAN-direct knobs (all default off).
	DiscoveryEnabled        bool                `json:"discovery_enabled"`
	SSHListenAddr           string              `json:"ssh_listen_addr,omitempty"`
	DiscoveryHTTPListenAddr string              `json:"discovery_http_listen_addr,omitempty"`
	PeerTrustPolicy         string              `json:"peer_trust_policy,omitempty"`
	AssignedHostname        string              `json:"assigned_hostname,omitempty"`
	OAuth2Email             string              `json:"oauth2_email,omitempty"`
	AdminUsers              []string            `json:"admin_users"`
	Apps                    []conf.AppConfig    `json:"apps"`
	ShellEnabled            bool                `json:"shell_enabled"`
	DesktopEnabled          bool                `json:"desktop_enabled"`
	ClipboardEnabled        bool                `json:"clipboard_enabled"`
	SSHEnabled              bool                `json:"ssh_enabled"`
	SSHAllowLocalForward    bool                `json:"ssh_allow_local_forward"`
	SSHAllowRemoteForward   bool                `json:"ssh_allow_remote_forward"`
	SSHAllowAgentForward    bool                `json:"ssh_allow_agent_forward"`
	SSHForwardSockets       []string            `json:"ssh_forward_sockets"`
	SFTPEnabled             bool                `json:"sftp_enabled"`
	FilesEnabled            bool                `json:"files_enabled"`
	FilesAllowWrite         bool                `json:"files_allow_write"`
	FilesScope              string              `json:"files_scope"`
	ClientOnly              bool                `json:"client_only"`
	Podman                  BuiltinView         `json:"podman"`
	Sandbox                 BuiltinView         `json:"sandbox"`
	Ollama                  BuiltinView         `json:"ollama"`
	OllamaPoolEnabled       bool                `json:"ollama_pool_enabled"`
	WarmServingEnabled      bool                `json:"warm_serving_enabled"`
	WarmBudgetFrac          float64             `json:"warm_budget_frac,omitempty"`
	WarmDesired             []string            `json:"warm_desired,omitempty"`
	LANInferenceEnabled     bool                `json:"lan_inference_enabled"`
	LANInferencePort        int                 `json:"lan_inference_port,omitempty"`
	MeshEnabled             bool                `json:"mesh_enabled"`
	MeshPort                int                 `json:"mesh_port,omitempty"`
	BashyServices           []conf.BashyService `json:"bashy_services,omitempty"`
	BashyVersion            string              `json:"bashy_version,omitempty"`
	ShardEnabled            bool                `json:"shard_enabled"`
	LoomEnabled             bool                `json:"loom_enabled"`
	LoomPort                int                 `json:"loom_port,omitempty"`
	MeetEnabled             bool                `json:"meet_enabled"`
	MeetPort                int                 `json:"meet_port,omitempty"`
	ZotEnabled              bool                `json:"zot_enabled"`
	ZotPort                 int                 `json:"zot_port,omitempty"`
	SeaweedfsEnabled        bool                `json:"seaweedfs_enabled"`
	SeaweedfsPort           int                 `json:"seaweedfs_port,omitempty"`
	KopiaEnabled            bool                `json:"kopia_enabled"`
	KopiaPort               int                 `json:"kopia_port,omitempty"`
	ActrunnerEnabled        bool                `json:"actrunner_enabled"`
	ActrunnerInstance       string              `json:"actrunner_instance,omitempty"`
	ActrunnerLabels         string              `json:"actrunner_labels,omitempty"`
	ActrunnerSandbox        bool                `json:"actrunner_sandbox"`
	ActrunnerSandboxImage   string              `json:"actrunner_sandbox_image,omitempty"`
	ActrunnerDockerHost     string              `json:"actrunner_docker_host,omitempty"`
	CloudDOEnabled          bool                `json:"cloud_do_enabled"`
	HasCloudDOToken         bool                `json:"has_cloud_do_token"`
	OtelEnabled             bool                `json:"otel_enabled"`
	OtelPoolEnabled         bool                `json:"otel_pool_enabled"`
	Ycode                   YcodeView           `json:"ycode"`
	YcodeShareEnabled       bool                `json:"ycode_share_enabled"`
	YcodeShareRequireLogin  bool                `json:"ycode_share_require_login"`
	// YcodeShareSurfaces is the catalog rendered as effective state:
	// every entry the SPA might offer, with the boolean folding the
	// per-surface overlay against the catalog's DefaultOn. The SPA
	// renders one toggle row per entry; the value drives the switch.
	YcodeShareSurfaces []YcodeShareSurfaceView `json:"ycode_share_surfaces"`
	UpdateMode         string                  `json:"update_mode"`
	LLMPool            LLMPoolStatusView       `json:"llm_pool"`
	PeerTiers          []PeerTierView          `json:"peer_tiers,omitempty"`
	Mesh               *MeshStatusView         `json:"mesh,omitempty"`
	AppHealth          []AppHealthView         `json:"app_health,omitempty"`
	ClusterLLM         ClusterLLMView          `json:"cluster_llm"`
	Cluster            ClusterView             `json:"cluster"`
	Outbound           []agent.OutboundView    `json:"outbound"`
	Defaults           map[string]string       `json:"defaults"`
}

SafeView is the redacted FileConfig sent over the API. Token never leaves the agent; presence is reported as has_token instead.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the stateful object that the HTTP layers share. Holds the FileConfig serialization mutex and the restart-debounce timer so that adminui and mcpapi calling the same operations in quick succession (e.g. the SPA toggling several builtins) collapse into a single save dance and a single restart.

func New

func New(deps Deps) (*Server, error)

New constructs an admincore.Server. Deps.ConfigPath is required; other fields are optional (nil-checked at the call sites that need them).

func (*Server) AppHealth added in v0.10.0

func (s *Server) AppHealth() []AppHealthView

AppHealth returns the latest per-app reachability measurements, or nil when the app-health service isn't wired.

func (*Server) AppSuggestions

func (s *Server) AppSuggestions() ([]Suggestion, error)

AppSuggestions probes well-known socket paths and the local ycode manifest, returning the apps the user could enable with one click. Never mutates configuration.

func (*Server) ApplyPendingUpgrade

func (s *Server) ApplyPendingUpgrade(ctx context.Context) (upgrade.Result, error)

ApplyPendingUpgrade — admincore-side wrapper around the Worker's LoadPending + Apply with Force=true. Same flow as the MCP tool outpost_apply_pending; exposed here so the adminui /api/upgrade/ apply route doesn't need its own copy of the worker handle.

func (*Server) AttachBackup added in v0.4.2

func (s *Server) AttachBackup(applier BackupApplier)

AttachBackup injects the live backup.Manager after admincore construction. Same setter pattern as AttachUpgrade — the manager needs the scheduler which is built alongside the errgroup, so it can't be passed through the initial Deps. Safe to call once at startup, no concurrent readers yet.

func (*Server) AttachUpgrade

func (s *Server) AttachUpgrade(worker *upgrade.Worker, ledger *upgrade.Ledger)

AttachUpgrade injects the upgrade Worker + Ledger after admincore construction. The Worker's Restart closure normally points at the admincore Server's ScheduleRestart, which means worker construction needs the Server to already exist — so we can't pass them through the initial Deps. Setter pattern instead; safe to call once at startup, no concurrent readers yet.

func (*Server) BackupHistory added in v0.4.2

func (s *Server) BackupHistory(n int) ([]backup.Candidate, error)

BackupHistory returns the last `n` ledger entries (newest last). n<=0 returns all. Used by the admin UI's "Recent backups" panel.

func (*Server) ClearKubeconfig

func (s *Server) ClearKubeconfig() (KubeconfigResult, error)

ClearKubeconfig disables DKS and removes cloud-issued membership while preserving the configured runtime set. Callers apply teardown through the pending restart.

func (*Server) ConnectOutbound

func (s *Server) ConnectOutbound(path, password string) error

ConnectOutbound runs the cloudbox elevate flow for the named mount using the supplied OS password and starts the matrix_elev pinger. Returns 404 when the path is unknown.

func (*Server) DeleteApp

func (s *Server) DeleteApp(name string) error

DeleteApp removes an app by name from FileConfig and from the live AppRegistry. No-op when the name isn't registered (idempotent — the SPA's "remove" button doesn't care about prior state).

func (*Server) DeleteOutbound

func (s *Server) DeleteOutbound(path string) error

DeleteOutbound removes an outbound mount by path. Idempotent — no error when the path doesn't exist.

func (*Server) DeleteSSHTarget added in v0.1.4

func (s *Server) DeleteSSHTarget(name string) error

DeleteSSHTarget is idempotent — no error when the alias doesn't exist (so a retry after a partial failure still succeeds).

func (*Server) Deps

func (s *Server) Deps() Deps

Deps returns the underlying dependency struct (read-only access for HTTP layers that need e.g. AgentName or CloudboxBase).

func (*Server) DisconnectOutbound

func (s *Server) DisconnectOutbound(path string) error

DisconnectOutbound drops the matrix_elev cookie for the named mount. Idempotent.

func (*Server) ExecSSH added in v0.1.4

func (s *Server) ExecSSH(ctx context.Context, p ExecSSHParams) (*ExecSSHResult, error)

ExecSSH resolves the target chain (including any Via hops), dials each leg, opens an in-process SSH client on the innermost connection, and runs Command.

Errors map as follows:

  • target missing → 404 NotFound
  • any chain target.User empty → 400 BadRequest with guidance
  • elev cookie missing/stale → 401 with EAUTHREQUIRED hint
  • cloudbox unreachable / SSH handshake → 502 BadGateway
  • timeout → wrapped as upstream() (502)
  • remote exit-code != 0 → NOT an error — result is returned with .ExitCode set; lets agents distinguish "command ran and failed" from "couldn't get to the host."

func (*Server) GetBackup added in v0.4.2

func (s *Server) GetBackup() (conf.BackupConfig, error)

GetBackup returns the persisted backup config — never nil. Empty fields mean "feature not configured yet" which the UI renders as a blank form.

func (*Server) GetSSHTarget added in v0.1.4

func (s *Server) GetSSHTarget(name string) (SSHTargetView, error)

GetSSHTarget returns one target by alias, or a 404 APIError.

func (*Server) GetSSOSecret added in v0.4.0

func (s *Server) GetSSOSecret(name string) (string, error)

GetSSOSecret returns the current SSO HMAC secret for the named app. Errors with 404 when the app doesn't exist and 400 when TrustCloudIdentity is off. This is what `outpost apps secret <name>` surfaces so the operator can paste it into the cooperating app's config.

func (*Server) JoinCluster added in v0.14.11

func (s *Server) JoinCluster() (KubeconfigResult, error)

JoinCluster is the symmetric partner to LeaveCluster: it re-ENABLES cluster mode, retaining the runtimes + NodeName LeaveCluster preserved. The cloud-issued credentials LeaveCluster cleared are re-fetched by the boot-time reattach — this method only flips the desired state on; the reconcile happens on the ensuing restart. Idempotent.

func (*Server) LeaveCluster added in v0.14.11

func (s *Server) LeaveCluster(ctx context.Context) (KubeconfigResult, error)

LeaveCluster is the per-node "leave DKS" state change — distinct from ClearKubeconfig's full wipe. It DISABLES cluster mode but PRESERVES the node identities + runtime set, clearing only the cloud-ISSUED membership so a rejoin's boot reattach re-fetches fresh values (new pod CIDR, overlay key, kubelet port, apiserver creds).

Disabling (not deleting) the Cluster block means the next boot takes the cluster-OFF path, which tears the runtime container down (main.go), instead of a stale k3s kubelet retry-looping forever on a Node cloudbox already deleted. RestartPending=true when the node was joined so the caller applies it.

func (*Server) ListApps

func (s *Server) ListApps() ([]conf.AppConfig, error)

ListApps returns the apps slice from the on-disk FileConfig. Returns an empty slice (never nil) when no apps are registered, so JSON serialization stays a list.

func (*Server) ListOutbound

func (s *Server) ListOutbound() []agent.OutboundView

ListOutbound returns the live state of every registered outbound mount. When no manager is wired (unpaired host), returns an empty slice instead of nil so JSON renders as a list.

func (*Server) ListSSHTargets added in v0.1.4

func (s *Server) ListSSHTargets() ([]SSHTargetView, error)

ListSSHTargets enumerates configured aliases, sorted by name. Returns an empty slice (never nil) when nothing is configured.

func (*Server) LoadConfig

func (s *Server) LoadConfig() (*conf.FileConfig, error)

LoadConfig is the exported read-only variant. HTTP layers use it for pure renders (GET /api/config, MCP resource reads) that don't need to hold the save mutex. Returns a copy view; mutators must go through the typed operations.

func (*Server) MeshCloseListen added in v0.10.0

func (s *Server) MeshCloseListen(addr string) error

MeshCloseListen closes the forward listener bound at addr.

func (*Server) MeshConsumeDelete added in v0.13.3

func (s *Server) MeshConsumeDelete(service, localAddr string) error

MeshConsumeDelete removes a persisted mesh consume and closes its live listener.

func (*Server) MeshConsumeUpsert added in v0.13.3

func (s *Server) MeshConsumeUpsert(service, peerID, localAddr string) (string, error)

MeshConsumeUpsert persists a mesh consume (service ← peer id → local addr) so the daemon re-establishes the forward on every boot, and establishes it live now if the forwarder is up. Keyed by (service, local_addr) so two consumes of the same service on different local ports coexist.

func (*Server) MeshConsumes added in v0.13.3

func (s *Server) MeshConsumes() ([]MeshConsumeView, error)

MeshConsumes lists the persisted (auto-established) mesh consumes.

func (*Server) MeshDial added in v0.10.0

func (s *Server) MeshDial(service, localAddr string) (addr, host string, err error)

MeshDial resolves a peer exposing the named service and opens a local forward listener to it, returning the bound local address + the chosen peer host — the zero-config consume side ("dial git" without knowing the peer id).

func (*Server) MeshExpose added in v0.10.0

func (s *Server) MeshExpose(service, addr string) error

MeshExpose registers a local loopback service reachable over the mesh.

func (*Server) MeshForwards added in v0.10.0

func (s *Server) MeshForwards() (MeshForwardView, error)

MeshForwards returns the forwarder's exposed services + active listeners.

func (*Server) MeshListen added in v0.10.0

func (s *Server) MeshListen(peerID, service, localAddr string) (string, error)

MeshListen opens a local TCP listener forwarding to (peerID, service) over the mesh and returns the bound local address. localAddr "" → 127.0.0.1:0.

func (*Server) MeshResolve added in v0.10.0

func (s *Server) MeshResolve(service string) ([]MeshResolvedPeer, error)

MeshResolve returns the peers exposing the named mesh service (the registry).

func (*Server) MeshServiceDelete added in v0.10.0

func (s *Server) MeshServiceDelete(name string) error

MeshServiceDelete removes a persisted mesh service and unexposes it live.

func (*Server) MeshServiceUpsert added in v0.10.0

func (s *Server) MeshServiceUpsert(name, addr string) error

MeshServiceUpsert persists a mesh service (name → loopback addr) so it is auto-exposed on every boot, and exposes it live now if the forwarder is up.

func (*Server) MeshServices added in v0.10.0

func (s *Server) MeshServices() ([]MeshServiceView, error)

MeshServices lists the persisted (auto-exposed) mesh services.

func (*Server) MeshStatus added in v0.10.0

func (s *Server) MeshStatus() *MeshStatusView

MeshStatus returns the libp2p mesh host's live status, or nil when the mesh data plane isn't wired.

func (*Server) MeshUnexpose added in v0.10.0

func (s *Server) MeshUnexpose(service string) error

MeshUnexpose removes a service from the allowlist.

func (*Server) Mirror added in v0.10.0

func (s *Server) Mirror() (MirrorView, error)

Mirror returns the persisted live-mirror config.

func (*Server) MirrorDelete added in v0.10.0

func (s *Server) MirrorDelete(source string) error

MirrorDelete removes a mirror job by source dir and schedules a restart. Disables the feature when the last job is removed.

func (*Server) MirrorUpsert added in v0.10.0

func (s *Server) MirrorUpsert(source, service string, lanOnly bool) error

MirrorUpsert adds (or updates) a mobility-aware mirror job keyed by source dir: mirror Source to the peer exposing mesh Service, only while reachable (and same-LAN when lanOnly). Enables the feature, persists, schedules a restart.

func (*Server) OutboundSuggestions

func (s *Server) OutboundSuggestions(ctx context.Context) ([]OutboundSuggestion, error)

OutboundSuggestions calls cloudbox's /api/v1/hosts and flattens it into one row per (host, app), plus a synthetic SSH row per host whose built-in /ssh is mounted. Returns ServiceUnavailable when the outpost isn't paired yet (no AccessToken to authenticate with).

func (*Server) Pair

func (s *Server) Pair(ctx context.Context, p PairParams) (PairResult, error)

Pair runs the portal exchange and merges the result into the persisted FileConfig (preserving locally-managed fields: Apps, Outbound, built-in toggles, Cluster). Schedules a restart so the new tunnel/identity takes effect.

func (*Server) PeerStatus added in v0.7.3

func (s *Server) PeerStatus(ctx context.Context) ([]peerstatus.Peer, error)

PeerStatus queries cloudbox for the peer status board — online state, a same-LAN/remote location hint, and the build/OS/arch details each host last reported — for the paired hosts this account can see (its owned hosts plus hosts shared with it). Requires a paired host (CloudboxBase + access token are set). Backs the outpost_peers_status MCP tool; the `outpost peers status` CLI calls peerstatus.Fetch directly so it works without the daemon running.

Cloudbox computes each peer's location by comparing the host's last-recorded egress IP to the caller's source IP — a heuristic that false-negatives (reports "remote" for hosts that ARE on the same LAN when the recorded egress IPs differ). When this daemon's mesh data plane holds a DIRECT (non-relayed) link to a peer, its link class is the ground truth, so we override the cloudbox hint with it.

func (*Server) PeerTiers added in v0.10.0

func (s *Server) PeerTiers() []PeerTierView

PeerTiers returns the latest measured peer-locality tiers, or nil when the peer-plane service isn't wired.

func (*Server) RefreshUserKubeconfig added in v0.1.0

func (s *Server) RefreshUserKubeconfig(ctx context.Context) (userkube.Status, error)

RefreshUserKubeconfig re-mints the kubectl-ready kubeconfig from cloudbox and rewrites the on-disk file. The admin UI's "Refresh" button under the Cluster section drives this; cloudbox-side token rotation is the canonical reason to call it. Returns the status after the attempt (so the UI can render the new state without a second round-trip).

func (*Server) RollbackUpgrade

func (s *Server) RollbackUpgrade(ctx context.Context) (upgrade.RollbackResult, error)

RollbackUpgrade — admincore-side wrapper around Worker.Rollback. Same return shape as the MCP tool outpost_rollback.

func (*Server) RotateProvisioningToken

func (s *Server) RotateProvisioningToken(name string) (string, error)

RotateProvisioningToken mints a new 32-byte hex bearer for the named app and updates both the persisted FileConfig and the live registry. Errors with 404 when the app doesn't exist and 400 when TrustCloudIdentity is off (rotation is only meaningful when the relay is in use).

func (*Server) RotateSSOSecret added in v0.4.0

func (s *Server) RotateSSOSecret(name string) (string, error)

RotateSSOSecret mints a new 32-byte hex HMAC key for the named app and updates both the persisted FileConfig and the live registry. Errors with 404 when the app doesn't exist and 400 when TrustCloudIdentity is off (rotation is only meaningful when the SSO handshake is in use). Rotating breaks the cooperating app until the operator pastes the new value — same trade-off as RotateProvisioningToken.

func (*Server) RunBackupNow added in v0.4.2

func (s *Server) RunBackupNow(ctx context.Context) ([]backup.Candidate, error)

RunBackupNow triggers an immediate fire against the currently- applied folders, regardless of Enabled. Returns the candidates so the admin UI can render the result inline ("3 folders scanned; 1 new file picked, 2 skipped").

func (*Server) SafeView

func (s *Server) SafeView() (SafeView, error)

SafeView returns the redacted view of the on-disk FileConfig + live state (built-in availability probes, outbound mount status, pool diagnostic). The Token / AccessToken / ProvisioningToken values are NEVER included — presence is reported via has_token only.

func (*Server) ScheduleRestart

func (s *Server) ScheduleRestart()

ScheduleRestart asynchronously triggers Deps.Restart after a short debounce so the in-flight HTTP response has time to flush AND so multiple back-to-back operations (the SPA auto-saves on every toggle) collapse into a single re-exec. Each call resets the timer.

func (*Server) SetAppEnabled added in v0.1.0

func (s *Server) SetAppEnabled(name string, enabled bool) (conf.AppConfig, error)

SetAppEnabled flips an app's Enabled flag without re-supplying the rest of its config — what `outpost apps stop`/`start` and the outpost_set_app_enabled MCP tool delegate to. Persists the change and updates the live AppRegistry: enabling re-mounts the proxy, disabling unregisters it. Idempotent — setting to the current value is a no-op (still returns the row so callers can confirm the state).

This only flips the proxy gate. The upstream container/process is untouched — operators stop those out-of-band (e.g. `podman stop`). 404s when the app name isn't registered.

func (*Server) SetBackup added in v0.4.2

func (s *Server) SetBackup(p BackupParams) (conf.BackupConfig, error)

SetBackup validates the params, persists them into FileConfig, and re-applies the live scheduler entry via the Applier. LIVE mutation — no restart needed (the scheduler's Register replaces any prior entry for the same name).

Validation:

  • Schedule, when non-empty, must parse under cron/v3's standard 5-field parser plus descriptors.
  • Folders are required when Enabled (no point in scheduling against nothing). Each path is checked for absoluteness only; existence is NOT enforced because the cooperating app may not have written its first artifact yet.

func (*Server) SetBuiltins

func (s *Server) SetBuiltins(p BuiltinsParams) (BuiltinsResult, error)

SetBuiltins applies the partial update p to the persisted FileConfig and (when the host is paired) schedules a restart so the new toggles take effect. On a first-time setup (AgentName empty) nothing is mounted yet, so the save is harmless and no restart is triggered.

func (*Server) SetCloudbox

func (s *Server) SetCloudbox(base, accessToken, agentName string)

SetCloudbox updates the cloudbox base URL + access token + agent name after a re-pair (Pair mutates the FileConfig but the in-memory deps snapshot is stale until callers refresh it). HTTP layers call this after Pair returns successfully.

func (*Server) SetNetworking

func (s *Server) SetNetworking(p NetworkingParams) (NetworkingResult, error)

SetNetworking applies the partial update to the persisted FileConfig and (if anything changed and the host is paired) schedules a restart so the new listener bind / allowlist takes effect. First-time-setup hosts (AgentName empty) skip the restart — nothing is mounted yet, so a save is harmless.

func (*Server) SetWarmDesired added in v0.12.32

func (s *Server) SetWarmDesired(models []string) error

SetWarmDesired persists the DESIRED warm set (the models cloudbox last asked this host to keep warm). Called by the warm executor whenever a /admin/warm load/shard/unload changes the set, so the intent survives a daemon restart. Writes through the shared config mutex so it can't race a concurrent builtins toggle; no restart is scheduled (the live executor already holds the in-memory set — this is durability only).

func (*Server) ShardLog added in v0.12.16

func (s *Server) ShardLog(ctx context.Context, host string) (string, error)

ShardLog returns the local node's (host == "") or a peer's recent prima-rank shard logs over the mesh — the captured exit reason a crashed shard left behind, no ssh.

func (*Server) ShardStatus added in v0.12.2

func (s *Server) ShardStatus(ctx context.Context, host string) (any, error)

ShardStatus returns the local node's (host == "") or a peer's shard readiness over the mesh.

func (*Server) ShardTrigger added in v0.12.2

func (s *Server) ShardTrigger(ctx context.Context, host, model string) error

ShardTrigger tells <host> to LEAD a shard for <model> over the mesh.

func (*Server) Status

func (s *Server) Status() (StatusView, error)

Status returns the lightweight paired-yet payload.

func (*Server) Unpair

func (s *Server) Unpair() (PairResult, error)

Unpair clears the portal-controlled fields (AgentName, Token, etc.) while preserving locally-managed config (Apps, Outbound, builtins). Schedules a restart so the daemon drops its tunnel and reverts to the unpaired admin-UI-only mode.

New capability — the admin UI doesn't expose this today, but agents occasionally need to nuke a stale pairing without editing agent.json by hand.

func (*Server) UpgradeOverview

func (s *Server) UpgradeOverview() (UpgradeOverview, error)

UpgradeOverview returns the consolidated payload for the Update tab. History is bounded to the most recent 20 entries — operators don't typically need more than that, and the JSONL ledger is unbounded in principle but rare in practice.

func (*Server) UpsertApp

func (s *Server) UpsertApp(p AppUpsertParams) (conf.AppConfig, error)

UpsertApp validates the params, persists the merged FileConfig, and mutates the live AppRegistry. No restart required — AppRegistry is concurrent-safe.

func (*Server) UpsertOutbound

func (s *Server) UpsertOutbound(p OutboundParams) error

UpsertOutbound validates the params, refuses collisions with local app names and other listener-binding mounts, persists to FileConfig, and re-registers the live OutboundManager so the change takes effect without a restart.

func (*Server) UpsertSSHTarget added in v0.1.4

func (s *Server) UpsertSSHTarget(t SSHTargetView) (SSHTargetView, error)

UpsertSSHTarget validates + persists. Idempotent.

User is optional at upsert time — the caller can leave it blank and ExecSSH will return a clear "user not set" error at run time. The CLI typically resolves the OS username from cloudbox before calling here so the on-disk record carries everything needed.

func (*Server) UserKubeconfigStatus added in v0.1.0

func (s *Server) UserKubeconfigStatus() userkube.Status

UserKubeconfigStatus returns the last-known state of the kubectl- ready kubeconfig file on disk — path, existence, refresh timestamp, last error. Rendered into the admin UI's Cluster section so the operator sees at-a-glance whether kubectl is ready + what to fix when it isn't.

type StatusView

type StatusView struct {
	Configured    bool            `json:"configured"`
	AgentName     string          `json:"agent_name,omitempty"`
	ServerAddr    string          `json:"server_addr,omitempty"`
	CloudboxURL   string          `json:"cloudbox_url,omitempty"`
	CurrentOSUser string          `json:"current_os_user,omitempty"`
	Build         agent.BuildInfo `json:"build"`
	BinaryPath    string          `json:"binary_path,omitempty"`
}

StatusView is the small "is outpost paired yet?" shape the SPA polls to decide what to render. Mirrors the legacy /api/status payload.

Build + BinaryPath are added so a remote operator (e.g. `outpost upgrade` running on another box that drives this daemon over MCP, or cloudbox's fleet view) can see the running daemon's provenance and the path of the binary to swap on disk.

type Suggestion

type Suggestion struct {
	Name     string `json:"name"`
	Scheme   string `json:"scheme"`
	Socket   string `json:"socket,omitempty"`
	Host     string `json:"host,omitempty"`
	Port     int    `json:"port,omitempty"`
	Role     string `json:"role"`
	Source   string `json:"source"`             // "wellKnown" | "ycodeManifest"
	Note     string `json:"note,omitempty"`     // human-readable hint
	Existing bool   `json:"existing,omitempty"` // already registered with this name
	Managed  bool   `json:"managed,omitempty"`  // bashy start/status/stop service
}

Suggestion is one auto-detected local app the operator can register with a single click (in the admin UI) or a single MCP call.

type UpgradeOverview

type UpgradeOverview struct {
	Build             agent.BuildInfo       `json:"build"`
	BinaryPath        string                `json:"binary_path,omitempty"`
	UpdateMode        string                `json:"update_mode"`
	RollbackAvailable bool                  `json:"rollback_available"`
	CurrentSource     *UpgradeSource        `json:"current_source,omitempty"`
	Pending           *upgrade.Envelope     `json:"pending,omitempty"`
	History           []upgrade.LedgerEntry `json:"history"`
}

UpgradeOverview is the wire shape rendered into the admin UI's Update tab. One round-trip surfaces everything the operator usually wants to see when thinking about versions: what build is running, where it came from, when it landed, whether a pending envelope is queued, and the recent ledger.

All fields are zero-valued / nil / empty on unpaired hosts (no Upgrader was threaded into Deps). The handler 404s before this runs on those hosts — but the struct stays well-formed either way.

type UpgradeSource

type UpgradeSource struct {
	Kind      string    `json:"kind"`          // "cloudbox" / "cli-url" / "cli-local" / "unknown"
	URL       string    `json:"url,omitempty"` // GitHub release URL for cloudbox / cli-url paths
	ReleaseID string    `json:"release_id,omitempty"`
	At        time.Time `json:"at,omitzero"`
}

UpgradeSource describes where the currently-running binary came from. Derived by walking the ledger backwards from the most recent swap_done entry; nil when no swap has ever run on this host (the binary is whatever the operator manually installed).

type YcodeShareSurfaceView added in v0.1.1

type YcodeShareSurfaceView struct {
	Name      string `json:"name"`
	Path      string `json:"path"`
	Label     string `json:"label"`
	Enabled   bool   `json:"enabled"`
	DefaultOn bool   `json:"default_on"`
}

YcodeShareSurfaceView is one row in the SPA's ycode-share toggle list — the catalog entry's metadata plus the effective on/off state (after applying per-surface overlay against catalog default).

type YcodeView added in v0.1.0

type YcodeView struct {
	Enabled           bool   `json:"enabled"`
	Running           bool   `json:"running"`
	Installed         bool   `json:"installed"`
	StaleManifest     bool   `json:"stale_manifest"`
	PlatformSupported bool   `json:"platform_supported"`
	BinaryPath        string `json:"binary_path,omitempty"`
	APIEndpoint       string `json:"api_endpoint,omitempty"`
	Version           string `json:"version,omitempty"`
	DownloadURL       string `json:"download_url"`
}

YcodeView is the redacted-and-flattened ycode status the admin UI / MCP API consume. Mirrors ycode.Info but flattens the State enum into named bools so the JS doesn't have to know the State vocabulary. Detection-only — outpost never spawns or restarts ycode itself.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL