Documentation
¶
Overview ¶
Package conf holds the matrix-agent runtime configuration.
Per-host elevation-cookie cache.
`outpost connect` writes the matrix_elev cookie for each host under
<UserCacheDir>/outpost/sessions/<host>.cookie (mode 0600)
so subsequent `outpost ssh` / `outpost ssh-proxy` / agentic-tool invocations can ride on it without re-prompting. The helpers live in `conf` rather than `cmd/outpost` so admincore can read the same cache when serving MCP-driven SSH execs.
SSH-target persistence: one JSON file per friendly alias under
$XDG_CONFIG_HOME/outpost/ssh/<name>.json (mode 0600)
A target maps a local alias ("lab") to a cloudbox-paired host name plus optional override OS user. The new `outpost ssh ...` subtree (and the matching MCP tools) read/write these files; the existing `outpost remote` / `outpost ssh-proxy` / `outpost ssh-config` commands are untouched.
Why a separate file per target rather than another field on FileConfig:
- FileConfig changes trigger a daemon restart (the restart-debounce timer in admincore fires on any save). Friendly-alias CRUD shouldn't restart anything.
- Mirrors the existing `outpost remote` pattern (`cmd/outpost/ remote.go`), which stores MCP-bearer caches the same way. One reviewer, one mental model.
- The admin UI / MCP / CLI can all converge on the same on-disk format without admincore mutex coordination.
Index ¶
- Constants
- func AppTargetFromURL(raw string) (scheme, host string, port int, socket string, err error)
- func DefaultCacheDir() (string, error)
- func DefaultConfigDir() (string, error)
- func DefaultConfigPath() (string, error)
- func DeleteSSHTarget(name string) error
- func EnsureAdminSessionKey(path string, fc *FileConfig) ([]byte, error)
- func EnsureAppSSOSecrets(path string, fc *FileConfig) ([]string, error)
- func EnsureFilesSigningKey(path string, fc *FileConfig) ([]byte, error)
- func EnsureMCPBearerToken(path string, fc *FileConfig) (string, error)
- func KnownHostsPath() (string, error)
- func LegacyCacheDir() (string, error)
- func LegacyConfigPath() (string, error)
- func NormalizeClusterMode(mode string) string
- func ReadSessionCookie(host string) (string, error)
- func ResolveCacheDir() (string, error)
- func ResolveConfigPath() (string, error)
- func RestoreLatestValid(path string) (int, error)
- func RotateMCPBearerToken(path string, fc *FileConfig) (string, error)
- func SSHTargetPath(name string) (string, error)
- func SSHTargetsDir() (string, error)
- func SaveFile(path string, fc *FileConfig) error
- func SaveSSHTarget(t SSHTarget) error
- func SessionCookiePath(host string) (string, error)
- func ValidRole(s string) bool
- func ValidSSHTargetName(name string) error
- func ValidUpdateMode(s string) bool
- func WriteSessionCookie(host, cookie string) error
- type AppConfig
- type BackupConfig
- type BashyService
- type ClusterConfig
- type Config
- type FileConfig
- func (fc *FileConfig) ActrunnerInstanceResolved() string
- func (fc *FileConfig) ActrunnerLabelsOrDefault() string
- func (fc *FileConfig) ActrunnerOn() bool
- func (fc *FileConfig) ActrunnerSandboxOn() bool
- func (fc *FileConfig) AutoRollbackOn() bool
- func (fc *FileConfig) ClipboardOn() bool
- func (fc *FileConfig) CloudDOOn() bool
- func (fc *FileConfig) ClusterLLMOn() bool
- func (fc *FileConfig) ClusterNodeName() string
- func (fc *FileConfig) ClusterOn() bool
- func (fc *FileConfig) DesktopOn() bool
- func (fc *FileConfig) DiscoveryOn() bool
- func (fc *FileConfig) EffectiveAssignedHostname() string
- func (fc *FileConfig) EffectivePeerTrustPolicy() string
- func (fc *FileConfig) FilesOn() bool
- func (fc *FileConfig) KopiaOn() bool
- func (fc *FileConfig) KopiaPortOrDefault() int
- func (fc *FileConfig) LANInferenceOn() bool
- func (fc *FileConfig) LANInferencePortOrDefault() int
- func (fc *FileConfig) LoomOn() bool
- func (fc *FileConfig) LoomPortOrDefault() int
- func (fc *FileConfig) MeshNeeded() bool
- func (fc *FileConfig) MeshOn() bool
- func (fc *FileConfig) MirrorOn() bool
- func (fc *FileConfig) OllamaOn() bool
- func (fc *FileConfig) OllamaPoolOn() bool
- func (fc *FileConfig) OtelOn() bool
- func (fc *FileConfig) OtelPoolOn() bool
- func (fc *FileConfig) PeerPlaneNeeded() bool
- func (fc *FileConfig) PeerPlaneOn() bool
- func (fc *FileConfig) PodmanOn() bool
- func (fc *FileConfig) SFTPOn() bool
- func (fc *FileConfig) SSHAllowAgentForwardOn() bool
- func (fc *FileConfig) SSHAllowLocalForwardOn() bool
- func (fc *FileConfig) SSHAllowRemoteForwardOn() bool
- func (fc *FileConfig) SSHOn() bool
- func (fc *FileConfig) SandboxOn() bool
- func (fc *FileConfig) SeaweedfsOn() bool
- func (fc *FileConfig) SeaweedfsPortOrDefault() int
- func (fc *FileConfig) ShardOn() bool
- func (fc *FileConfig) ShellOn() bool
- func (fc *FileConfig) UpdateModeName() string
- func (fc *FileConfig) WarmBudgetFracOrDefault() float64
- func (fc *FileConfig) WarmServingOn() bool
- func (fc *FileConfig) YcodeOn() bool
- func (fc *FileConfig) YcodeShareOn() bool
- func (fc *FileConfig) YcodeShareRequireLoginOn() bool
- func (fc *FileConfig) ZotOn() bool
- func (fc *FileConfig) ZotPortOrDefault() int
- type MeshConsume
- type MeshService
- type MirrorConfig
- type MirrorJob
- type OutboundConfig
- type SSHTarget
- type ShardConfig
Constants ¶
const ( DefaultLocalAddr = "127.0.0.1:0" DefaultVNCAddr = "127.0.0.1:5900" DefaultAdminAddr = "127.0.0.1:17777" DefaultServerAddr = "127.0.0.1" DefaultServerPort = 7000 )
Default values applied at the call site when neither env nor file supplied anything. Centralized so the CLI help text, the admin UI placeholder, and the boot code all agree on the same values.
const ( ClusterModeAgentMode = "agent" ClusterModeVKPodman = "vk-podman" ClusterModeVKNative = "vk-native" ClusterModeVKOllama = "vk-ollama" )
Canonical --cluster-mode values, after normalization. These are the modes the operator selects between:
- ClusterModeAgentMode — real `k3s agent` subprocess (libpod-hosted kubelet) joining via the matrix-tunnel STCP visitor.
- ClusterModeVKPodman — v1 virtual-kubelet, Pods → local libpod containers (vknode podmanBackend).
- ClusterModeVKNative — virtual-kubelet, Pods → native host processes (vknode nativeProcessBackend).
- ClusterModeVKOllama — virtual-kubelet, Pods → native host processes using the legacy ollama defaults.
const ( UpdateModeAuto = "auto" UpdateModeManual = "manual" UpdateModeNever = "never" )
UpdateModeAuto / UpdateModeManual / UpdateModeNever are the legal values of FileConfig.UpdateMode. Kept as package constants so the validation layers (admincore, MCP arg parsing) share one source of truth.
const DefaultSSHPort = 22
DefaultSSHPort is the port assumed for hop destinations when Port is left zero. Mirrors openssh's defaults.
const MaxSSHTargetChainDepth = 8
MaxSSHTargetChainDepth bounds the depth of `Via` chains. Generous — real-world chains rarely exceed two hops; this is just a cycle and runaway-recursion guard.
Variables ¶
This section is empty.
Functions ¶
func AppTargetFromURL ¶
AppTargetFromURL parses a single URL string ("http://localhost:8080", "unix:///run/podman/podman.sock", etc.) into the scheme/host/port/ socket fields that AppConfig stores. The admin UI sends URLs; the server splits them here so the persisted record stays in the same shape that older configs and the AppRegistry already understand.
http/https URLs use the default port when none is given (80/443). unix URLs may use either a `unix:///abs/path` or `unix:/abs/path` form; both are accepted. Returns an error on anything else.
func DefaultCacheDir ¶
DefaultCacheDir returns the canonical outpost cache directory (pidfile, log, session/outbound cookies, jobs, cluster token).
func DefaultConfigDir ¶
DefaultConfigDir returns the canonical parent directory of agent.json (and the SSH host key). Caller is responsible for MkdirAll.
func DefaultConfigPath ¶
DefaultConfigPath returns the canonical agent.json location.
func DeleteSSHTarget ¶ added in v0.1.4
DeleteSSHTarget removes the file. Idempotent — a missing file is reported as success so callers (CLI rm, MCP remove) don't need to special-case it.
func EnsureAdminSessionKey ¶
func EnsureAdminSessionKey(path string, fc *FileConfig) ([]byte, error)
EnsureAdminSessionKey returns fc.AdminSessionKey, generating a fresh 32-byte random key (and persisting it via SaveFile at path) if the field is empty. Callers MUST pass a non-nil fc that they've already loaded (or freshly constructed). The returned slice points at the same backing array as fc.AdminSessionKey.
Why this lives here: the key has to outlive the process, so it belongs in the on-disk FileConfig; but it's the admin UI server that uses it. Centralizing the load-or-create here lets main.go thread it into adminui.Deps without duplicating the IO dance.
func EnsureAppSSOSecrets ¶ added in v0.4.1
func EnsureAppSSOSecrets(path string, fc *FileConfig) ([]string, error)
EnsureAppSSOSecrets walks fc.Apps and generates a 32-byte hex SSOSecret for any app that has TrustCloudIdentity set but no secret. Returns the list of app names that received a freshly minted secret (so callers can log them — the operator usually wants to paste the new value into the upstream app's config). Persists via SaveFile when anything changes and path != "".
The admin UI already auto-generates the secret alongside ProvisioningToken when the operator flips TrustCloudIdentity on; this function is the boot-time safety net for configs written by hand, by older outpost versions, or by automation that set TrustCloudIdentity without also seeding the secret. Skipping the HMAC because the secret is empty is a real LAN-spoof exposure (host-a's `kg` tile was the trigger for adding this).
func EnsureFilesSigningKey ¶ added in v0.9.3
func EnsureFilesSigningKey(path string, fc *FileConfig) ([]byte, error)
EnsureFilesSigningKey returns fc.FilesSigningKey, generating a fresh 64-byte random key (and persisting it via SaveFile at path) if the field is empty. Same shape as EnsureAdminSessionKey. The embedded File Browser is stateless (no database), so this is the only File Browser secret that must outlive the process — keeping its session JWTs valid across daemon restarts.
func EnsureMCPBearerToken ¶
func EnsureMCPBearerToken(path string, fc *FileConfig) (string, error)
EnsureMCPBearerToken returns fc.MCPBearerToken, generating a fresh 32-byte random hex string (and persisting it via SaveFile at path) if the field is empty. Same shape as EnsureAdminSessionKey; the MCP token is hex (not raw bytes) so it can be pasted into a .mcp.json file verbatim.
func KnownHostsPath ¶ added in v0.1.4
KnownHostsPath is the OpenSSH-format known_hosts file the in-process SSH client uses for trust-on-first-use host-key pinning. Lives next to the per-target files so removing the outpost config dir wipes both at once.
func LegacyCacheDir ¶
LegacyCacheDir returns where outpost stored cache files pre-migration — Go's `os.UserCacheDir()/outpost`.
func LegacyConfigPath ¶
LegacyConfigPath returns where outpost stored agent.json before the XDG migration — Go's `os.UserConfigDir()/matrix/agent.json`. On Linux without XDG_CONFIG_HOME this equals DefaultConfigPath (no migration needed); on macOS/Windows it's a separate location.
func NormalizeClusterMode ¶ added in v0.10.0
NormalizeClusterMode canonicalizes a raw --cluster-mode flag value or a persisted ClusterConfig.Mode into one of the ClusterMode* constants.
Back-compat aliases — the persisted wire value MUST keep working:
- "" → vk-podman (legacy default before vk-ollama existed)
- "vkpodman" → vk-podman (the original on-disk spelling)
Unknown values are lower-cased/trimmed and returned as-is so callers can detect and reject them; the canonical values round-trip unchanged.
func ReadSessionCookie ¶ added in v0.1.4
ReadSessionCookie returns the cached cookie for the host, or an empty string + nil error when no cookie has been cached yet (the "elevation required" state). A read error other than NotExist is propagated so callers can distinguish IO problems from "no entry."
func ResolveCacheDir ¶
ResolveCacheDir returns the canonical cache directory, migrating the entire legacy directory (pidfile, log, sessions/, outbounds/, jobs/, cluster-token) on first call when the canonical location is empty. Best-effort: errors are logged but don't fail the boot path — cache files are transient and rebuild themselves if lost.
func ResolveConfigPath ¶
ResolveConfigPath returns the canonical agent.json path, migrating from the legacy os.UserConfigDir() location if necessary.
Behavior matrix:
canonical exists, legacy doesn't → use canonical (steady state)
canonical doesn't, legacy exists → rename legacy → canonical,
also migrate ssh_host_ed25519
both exist → use whichever has the later
mtime; rename the loser to
*.bak.<unix-ts> so nothing
is silently lost
neither exists → use canonical (first boot)
Idempotent — second call is a stat-only no-op.
func RestoreLatestValid ¶ added in v0.3.0
RestoreLatestValid scans the journal (newest-first) for a snapshot that round-trips through LoadFile. If found, copies it over `path` and returns the index restored (1..N) + nil. Returns (0, nil) if the live file already loads cleanly OR no valid snapshot exists. Used by Layer-2 selfcheck at boot when the primary agent.json fails to parse.
func RotateMCPBearerToken ¶
func RotateMCPBearerToken(path string, fc *FileConfig) (string, error)
RotateMCPBearerToken forces a fresh token regardless of the current value, persists it, and returns the new value. The old token stops authenticating immediately. Callers (admin UI / CLI / MCP itself) must surface the new value so the operator can update their .mcp.json before the next call.
func SSHTargetPath ¶ added in v0.1.4
SSHTargetPath is the canonical on-disk path for a given alias.
func SSHTargetsDir ¶ added in v0.1.4
SSHTargetsDir is `<UserConfigDir>/outpost/ssh`. Created on demand.
func SaveFile ¶
func SaveFile(path string, fc *FileConfig) error
SaveFile writes fc atomically (write+rename) to path, creating parents. Layer-2 defense: before overwriting an existing agent.json, snapshot the prior version into a journal (agent.json.1..agent.json.N, newest at .1). Keeps the last journalRingSize snapshots so a corrupted save (truncated mid-write, accidental SaveFile of empty struct, etc.) can be recovered by RestoreLatestValid at boot.
func SaveSSHTarget ¶ added in v0.1.4
SaveSSHTarget writes the target atomically (write to tmp + rename). Overwrites an existing file with the same name.
func SessionCookiePath ¶ added in v0.1.4
SessionCookiePath returns the canonical on-disk path for the matrix_elev cookie for the given paired host. The directory is created with mode 0700 on first call.
The filename is sanitized — cloudbox accepts arbitrary host names but we restrict the on-disk byte sequence to letters/digits/-_. so a hostile name can't traverse out of the sessions directory. Anything else is replaced with `_`.
func ValidSSHTargetName ¶ added in v0.1.4
ValidSSHTargetName guards against path traversal — the name lands directly in a filesystem path. Same charset rule remote.go uses for cached MCP-bearer aliases, intentionally consistent so operators don't have to remember two flavors of "what's a valid alias."
In addition to the charset, "." and ".." are rejected outright (they are valid character sequences but would resolve to filesystem path components — a `..` target file would be readable as the sessions dir's parent rather than a per-alias file). Leading "." is also rejected to avoid creating hidden files inadvertently.
func ValidUpdateMode ¶
ValidUpdateMode reports whether s is a legal value for UpdateMode. Mutators (admincore.SetBuiltins, MCP tool args) use this to reject bad inputs at the boundary.
func WriteSessionCookie ¶ added in v0.1.4
WriteSessionCookie persists the cookie value to disk atomically (write to tmp + rename). Mode 0600 — same OS user only.
Types ¶
type AppConfig ¶
type AppConfig struct {
Name string `json:"name"`
Icon string `json:"icon,omitempty"`
Scheme string `json:"scheme"`
Host string `json:"host,omitempty"`
Port int `json:"port,omitempty"`
Socket string `json:"socket,omitempty"`
Enabled bool `json:"enabled"`
// RequireLogin: when true, outpost serves /app/<name>/* only when
// the inbound request carries cloudbox-vouched proof of local-OS
// authentication (the X-Periscope-Role header cloudbox stamps
// after a successful /elevate flow). Without it the request gets
// 403. Default true; the opt-out is for genuinely public surfaces.
// Replaces the legacy three-tier `role` field.
RequireLogin bool `json:"require_login"`
// ElevationRequired: when true, cloudbox additionally requires the
// OS-password (PAM) elevation before serving — the historic owner
// behavior. Only meaningful alongside RequireLogin. Default false:
// a require_login app authenticates the caller (owner or sharee)
// without forcing the owner through a second OS-password prompt; the
// app is expected to enforce its own authorization. Opt in for apps
// that genuinely want OS-level proof. The OS-password gate for
// builtins (shell/ssh/desktop) is independent of this flag.
ElevationRequired bool `json:"elevation_required,omitempty"`
// LANOnlyPaths lists path prefixes (e.g. "/kiosk") that must NOT
// be reachable through cloudbox. Outpost 404s when the inbound
// request carries X-Forwarded-Prefix (= came via cloud) AND its
// post-/app/<name>/ path matches one of these. Direct loopback /
// LAN access (no cloudbox hop) keeps working — that's where
// kiosk-style public-but-local endpoints belong.
LANOnlyPaths []string `json:"lan_only_paths,omitempty"`
// IndexPath is an optional landing-page sub-path the cloudbox SPA
// prepends to this app's tile URL. Default empty (= "/"). Lets
// two AppConfig rows point at the same host:port and present as
// two tiles — e.g. one row "class" with IndexPath="" lands on
// the home page, a second row "class-admin" with
// IndexPath="/admin" lands on the admin page. The proxy itself
// does NOT use IndexPath when forwarding — it just forwards
// `rest` literally. The payoff is per-tier sharing: each
// virtual app gets its own HostShare rows, its own Connect /
// cookie scope, its own RequireLogin and LANOnlyPaths.
IndexPath string `json:"index_path,omitempty"`
// TrustCloudIdentity opts this app into the trusted-header SSO
// contract: when set, outpost forwards the cloudbox-vouched caller
// identity to the upstream as Remote-User / Remote-Email /
// Remote-Groups (the Authelia / oauth2-proxy / nginx-auth-request
// lingua franca) and also passes through X-Periscope-User /
// X-Periscope-Role. Off by default so existing apps keep their own
// login UI; flip on for apps configured to trust dhnt.io.
//
// Stamping is conditional on the request having come through the
// matrix tunnel (X-Forwarded-Prefix present). Direct loopback /
// LAN hits never carry stamped identity regardless of this flag —
// see the always-on Remote-* / X-Periscope-* sanitization in
// internal/agent/apps.go's Rewrite callback.
TrustCloudIdentity bool `json:"trust_cloud_identity,omitempty"`
// ProvisioningToken is the opaque bearer the app uses when
// pushing user grants up to cloudbox via outpost's
// /_periscope/apps/<name>/users relay. Auto-generated (32 bytes,
// hex) when the admin UI flips TrustCloudIdentity on or the
// operator rotates it. Empty means provisioning is not yet
// enabled — the relay endpoint refuses the request. Stored in
// agent.json (mode 0600) and redacted out of the admin UI's
// safeView (presence reported separately).
ProvisioningToken string `json:"provisioning_token,omitempty"`
// SSOSecret is the per-app HMAC key outpost uses to sign the
// identity headers it stamps on proxied requests. Auto-generated
// (32 bytes, hex) alongside ProvisioningToken when TrustCloudIdentity
// is on. The cooperating upstream app verifies the signature with
// the same secret — pasted in by the operator from `outpost apps
// secret <name>` — and only then trusts Remote-User / Remote-Groups.
// Closes the LAN spoof window: a local-network attacker can set the
// trusted headers but cannot mint a valid signature without the
// secret. Empty means SSO authn is not bootstrapped; cooperating
// apps fall back to their own login UI. Stored in agent.json (mode
// 0600) and redacted out of the admin UI's safeView.
SSOSecret string `json:"sso_secret,omitempty"`
// Role is deprecated. Kept for back-compat parsing of older
// agent.json files. NewFromJSON migrates "guest" → RequireLogin
// false; "user"/"admin"/empty → true.
Role string `json:"role,omitempty"`
}
type BackupConfig ¶ added in v0.4.2
type BackupConfig struct {
// Enabled is the master switch. When false the scheduler entry is
// not registered and manual /api/backup/run still works but
// records the candidate as a dry-run.
Enabled bool `json:"enabled"`
// Schedule is a cron expression. Empty disables auto-fire; the
// operator can still trigger the worker manually from the admin
// UI. Cron syntax follows robfig/cron/v3: 5-field "M H D M W" or
// descriptors ("@daily", "@every 6h", etc.). Sub-second @every
// values clamp to 1 s — irrelevant for backup cadence.
Schedule string `json:"schedule,omitempty"`
// Folders is the list of directories to watch. Each is scanned at
// fire time for the newest regular file (mtime descending); sub-
// directories and dotfiles are ignored. Empty Folders is a no-op
// (the worker logs once and skips).
Folders []string `json:"folders,omitempty"`
// LedgerPath overrides the default location for the JSONL backup
// history file. Default empty → <UserCacheDir>/outpost/backup.log,
// mirroring the upgrade ledger convention.
LedgerPath string `json:"ledger_path,omitempty"`
}
AppConfig is one custom reverse-proxy target. It is mounted under /app/<name>/ on the agent and the cloud reaches it through the tunnel.
Scheme picks the transport:
- "http" / "https": classic TCP target. Use Host (default 127.0.0.1) and Port. Socket is ignored.
- "unix": AF_UNIX socket at Socket. Works on Linux, macOS, and Windows (AF_UNIX since Win10 1803). Host/Port are ignored.
- "npipe": Windows named pipe at Socket (e.g. \\.\pipe\docker_engine). Only supported on Windows builds; non-Windows builds reject it at request time. Host/Port are ignored.
- "tcp": raw TCP target at Host:Port. The agent does NOT speak HTTP to such an app; instead the /app/<name>/ route accepts a WebSocket upgrade and byte-bridges WS↔TCP. Reached from a remote outpost via a tcp-scheme outbound (see OutboundConfig). Used for ssh, postgres, mysql, redis and other non-HTTP services.
BackupConfig is the operator-declared "what folders to watch and how often" for the off-host backup scheduler. One cron entry fires on Schedule; each fire iterates Folders and picks the newest regular file from each by mtime. Dedup is by file sha256 (a folder that hasn't grown a new file since the last fire is a no-op).
The configuration is local — the operator owns it via the admin UI. Cloudbox-side coordination (which peer to ship to, encryption-key recipient) is added in Phase 3 and lives on the cloudbox BackupPolicy row, not here.
type BashyService ¶ added in v0.13.0
type BashyService struct {
Name string `json:"name"`
Enabled bool `json:"enabled,omitempty"`
AppName string `json:"app_name,omitempty"`
AppPort int `json:"app_port,omitempty"`
RequireLogin bool `json:"require_login,omitempty"`
MeshService string `json:"mesh_service,omitempty"`
RootURL string `json:"root_url,omitempty"`
Args []string `json:"args,omitempty"`
// Command overrides the base argv the supervisor invokes as
// `bashy <Command...> {start|status|stop}`. Empty defaults to [Name] (so
// loom → `bashy loom start`). Set it for services whose lifecycle lives under
// a subcommand, e.g. the SDLC loop uses ["sdlc","service"] →
// `bashy sdlc service start`.
Command []string `json:"command,omitempty"`
// SecretsEnv, unless explicitly false, makes the supervisor inject the host's
// `bashy secrets env` (the cloudbox vault rendered through the local binding
// template) into the service's environment at START — so a service that needs
// GITHUB_TOKEN etc. gets it with no human step now that the host is paired,
// while staying fully decoupled from the vault (it just reads env vars). Only
// at start (the verb that launches the long-running process); status/stop are
// quick control calls that must not hit cloudbox on every 30s poll. Opt out
// with "secrets_env": false.
SecretsEnv *bool `json:"secrets_env,omitempty"`
}
BashyService is one generic local service supervised by outpost through the convention `bashy <name> start|status|stop`. It can optionally be published as a cloudbox app and as an outpost mesh service.
func DefaultBashyServices ¶ added in v0.13.0
func DefaultBashyServices() []BashyService
func (BashyService) SecretsEnvOn ¶ added in v0.13.1
func (s BashyService) SecretsEnvOn() bool
SecretsEnvOn reports whether the supervisor should inject `bashy secrets env` into this service at start. Default ON (nil), opt out with secrets_env:false.
type ClusterConfig ¶
type ClusterConfig struct {
// Enabled is the master switch. When false, the rest is ignored and
// neither the vkpodman loop nor the k3s-agent supervisor starts.
Enabled bool `json:"enabled,omitempty"`
// Mode selects which runtime joins the cluster on this outpost:
// - "" or "vkpodman" or "vk-podman" — v1 virtual-kubelet that
// translates k8s Pods to local podman containers (per-outpost
// pod-shape limits: no PodIP, PVC, init/sidecar containers, etc.)
// - "vk-native" — virtual-kubelet with the generic native-process
// backend: Pods become host processes on this OS.
// - "vk-ollama" — legacy/specialized native-process mode with the
// ollama marker image and data dir (Metal/CUDA-capable).
// - "agent" — real `k3s agent` subprocess that joins as a normal
// kubelet via the matrix-tunnel STCP visitor (Phase 1 of the
// "real shared k8s" plan; Linux-only).
// The "" and "vkpodman" spellings are back-compat aliases for
// vk-podman — see NormalizeClusterMode. Cloudbox does not push a Mode
// at pairing time — operator sets this via `outpost builtins set
// --cluster-mode=vk-native`.
Mode string `json:"mode,omitempty"`
// APIURL is the cluster's apiserver — typically the cloudbox-proxied
// URL like https://ai.dhnt.io/api/cluster/agent for production, or
// https://127.0.0.1:6443 against a local k3s for dev/PoC.
APIURL string `json:"api_url,omitempty"`
// Token is the bearer credential. For production this is a
// per-host ServiceAccount token cloudbox issued; for dev/PoC it
// can come straight out of /etc/rancher/k3s/k3s.yaml.
Token string `json:"token,omitempty"`
// CA is the apiserver's TLS CA bundle (PEM). Required when APIURL
// is https://. Empty means "trust the system roots" — fine when
// cloudbox fronts the apiserver behind a real cert.
CA []byte `json:"ca,omitempty"`
// NodeName is the name we register with. Defaults to AgentName when
// empty (so `kubectl get nodes` shows the same hostname the portal
// uses) but can be overridden if multiple outposts on the same host
// want distinct cluster identities.
NodeName string `json:"node_name,omitempty"`
// NodeToken is the k3s join token (K10…::node:…) cloudbox handed
// out at register time. Consumed only by Mode="agent"; passed as
// `k3s agent --token`. Empty when cloudbox isn't running in cluster
// mode or hasn't materialized the token yet (re-pair to refresh).
NodeToken string `json:"node_token,omitempty"`
// STCPSecret authenticates the local frp STCP visitor that opens a
// 127.0.0.1:<K8sAPIPort> listener and tunnels each accepted conn to
// cloudbox's embedded apiserver. Cluster-wide; minted by cloudbox at
// register time. Consumed only by Mode="agent".
STCPSecret string `json:"stcp_secret,omitempty"`
// K8sAPIPort is the TCP port the STCP visitor binds locally for the
// apiserver listener. `k3s agent --server` dials
// https://127.0.0.1:<K8sAPIPort>. Matches cloudbox's
// ClusterAPIServerPort so kubeconfigs round-trip cleanly. Default
// 6443 when empty.
K8sAPIPort int `json:"k8s_api_port,omitempty"`
// KubeletProxyPort is the per-host loopback port ON CLOUDBOX where
// the matrix tunnel exposes this outpost's kubelet (Phase 2). The
// outpost's matrix-tunnel client registers a TCPProxy with
// LocalPort=10250, RemotePort=KubeletProxyPort so cloudbox's
// embedded apiserver can dial through 127.0.0.1:<KubeletProxyPort>
// to reach `kubectl logs/exec` targets. Empty when cloudbox has
// cluster mode off OR when the kubelet port pool was exhausted at
// Exchange time — in which case the outpost just doesn't publish
// the proxy (the rest of cluster-agent mode still works).
KubeletProxyPort int `json:"kubelet_proxy_port,omitempty"`
// OverlayLoginServer is the URL the outpost's tailscaled connects
// to (--login-server) for coordination. In production this is
// cloudbox's public URL + /overlay/headscale. Empty when the
// cloudbox-side overlay is off — outpost then doesn't start
// tailscaled and no overlay/CNI plumbing is set up. Phase 3.
OverlayLoginServer string `json:"overlay_login_server,omitempty"`
// OverlayAuthKey is the one-shot pre-auth key the outpost passes
// as `tailscale up --authkey=<key>`. Minted by cloudbox at
// Exchange time. Phase 3.
OverlayAuthKey string `json:"overlay_auth_key,omitempty"`
// OverlayPodCIDR is the /24 cloudbox allocated to this outpost
// from CLUSTER_POD_CIDR. The outpost passes it as
// `tailscale up --advertise-routes=<cidr>` so other outposts can
// route to this node's pods, AND the (Phase 3b) CNI plugin uses
// it as the per-pod IP pool. Phase 3.
OverlayPodCIDR string `json:"overlay_pod_cidr,omitempty"`
// MetricsRemoteURL / LogsRemoteURL / TracesRemoteURL are the
// observability fleet-aggregation endpoints cloudbox has
// provisioned in the cluster (typically backed by VictoriaMetrics /
// VictoriaLogs / Jaeger Apache 2.0 stacks deployed via the
// AppStore). When non-empty, ycode's collector is expected to
// remote_write metrics / push logs / OTLP-export traces to these
// URLs through the tailscale overlay — the symmetric "push" side
// of the per-host /app/otel-* reverse-proxy surfaces, supplying
// fleet-wide dashboards without cloudbox itself storing anything.
//
// Resolution path: cluster Service DNS reachable on the overlay
// (e.g. http://vmsingle.observability.svc.cluster.local:8428/api/v1/write).
// Empty values mean "no fleet aggregation configured" — the local
// per-outpost stack is still queryable through the matrix tunnel.
//
// Persisted at register time from the Exchange response; cloudbox
// is the source of truth. Outpost doesn't synthesize defaults
// because the cluster service names depend on operator naming
// choices at AppStore install time.
MetricsRemoteURL string `json:"metrics_remote_url,omitempty"`
LogsRemoteURL string `json:"logs_remote_url,omitempty"`
TracesRemoteURL string `json:"traces_remote_url,omitempty"`
// HostCert is the cloudbox-CA-signed SSH host certificate
// (roadmap item #11). Refreshed by internal/agent/certs/
// every CertRefreshInterval (default 7 days) via the
// /api/v1/ca/sign-host-cert endpoint. Presented in the
// PeerHello during /api/v1/discover/hello + /probe so peers
// can verify same-cloudbox-fleet membership without a TOFU
// roundtrip. Empty when this outpost hasn't successfully
// fetched a cert yet (e.g. cluster mode off, or first boot
// before the boot fetch ran).
HostCert string `json:"host_cert,omitempty"`
// CAPubkey is the cloudbox CA pubkey pinned at first fetch
// (OpenSSH wire format). Used by /probe verifiers to check
// peer cert signatures locally without per-handshake
// cloudbox roundtrips. Refreshed alongside HostCert.
CAPubkey string `json:"ca_pubkey,omitempty"`
}
ClusterConfig persists the kubeconfig fields cloudbox issues at "join cluster" time. APIURL/Token/CA together build a client-go rest.Config; NodeName defaults to AgentName.
We store the three credential fields directly (rather than parsing a kubeconfig file on every boot) so the join flow can accept a pasted kubeconfig once, persist what matters, and be done. Token rotation becomes a one-line file save instead of a file-format dance.
func (*ClusterConfig) ClusterMode ¶ added in v0.10.0
func (c *ClusterConfig) ClusterMode() string
ClusterMode returns the normalized cluster mode for this config — always one of the ClusterMode* constants for a valid config. A nil receiver normalizes the same way an empty Mode does (vk-podman).
func (*ClusterConfig) ClusterModeAgent ¶ added in v0.1.0
func (c *ClusterConfig) ClusterModeAgent() bool
ClusterModeAgent reports whether the outpost should run the real `k3s agent` path rather than a virtual-kubelet backend. Centralized so future modes can be added without touching every call site.
func (*ClusterConfig) ClusterModeNativeProcess ¶ added in v0.14.3
func (c *ClusterConfig) ClusterModeNativeProcess() bool
ClusterModeNativeProcess reports whether the selected mode uses vknode's native host-process backend family.
func (*ClusterConfig) ClusterModeVKNative ¶ added in v0.14.3
func (c *ClusterConfig) ClusterModeVKNative() bool
ClusterModeVKNative reports whether the outpost should run the virtual-kubelet with the generic native host-process backend rather than the libpod backend.
func (*ClusterConfig) ClusterModeVKOllama ¶ added in v0.10.0
func (c *ClusterConfig) ClusterModeVKOllama() bool
ClusterModeVKOllama reports whether the outpost should run the virtual-kubelet with the legacy ollama host-process backend.
type Config ¶
type Config struct {
// Local loopback HTTP server (cloudbox reaches this via the matrix
// tunnel).
LocalAddr string
// VNCAddr is the upstream the built-in /desktop route bridges to.
// Empty here means "fall through to FileConfig.VNCAddr or the
// hardcoded 127.0.0.1:5900 default at the call site".
VNCAddr string
// AdminAddr is the bind address for the admin UI + MCP loopback
// listener. Same fall-through semantics as VNCAddr.
AdminAddr string
// Identity displayed in the cloud portal's host list.
AgentName string
// Matrix tunnel connection to cloudbox.
ServerAddr string
ServerPort int
// Protocol is the matrix-tunnel transport ("tcp", "ws", or "wss").
// When unset the agent uses raw TCP (legacy default). The register
// exchange usually fills this in based on how cloudbox is fronted.
Protocol string
Token string
// RemotePort the agent asks cloudbox to reserve for its local HTTP
// server (so the cloud's HostRegistry can dial it). 0 means
// auto-assign.
RemotePort int
// Apps registered with this agent. Comma-separated "name=url" pairs,
// e.g. "ycode=http://127.0.0.1:8765,pihole=http://192.168.1.5/admin".
Apps string
// AdminUsers is the optional allowlist of OAuth-identified emails
// that should be treated as admin when authenticating via the host
// OS path. Comma-separated when sourced from env (`MATRIX_ADMIN_USERS`).
// Default behavior is admin-on-OS-success (anyone who can prove the
// OS password owns the box). Setting this list switches to a strict
// "only these emails are admin" mode for the OS-auth branch. The
// AuthURL branch ignores this entirely — the external auth endpoint
// owns role assignment.
AdminUsers string
// AuthURL, when non-empty, makes the agent's /auth handler delegate
// credential verification to an external HTTP endpoint instead of the
// host OS. The endpoint receives {user, password} and is expected to
// return {user, role}. Application-level users live behind this URL.
AuthURL string
}
Config drives the matrix-agent binary. Layering precedence in `outpost start` is: CLI flag > env var > FileConfig value > hardcoded default. Load() returns only what the environment supplies — empty strings / zero ints mean "use the FileConfig value if any, else fall back to a default at the call site". Don't bake defaults into Load(); that defeats file-based overrides.
func Load ¶
Load reads env vars into a Config. Returns empty strings / zero ints when env is absent — callers layer FileConfig values and hardcoded defaults afterwards. This is deliberate: baking defaults into Load would make file-based overrides impossible (the env-supplied default always wins over an empty file field).
type FileConfig ¶
type FileConfig struct {
AgentName string `json:"agent_name"`
ServerAddr string `json:"server_addr"`
ServerPort int `json:"server_port"`
// Protocol is "tcp" (default for legacy raw-TCP matrix-tunnel
// deploys), "ws", or "wss". Returned by /api/register/exchange so
// the outpost knows how cloudbox expects to be dialed. Empty == "tcp".
Protocol string `json:"protocol,omitempty"`
Token string `json:"token"`
RemotePort int `json:"remote_port"`
AuthURL string `json:"auth_url,omitempty"`
// AccessToken is the per-outpost scoped JWT cloudbox issues at
// register time. Bearer-auth credential for /h/:host/ssh (used by
// `outpost ssh-proxy`) and /api/v1/ssh/* (used by `outpost
// ssh-config`). Distinct from Token, which is the *matrix-tunnel*
// shared secret used by the FRP client.
AccessToken string `json:"access_token,omitempty"`
// CloudboxTicketPubkey is the PEM-encoded ed25519 public key
// cloudbox uses to sign peer tickets — short-lived JWTs the client
// trades the matrix_elev cookie for at cloudbox and presents to a
// peer outpost on the LAN-direct path. The receiving outpost
// verifies signatures locally with this key, so peer-to-peer SSH
// (and the other LAN-direct flows that will follow) stays
// passwordless after the first `outpost connect` without putting
// cloudbox on the data path.
//
// Populated at pairing time (POST /api/register/exchange returns
// `cloudbox_ticket_pubkey`). Empty means this outpost can't verify
// peer tickets yet, so LAN-direct callers fall through to the
// X-Periscope-Role path (loopback only) — equivalent to the
// pre-peer-ticket world.
CloudboxTicketPubkey string `json:"cloudbox_ticket_pubkey,omitempty"`
// ClientOnly marks this machine as a credential vehicle that should
// never accept inbound traffic — the user wants to ssh OUT to other
// paired hosts but not BE one. When true: `outpost start` skips
// NewTunnel + the local gin server, /apps/etc. don't bind, and the
// admin UI is the only loopback listener (for managing this row).
ClientOnly bool `json:"client_only,omitempty"`
// Apps managed through the admin UI. When this field is present (even
// empty), it is authoritative — the legacy MATRIX_APPS env is ignored.
// When absent (nil) on a config written before the admin UI shipped,
// `start` falls back to MATRIX_APPS for back-compat.
Apps []AppConfig `json:"apps,omitempty"`
// LocalAddr is the local-loopback bind for the main HTTP server
// (the one cloudbox reaches through the matrix tunnel). Default
// "127.0.0.1:0" — random port. Persist a fixed port here if the
// operator wants stable reverse-proxy rules or audit hooks pointed
// at the matrix-tunnel ingress.
LocalAddr string `json:"local_addr,omitempty"`
// VNCAddr is the upstream the built-in /desktop route bridges to.
// Default "127.0.0.1:5900" — the standard VNC port. Persist a
// non-default value when the VNC daemon lives elsewhere.
VNCAddr string `json:"vnc_addr,omitempty"`
// AdminAddr is the loopback (or LAN) bind for the admin UI + MCP
// server. Default "127.0.0.1:17777". Override here, via the
// $OUTPOST_ADMIN_ADDR env var, or via the --admin-addr CLI flag;
// the precedence is CLI flag > env > file > default. LAN binds
// (0.0.0.0:17777) log a warning and force the auth gate on every
// request — see adminui.requireSession.
AdminAddr string `json:"admin_addr,omitempty"`
// AdminUsers is an optional allowlist of OAuth-identified emails
// who should be treated as admin when authenticating via the host
// OS path. Empty list = the legacy "anyone who can prove the OS
// password is admin" behavior. Non-empty = only listed emails get
// admin; others get user. Ignored when AuthURL is set (the
// external endpoint owns role assignment then). Was previously
// reachable only as $MATRIX_ADMIN_USERS.
AdminUsers []string `json:"admin_users,omitempty"`
// Built-in route toggles. Pointer-bool so a missing field on an old
// config means "default on", which matches the pre-admin-UI behavior.
// Use ShellOn()/DesktopOn()/ClipboardOn()/SSHOn() to read; never deref directly.
ShellEnabled *bool `json:"shell_enabled,omitempty"`
DesktopEnabled *bool `json:"desktop_enabled,omitempty"`
ClipboardEnabled *bool `json:"clipboard_enabled,omitempty"`
SSHEnabled *bool `json:"ssh_enabled,omitempty"`
// Files builtin — embedded File Browser, the GUI sibling of /shell +
// /ssh for remote view/download. Default ON like the other
// outpost-owned route builtins above (it serves an in-process handler,
// not an external daemon). Read-only + download-only by default;
// FilesAllowWrite flips every write op (create/upload, modify, rename,
// delete) together and is meant to be a LAN/admin-plane decision.
// FilesScope is the root directory the browser is confined to (empty =
// the OS user's home).
FilesEnabled *bool `json:"files_enabled,omitempty"`
FilesScope string `json:"files_scope,omitempty"`
FilesAllowWrite bool `json:"files_allow_write,omitempty"`
// FilesSigningKey is the JWT signing key the embedded (stateless,
// DB-less) File Browser uses for its session tokens. Persisted here so
// the key is stable across daemon restarts — otherwise every restart
// would invalidate open File Browser sessions. Auto-generated on first
// use via EnsureFilesSigningKey.
FilesSigningKey []byte `json:"files_signing_key,omitempty"`
// SSHAllowLocalForward gates whether the built-in /ssh server accepts
// `direct-tcpip` channels — the primitive behind stock `ssh -L` /
// `ssh -D`. Default-on (matches pre-toggle behavior was rejection;
// flipping the default to "on" is the whole point of adding this
// switch). Loopback-only destinations regardless of this flag — see
// the agent ssh.go `allowDirectTCPIPDest` allowlist. Disable here
// (admin UI / JSON) to refuse the channel entirely.
SSHAllowLocalForward *bool `json:"ssh_allow_local_forward,omitempty"`
// SSHAllowRemoteForward gates whether the built-in /ssh server honors
// `tcpip-forward` global requests — the primitive behind stock
// `ssh -R`. Default-on. Bind address is loopback-only regardless of
// this flag (see ssh.go `allowTCPIPForwardBind`); the operator who
// can pass the OS-password gate already has equivalent reach via a
// session-channel `nc` invocation, so adding this adds no authority.
SSHAllowRemoteForward *bool `json:"ssh_allow_remote_forward,omitempty"`
// SSHAllowAgentForward gates whether the built-in /ssh server accepts
// `auth-agent-req@openssh.com` channel requests — the primitive
// behind `ssh -A`. Default-on. When enabled, the server creates a
// per-session Unix socket and sets SSH_AUTH_SOCK in the runner env;
// agent traffic is byte-bridged back to the client via
// `auth-agent@openssh.com` channels. Trust model: the SSH-auth-agent
// protocol is opaque to the bridge, so the agent can only sign with
// keys the client's local ssh-agent already trusts to sign. Disable
// here to refuse the channel-request entirely.
SSHAllowAgentForward *bool `json:"ssh_allow_agent_forward,omitempty"`
// SFTPEnabled gates whether the embedded SSH server accepts the
// "sftp" subsystem channel. Default-on: modern openssh `scp` (8.8+)
// uses sftp under the hood, so leaving this off breaks scp for new
// clients. Disable explicitly if you want to force legacy `scp -O`
// (slower, rides the exec channel).
SFTPEnabled *bool `json:"sftp_enabled,omitempty"`
// SSHForwardSockets extends the default unix-socket allowlist that
// gates `direct-streamlocal@openssh.com` channel-opens — the primitive
// behind `podman --connection=<host>` (and any other SSH client that
// asks to forward to a remote unix socket, including `ssh -L
// localport:/remote.sock`). Defaults to the podman/docker sockets
// outpost can discover automatically (see DetectPodman + the canonical
// docker socket paths in ssh.go). Add absolute paths here to allow
// additional sockets; entries are exact-matched after filepath.Clean
// (no globbing). When SSHAllowLocalForward is off, this list is
// ignored — the master switch wins.
SSHForwardSockets []string `json:"ssh_forward_sockets,omitempty"`
// Built-in proxies for local daemons. Default off (plain bool) — these
// expose external infrastructure rather than outpost-owned routes, so
// they require explicit opt-in via the admin UI. The UI greys these
// toggles out when the daemon isn't actually running on this host.
PodmanEnabled bool `json:"podman_enabled,omitempty"`
OllamaEnabled bool `json:"ollama_enabled,omitempty"`
// SandboxEnabled gates the safe-by-default container "sandbox" proxy
// — a FILTERED libpod/docker endpoint (strips privileged / host
// namespaces / host binds / added caps / devices, injects resource
// caps) distinct from the raw, admin-only /app/podman/ passthrough.
// This is the mount a thin client or an untrusted tenant talks to.
// Off by default like the other daemon proxies: it requires the same
// podman socket PodmanEnabled does, plus an explicit opt-in because
// it widens who can run containers on the host.
SandboxEnabled bool `json:"sandbox_enabled,omitempty"`
// Sandbox resource policy. Zero values mean "no explicit limit" — the
// filter then leaves the caller's value (or the daemon default)
// untouched. The escape knobs (privileged, host ns, …) are NOT
// tunable: the sandbox mount always strips them.
//
// SandboxMaxMemoryMB per-container memory ceiling, MiB (0 = off)
// SandboxCPUs per-container CPU cap, cores (0 = off)
// SandboxPidsLimit per-container process cap (0 = off)
// SandboxMaxContainers advertised concurrency ceiling (0 = unset)
// SandboxAllowedImages optional image allowlist (exact or repo/*;
// empty = any image)
// SandboxScratchDir single host path prefix under which bind
// mounts are allowed (empty = forbid host
// binds entirely; named volumes/tmpfs always ok)
SandboxMaxMemoryMB int64 `json:"sandbox_max_memory_mb,omitempty"`
SandboxCPUs float64 `json:"sandbox_cpus,omitempty"`
SandboxPidsLimit int64 `json:"sandbox_pids_limit,omitempty"`
SandboxMaxContainers int `json:"sandbox_max_containers,omitempty"`
SandboxAllowedImages []string `json:"sandbox_allowed_images,omitempty"`
SandboxScratchDir string `json:"sandbox_scratch_dir,omitempty"`
// SandboxPrewarmImages is the set of images the prewarmer keeps pulled
// on the local podman daemon so a remote sandbox create+start skips the
// (dominant) image-pull cost. Empty falls back to the non-wildcard
// entries of SandboxAllowedImages — pre-pulling exactly what callers
// are allowed to run. Empty + no allowlist disables prewarming.
SandboxPrewarmImages []string `json:"sandbox_prewarm_images,omitempty"`
// OllamaPoolEnabled gates whether this outpost participates in
// cloudbox's virtual LLM pool — the watcher pushes the local
// /api/tags inventory to cloudbox and the /app/ollama/_pool/capacity
// endpoint is mounted. Distinct from OllamaEnabled: the user can
// expose their local Ollama as a per-host app (a private endpoint
// only they reach) without contributing it to the shared multi-host
// pool friends and other paired hosts can route to. Pointer-bool
// with OllamaPoolOn() helper so existing configs (written before
// pooling shipped) default to on whenever OllamaEnabled is on —
// the most useful behavior for the typical operator.
OllamaPoolEnabled *bool `json:"ollama_pool_enabled,omitempty"`
// WarmServingEnabled opts this outpost into the adaptive, considerate,
// always-on warm-serving plane: it keeps a small, conservative set of
// LLM models WARM (resident, zero cold-start) but YIELDS (unloads) the
// moment the user is busy with other work, restoring them when idle.
// Pointer-bool with WarmServingOn() so existing configs (written before
// warm serving shipped) default to ON whenever Ollama is on — the
// useful behavior. Explicit false opts out. The boot path additionally
// gates on pairing (cloudbox is what asks a host to warm a model).
WarmServingEnabled *bool `json:"warm_serving_enabled,omitempty"`
// WarmBudgetFrac is the fraction of usable memory the host will
// dedicate to warm preload (the conservative resident set). Default
// 0.33 (WarmBudgetFracOrDefault) — leaves ~2/3 for the OS + the user's
// own apps (e.g. ~10 GB on a 32 GB host). Clamped to (0,1]; out-of-range
// falls back to the default. The budget drops to 0 whenever the host is
// busy, so this is the idle-time ceiling, not a reservation.
WarmBudgetFrac float64 `json:"warm_budget_frac,omitempty"`
// WarmDesired is the persisted DESIRED warm set — the models cloudbox
// last asked this host to keep warm. The considerate supervisor unloads
// this set while the host is busy and restores it (within the current
// warm budget) once idle. Persisted so the intent survives restarts.
WarmDesired []string `json:"warm_desired,omitempty"`
// LANInferenceEnabled opts this outpost into serving its local LLM
// inference DIRECTLY to same-LAN callers, bypassing the cloudbox relay
// for lower latency. When on, the daemon binds a LAN-reachable listener
// (0.0.0.0:LANInferencePort) that reverse-proxies the OpenAI /v1 + /api
// surface to the local inference server (127.0.0.1:11434 — Ollama, or
// the shard leader's llama-server), and advertises that URL to cloudbox
// in the LLM-pool registry push (RegistryPushPayload.LANEndpoint).
//
// This is a LAN-TRUST endpoint: it is NOT authenticated per-request, so
// enabling it means the operator acknowledges their LAN is trusted.
// Untrusted / org networks should leave it OFF and use the Bearer-authed
// cloudbox /v1 gateway instead. Default OFF (explicit opt-in); also
// requires the local Ollama proxy on + pairing (cloudbox is what hands
// the LAN endpoint to same-LAN callers), same as OllamaPoolEnabled.
LANInferenceEnabled *bool `json:"lan_inference_enabled,omitempty"`
// LANInferencePort is the TCP port the LAN inference listener binds on
// all interfaces (0.0.0.0). Default 11435 — deliberately distinct from
// the inference server's own 11434 so the two don't collide.
LANInferencePort int `json:"lan_inference_port,omitempty"`
// PeerPlaneEnabled opts this outpost into the p2p peer-plane locality
// service: announce interface candidates to cloudbox's signaler, run a
// probe responder, and measure RTT to peers to classify TP/LAN/WAN
// tiers (the "measure, don't guess" signal that finds the dedicated
// LAN/hub path cloudbox's egress-IP heuristic misses). Default OFF.
PeerPlaneEnabled *bool `json:"peer_plane_enabled,omitempty"`
// MeshEnabled opts this outpost into the libp2p mesh data plane: a
// peer node with TCP+QUIC transports, Noise/TLS security, AutoNAT, and
// DCUtR hole-punching. It is the robust, secure, NAT-traversing
// transport under shard-RPC, peer-backup, and the resource fabric —
// cloudbox signals, data goes peer-to-peer direct. Default OFF.
// See docs/libp2p-mesh-transport.md.
MeshEnabled *bool `json:"mesh_enabled,omitempty"`
// MeshPort is the TCP+QUIC listen port for the mesh host. 0 (the
// default) picks an ephemeral port per transport; a stable port helps
// NAT/hole-punch and the loopback forwarder.
MeshPort int `json:"mesh_port,omitempty"`
// MeshServices are local loopback services persistently exposed over the
// mesh (the "wrap harness" — the declarative form of `mesh expose`). The
// daemon auto-exposes them when the mesh host comes up, so a peer can reach
// e.g. a git server or registry by name without re-running expose after a
// restart. See docs/mesh-app-platform.md.
MeshServices []MeshService `json:"mesh_services,omitempty"`
// MeshConsumes are persistent mesh CONSUMES (the dial side): local listeners
// the daemon re-establishes on every boot, each bridging to a (peer, service)
// over the mesh by peer id. The declarative form of `mesh consume` — it makes
// a cross-host dependency (e.g. an act_runner reaching a loom on another host)
// survive a restart, which a one-shot `mesh dial` does not.
MeshConsumes []MeshConsume `json:"mesh_consumes,omitempty"`
// BashyServices are generic service lifecycles managed by outpost through
// the convention `bashy <name> start|status|stop`. When enabled, outpost
// starts the service, restarts it if status reports stopped, stops it on
// shutdown, and can publish it as both a cloudbox app and a mesh service.
BashyServices []BashyService `json:"bashy_services,omitempty"`
// BashyVersion pins the bashy release the daemon auto-installs when bashy
// is missing (the self-heal path for supervised services). Empty or
// "latest" fetches the newest release; a tag (e.g. "v0.3.0") pins it. In
// production, pin it — an unpinned "latest" means a restart can silently
// pull a new bashy. The pin governs only the auto-install of a MISSING
// bashy; an already-installed bashy on PATH is used as-is.
BashyVersion string `json:"bashy_version,omitempty"`
// LoomEnabled opts this outpost into running the loom git forge (Gitea) as a
// managed external binary (coreutils/external/loom via pkg/binmgr — not
// compiled in) on a loopback port, auto-exposed over the mesh as the `git`
// service. The wrap-harness "tool lifecycle" builtin. Default OFF.
LoomEnabled *bool `json:"loom_enabled,omitempty"`
// LoomPort is loom's loopback HTTP port (default 31880).
LoomPort int `json:"loom_port,omitempty"`
// ZotEnabled opts this outpost into running the Zot OCI registry as a managed
// external binary (coreutils/external/zot via pkg/binmgr — not compiled in)
// on a loopback port, auto-exposed over the mesh as the `registry` service.
// Serves container images + Ollama models (OCI). Default OFF.
ZotEnabled *bool `json:"zot_enabled,omitempty"`
// ZotPort is zot's loopback HTTP port (default 5000).
ZotPort int `json:"zot_port,omitempty"`
// SeaweedfsEnabled opts this outpost into running SeaweedFS (object/blob
// store, S3 gateway) as a managed external binary (coreutils/external/
// seaweedfs via pkg/binmgr — not compiled in) on a loopback port, auto-exposed
// over the mesh as the `s3` service. Can also back zot's blob store. Default OFF.
SeaweedfsEnabled *bool `json:"seaweedfs_enabled,omitempty"`
// SeaweedfsPort is SeaweedFS's loopback S3-gateway port (default 8333).
SeaweedfsPort int `json:"seaweedfs_port,omitempty"`
// KopiaEnabled opts this outpost into running the Kopia snapshot-backup
// repository server as a managed external binary (coreutils/external/kopia via
// pkg/binmgr — not compiled in) on a loopback port, auto-exposed over the mesh
// as the `backup` service (many nodes back up into one repo). Default OFF.
KopiaEnabled *bool `json:"kopia_enabled,omitempty"`
// KopiaPort is Kopia's loopback server port (default 51515).
KopiaPort int `json:"kopia_port,omitempty"`
// ActrunnerEnabled opts this outpost into running Gitea act_runner (the CI
// executor) as a managed external binary (coreutils/external/actrunner via
// pkg/binmgr — not compiled in). Unlike loom/zot it's a CONSUMER, not a mesh
// service: it registers against a Gitea instance and dials OUT to execute
// .gitea/workflows/*.yml. Default OFF. See docs/local-p2p-cicd.md.
ActrunnerEnabled *bool `json:"actrunner_enabled,omitempty"`
// ActrunnerInstance is the Gitea base URL the runner registers against. Empty
// uses the local loom forge (http://127.0.0.1:<loom_port>) when loom is on —
// the hub-node case. For a build-only node, set this to a mesh-reachable git
// addr (e.g. from `outpost mesh dial git`).
ActrunnerInstance string `json:"actrunner_instance,omitempty"`
// ActrunnerToken is the runner registration token (minted in Gitea). Required
// to register on first boot; ignored once the runner's .runner file exists.
ActrunnerToken string `json:"actrunner_token,omitempty"`
// CloudDOEnabled opts this host into Digital Ocean provider support (the
// cloud venue): it advertises DO capability and exports the token below as
// DIGITALOCEAN_ACCESS_TOKEN so `bashy doctl` / DO provisioning works. Off by
// default (opt-in). The first cloud-provider knob; aws/gcloud follow the
// same shape.
CloudDOEnabled *bool `json:"cloud_do_enabled,omitempty"`
// CloudDOToken is the Digital Ocean API token, exported as
// DIGITALOCEAN_ACCESS_TOKEN to doctl-invoking tools. Redacted from SafeView
// like other secrets (presence reported as has_cloud_do_token).
CloudDOToken string `json:"cloud_do_token,omitempty"`
// ActrunnerLabels are the executor labels (default "host:host" — jobs run on
// the host shell, no container runtime needed for the runner itself).
ActrunnerLabels string `json:"actrunner_labels,omitempty"`
// ActrunnerSandbox opts the runner into the tier-3 SANDBOX executor: it
// additionally advertises a "sandbox" docker-executor label, so a
// `runs-on: sandbox` job runs inside an OCI container (via bashy podman)
// instead of on the host shell. Additive — the runner still offers the
// host build lane (`runs-on: host`, e.g. Go-toolchain outpost/bashy builds).
// Needs a reachable container runtime (ActrunnerDockerHost / bashy podman).
ActrunnerSandbox *bool `json:"actrunner_sandbox,omitempty"`
// ActrunnerSandboxImage overrides the OCI image the sandbox executor runs
// jobs in (default: actrunner.DefaultSandboxImage — a node image carrying
// git+node+bash). Only meaningful when ActrunnerSandbox is on.
ActrunnerSandboxImage string `json:"actrunner_sandbox_image,omitempty"`
// ActrunnerDockerHost is the DOCKER_HOST the sandbox executor dials to reach
// the container runtime. Empty → the daemon auto-resolves bashy podman's
// host-side socket. Only meaningful when ActrunnerSandbox is on.
ActrunnerDockerHost string `json:"actrunner_docker_host,omitempty"`
// Shard configures the Ollama sharding sub-feature: serve a model bigger
// than any single node by splitting it across mesh peers via llama.cpp
// RPC carried over the mesh forwarder. Under Ollama; default off.
// See docs/ollama-sharding-builtin-plan.md.
Shard *ShardConfig `json:"shard,omitempty"`
// ClusterLLMEndpoint is the base URL of an intra-home
// distributed-inference backend (GPUStack first; any runtime that
// publishes the same OpenAI /v1-openai surface later) that this home
// runs to serve a model too large for any single machine. Empty (the
// default) disables detection entirely — the outpost stays a
// single-machine pool member. When set, the ollama watcher attaches a
// cluster descriptor to its registry push so cloudbox's tier-0 router
// can send a too-big-for-one-node model to this home. Detection is
// HTTP-probe only; outpost never launches the backend (the operator
// runs it as a container against the ycode-published podman socket).
ClusterLLMEndpoint string `json:"cluster_llm_endpoint,omitempty"`
// ClusterLLMAPIKey is the optional Bearer key for the backend's
// management API. Without it the backend is still detected as running
// (so the admin UI shows it), but the worker/VRAM aggregation that
// powers the cloudbox size filter needs the key — GPUStack's
// management API is auth-gated — so the filter stays inert until a key
// is supplied. Redacted from SafeView like other secrets.
ClusterLLMAPIKey string `json:"cluster_llm_api_key,omitempty"`
// OtelEnabled gates the observability built-in apps that proxy
// ycode's embedded Prometheus / Alertmanager / VictoriaLogs /
// Jaeger / Perses stack through the matrix tunnel. Off by default
// — this is a substantial surface increase (each ycode sub-path
// becomes reachable through /h/<host>/app/otel-*) and it only
// makes sense when ycode-serve is running.
OtelEnabled bool `json:"otel_enabled,omitempty"`
// OtelPoolEnabled controls whether cloudbox is allowed to federate
// queries across this outpost. Distinct from OtelEnabled the same
// way OllamaPoolEnabled is from OllamaEnabled: an operator can
// expose the surfaces privately (per-host access only) without
// contributing to the fleet-wide dashboard. Pointer-bool with
// OtelPoolOn() so existing configs default to on whenever
// OtelEnabled is on.
OtelPoolEnabled *bool `json:"otel_pool_enabled,omitempty"`
// YcodeEnabled gates outpost's ycode-aware features (the
// YcodeShare surfaces below, the OTel proxy wiring in main.go).
// Detection-only — outpost never spawns or restarts `ycode
// serve`. The operator manages ycode's lifecycle directly so
// the flags they launched it with stay intact; outpost just
// reads the manifest and reports state.
//
// ycode is the under-the-hood agentic engine outpost delegates
// to for inference / podman / Gitea; one `ycode serve` per OS
// user account. Distributed as a separate binary; the admin UI
// surfaces a download link when no binary is found, mirroring
// ycode's own TUI install flow.
YcodeEnabled bool `json:"ycode_enabled,omitempty"`
// SPA served at /ycode/ on ycode's bearer-authed proxy) is exposed
// through the matrix tunnel as a regular `ycode` built-in app.
// When on, cloudbox renders a tile and users can open the ycode UI
// from the portal; when off, ycode stays purely-local and unreachable
// through cloudbox. Pointer-bool with YcodeShareOn() helper so the
// default is on whenever ycode itself is on — the typical operator
// who turned ycode on probably wants to reach it remotely too.
YcodeShareEnabled *bool `json:"ycode_share_enabled,omitempty"`
// elevation gate for the `ycode` built-in app. Default false
// (matches custom-app conventions): owners of the host reach their
// own ycode in one click. Flip to true to require an OS-password
// elevation hop the way /shell and /desktop do — useful when the
// host is shared with non-trivially-trusted users. Pointer-bool so
// the absent-key case folds to the safer default ("no extra dance
// for owners"); explicit true is honored.
YcodeShareRequireLogin *bool `json:"ycode_share_require_login,omitempty"`
// ycode-share catalog (see internal/agent/otel/ycode_surfaces.go).
// Map keys are tile names (`ycode`, `ycode-canvas`, `ycode-ollama`,
// `ycode-git`, `ycode-memos`, `ycode-graph`); values are explicit
// on/off. Absent keys fall back to the catalog's DefaultOn — today
// only `ycode` (the chat) is default-on, so an operator who just
// flips ycode_share_enabled gets the chat tile and nothing else
// until they opt in to additional surfaces from the SPA.
YcodeShareSurfaces map[string]bool `json:"ycode_share_surfaces,omitempty"`
// UpdateMode is the per-host policy for cloudbox-pushed
// self-upgrades at POST /admin/upgrade. Three values:
//
// - "auto" — default. Stage + probe + swap + restart on push.
// - "manual" — daemon persists the envelope to
// <cacheDir>/outpost/upgrade.pending.json and
// returns 202 pending_manual. Operator applies via
// `outpost upgrade apply` or cloudbox's UI button
// (which re-POSTs with Force=true to bypass the
// manual gate).
// - "never" — refuse all cloudbox pushes; daemon returns 403.
//
// Empty / missing → "auto" (the default for paired hosts; making
// it opt-in per host would defeat the "press button, fleet rolls"
// promise). Use UpdateModeName() to read — it folds the empty
// case and normalizes legacy AutoUpgrade *bool configs.
UpdateMode string `json:"update_mode,omitempty"`
// AutoUpgrade is the legacy boolean. Kept on the struct so old
// agent.json files round-trip without losing data; LoadFile maps
// it into UpdateMode on read (true → auto, false → never) and
// writes clear UpdateMode going forward. New code reads via
// UpdateModeName() — do not consult this field directly.
AutoUpgrade *bool `json:"auto_upgrade,omitempty"`
// AutoRollbackEnabled arms the auto-rollback watchdog's DESTRUCTIVE
// revert: when a self-upgrade's new binary fails to confirm healthy
// (never stays up long enough), the supervisor swaps <binary>.previous
// back. Default OFF (nil/false): the watchdog still observes and logs
// "would auto-rollback …", but does not revert until an operator opts
// in. Read via AutoRollbackOn(). Read by the supervisor process (which
// owns the revert), not just the daemon.
AutoRollbackEnabled *bool `json:"auto_rollback_enabled,omitempty"`
// AdminSessionKey is the HMAC secret used to sign admin-UI session
// cookies. Persisting it across restarts is what keeps the admin user
// logged in when a built-in toggle re-execs the binary. Base64-encoded
// in the JSON (32 random bytes worth of entropy). Auto-generated and
// saved on first boot via EnsureAdminSessionKey.
AdminSessionKey []byte `json:"admin_session_key,omitempty"`
// MCPBearerToken is the shared secret agent tools (Claude Code,
// Windsurf, the outpost CLI, ...) present in Authorization: Bearer
// headers when calling the MCP server mounted at /mcp/* on the same
// loopback listener as the admin UI. Distinct from the session
// cookie used by humans hitting /api/*. 32 random bytes encoded as
// hex (64 chars) so it can be pasted into a .mcp.json verbatim.
// Auto-generated on first boot via EnsureMCPBearerToken; the admin
// UI exposes a "rotate" action that re-mints it.
MCPBearerToken string `json:"mcp_bearer_token,omitempty"`
// Outbound configures local mount paths that proxy through cloudbox to
// remote outposts' apps. The local outpost holds an in-memory
// elevation cookie per entry (captured by Connect); after that, the
// local URL http://localhost:17777/<path>/ proxies to
// https://<cloudbox>/h/<host>/app/<name>/<rest>. See
// internal/agent/outbound.go.
Outbound []OutboundConfig `json:"outbound,omitempty"`
// Cluster, when present and Enabled, opts this outpost into the
// cloudbox virtual-podman cluster: vkpodman joins a cloud-side k3s
// API server as a virtual node and runs scheduled Pods as local
// podman containers. See internal/agent/vknode. Off by default.
Cluster *ClusterConfig `json:"cluster,omitempty"`
// AssignedHostname is the cloudbox-issued DNS-safe slug returned at
// register/exchange time (e.g. "host-a-7a3b"). Used as the mDNS
// service-instance name and as the assumed hostname for
// `<assigned_hostname>.local` resolution. Cloudbox-side issuance
// lands in Wave 3A.2; until then this falls back to os.Hostname()
// in the daemon's startup path.
AssignedHostname string `json:"assigned_hostname,omitempty"`
// OAuth2Email is the cloudbox account-owner identity (the OAuth2
// "I am the resource owner" claim). Returned by the
// register/exchange flow in Wave 3A.2. Used as the Tier-2 trust
// anchor by PeerTrustPolicy="same-owner".
OAuth2Email string `json:"oauth2_email,omitempty"`
// OSUsername is the OS user the outpost daemon runs as
// (informational + future SSH user-cert flow). Populated from
// hostauth.CurrentUser() at boot when empty.
OSUsername string `json:"os_username,omitempty"`
// DiscoveryEnabled gates mDNS advertisement and the HTTP /discover
// surface mount. Default off — flip on with `outpost config set
// --discovery=on` once the operator understands the privacy
// posture (mDNS broadcasts hostname + fingerprint on the LAN).
DiscoveryEnabled *bool `json:"discovery_enabled,omitempty"`
// SSHListenAddr is the optional LAN TCP bind for the in-process SSH
// server (e.g. "0.0.0.0:2222"). Empty disables the LAN listener;
// the matrix tunnel /ssh endpoint stays the only path until the
// operator explicitly opts into LAN exposure. The same handleSSHConn
// services WS and LAN paths; PasswordCallback authentication
// applies on LAN-direct (no cloudbox vouching available).
SSHListenAddr string `json:"ssh_listen_addr,omitempty"`
// SSHWSListenAddr is the optional LAN WebSocket-mounted SSH
// listener (e.g. "0.0.0.0:2223"). Same /ssh handler as the
// loopback bind — clients open a WSS upgrade with
// `Authorization: Bearer <peer-ticket>` and the receiver
// verifies the ticket locally against CloudboxTicketPubkey.
// Enables passwordless LAN-direct `outpost ssh` without putting
// cloudbox on the data plane. Distinct from SSHListenAddr
// (plain-TCP PAM-gated) so an operator can run both transports
// during migration without port-conflict. Empty disables.
SSHWSListenAddr string `json:"ssh_ws_listen_addr,omitempty"`
// DiscoveryHTTPListenAddr is the optional LAN bind for the HTTP
// /api/v1/discover/* surface (e.g. "0.0.0.0:17778"). Empty disables.
// When set, advertised in mDNS TXT and cloudbox peer-hints so
// other outposts can probe us directly.
DiscoveryHTTPListenAddr string `json:"discovery_http_listen_addr,omitempty"`
// PeerTrustPolicy controls which discovered peers we'll accept for
// Tier-2 operations (ssh exec, jump, sftp, repair). One of:
//
// "same-owner" — default; require oauth2_email match
// "same-cloudbox" — accept any peer paired with our cloudbox
// "tofu-allow" — fall back to TOFU on fingerprint when no cert
//
// The default refuses peers in the same cloudbox but a different
// OAuth2 account — strangers shouldn't be able to jump-host or
// install-upgrade through my outpost just because we share a
// cloudbox tenant.
PeerTrustPolicy string `json:"peer_trust_policy,omitempty"`
// Backup configures the folder-watcher scheduler that ships the
// newest file from each listed folder to peer outposts (Phase 3
// adds the actual peer push; Phase 2 only records candidates in a
// local ledger). App-opaque by design: the agent doesn't run any
// app-specific snapshot logic — the cooperating app (classgo,
// kg, …) is responsible for producing backup artifacts inside one
// of these folders on its own schedule, and outpost picks the
// newest by mtime.
Backup *BackupConfig `json:"backup,omitempty"`
// Mirror is the continuous live directory-mirror config — distinct from
// Backup (which is age-encrypted point-in-time snapshots pushed to cloudbox
// for disaster recovery). Mirror keeps a peer's replica in near-real-time via
// coreutils/pkg/mirror (recursive watch + rclone sync), for availability /
// failover. See docs/external-binary-builtins.md.
Mirror *MirrorConfig `json:"mirror,omitempty"`
}
FileConfig is what the register command writes and `start` reads from disk. It pins everything the agent needs to dial the cloud — no more env juggling once registration has completed.
AuthURL, when non-empty, switches the agent's /auth handler from the host OS (PAM / dscl / LogonUserW) to an external HTTP endpoint that owns its own application-level user list.
func LoadFile ¶
func LoadFile(path string) (*FileConfig, error)
LoadFile reads a previously-saved FileConfig. Returns (nil, nil) if the file doesn't exist — callers should fall back to env.
func (*FileConfig) ActrunnerInstanceResolved ¶ added in v0.10.0
func (fc *FileConfig) ActrunnerInstanceResolved() string
ActrunnerInstanceResolved returns the Gitea base URL the runner registers against: an explicit ActrunnerInstance wins; otherwise the local loom forge (http://127.0.0.1:<loom_port>) when loom is on; else "" (caller must skip / surface a clear error — a build-only node needs an explicit instance).
func (*FileConfig) ActrunnerLabelsOrDefault ¶ added in v0.10.0
func (fc *FileConfig) ActrunnerLabelsOrDefault() string
ActrunnerLabelsOrDefault returns the configured runner labels, or "host:host".
func (*FileConfig) ActrunnerOn ¶ added in v0.10.0
func (fc *FileConfig) ActrunnerOn() bool
ActrunnerOn reports whether the act_runner CI-executor builtin is enabled.
func (*FileConfig) ActrunnerSandboxOn ¶ added in v0.13.7
func (fc *FileConfig) ActrunnerSandboxOn() bool
ActrunnerSandboxOn reports whether the tier-3 sandbox (container) executor is enabled on the act_runner builtin.
func (*FileConfig) AutoRollbackOn ¶ added in v0.9.0
func (fc *FileConfig) AutoRollbackOn() bool
ShellOn reports whether the built-in /shell route should be mounted. Missing field (old configs) defaults to true. AutoRollbackOn reports whether the auto-rollback watchdog may perform the destructive revert. Default OFF (observe-only) — unlike most toggles it must be explicitly opted into.
func (*FileConfig) ClipboardOn ¶
func (fc *FileConfig) ClipboardOn() bool
ClipboardOn reports whether the built-in /clipboard route should be mounted.
func (*FileConfig) CloudDOOn ¶ added in v0.14.0
func (fc *FileConfig) CloudDOOn() bool
CloudDOOn reports whether Digital Ocean provider support is enabled.
func (*FileConfig) ClusterLLMOn ¶ added in v0.7.3
func (fc *FileConfig) ClusterLLMOn() bool
ClusterLLMOn reports whether an intra-home distributed-inference backend endpoint is configured, i.e. whether the outpost should probe for a cluster and advertise it on the registry push. Detection is purely opt-in via a non-empty ClusterLLMEndpoint.
func (*FileConfig) ClusterNodeName ¶
func (fc *FileConfig) ClusterNodeName() string
ClusterNodeName returns the node identity to register with — the explicit override when set, otherwise AgentName.
func (*FileConfig) ClusterOn ¶
func (fc *FileConfig) ClusterOn() bool
ClusterOn reports whether this outpost should join the cloudbox virtual-podman cluster on boot. Missing field or Enabled=false ⇒ false.
func (*FileConfig) DesktopOn ¶
func (fc *FileConfig) DesktopOn() bool
DesktopOn reports whether the built-in /desktop route should be mounted.
func (*FileConfig) DiscoveryOn ¶ added in v0.1.6
func (fc *FileConfig) DiscoveryOn() bool
DiscoveryOn reports whether LAN discovery (mDNS + HTTP /discover) should be active. Default off; the *bool gives us an explicit opt-in semantic that survives the absent-key case.
func (*FileConfig) EffectiveAssignedHostname ¶ added in v0.1.6
func (fc *FileConfig) EffectiveAssignedHostname() string
EffectiveAssignedHostname returns AssignedHostname when set, otherwise a DNS-safe form of AgentName, otherwise os.Hostname(). The Wave 3A.1 daemon uses this until cloudbox-side issuance lands in 3A.2.
func (*FileConfig) EffectivePeerTrustPolicy ¶ added in v0.1.6
func (fc *FileConfig) EffectivePeerTrustPolicy() string
EffectivePeerTrustPolicy returns the configured policy with a default of "same-owner" when unset or invalid. Centralized so every consumer reaches the same fallback.
func (*FileConfig) FilesOn ¶ added in v0.9.0
func (fc *FileConfig) FilesOn() bool
FilesOn reports whether the embedded File Browser builtin should mount. Default-on like the other outpost-owned route builtins.
func (*FileConfig) KopiaOn ¶ added in v0.10.0
func (fc *FileConfig) KopiaOn() bool
func (*FileConfig) KopiaPortOrDefault ¶ added in v0.10.0
func (fc *FileConfig) KopiaPortOrDefault() int
KopiaPortOrDefault returns the configured Kopia server port, or 51515.
func (*FileConfig) LANInferenceOn ¶ added in v0.12.31
func (fc *FileConfig) LANInferenceOn() bool
LANInferenceOn reports whether this outpost serves its local LLM inference directly to same-LAN callers (the lan_inference toggle). Like OllamaPoolOn it is a strict extension of the local Ollama proxy — false when Ollama is off. Unlike the pool, it defaults OFF (nil ⇒ off): it is a LAN-TRUST endpoint (no per-request auth), so it must be an explicit opt-in by an operator who accepts that their LAN is trusted. The boot path additionally gates on inference being reachable and on pairing (cloudbox is what advertises the LAN endpoint to same-LAN callers).
func (*FileConfig) LANInferencePortOrDefault ¶ added in v0.12.31
func (fc *FileConfig) LANInferencePortOrDefault() int
LANInferencePortOrDefault returns the configured LAN inference listen port, or 11435 (kept distinct from the inference server's own 11434).
func (*FileConfig) LoomOn ¶ added in v0.10.0
func (fc *FileConfig) LoomOn() bool
MeshOn reports whether this outpost runs the libp2p mesh data plane (the peer node carrying authenticated, NAT-traversing peer↔peer streams). Opt-in (default OFF) — gated on MeshEnabled=true AND a paired access token (cloudbox is the rendezvous, so an unpaired host has no signaler to find peers through).
func (*FileConfig) LoomPortOrDefault ¶ added in v0.10.0
func (fc *FileConfig) LoomPortOrDefault() int
LoomPortOrDefault returns the configured loom port, or 31880.
func (*FileConfig) MeshNeeded ¶ added in v0.12.0
func (fc *FileConfig) MeshNeeded() bool
MeshNeeded reports whether the libp2p mesh data plane must run — either explicitly enabled (MeshOn) or implicitly because sharding (which rides it) is on. The daemon gates mesh startup on this so zero-config sharding brings its own transport up without a separate opt-in.
func (*FileConfig) MeshOn ¶ added in v0.10.0
func (fc *FileConfig) MeshOn() bool
MeshOn reports whether this outpost runs the libp2p mesh data plane (the peer node carrying authenticated, NAT-traversing peer↔peer streams). Default ON for paired hosts — zero-config peer reachability; opt out with an explicit mesh_enabled=false. The boot path additionally gates on a paired access token (cloudbox is the rendezvous, so an unpaired host has no signaler to find peers through). Enabling the mesh alone exposes nothing — the forwarder carries only services you explicitly add.
func (*FileConfig) MirrorOn ¶ added in v0.10.0
func (fc *FileConfig) MirrorOn() bool
MirrorOn reports whether the live directory mirror is enabled with ≥1 job.
func (*FileConfig) OllamaOn ¶
func (fc *FileConfig) OllamaOn() bool
OllamaOn reports whether the built-in Ollama proxy is enabled.
func (*FileConfig) OllamaPoolOn ¶
func (fc *FileConfig) OllamaPoolOn() bool
OllamaPoolOn reports whether this outpost should join cloudbox's LLM pool. Returns false when Ollama itself is off (the pool is a strict extension of the per-host proxy). When OllamaPoolEnabled is nil, the default is to follow OllamaOn — pooling is the useful behavior, and configs written before pooling shipped should opt in automatically. Explicit false (operator turned it off) is honored.
func (*FileConfig) OtelOn ¶ added in v0.1.0
func (fc *FileConfig) OtelOn() bool
OtelOn reports whether the built-in observability proxies are enabled.
func (*FileConfig) OtelPoolOn ¶ added in v0.1.0
func (fc *FileConfig) OtelPoolOn() bool
OtelPoolOn reports whether this outpost participates in cloudbox's federated dashboard / alert fan-out. Mirrors OllamaPoolOn: false when OtelOn() is false; defaults to on when OtelPoolEnabled is nil.
func (*FileConfig) PeerPlaneNeeded ¶ added in v0.12.0
func (fc *FileConfig) PeerPlaneNeeded() bool
PeerPlaneNeeded reports whether the peer-plane locality service must run — either explicitly enabled (PeerPlaneOn) or implicitly because sharding (which discovers its same-LAN ring via the peer-plane) is on. Gating on this lets zero-config sharding bring up its discovery service without a separate opt-in.
func (*FileConfig) PeerPlaneOn ¶ added in v0.10.0
func (fc *FileConfig) PeerPlaneOn() bool
PeerPlaneOn reports whether this outpost runs the p2p peer-plane locality service: announce interface candidates to cloudbox's signaler, run a probe responder, and measure RTT to peers to classify TP/LAN/WAN tiers. Opt-in (default OFF) — gated on PeerPlaneEnabled=true AND a paired access token.
func (*FileConfig) PodmanOn ¶
func (fc *FileConfig) PodmanOn() bool
PodmanOn reports whether the built-in podman proxy is enabled in this config. Unlike the loopback-only builtins above, podman is off by default — the admin UI flips it on after the daemon is detected.
func (*FileConfig) SFTPOn ¶
func (fc *FileConfig) SFTPOn() bool
SSHAllowLocalForwardOn reports whether the SSH server should honor `direct-tcpip` channel-open requests (stock `ssh -L` / `ssh -D`). Missing field (old configs) defaults to true — the channel is still gated by a loopback-only destination allowlist regardless. SFTPOn reports whether the embedded SSH server should accept the "sftp" subsystem. Default-on for the same reason scp-just-works matters.
func (*FileConfig) SSHAllowAgentForwardOn ¶
func (fc *FileConfig) SSHAllowAgentForwardOn() bool
SSHAllowAgentForwardOn reports whether the SSH server should accept `auth-agent-req@openssh.com` channel-request (stock `ssh -A`). Missing field (old configs) defaults to true — the per-session socket is created in a private tempdir with 0600 perms.
func (*FileConfig) SSHAllowLocalForwardOn ¶
func (fc *FileConfig) SSHAllowLocalForwardOn() bool
func (*FileConfig) SSHAllowRemoteForwardOn ¶
func (fc *FileConfig) SSHAllowRemoteForwardOn() bool
SSHAllowRemoteForwardOn reports whether the SSH server should honor `tcpip-forward` global requests (stock `ssh -R`). Missing field (old configs) defaults to true — the bind address is still locked to loopback by the agent regardless.
func (*FileConfig) SSHOn ¶
func (fc *FileConfig) SSHOn() bool
SSHOn reports whether the built-in /ssh route (real SSH server reached over WebSocket through the matrix tunnel) should be mounted.
func (*FileConfig) SandboxOn ¶ added in v0.7.3
func (fc *FileConfig) SandboxOn() bool
SandboxOn reports whether the filtered container sandbox proxy is enabled. Off by default — it both needs the podman socket and widens who can run containers, so it requires explicit opt-in.
func (*FileConfig) SeaweedfsOn ¶ added in v0.10.0
func (fc *FileConfig) SeaweedfsOn() bool
func (*FileConfig) SeaweedfsPortOrDefault ¶ added in v0.10.0
func (fc *FileConfig) SeaweedfsPortOrDefault() int
SeaweedfsPortOrDefault returns the configured SeaweedFS S3 port, or 8333.
func (*FileConfig) ShardOn ¶ added in v0.10.0
func (fc *FileConfig) ShardOn() bool
ShardOn reports whether Ollama sharding is enabled. Zero-config by design: ON by default for a paired, Ollama-on node (an owner-registered node "just works" as a shard participant); explicit Shard.Enabled=false opts out. It states the INTENT — the mesh data plane it rides is auto-enabled as a dependency (see MeshNeeded), so zero-config sharding needs no separate mesh opt-in.
func (*FileConfig) ShellOn ¶
func (fc *FileConfig) ShellOn() bool
func (*FileConfig) UpdateModeName ¶
func (fc *FileConfig) UpdateModeName() string
UpdateModeName returns the normalized update-mode for this config. Folds the legacy AutoUpgrade *bool (true → auto, false → never) and defaults empty to "auto". Always one of the UpdateMode* constants; never returns "".
func (*FileConfig) WarmBudgetFracOrDefault ¶ added in v0.12.32
func (fc *FileConfig) WarmBudgetFracOrDefault() float64
WarmBudgetFracOrDefault returns the configured warm-budget fraction, or 0.33 when unset / out of the (0,1] range.
func (*FileConfig) WarmServingOn ¶ added in v0.12.32
func (fc *FileConfig) WarmServingOn() bool
WarmServingOn reports whether this outpost runs the considerate warm-serving plane. Like OllamaPoolOn it is a strict extension of the local Ollama proxy — false when Ollama is off. When WarmServingEnabled is nil the default is ON (keeping a conservative resident set warm is the useful behavior, and it's considerate by construction — it yields on busy). Explicit false opts out. The boot path additionally gates on pairing (cloudbox is what asks a host to warm a model).
func (*FileConfig) YcodeOn ¶ added in v0.1.0
func (fc *FileConfig) YcodeOn() bool
YcodeOn reports whether outpost's ycode-aware features (share surfaces, OTel wiring) are enabled. Detection-only — outpost never spawns or restarts `ycode serve`. Plain bool, no implicit default. See YcodeEnabled in the struct doc.
func (*FileConfig) YcodeShareOn ¶ added in v0.1.0
func (fc *FileConfig) YcodeShareOn() bool
YcodeShareOn reports whether ycode's home/landing page should be exposed through the matrix tunnel as a `ycode` built-in app. Returns false when ycode itself is off (the share is a strict extension of the per-host proxy). When YcodeShareEnabled is nil, the default is to follow YcodeOn — operators who turned ycode on probably want it reachable. Explicit false (operator turned it off) is honored.
func (*FileConfig) YcodeShareRequireLoginOn ¶ added in v0.1.1
func (fc *FileConfig) YcodeShareRequireLoginOn() bool
YcodeShareRequireLoginOn reports whether the cloudbox-side OS-password elevation gate should fire for the `ycode` built-in app. Default false — owners reach their own ycode without the OS-password popup; flipping to true makes cloudbox treat ycode like /shell or /desktop.
func (*FileConfig) ZotOn ¶ added in v0.10.0
func (fc *FileConfig) ZotOn() bool
func (*FileConfig) ZotPortOrDefault ¶ added in v0.10.0
func (fc *FileConfig) ZotPortOrDefault() int
ZotPortOrDefault returns the configured zot port, or 5000.
type MeshConsume ¶ added in v0.13.3
type MeshConsume struct {
// Service is the remote mesh service name to reach (e.g. "git").
Service string `json:"service"`
// PeerID is the libp2p peer id of the host exposing Service.
PeerID string `json:"peer_id"`
// LocalAddr is the fixed local listen address (e.g. "127.0.0.1:31880") so the
// consuming config (actrunner_instance, etc.) stays stable. Empty → ephemeral.
LocalAddr string `json:"local_addr,omitempty"`
}
MeshConsume is one persistent mesh CONSUME (the dial/listen side, symmetric to MeshService's expose side): a local loopback listener the daemon re-establishes on every boot, bridging to a (peer, service) over the mesh. It dials by peer id directly (not the cloudbox service resolver), so it's stable across restarts and immune to resolver propagation lag — the load-bearing property for a build-only node whose act_runner must reach a loom forge on another host at boot.
type MeshService ¶ added in v0.10.0
type MeshService struct {
// Name is the mesh service name a peer dials (e.g. "git", "registry").
Name string `json:"name"`
// Addr is the local loopback address to bridge to (e.g. "127.0.0.1:3000").
Addr string `json:"addr"`
}
MeshService is one local loopback service persistently exposed over the mesh.
type MirrorConfig ¶ added in v0.10.0
type MirrorConfig struct {
// Enabled is the master switch. When false no mirror jobs run.
Enabled bool `json:"enabled"`
// Jobs is the list of source→dest mirror jobs.
Jobs []MirrorJob `json:"jobs,omitempty"`
}
MirrorConfig is the continuous live directory-mirror config: a master switch plus a list of jobs, each keeping a destination in near-real-time sync with a source directory (coreutils/pkg/mirror — recursive watch + rclone sync, all permissive parts). Distinct from BackupConfig (encrypted scheduled snapshots).
type MirrorJob ¶ added in v0.10.0
type MirrorJob struct {
// Source is the local directory to mirror.
Source string `json:"source"`
// Service is the mesh service name the replica peer exposes to receive the
// mirror (e.g. an `rclone serve webdav` advertised as "webdav"). Resolved
// dynamically — the mirror runs only while a peer offers it and is reachable.
Service string `json:"service"`
// LANOnly, when true, mirrors only while the peer is on the same LAN (the
// fast, direct path) — so a roaming node syncs at home, not over slow WAN.
LANOnly bool `json:"lan_only,omitempty"`
}
MirrorJob is one continuous, mobility-aware mirror: keep a peer's replica in sync with Source, but ONLY while that peer is reachable over the mesh. The supervisor resolves Service via the mesh and opens a forward to the peer exposing it (a `rclone serve` the replica runs); when the peer is away the resolve/forward fails, so the mirror pauses, and it resumes when the peer reappears (e.g. a laptop returns to the LAN). This is the dynamic-peer / mobility behaviour — no static address is pinned.
type OutboundConfig ¶
type OutboundConfig 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"`
}
OutboundConfig is one local mount that proxies to a remote outpost.
- Path : local mount identifier. For Scheme=="http" this is the subpath under the admin UI listener — e.g. "kg" makes the remote app reachable at http://localhost:17777/kg/. For Scheme=="tcp" it is also the addressing key (used in the API URLs and for state lookup) but no HTTP subpath is mounted.
- Name : the remote outpost's app name (e.g. "ollama", "postgres"). Matched against the remote's AppRegistry by the cloudbox host-proxy.
- Host : the remote outpost's name as registered with cloudbox.
- User : the OS user on the remote outpost (used at Connect time when POSTing to /h/<host>/elevate).
- Scheme:
- "http" (default): local mount is the admin-UI subpath http://localhost:17777/<Path>/... proxied through cloudbox to the remote outpost's /app/<Name>/ http app.
- "tcp": local outpost opens a 127.0.0.1:LocalPort listener after Connect and bridges every accepted TCP conn through cloudbox as a WebSocket to the remote outpost's tcp-scheme app named <Name>. Lets unmodified clients reach non-HTTP services (ssh, psql, mysql) the remote outpost has registered as TCP apps.
- "ssh": same listener+WS-bridge shape as "tcp", but the bridge targets the remote outpost's built-in /ssh endpoint (the in-process Go SSH server) directly — no app registration on the remote required. Name is ignored. Elevate flow uses host-level /h/<Host>/elevate (the same one outpost ssh-proxy /outpost connect uses), so the matrix_elev cookie scope is the whole host rather than a single app.
- LocalPort: required for Scheme=="tcp" or "ssh". Ignored otherwise.
- TTLSeconds: per-mount override for cloudbox's absolute-expiry cap on the matrix_elev cookie. 0 (unset) uses the cloudbox default; math.MaxInt64 means "no absolute cap, only idle expiry" — useful for long-running agentic sessions. Cloudbox must honor the ttl_seconds field in the elevate POST body for this to take effect; older cloudbox versions ignore it and apply their default.
func (OutboundConfig) BindsListener ¶
func (oc OutboundConfig) BindsListener() bool
BindsListener reports whether this outbound, when Connected, owns a 127.0.0.1:LocalPort TCP listener. True for "tcp" and "ssh" (both expose the remote service as a local port); false for "http" (which is served as a subpath under the admin-UI listener).
func (OutboundConfig) BuiltinSSH ¶
func (oc OutboundConfig) BuiltinSSH() bool
BuiltinSSH reports whether this outbound targets the remote outpost's built-in /ssh WebSocket endpoint (rather than a registered app under /app/<name>/). True only for Scheme=="ssh".
func (OutboundConfig) SchemeNorm ¶
func (oc OutboundConfig) SchemeNorm() string
SchemeNorm returns the effective scheme — empty defaults to "http" so configs written before TCP support landed keep their old behavior. Recognized values: "http", "tcp", "ssh".
type SSHTarget ¶ added in v0.1.4
type SSHTarget struct {
// Name is the local alias the operator types: `outpost ssh <name>`.
// One alias per file; the filename is `<name>.json`.
Name string `json:"name"`
// Host is the destination this target reaches:
// - when Via == "": a cloudbox-paired host name (`outpost
// ssh-config` would print this as the `Host` stanza, and
// cloudbox routes `/h/<host>/ssh` to it).
// - when Via != "": the hop-side destination address — i.e.,
// what the upstream Via target's outpost can reach over its
// LAN / peer allowlist via an SSH direct-tcpip channel. Often
// a paired peer hostname (the remote outpost's SSH server
// accepts peer destinations via peerhosts) or, after the
// operator widens SSHAllowLocalForward, a LAN IP.
// Required.
Host string `json:"host"`
// Port is the destination's SSH port, used only when Via != "".
// Defaults to 22 (canonical sshd). Ignored when reaching cloudbox-
// paired hosts directly — the WS path doesn't carry a port.
Port int `json:"port,omitempty"`
// User overrides the OS username the remote outpost's /auth gate
// expects. Empty = resolve from cloudbox's /api/v1/ssh/hosts
// at connect time (same fallback chain `outpost connect` uses).
User string `json:"user,omitempty"`
// Via is the alias of another configured target to ProxyJump
// through. When set, dialing this target first dials Via, then
// opens an SSH direct-tcpip channel to Host:Port, and layers SSH
// on that channel. Chains are walked recursively.
//
// This is the equivalent of ssh's ProxyJump (`ssh -J <via>
// <name>`) but resolved at our config layer so MCP tools / CLI
// can use it uniformly.
Via string `json:"via,omitempty"`
// Direct, when true, dials the target via a plain TCP connection
// to Host:Port (defaulting to port 22 when Port is zero), bypassing
// the cloudbox WS path. The remote outpost must have its
// SSHListenAddr bound to a LAN address (FileConfig.SSHListenAddr).
//
// Trust on a Direct target is TOFU on the SSH host-key fingerprint
// (Wave 3A.1) — same as the cloudbox-WS path. Wave 3A.2 lifts to
// cloudbox-CA-signed certs.
Direct bool `json:"direct,omitempty"`
// Description is a freeform note for the operator's benefit
// (printed by `outpost ssh list`). Not interpreted.
Description string `json:"description,omitempty"`
}
SSHTarget is the on-disk shape of one friendly alias.
func ListSSHTargets ¶ added in v0.1.4
ListSSHTargets enumerates all on-disk targets, sorted by name. Files that fail to parse are silently skipped so one bad file doesn't break the listing — a parser would still surface them individually via LoadSSHTarget.
func LoadSSHTarget ¶ added in v0.1.4
LoadSSHTarget reads one target by alias. Returns a clear "no such target" error when the file doesn't exist — distinguishable from a parse failure so callers can surface a useful message.
func ResolveSSHTargetChain ¶ added in v0.1.4
ResolveSSHTargetChain walks the Via field starting at `name` and returns the chain in DIAL order: the first element is the cloudbox- reachable outpost (the outermost hop), and the last element is `name` itself (the innermost endpoint we want to reach).
Cycles and overlong chains are detected and surfaced as errors so a hand-edited config can't trap the dial loop.
`override` is the alias whose Via should be substituted for the target's persisted Via — used by `--jump <alias>` runtime flags and the MCP `jump` field. Pass "" to use the on-disk Via.
type ShardConfig ¶ added in v0.10.0
type ShardConfig struct {
// Enabled is a tri-state opt-out: nil → the zero-config default (ON for a
// paired, Ollama-on node — an owner-registered node "just works" as a shard
// participant); explicit false opts out; true forces on. Mirrors
// OllamaPoolEnabled.
Enabled *bool `json:"enabled,omitempty"`
// Peers is the selected worker hostnames; empty or "auto" = every
// reachable same-vicinity mesh peer.
Peers []string `json:"peers,omitempty"`
// Role is "auto" (default; most-VRAM host leads), "leader", or "worker".
Role string `json:"role,omitempty"`
// NodeBytes is this node's model-memory budget (VRAM + usable RAM). The
// auto-trigger shards a local model larger than this; 0 → auto-trigger OFF
// (an operator opt-in — sharding can still be driven explicitly).
NodeBytes uint64 `json:"node_bytes,omitempty"`
// ServerBin / WorkerBin are this node's Prima binaries (llama-server for a
// leader, llama-cli for a worker). Empty → forming fails until deployed.
ServerBin string `json:"server_bin,omitempty"`
WorkerBin string `json:"worker_bin,omitempty"`
}
ShardConfig is the Ollama sharding sub-feature config (under Ollama).