config

package
v0.8.2 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package config provides configuration types and loading for shed.

Index

Constants

View Source
const (
	MaxFirecrackerCPUs            = 32
	MaxFirecrackerMemoryMB        = 256 * 1024 // 256 GB
	MaxFirecrackerDiskGB          = 1024       // 1 TB
	MaxVsockCID            uint32 = 65535
	MaxVsockPort           uint32 = 65535
	MinTimeout                    = 1 * time.Second
	MaxTimeout                    = 30 * time.Minute

	// Guest-MTU bounds, shared by the auto-detection clamp
	// (vmutil.ClampGuestMTU) and the guest_mtu override validation on both
	// backends. Floor is the IPv6 minimum link MTU (RFC 8200) — guaranteed
	// routable on any path; ceiling is the host vmnet/TAP MTU, above which a
	// guest packet would itself black-hole.
	MinGuestMTU = 1280
	MaxGuestMTU = 1500
)

Firecracker validation upper bounds.

View Source
const (
	MaxVZCPUs     = 32
	MaxVZMemoryMB = 256 * 1024 // 256 GB
	MaxVZDiskGB   = 1024       // 1 TB
)

VZ validation upper bounds (decoupled from Firecracker).

View Source
const (
	MinUpperSizeBytes int64 = 1 * 1024 * 1024 * 1024
	MaxUpperSizeBytes int64 = 100 * 1024 * 1024 * 1024
)

UpperSize bounds. The plan caps configurable upper size at 1G-100G; values outside this range almost always indicate a config typo and should fail-fast at validation time.

View Source
const (
	StatusRunning  = "running"
	StatusStopped  = "stopped"
	StatusStarting = "starting"
	StatusError    = "error"
)

Shed status constants.

View Source
const (
	// DefaultSessionName is the name used when no session is specified.
	DefaultSessionName = "default"

	// MaxSessionNameLength is the maximum allowed length for a session name.
	MaxSessionNameLength = 63
)

Session constants.

View Source
const (
	ErrShedNotFound       = "SHED_NOT_FOUND"
	ErrShedAlreadyExists  = "SHED_ALREADY_EXISTS"
	ErrShedAlreadyRunning = "SHED_ALREADY_RUNNING"
	ErrShedAlreadyStopped = "SHED_ALREADY_STOPPED"
	ErrShedNotStopped     = "SHED_NOT_STOPPED"
	ErrInvalidShedName    = "INVALID_SHED_NAME"
	ErrInvalidRepoURL     = "INVALID_REPO_URL"
	ErrCloneFailed        = "CLONE_FAILED"
	ErrBackendNotEnabled  = "BACKEND_NOT_ENABLED"
	ErrUnknownImage       = "UNKNOWN_IMAGE"
	ErrImageNotFound      = "IMAGE_NOT_FOUND"
	ErrImageInUse         = "IMAGE_IN_USE"
	ErrBackendError       = "BACKEND_ERROR"
	ErrInternalError      = "INTERNAL_ERROR"
	ErrSessionNotFound    = "SESSION_NOT_FOUND"
	ErrInvalidSessionName = "INVALID_SESSION_NAME"
	ErrTmuxNotAvailable   = "TMUX_NOT_AVAILABLE"
	ErrInvalidLocalDir    = "INVALID_LOCAL_DIR"
	ErrInvalidRequest     = "INVALID_REQUEST"

	ErrSnapshotNotFound        = "SNAPSHOT_NOT_FOUND"
	ErrSnapshotAlreadyExists   = "SNAPSHOT_ALREADY_EXISTS"
	ErrSnapshotSourceRunning   = "SNAPSHOT_SOURCE_RUNNING"
	ErrSnapshotBackendMismatch = "SNAPSHOT_BACKEND_MISMATCH"
	ErrInvalidSnapshotName     = "INVALID_SNAPSHOT_NAME"

	ErrProfileNotFound = "PROFILE_NOT_FOUND"
	ErrProfileReserved = "PROFILE_RESERVED" // name collides with a config/reserved profile
	ErrProfileInUse    = "PROFILE_IN_USE"   // referenced by one or more sheds
)

Error codes for API responses.

View Source
const (
	BackendFirecracker = "firecracker"
	BackendVZ          = "vz"
	BackendDetect      = "detect"
)

Backend type constants for Shed.Backend field.

View Source
const (
	SSHAuthOff     = "off"     // accept all keys (legacy default)
	SSHAuthWarn    = "warn"    // log would-deny attempts, but still accept
	SSHAuthEnforce = "enforce" // reject keys not in the allowlist
)

SSH auth modes for SSHAuthConfig.Mode.

View Source
const (
	AuthModeOpen  = "open"  // default: no enforcement (tailnet/LAN posture)
	AuthModeToken = "token" // SSH allowlist + HTTP tokens + TLS, all enforced
	// AuthModeMTLS: the client credential is a short-lived certificate issued
	// over the SSH bootstrap channel; no bearer tokens exist in this mode.
	// Shares every other token-mode invariant (SSH allowlist enforce, TLS-only,
	// https_port default) — see AuthEnforced in server.go.
	AuthModeMTLS = "mtls"
)

Auth modes for AuthConfig.Mode — the secure-by-default switch. "secure" is the pre-rename spelling of AuthModeToken, kept as a deprecated alias that config load normalizes to AuthModeToken (see normalizeAuthMode in server.go) — downstream code only ever observes AuthModeToken.

View Source
const DefaultFirecrackerImagesDir = "/var/lib/shed/firecracker/images"

DefaultFirecrackerImagesDir is the default directory for Firecracker rootfs images. This is a subdirectory of /var/lib/shed/firecracker/ to avoid mixing image files with the kernel, instance directories, and sockets that already live there.

View Source
const DefaultPullConcurrency = 3

DefaultVZImagesDir is the default directory for VZ rootfs images. DefaultPullConcurrency is the default number of image blobs downloaded in parallel per registry pull (Docker's default). Bounded to avoid tripping registry connection/rate limits.

View Source
const DefaultTokenTTL = 24 * time.Hour

DefaultTokenTTL is the lifetime of a bootstrap-minted HTTP token when auth.token_ttl is unset.

View Source
const DefaultUpperSize = "5G"

DefaultUpperSize is the fallback upper-layer size when the config omits upper_size_default. Plan-aligned at 5 GB sparse — a working guess for typical "build a project" workloads.

View Source
const DefaultVZImagesDir = "~/Library/Application Support/shed/vz"
View Source
const HomePath = "/home/shed"

HomePath is the shed user's home directory inside the VM. Repos, --local-dir, and --add-dir mounts all live under here, and interactive logins land here by default (or in a project subdirectory when one is present).

View Source
const MaxShedNameLength = 63

MaxShedNameLength is the maximum allowed length for a shed name.

View Source
const SnapshotSchemaVersion = 2

SnapshotSchemaVersion is the current snapshot schema version. Bumped from 1 to 2 with the introduction of LowerDigest tracking.

Variables

View Source
var (
	// ErrShedNotFoundSentinel is returned when a shed does not exist.
	ErrShedNotFoundSentinel = errors.New("shed not found")

	// ErrShedAlreadyExistsSentinel is returned when creating a shed that already exists.
	ErrShedAlreadyExistsSentinel = errors.New("shed already exists")

	// ErrShedAlreadyRunningSentinel is returned when starting a shed that is already running.
	ErrShedAlreadyRunningSentinel = errors.New("shed is already running")

	// ErrSessionNotFoundSentinel is returned when a tmux session does not exist.
	ErrSessionNotFoundSentinel = errors.New("session not found")

	// ErrTmuxNotAvailableSentinel is returned when tmux is not installed in the container.
	ErrTmuxNotAvailableSentinel = errors.New("tmux is not available in this container")

	// ErrShedNotRunningSentinel is returned when an operation requires a running shed.
	ErrShedNotRunningSentinel = errors.New("shed is not running")

	// ErrShedNotStoppedSentinel is returned when an operation requires a
	// stopped shed (e.g. shed reset, shed snapshot create).
	ErrShedNotStoppedSentinel = errors.New("shed must be stopped first")

	// ErrUnknownImageSentinel is returned when a requested image variant does not exist.
	ErrUnknownImageSentinel = errors.New("unknown image")

	// ErrImageNotFoundSentinel is returned when a cached image does not exist.
	ErrImageNotFoundSentinel = errors.New("image not found")

	// ErrImageInUseSentinel is returned when trying to delete an image referenced by config.
	ErrImageInUseSentinel = errors.New("image is referenced by config")

	// ErrNotSupportedSentinel is returned when an operation is not supported by a backend.
	ErrNotSupportedSentinel = errors.New("not supported by this backend")

	// ErrSnapshotNotFoundSentinel is returned when a snapshot does not exist.
	ErrSnapshotNotFoundSentinel = errors.New("snapshot not found")

	// ErrSnapshotAlreadyExistsSentinel is returned when creating a snapshot that already exists.
	ErrSnapshotAlreadyExistsSentinel = errors.New("snapshot already exists")

	// ErrSnapshotSourceRunningSentinel is returned when snapshotting a running shed.
	ErrSnapshotSourceRunningSentinel = errors.New("source shed is running; stop it before snapshotting")

	// ErrSnapshotBackendMismatchSentinel is returned when spawning a snapshot on the wrong backend.
	ErrSnapshotBackendMismatchSentinel = errors.New("snapshot backend does not match target")

	// ErrInvalidShedRequestSentinel is the catch-all sentinel for CreateShedRequest
	// field-level validation that maps to HTTP 400 INVALID_REQUEST. Add new
	// field-conflict cases (e.g., --from-snapshot combined with --image or --repo)
	// under this sentinel rather than minting per-conflict sentinels.
	ErrInvalidShedRequestSentinel = errors.New("invalid create-shed request")

	// ErrStopIncompleteSentinel is returned when StopShed asked the VMM to
	// terminate but the recorded PID is still alive after the stop
	// sequence (vm.Stop returned without error). Surfacing this prevents
	// the metadata from advertising status=stopped while a zombie VMM
	// still holds the workspace upper / vsock sockets.
	ErrStopIncompleteSentinel = errors.New("VMM did not exit after stop sequence")

	// ErrZombiePresentSentinel is returned when StartShed sees a non-zero
	// PID in metadata whose process is still alive AND looks like the
	// expected VMM binary (vfkit / firecracker). This protects against
	// silently spawning a second VMM under the same name when metadata
	// got out of sync (partial save / external tampering / server crash
	// between vm.Start and metadata.Save).
	ErrZombiePresentSentinel = errors.New("recorded VMM pid is still alive; refusing to spawn a second instance")
)

Sentinel errors for shed and session operations.

View Source
var ErrCredentialPairMismatch = creds.ErrPairMismatch

ErrCredentialPairMismatch reports a stored certificate and private key that do not belong together.

It is a RECOVERABLE state, not a corruption to report to the user: a crash between the two renames of a rotation leaves exactly this, and so does an interrupted restore. Callers treat it as "no credential" and re-enroll — see LoadClientCredentials.

Functions

func AddKnownHost

func AddKnownHost(host string, port int, hostKey string) error

AddKnownHost adds an SSH host key to the known_hosts file.

func ClientCredentialPaths added in v0.8.1

func ClientCredentialPaths(name string) (certPath, keyPath string)

ClientCredentialPaths returns the certificate and key paths for a named server, without touching the filesystem.

func CredentialMountTag

func CredentialMountTag(name string) string

CredentialMountTag returns the VirtioFS/9P mount tag for a credential share. Tags use the format "cred-{name}" to avoid collisions with project mounts.

func CredsStagingRoot added in v0.8.1

func CredsStagingRoot() string

CredsStagingRoot returns the directory holding not-yet-adopted credential material (~/.shed/creds/%staging). Exported so a test can make a staging write fail without reaching into this package's internals.

func EnsureConfigDir

func EnsureConfigDir() error

EnsureConfigDir ensures the config directory exists.

func ExpandPath

func ExpandPath(path string) string

ExpandPath expands ~ to the user's home directory.

func ExpandRepoShorthand

func ExpandRepoShorthand(repo string) string

ExpandRepoShorthand expands owner/repo shorthand to a full git SSH URL. Full URLs (with scheme or git@ prefix) pass through unchanged. Empty strings pass through unchanged.

func GetClientConfigDir

func GetClientConfigDir() string

GetClientConfigDir returns the path to the shed config directory.

func GetClientConfigPath

func GetClientConfigPath() string

GetClientConfigPath returns the path to the client config file.

func GetCredsDir added in v0.8.1

func GetCredsDir() string

GetCredsDir returns the root of the client-credential store (~/.shed/creds).

func GetKnownHostsPath

func GetKnownHostsPath() string

GetKnownHostsPath returns the path to the known_hosts file.

func GetSyncConfigPath

func GetSyncConfigPath() string

GetSyncConfigPath returns the path to the sync config file.

func GetTunnelLogDir added in v0.7.6

func GetTunnelLogDir() string

GetTunnelLogDir returns the directory holding background tunnel daemon logs.

func GetTunnelLogPath added in v0.7.6

func GetTunnelLogPath(shedName string) string

GetTunnelLogPath returns the log path for a shed's background tunnel daemon. shedName is escaped so a name containing path separators can't traverse out of the log directory (the daemon opens this path with O_TRUNC).

func GetTunnelStatePath

func GetTunnelStatePath() string

GetTunnelStatePath returns the path to the tunnel state file.

func GetTunnelsConfigPath

func GetTunnelsConfigPath() string

GetTunnelsConfigPath returns the path to the tunnels config file.

func HasWritableHostMount added in v0.7.7

func HasWritableHostMount(projectMounts []MountConfig, serverCfg *ServerConfig) bool

HasWritableHostMount reports whether a shed with the given project mounts (--local-dir/--add-dir), running under serverCfg, has any writable host-backed directory. A destroy/delete SIGKILLs the guest without a graceful shutdown; if any such mount exists the guest must be synced first, or unsynced writes to that host data are lost. (The discarded upper needs no sync — only host-backed mounts, which outlive the shed, do.) serverCfg may be nil.

func IsReservedEgressName added in v0.7.5

func IsReservedEgressName(name string) bool

IsReservedEgressName reports whether name is reserved (cannot be a user/config profile name): "off", "none", "default" (case-insensitive).

func IsSSHRepoURL

func IsSSHRepoURL(repo string) bool

IsSSHRepoURL reports whether a repo URL uses SSH transport. Matches both `git@host:path` (SCP-like) and `ssh://...` schemes. HTTPS, git://, http://, and empty strings return false.

func KnownHostLine added in v0.8.1

func KnownHostLine(host string, port int, hostKey string) string

KnownHostLine renders the exact ~/.shed/known_hosts line for an endpoint, in OpenSSH's syntax: a bare hostname on port 22, the bracketed "[host]:port" form otherwise.

It is exported because it is also the IDENTITY of a line: RemoveKnownHost matches on it, and `shed server add` keeps it around so a failed add can undo exactly the line it wrote and nothing else.

func LegacyWireAuthMode added in v0.8.1

func LegacyWireAuthMode(mode string) string

LegacyWireAuthMode maps the canonical "token" spelling back to the legacy "secure" for wire surfaces that released pre-rename clients consume.

The one such surface is /api/info's auth_mode field: an old client decides whether to bootstrap a credential at all by checking the exact string "secure" (cmd/shed/server.go:352 at v0.8.0). Reporting "token" there makes the old client skip the bootstrap and save an entry with no credential, which then 401s on every command — so token mode keeps the legacy spelling on that wire indefinitely, and clients normalize on decode (NormalizeAuthMode). "open" and "mtls" pass through: "open" predates the rename unchanged, and mtls-mode /api/info is client-certificate-gated, so no pre-rename client can ever read it.

func LoadClientCredentials added in v0.8.1

func LoadClientCredentials(name, certPath, keyPath string) (*tls.Certificate, error)

LoadClientCredentials reads a server's stored certificate + key under that server's credential lock and assembles them for the TLS stack.

It returns (nil, err) for EVERY unusable state — absent files, unreadable files, malformed PEM, and a certificate that does not match the key — because all of them mean the same thing to the caller: there is no credential to present, so re-enroll.

name identifies the lock, not the paths: the paths come from the config entry (which the user may have edited) while the lock is keyed on the entry's name, matching what WriteClientCredentials holds. An empty name skips the lock.

func LockServerCredentials added in v0.8.1

func LockServerCredentials(name string) (func(), error)

LockServerCredentials takes the exclusive advisory lock guarding one server's credential pair and returns the release function.

It is exported so a caller can hold that lock across a SEQUENCE the store cannot see as one operation — specifically, the CLI's credential persists, which have to update config.yaml and the credential files together. Writing the files and updating the config are two separately-locked commits, and a token↔mtls flip racing an mtls enrollment can interleave them into "config names a certificate that the other persist just deleted". Holding this lock across both halves is what makes that pair atomic.

LOCK ORDERING (the whole invariant, in one place): this lock is taken BEFORE the client-config file lock, never after, and no path takes it while holding the config lock. Update's mutation closure must therefore never call anything in this file — Load/Write/Remove/Stage/Commit all sit under this lock.

It is NOT re-entrant: flock keys on the open file description, so calling LoadClientCredentials or WriteClientCredentials (which take the same lock internally) while holding this one deadlocks the process against itself. Callers hold it around Remove — which does not lock — and around config updates.

func MatchesExcludePatterns

func MatchesExcludePatterns(relPath string, patterns []string) bool

MatchesExcludePatterns reports whether relPath matches any of the given glob patterns. Patterns like "dir/*" also match the directory itself and deeply nested paths (e.g., "dir/sub/deep/file").

func NormalizeAuthMode added in v0.8.1

func NormalizeAuthMode(mode string) string

NormalizeAuthMode maps the deprecated "secure" auth.mode spelling to the canonical "token"; every other value passes through unchanged. This is the client-boundary half of the wire-compat contract (see LegacyWireAuthMode): any code that decodes an auth mode string off the wire — /api/info from a released pre-rename server (which reports "secure"), or from a current server in token mode (which deliberately still reports "secure") — must normalize before comparing against the AuthMode* constants.

func ParseUpperSize

func ParseUpperSize(s string) (int64, error)

ParseUpperSize accepts a human-friendly suffix (G, M, or bare bytes) and returns the size in bytes. Validates the range against MinUpperSizeBytes/MaxUpperSizeBytes.

Pre-checks that n*mul won't overflow int64 — otherwise a value like "10000000000G" would silently wrap to a tiny positive number and slip past the range bounds.

func ProjectAddDirTargets added in v0.6.6

func ProjectAddDirTargets(mounts []MountConfig, landingDir string) []string

ProjectAddDirTargets returns the guest target paths of a shed's --add-dir mounts: every project mount except the --local-dir mount the shed lands in (identified by landingDir). Order is preserved. Returns nil when there are no add-dir mounts (a bare, --repo, or --local-dir-only shed). Used to expose SHED_ADD_DIRS to sessions and provisioning hooks.

func ProjectMountBasename added in v0.6.4

func ProjectMountBasename(hostDir string) (string, error)

ProjectMountBasename validates a host directory destined to be mounted under the shed user's home directory and returns the guest-directory basename it will be mounted at (HomePath/<basename>).

Dotfile-style names are rejected because project mounts become siblings of the home directory's own infrastructure (~/.ssh, ~/.config, ~/.local, ...), all of which are dot-prefixed; refusing leading-dot basenames prevents a mount from shadowing them.

func ProjectMountSources added in v0.6.4

func ProjectMountSources(mounts []MountConfig) []string

ProjectMountSources returns the host source directories of the given mounts.

func ProjectMountTag added in v0.6.4

func ProjectMountTag(basename string) string

ProjectMountTag returns a stable, unique, virtio-fs-safe mount tag for a project mount (--local-dir / --add-dir) identified by its guest-dir basename. The tag is "proj-<sanitized>-<hash>": the hash keeps tags distinct even when two different basenames sanitize to the same prefix, and the whole tag is capped at maxMountTagLen.

func ProjectMountTagForTarget added in v0.6.4

func ProjectMountTagForTarget(target string) string

ProjectMountTagForTarget returns the VirtioFS/9P mount tag for a project mount given its guest target path (HomePath/<basename>). Guest paths always use '/'.

func RemoveKnownHost added in v0.8.1

func RemoveKnownHost(host string, port int, hostKey string) error

RemoveKnownHost deletes ONE line from ~/.shed/known_hosts: the exact line AddKnownHost would have written for this endpoint and key.

It exists so a `shed server add` that pins a host key and then fails — an unauthorized SSH key, a verification that does not answer, a duplicate name — can leave the file exactly as it found it. Pinning a key for a server that was never added is quiet residue: it silently pre-trusts that endpoint for the next attempt, which is precisely the decision the operator was in the middle of making.

The match is on the whole rendered line, so an entry the user (or an earlier successful add) put there is never touched — not even for the same host with a different key, and not a "@cert-authority"/"@revoked" marked line, whose leading token makes it a different line. Only the LAST occurrence is removed: the line this call is undoing is the one most recently appended.

A missing file, or a file with no such line, is not an error — the caller is undoing something it may never have done.

func RemoveServerCredentials added in v0.8.1

func RemoveServerCredentials(name string) error

RemoveServerCredentials deletes a server's credential directory. It is called by `shed server rm`: leaving a private key behind for a server the user has explicitly forgotten is exactly the kind of quiet residue that turns up years later. A missing directory is not an error.

func RepoDirName added in v0.6.4

func RepoDirName(repo string) (string, error)

RepoDirName returns the directory name `git clone <repo>` would create: the last path segment of the URL with a trailing ".git" removed. It mirrors git's own default-directory logic for the URL forms shed accepts (https://, git://, ssh://, git@host:path, and expanded owner/repo shorthand).

func ResolveBackend

func ResolveBackend(backend, goos, goarch string) (string, error)

ResolveBackend resolves a backend string to a concrete backend type. When backend is "detect", it selects based on the platform: darwin/arm64 → vz, linux → firecracker.

func SanitizeRepoURL

func SanitizeRepoURL(repo string) string

SanitizeRepoURL returns repo with the password component removed from the URL's userinfo, if any. The username is preserved (it's informational, not a secret). SSH-form URLs (git@host:path) and shorthand pass through unchanged. Use this when logging URLs to avoid leaking embedded credentials from forms like `https://user:password@host/repo.git`.

func ServerCredsDir added in v0.8.1

func ServerCredsDir(name string) string

ServerCredsDir returns the credential directory for a named server entry.

func ValidGitHubUsername added in v0.7.0

func ValidGitHubUsername(s string) bool

ValidGitHubUsername reports whether s is a syntactically valid GitHub username (and, by construction, safe to interpolate into the .keys URL).

func ValidateGitRepoURL

func ValidateGitRepoURL(repoURL string) error

ValidateGitRepoURL validates that a git repository URL is well-formed. Accepts https://, git://, ssh://, and git@host:path formats.

func ValidateMountDir added in v0.6.4

func ValidateMountDir(path string) error

ValidateMountDir checks that a project-mount host directory is usable: an absolute path, comma-free (commas break vfkit VirtioFS device arguments), existing, and a directory. The returned errors are phrased as predicates ("must be an absolute path", "does not exist", ...) so callers can prefix them with the relevant flag/field name. Shared by the CLI (cmd/shed) and the API handler so both validate identically.

func ValidateSessionName

func ValidateSessionName(name string) error

ValidateSessionName validates that a session name is valid. Names must be alphanumeric with underscores and hyphens allowed, must start with an alphanumeric character, and must be at most 63 characters.

func ValidateShedName

func ValidateShedName(name string) error

ValidateShedName validates that a shed name is valid. Names must be lowercase alphanumeric with hyphens allowed (not at start/end), must start with a letter, and must be at most 63 characters.

func ValidateSnapshotName

func ValidateSnapshotName(name string) error

ValidateSnapshotName validates that a snapshot name is valid. Snapshot names follow the same rules as shed names.

func WriteClientCredentials added in v0.8.1

func WriteClientCredentials(name string, certPEM, keyPEM []byte) (certPath, keyPath string, err error)

WriteClientCredentials persists a freshly issued client certificate and its private key for the named server, returning the paths written.

Types

type APIError

type APIError struct {
	Error APIErrorDetail `json:"error"`
}

APIError represents an error response from the API.

func NewAPIError

func NewAPIError(code, message string) APIError

NewAPIError creates a new APIError with the given code and message.

type APIErrorDetail

type APIErrorDetail struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

APIErrorDetail contains the error code and message.

type AuthConfig added in v0.7.0

type AuthConfig struct {
	// Mode is open | token | mtls (default open; the deprecated "secure"
	// spelling is normalized to "token" at config load, with one startup
	// deprecation warning). token derives: SSH allowlist enforce, HTTP
	// bearer-token enforce, and TLS on (the server serves the TLS listener
	// only). mtls derives the same SSH-allowlist-enforce and TLS-only posture,
	// but the client credential is a short-lived certificate rather than a
	// bearer token. Both require at least one SSH key source.
	Mode string `yaml:"mode,omitempty"`
	// TokenTTL is the lifetime of a bootstrap-minted HTTP token (default 24h).
	TokenTTL Duration `yaml:"token_ttl,omitempty"`
	// SSH configures the SSH public-key allowlist (key sources + advanced mode).
	SSH *SSHAuthConfig `yaml:"ssh,omitempty"`
}

AuthConfig configures authentication. The headline control is Mode (the open|token|mtls switch). The SSH sub-block carries key sources and the advanced SSH mode override. HTTP bearer-token enforcement is derived purely from token mode — there is no HTTP sub-block.

type ClientConfig

type ClientConfig struct {
	Servers       map[string]ServerEntry `yaml:"servers"`
	DefaultServer string                 `yaml:"default_server"`
	Sheds         map[string]ShedCache   `yaml:"sheds"`
	CreateTimeout time.Duration          `yaml:"create_timeout,omitempty"`

	// Machines is an OPAQUE passthrough of the `machines:` section owned by the
	// Rust porcelain (sx) — remote-machine targets for rc-session kickoff. Go
	// neither reads nor validates it (the schema is defined Rust-side); the field
	// exists ONLY so SaveToPath's whole-document rewrite round-trips the subtree
	// instead of silently deleting user data on the next `shed` command that
	// updates this file (cache refresh, `shed server add`, token mint…).
	Machines yaml.Node `yaml:"machines,omitempty"`
	// contains filtered or unexported fields
}

ClientConfig represents the CLI-side configuration.

func LoadClientConfig

func LoadClientConfig() (*ClientConfig, error)

LoadClientConfig loads the client configuration from the default location.

func LoadClientConfigFromPath

func LoadClientConfigFromPath(path string) (*ClientConfig, error)

LoadClientConfigFromPath loads client configuration from a specific path.

func (*ClientConfig) AddServer

func (c *ClientConfig) AddServer(name string, entry ServerEntry) error

AddServer adds a new server to the configuration.

Like CacheShed, it is NOT safe as an Update mutation: it stamps AddedAt with time.Now() (two applications, two instants) and it can FAIL — and an Update mutation that fails on the second application fails after the file has already been written, turning a successful add into a reported error. Callers under Update validate first and then assign a fully-formed entry; see cmd/shed/server_add.go:saveAddedServer.

func (*ClientConfig) CacheShed

func (c *ClientConfig) CacheShed(name string, server string, status string)

CacheShed caches a shed's location, stamped now.

It is NOT safe inside an Update mutation: Update runs the mutation twice (the fresh on-disk snapshot, then the caller's in-memory one) and the two calls would stamp two different instants, so the file and the running process would hold rows that differ in a field neither ever meant to disagree on. Use CacheShedAt with a timestamp taken once, outside the mutation.

func (*ClientConfig) CacheShedAt added in v0.8.1

func (c *ClientConfig) CacheShedAt(name string, server string, status string, at time.Time)

CacheShedAt is CacheShed with the timestamp supplied by the caller, which is what makes a cache write a deterministic function of its inputs and therefore usable as an Update mutation.

func (*ClientConfig) GetCreateTimeout

func (c *ClientConfig) GetCreateTimeout() time.Duration

GetCreateTimeout returns the configured create timeout or the default (10 minutes). One per-manifest mkfs.erofs over a merged tree is single-digit seconds even on cold cache; only the first-pull from the registry adds notable wallclock, and 10 min is enough headroom even for a slow network on a multi-GB image. Cache hits on subsequent creates are sub-second.

func (*ClientConfig) GetDefaultServer

func (c *ClientConfig) GetDefaultServer() (*ServerEntry, string, error)

GetDefaultServer returns the default server entry.

func (*ClientConfig) GetServer

func (c *ClientConfig) GetServer(name string) (*ServerEntry, error)

GetServer returns a server by name.

func (*ClientConfig) GetShedServer

func (c *ClientConfig) GetShedServer(name string) (string, error)

GetShedServer returns the server that hosts a shed.

func (*ClientConfig) RemoveServer

func (c *ClientConfig) RemoveServer(name string) error

RemoveServer removes a server from the configuration.

func (*ClientConfig) RemoveShedCache

func (c *ClientConfig) RemoveShedCache(name string)

RemoveShedCache removes a shed from the cache.

func (*ClientConfig) Save

func (c *ClientConfig) Save() error

Save writes the configuration to disk.

It is the UNSYNCHRONIZED writer: it renames this whole snapshot over the file, discarding anything another process committed since this one loaded. Reach for Update instead for any mutation the CLI performs; Save survives for the first-write paths that own the file outright and for tests.

func (*ClientConfig) SaveToPath

func (c *ClientConfig) SaveToPath(path string) error

SaveToPath writes the configuration to a specific path.

func (*ClientConfig) SetDefaultServer

func (c *ClientConfig) SetDefaultServer(name string) error

SetDefaultServer sets the default server.

func (*ClientConfig) Update added in v0.8.1

func (c *ClientConfig) Update(mutate func(*ClientConfig)) error

Update applies mutate to the client configuration as a locked read-modify-write against what is CURRENTLY on disk, and is the only supported way to change a config that other processes may also be changing.

The problem it solves is that SaveToPath writes the whole document: a `shed list` that loaded the config a minute ago and then refreshes its shed cache renames its entire stale snapshot over whatever a concurrent `shed server add` — or a credential re-mint — just committed. Last writer wins, and what it wins with is everything, including the fields it never touched.

mutate cannot fail, by type. That is the whole reason this signature is not `func(*ClientConfig) error`: the mutation is applied TWICE (see UpdateChecked), with the file write in between, so a mutation that could fail on its second application would report an error for a change already durably committed. Anything that needs to say no is a precondition, and preconditions belong in UpdateChecked's check — which runs once, on the fresh snapshot, before anything is written.

func (*ClientConfig) UpdateChecked added in v0.8.1

func (c *ClientConfig) UpdateChecked(check func(*ClientConfig) error, mutate func(*ClientConfig)) error

UpdateChecked is Update with a precondition evaluated under the lock, against the config as it is on disk at that instant.

It exists because "validate, then update" is not a safe shape when another process may be writing the same file: a check performed before the lock is taken reads a snapshot that can be stale by the time the write lands — two concurrent `shed server add`s for one name both see "not taken", and both succeed. The check has to happen inside the locked section, against the fresh snapshot, and it has to happen exactly once.

The sequence is:

  1. take updateMu (in-process) and then the config file lock (cross-process, blocking; ORDER: the creds lock, when a caller needs both, is taken by that caller BEFORE this — see cmd/shed/client.go — and nothing here ever takes a creds lock);
  2. load a FRESH snapshot from the receiver's path (a missing file is the empty config, exactly as LoadClientConfigFromPath treats it);
  3. run check on that snapshot; a non-nil error aborts with NOTHING written and the receiver untouched;
  4. run mutate on the snapshot and save it;
  5. run the SAME mutate on the receiver.

Step 5 is a re-application rather than a wholesale replacement on purpose: concurrent readers of the in-memory config hold live map values, and swapping the maps out from under them would make an unrelated entry momentarily vanish. It also means mutate must be a deterministic function of the config it is handed — one that stamps its own time.Now() leaves the file and the running process holding rows that differ (which is why CacheShedAt exists beside CacheShed, and why AddServer must not be called from here).

check is evaluated ONLY on the fresh snapshot. The receiver is this process's view of that same file, so re-checking it would either be redundant or, in the one case where the two disagree, would fail after the write committed.

type CreateShedRequest

type CreateShedRequest struct {
	Name        string `json:"name"`
	Repo        string `json:"repo,omitempty"`
	Image       string `json:"image,omitempty"`
	NoProvision bool   `json:"no_provision,omitempty"`

	// Backend specifies which backend to use ("firecracker" or "vz").
	// If empty, uses the server's configured backend.
	Backend string `json:"backend,omitempty"`

	// CPUs specifies the number of vCPUs (firecracker/vz only)
	CPUs int `json:"cpus,omitempty"`

	// MemoryMB specifies the memory in MB (firecracker/vz only)
	MemoryMB int `json:"memory_mb,omitempty"`

	// LocalDir mounts a host directory under the home directory (at
	// /home/shed/<basename>) and makes it the landing directory.
	// Mutually exclusive with Repo.
	LocalDir string `json:"local_dir,omitempty"`

	// AddDirs mounts additional host directories under the home directory
	// (each at /home/shed/<basename>) as reference siblings of LocalDir.
	// Only valid together with LocalDir.
	AddDirs []string `json:"add_dirs,omitempty"`

	// Egress assigns Level-1 egress-control profiles to this shed (composed,
	// first-match). Empty inherits the server `egress.default`; ["off"]
	// disables egress for this shed even when a default is set.
	Egress []string `json:"egress,omitempty"`

	// FromSnapshot spawns the shed from a snapshot's rootfs instead of a base image.
	// Mutually exclusive with Image and Repo. Provisioning steps (repo clone, install
	// hook, first-time auto-sync) are skipped because the snapshot is already provisioned.
	FromSnapshot string `json:"from_snapshot,omitempty"`

	// UpperSizeBytes is the logical size of the per-shed writable upper.
	// Zero falls back to the backend's upper_size_default config value.
	UpperSizeBytes int64 `json:"upper_size_bytes,omitempty"`
}

CreateShedRequest is the request body for POST /api/sheds.

type DiskSize

type DiskSize struct {
	LogicalBytes  int64 `json:"logical_bytes"`
	PhysicalBytes int64 `json:"physical_bytes"`
}

DiskSize captures both apparent (logical) and allocated (physical) bytes. PhysicalBytes comes from stat.Blocks * 512. On APFS and other reflink-capable filesystems, a file's st_blocks counts cloned-but-unmodified extents against every referencing file, so summing PhysicalBytes across files that share extents (via clonefile, FICLONE, or hardlinks) overcounts the actual on-disk usage. This is accepted for v1 and surfaced in DiskUsage.Notes.

type DiskUsage

type DiskUsage struct {
	ServerName  string    `json:"server_name"`
	Backend     string    `json:"backend"` // "vz" | "firecracker" | "none"
	GeneratedAt time.Time `json:"generated_at"`

	Images []ImageDiskEntry `json:"images"`
	Kernel *FileEntry       `json:"kernel,omitempty"`
	Initrd *FileEntry       `json:"initrd,omitempty"` // VZ only

	Sheds     []ShedDiskEntry     `json:"sheds"`
	Snapshots []SnapshotDiskEntry `json:"snapshots,omitempty"`
	Orphans   []FileEntry         `json:"orphans"`

	Totals DiskUsageTotals `json:"totals"`

	// Notes carries advisory caveats (APFS overcount, hardlink double-count, etc.).
	Notes []string `json:"notes,omitempty"`
}

DiskUsage is the payload returned by GET /api/system/df.

type DiskUsageOrError

type DiskUsageOrError struct {
	ServerName string     `json:"server_name"`
	Usage      *DiskUsage `json:"usage,omitempty"`
	Error      string     `json:"error,omitempty"`
}

DiskUsageOrError is one entry in a multi-server SystemDFResponse. Exactly one of Usage or Error is populated.

type DiskUsageTotals

type DiskUsageTotals struct {
	Images    DiskSize `json:"images"` // includes kernel + initrd
	Sheds     DiskSize `json:"sheds"`
	Snapshots DiskSize `json:"snapshots"`
	Orphans   DiskSize `json:"orphans"`
	All       DiskSize `json:"all"`
}

DiskUsageTotals aggregates bytes across df sections.

type Duration

type Duration time.Duration

Duration is a wrapper around time.Duration for YAML marshaling

func (Duration) Duration

func (d Duration) Duration() time.Duration

Duration returns the time.Duration value

func (Duration) MarshalYAML

func (d Duration) MarshalYAML() (interface{}, error)

MarshalYAML implements yaml.Marshaler for Duration

func (*Duration) UnmarshalYAML

func (d *Duration) UnmarshalYAML(unmarshal func(interface{}) error) error

UnmarshalYAML implements yaml.Unmarshaler for Duration

type EgressConfig added in v0.7.1

type EgressConfig struct {
	// Enabled is the master switch. When false the proxy child is never started
	// and no guest injection happens.
	Enabled bool `yaml:"enabled"`

	// PortRange is the inclusive "lo-hi" range for per-shed listener ports.
	PortRange string `yaml:"port_range,omitempty"`

	// Default is the profile list applied to sheds created without --egress.
	// ABSENT or [] means no egress (NOT [audit]); a list applies those profiles.
	Default []string `yaml:"default,omitempty"`

	// Profiles are reusable named policy fragments. Names "off", "none", and
	// "default" are reserved.
	Profiles map[string]EgressProfile `yaml:"profiles,omitempty"`
}

EgressConfig is the server-level Level-1 (audit-first, cooperative) egress policy. Off by default — the common case is unrestricted. See docs/reference/egress.md. The egress proxy is a child process of shed-server that is launched only when Enabled.

func (*EgressConfig) PortRangeBounds added in v0.7.1

func (c *EgressConfig) PortRangeBounds() (int, int)

PortRangeBounds returns the configured [lo, hi] listener-port range, or a sensible default when unset.

func (*EgressConfig) ResolveProfiles added in v0.7.1

func (c *EgressConfig) ResolveProfiles(req []string, user map[string]EgressProfile) ([]egress.ProfileSpec, error)

ResolveProfiles returns the composed profile specs for a shed's requested profile list (empty inherits Default), resolving names from the config profiles MERGED with the caller-supplied runtime user profiles. It returns (nil, nil) when egress is disabled or the effective list is empty / "off" / "none" — meaning this shed gets no egress proxy at all.

user is the UserProfileStore snapshot (pass store.List(), nil-safe); a nil/empty map makes resolution byte-identical to the config-only behavior.

func (*EgressConfig) Validate added in v0.7.1

func (c *EgressConfig) Validate() error

Validate fails fast at config load on a bad port range, a reserved/dangling profile name, or an uncompilable glob/CEL rule.

type EgressProfile added in v0.7.1

type EgressProfile struct {
	Mode  string   `json:"mode,omitempty" yaml:"mode,omitempty"`
	Allow []string `json:"allow,omitempty" yaml:"allow,omitempty"`
	Deny  []string `json:"deny,omitempty" yaml:"deny,omitempty"`
	Rule  string   `json:"rule,omitempty" yaml:"rule,omitempty"`
}

EgressProfile is one named policy fragment. Allow/Deny are domain globs ("*.github.com" suffix, "github.com" exact); Rule is a CEL expression (the power path); Mode "audit" makes the fall-through allow+log instead of deny.

The json tags are the wire contract for the `rules` map in `shed egress show --json` (GET /api/egress/{name}). They are snake_case to match the rest of the API (egress.AuditRecord, EgressStatus); omitempty keeps the output to the fields a profile actually sets.

type EgressProfileInfo added in v0.7.5

type EgressProfileInfo struct {
	Name    string        `json:"name"`
	Source  string        `json:"source"` // "config" | "user"
	Profile EgressProfile `json:"profile"`
}

EgressProfileInfo is one entry in the `GET /api/egress/profiles` response: a named profile plus whether it comes from the server config (read-only baseline) or the runtime user store.

type EgressSetRequest added in v0.7.1

type EgressSetRequest struct {
	Profiles []string `json:"profiles"`
}

EgressSetRequest is the `POST /api/egress/{name}` body (live `shed egress set`).

type EgressStatus added in v0.7.1

type EgressStatus struct {
	Shed     string                   `json:"shed"`
	Enabled  bool                     `json:"enabled"`            // server-level egress master switch
	Profiles []string                 `json:"profiles,omitempty"` // effective profiles for this shed
	Port     int                      `json:"port,omitempty"`     // assigned listener port
	Rules    map[string]EgressProfile `json:"rules,omitempty"`    // definitions of the active profiles
	Recent   []egress.AuditRecord     `json:"recent,omitempty"`   // recent egress decisions for this shed
}

EgressStatus is the `shed egress show` response: a shed's active egress assignment, the resolved definitions of its profiles, and recent decisions.

type ExtensionHealthInfo

type ExtensionHealthInfo struct {
	Guest string `json:"guest"` // "running", "stopped", "failed"
	Host  string `json:"host"`  // "connected", "unreachable", "unknown"
}

ExtensionHealthInfo is the API-facing extension health for a shed.

type ExtensionsConfig

type ExtensionsConfig struct {
	// Enabled lists the extension namespaces to activate in VMs
	// (e.g., ["ssh-agent", "aws-credentials"]).
	Enabled []string `yaml:"enabled"`
}

ExtensionsConfig configures which extensions the agent should enable.

func (*ExtensionsConfig) Validate

func (e *ExtensionsConfig) Validate() error

Validate checks that all extension namespaces are valid and unique.

type FileEntry

type FileEntry struct {
	Path string   `json:"path"`
	Size DiskSize `json:"size"`
	// Kind is one of: "rootfs" | "console_log" | "kernel" | "initrd" |
	// "lock" | "tmp" | "source" | "metadata" | "snapshot_orphan".
	Kind string `json:"kind,omitempty"`
}

FileEntry describes a single file with its size and classification.

type FirecrackerConfig

type FirecrackerConfig struct {
	// KernelPath is the path to the Linux kernel image
	KernelPath string `yaml:"kernel_path"`

	// DefaultImage is the Docker ref (or local rootfs path) used for new
	// sheds when no --image is given. Docker refs are resolved by their
	// io.shed.source-ref identity and pulled per PullPolicy on first use.
	DefaultImage string `yaml:"default_image"`

	// ImageAliases maps short alias names to Docker refs (or paths) for
	// convenience with: shed create mydev --image <alias>. Aliases resolve
	// to the underlying ref; image listings always show the resolved ref.
	ImageAliases map[string]string `yaml:"image_aliases,omitempty"`

	// PullPolicy controls cache-vs-pull at create: "missing" (default —
	// use the cached ref, pull only if absent), "always" (always pull),
	// or "never" (error if not cached). Ignored for local-path images.
	PullPolicy string `yaml:"pull_policy,omitempty"`

	// PullConcurrency caps how many image blobs (layers + kernel/initrd/
	// erofs) download in parallel during a registry pull. Defaults to
	// DefaultPullConcurrency; must be >= 1 (1 == serial).
	PullConcurrency int `yaml:"pull_concurrency,omitempty"`

	// ImagesDir is the directory for the content-addressed image store.
	ImagesDir string `yaml:"images_dir,omitempty"`

	// InstanceDir is the directory for instance data
	InstanceDir string `yaml:"instance_dir"`

	// SnapshotsDir is the directory where shed snapshots are stored.
	SnapshotsDir string `yaml:"snapshots_dir,omitempty"`

	// UppersDir is the directory where per-shed writable upper layers
	// (sparse ext4 files) are stored.
	UppersDir string `yaml:"uppers_dir,omitempty"`

	// UpperSizeDefault is the default logical size of the per-shed
	// writable upper. Accepted units: G (GB) and M (MB). Range 1G-100G.
	UpperSizeDefault string `yaml:"upper_size_default,omitempty"`

	// SocketDir is the directory for Firecracker API sockets
	SocketDir string `yaml:"socket_dir"`

	// DefaultCPUs is the default number of vCPUs for new VMs
	DefaultCPUs int `yaml:"default_cpus"`

	// DefaultMemoryMB is the default memory in MB for new VMs
	DefaultMemoryMB int `yaml:"default_memory_mb"`

	// DefaultDiskGB is the default disk size in GB for new VMs
	DefaultDiskGB int `yaml:"default_disk_gb"`

	// VsockBaseCID is the starting CID for vsock allocation
	VsockBaseCID uint32 `yaml:"vsock_base_cid"`

	// ConsolePort is the vsock port for console/exec connections
	ConsolePort uint32 `yaml:"console_port"`

	// NotifyPort is the vsock port for the message channel (health checks, plugins, credentials)
	NotifyPort uint32 `yaml:"notify_port"`

	// TCPProxyPort is the vsock port for the TCP proxy (used by DialService to
	// reach VM services). Must match the guest agent's flagless default (1028);
	// the systemd unit starts shed-agent without a --tcp-proxy-port override.
	TCPProxyPort uint32 `yaml:"tcp_proxy_port"`

	// StartTimeout is the timeout for VM startup
	StartTimeout Duration `yaml:"start_timeout"`

	// StopTimeout is the timeout for graceful VM shutdown
	StopTimeout Duration `yaml:"stop_timeout"`

	// GuestMTU, when non-zero, forces the guest's primary interface MTU
	// instead of auto-detecting the host's egress path MTU at VM start.
	// 0 (the default) means auto-detect: behind a reduced-MTU path (e.g. a
	// VPN/overlay) the guest is lowered to match; otherwise it stays at
	// 1500. Set this only to pin a value when detection misses. Validated to
	// [MinGuestMTU, MaxGuestMTU] when non-zero.
	GuestMTU int `yaml:"guest_mtu,omitempty"`

	// BridgeName is the name of the Linux bridge for VM networking
	BridgeName string `yaml:"bridge_name"`

	// BridgeCIDR is the CIDR for the bridge network (e.g., "172.30.0.1/24")
	BridgeCIDR string `yaml:"bridge_cidr"`

	// TAPPrefix is the prefix for TAP device names
	TAPPrefix string `yaml:"tap_prefix"`
}

FirecrackerConfig contains Firecracker-specific configuration.

func DefaultFirecrackerConfig

func DefaultFirecrackerConfig() *FirecrackerConfig

DefaultFirecrackerConfig returns a FirecrackerConfig with default values.

Cross-backend alignment (DO NOT drift between this and DefaultVZConfig without an explicit reason — see DefaultVZConfig's header comment for the rationale on each field):

  • DefaultCPUs / DefaultMemoryMB / DefaultDiskGB: same physical resource shape per shed on both backends.
  • ConsolePort (1024) / NotifyPort (1026) / TCPProxyPort (1028): the shared agent vsock contract. TCPProxyPort is aligned across backends as of the hub-parity change — FC's DialService routes through the guest agent's TCP proxy on this vsock port exactly like VZ (previously FC dialed the bridge IP directly and could not reach loopback-bound guest services such as the rc hub).
  • StopTimeout (10 s): same in-guest stop sequence.

Intentionally divergent from VZ:

  • StartTimeout (30 s vs VZ 60 s): FC's create wall time is around 3.7 s baseline (mini3 measurements in §11 of the runtime-opt doc); ~8× headroom. FC's in-guest mkfs.ext4 is ~0.18 s vs VZ's ~4.2 s on vfkit's slower virtio-blk write path — the historical reason VZ's StartTimeout is bigger does not apply here.
  • SocketDir under /var/run/shed/firecracker (not a $HOME path) because Firecracker hosts run shed-server as root via systemd (see packaging/shed-server.service); the VZ equivalent runs in $HOME because macOS users typically run via homebrew.
  • VsockBaseCID (100): FC needs an explicit CID per VM (vsock CIDs are integers ≥ 3); 100 leaves room for hand-assigned CIDs below it. VZ assigns CIDs through Apple's Virtualization framework, which uses its own scheme, so the field doesn't apply there.
  • BridgeName / BridgeCIDR / TAPPrefix: FC uses a Linux bridge + TAP devices for its NAT-style network; VZ uses Apple's built-in vmnet shared/NAT network, which has no analogous tunables.

(KernelPath and BaseRootfs left empty by default — Phase A retired the flat-file layout, Phase B made the in-blob kernel canonical. Operators who want the legacy fallbacks set them explicitly.)

func (*FirecrackerConfig) GetDefaultImage added in v0.6.0

func (c *FirecrackerConfig) GetDefaultImage() string

GetDefaultImage implements vmimage.ImageConfig.

func (*FirecrackerConfig) GetExtractKernel

func (c *FirecrackerConfig) GetExtractKernel() bool

GetExtractKernel implements vmimage.ImageConfig.

func (*FirecrackerConfig) GetImageAliases added in v0.6.0

func (c *FirecrackerConfig) GetImageAliases() map[string]string

GetImageAliases implements vmimage.ImageConfig.

func (*FirecrackerConfig) GetImagesDir

func (c *FirecrackerConfig) GetImagesDir() string

GetImagesDir implements vmimage.ImageConfig.

func (*FirecrackerConfig) GetNeedsInitrd

func (c *FirecrackerConfig) GetNeedsInitrd() bool

GetNeedsInitrd implements vmimage.ImageConfig.

Both VZ and Firecracker boot through the shed-overlay initramfs (it owns overlayfs assembly + pivot_root), so an initrd blob must be installed alongside every shed image regardless of backend.

func (*FirecrackerConfig) GetPlatform

func (c *FirecrackerConfig) GetPlatform() string

GetPlatform implements vmimage.ImageConfig.

func (*FirecrackerConfig) GetPullConcurrency added in v0.6.2

func (c *FirecrackerConfig) GetPullConcurrency() int

GetPullConcurrency implements vmimage.ImageConfig.

func (*FirecrackerConfig) GetPullPolicy added in v0.6.0

func (c *FirecrackerConfig) GetPullPolicy() string

GetPullPolicy implements vmimage.ImageConfig.

func (*FirecrackerConfig) ResolveBaseRootfs

func (c *FirecrackerConfig) ResolveBaseRootfs() (ResolvedImage, error)

ResolveBaseRootfs resolves the default image (used when no --image is given).

func (*FirecrackerConfig) ResolveImage

func (c *FirecrackerConfig) ResolveImage(image string) (ResolvedImage, error)

ResolveImage resolves an image selector to a local path or Docker ref.

func (*FirecrackerConfig) Validate

func (c *FirecrackerConfig) Validate() error

Validate checks that the Firecracker configuration is valid.

type GitConfig

type GitConfig struct {
	// ExtraKnownHosts contains additional lines to append to the in-VM
	// ~/.ssh/known_hosts before `git clone` runs over SSH. Each entry must be
	// a valid known_hosts line (e.g., "github.com ssh-ed25519 AAAAC3..."),
	// typically obtained by running `ssh-keyscan <host>` on a trusted machine.
	// Built-in defaults (currently GitHub's published host keys) are always
	// included; this list extends them.
	ExtraKnownHosts []string `yaml:"extra_known_hosts,omitempty"`
}

GitConfig configures git behaviour for in-VM clones.

func (*GitConfig) Validate

func (g *GitConfig) Validate() error

Validate checks that each extra_known_hosts entry is a syntactically valid known_hosts line. The check is deliberately simple — it catches typos and obvious garbage but does not try to parse the base64 key material.

type ImageDescriptor

type ImageDescriptor struct {
	MediaType   string            `json:"media_type"`
	Digest      string            `json:"digest"`
	Size        int64             `json:"size"`
	Annotations map[string]string `json:"annotations,omitempty"`
}

ImageDescriptor is the wire-format counterpart of vmimage.Descriptor.

type ImageDiskEntry

type ImageDiskEntry struct {
	Name      string   `json:"name"`
	Path      string   `json:"path"`
	DockerRef string   `json:"docker_ref,omitempty"`
	Size      DiskSize `json:"size"`
}

ImageDiskEntry is the df view of a cached image variant, carrying both logical and physical bytes. Kept separate from ImageInfo so /api/images wire format stays stable.

type ImageInfo

type ImageInfo struct {
	Name      string `json:"name"`
	Path      string `json:"path,omitempty"`
	DockerRef string `json:"docker_ref,omitempty"`
	SizeBytes int64  `json:"size_bytes,omitempty"`
	Source    string `json:"source"` // "config", "user", or "dangling"
	Cached    bool   `json:"cached"`
	Digest    string `json:"digest,omitempty"` // "sha256:..." digest of the blob
	Tag       string `json:"tag,omitempty"`    // tag name pointing at this blob
	InUse     bool   `json:"in_use,omitempty"` // protected by a shed/snapshot reference
	// Alias is the friendly image_aliases key (e.g. "base"), set only for
	// config-sourced images; empty for user-pulled or dangling blobs.
	Alias string `json:"alias,omitempty"`
	// IsDefault is true for the config image whose ref is default_image.
	IsDefault bool `json:"is_default,omitempty"`
	// BootOnly is true when the image was pulled without layer tarballs
	// (boots fine; `shed image push` needs a `--with-layers` re-pull first).
	BootOnly bool `json:"boot_only,omitempty"`
}

ImageInfo describes an available image variant.

Storage model is content-addressed: Digest pins the underlying blob, Tag is the optional human-readable name (matches Name for tagged images, empty for dangling blobs).

type ImageInspectResponse

type ImageInspectResponse struct {
	Image    ImageInfo     `json:"image"`
	Manifest ImageManifest `json:"manifest"`
}

ImageInspectResponse is returned by GET /api/images/{tag-or-digest}.

type ImageManifest

type ImageManifest struct {
	Digest        string            `json:"digest"`
	SchemaVersion int               `json:"schema_version"`
	MediaType     string            `json:"media_type,omitempty"`
	Config        ImageDescriptor   `json:"config"`
	Layers        []ImageDescriptor `json:"layers,omitempty"`
	Annotations   map[string]string `json:"annotations,omitempty"`
	// Convenience fields lifted from annotations for backwards
	// compatibility with consumers that read SourceRef directly.
	SourceRef         string `json:"source_ref,omitempty"`
	Variant           string `json:"variant,omitempty"`
	KernelDigest      string `json:"kernel_digest,omitempty"`
	InitrdDigest      string `json:"initrd_digest,omitempty"`
	RootfsLogicalSize int64  `json:"rootfs_logical_size,omitempty"`
}

ImageManifest mirrors vmimage.OCIManifest for the wire format. Shape parallels the OCI image manifest spec; foreign tools (crane, oras, skopeo) inspecting shed's store see this same JSON on disk at blobs/sha256/<manifest-digest>.

type ImagePullRequest

type ImagePullRequest struct {
	DockerRef string `json:"docker_ref"`
	Tag       string `json:"tag"`
	// Platform is an optional override (e.g. "linux/arm64"). Empty
	// means the server-side backend's native platform.
	Platform string `json:"platform,omitempty"`
	// WithLayers pulls the full image (layer tarballs included). Default
	// (false) pulls boot-only — config + kernel + initrd + erofs only —
	// which the host boots from without the layers. Set true (the CLI's
	// --with-layers) when the image will be re-pushed.
	WithLayers bool `json:"with_layers,omitempty"`
}

ImagePullRequest is the body of POST /api/images/pull.

type ImagePullResponse

type ImagePullResponse struct {
	Tag    string `json:"tag"`
	Digest string `json:"digest"`
}

ImagePullResponse is the response of POST /api/images/pull.

type ImagePushRequest

type ImagePushRequest struct {
	// Source is the local tag or digest to push.
	Source string `json:"source"`
	// Destination is the registry reference (e.g. "ghcr.io/org/repo:v1").
	Destination string `json:"destination"`
}

ImagePushRequest is the body of POST /api/images/push.

type ImagePushResponse

type ImagePushResponse struct {
	Source      string `json:"source"`
	Destination string `json:"destination"`
}

ImagePushResponse is the response of POST /api/images/push.

type ImageTagRequest

type ImageTagRequest struct {
	Source string `json:"source"` // tag name or digest
	Target string `json:"target"` // new tag name
}

ImageTagRequest is the body of POST /api/images/tag.

type ImagesResponse

type ImagesResponse struct {
	Images []ImageInfo `json:"images"`
}

ImagesResponse is returned by GET /api/images.

type MountConfig

type MountConfig struct {
	Source   string   `yaml:"source" json:"source,omitempty"`
	Target   string   `yaml:"target" json:"target,omitempty"`
	ReadOnly bool     `yaml:"readonly" json:"readonly,omitempty"`
	Exclude  []string `yaml:"exclude,omitempty" json:"exclude,omitempty"`
}

MountConfig represents a bind mount configuration. JSON tags are present so it serializes cleanly when embedded in shed metadata and API responses (config.Shed.ProjectMounts); the yaml tags drive server-config parsing.

func BuildProjectMounts added in v0.6.4

func BuildProjectMounts(localDir string, addDirs []string) ([]MountConfig, string, error)

BuildProjectMounts performs the structural validation of --local-dir and --add-dir and returns the ordered project mounts (the --local-dir entry first, then each --add-dir) together with the landing directory.

It validates flag combinations and basename uniqueness but performs NO filesystem checks (existence / is-a-directory) — those are done separately on whichever host actually owns the directories. When no local dir is given it returns (nil, HomePath, nil).

func ResolveCreateLayout added in v0.6.4

func ResolveCreateLayout(req CreateShedRequest) (mounts []MountConfig, landingDir string, err error)

ResolveCreateLayout computes the project mounts and landing directory for a (already-validated) create request. --repo lands in the cloned repository directory (no project mounts); --local-dir/--add-dir mount under the home directory and land in the --local-dir mount; otherwise the home directory.

func (MountConfig) MatchesExclude

func (m MountConfig) MatchesExclude(relPath string) bool

MatchesExclude reports whether the given relative path matches any of the mount's exclude patterns. Patterns use filepath.Match glob syntax.

type PruneImagesResponse

type PruneImagesResponse struct {
	Deleted []ImageInfo `json:"deleted"`
}

PruneImagesResponse is returned by POST /api/images/prune.

type PruneReport

type PruneReport struct {
	DryRun     bool              `json:"dry_run"`
	ServerName string            `json:"server_name"`
	Scope      []string          `json:"scope"`
	Until      string            `json:"until"`
	Items      []PrunedItem      `json:"items"`
	Skipped    []SkippedItem     `json:"skipped,omitempty"`
	Notes      []string          `json:"notes,omitempty"`
	Totals     PruneReportTotals `json:"totals"`
}

PruneReport is the payload returned by POST /api/system/prune.

type PruneReportOrError

type PruneReportOrError struct {
	ServerName string       `json:"server_name"`
	Report     *PruneReport `json:"report,omitempty"`
	Error      string       `json:"error,omitempty"`
}

PruneReportOrError is one entry in a multi-server aggregated response.

type PruneReportTotals

type PruneReportTotals struct {
	Freed DiskSize `json:"freed"`
	Items int      `json:"items"`
}

PruneReportTotals summarizes what the prune pass did (or would do).

type PrunedItem

type PrunedItem struct {
	// Kind is one of: "image" | "rootfs" | "console_log" | "metadata" |
	// "instance" | "lock" | "tmp" | "source" | "snapshot_orphan".
	Kind string `json:"kind"`
	Path string `json:"path,omitempty"`
	// Name is the shed or image name when applicable.
	Name string `json:"name,omitempty"`
	// Action is "deleted" or "truncated".
	Action string `json:"action"`
	// Freed is the bytes attributed to this item. For clones or hardlinks
	// the physical count reflects attribution, not necessarily reclamation —
	// see PruneReport.Notes.
	Freed DiskSize `json:"freed"`
	// Reason is a human-readable justification (e.g. "stopped 5d ago").
	Reason string `json:"reason,omitempty"`
}

PrunedItem describes one file or object removed (or proposed for removal) by `shed system prune`.

type ResolvedImage

type ResolvedImage struct {
	// Path is set when the ext4 image already exists on disk.
	Path string

	// DockerRef is set when the image needs to be pulled and converted.
	DockerRef string

	// Name is the variant name, used for caching (e.g., "default" → "default-rootfs.ext4").
	Name string

	// Digest is set when Path came from a tag in the content-addressed
	// blob store. Empty for the legacy hardcoded-path escape hatch
	// (where the caller can't tell us a digest). Carries the digest
	// forward so EnsureImage doesn't have to re-do tag lookup — that
	// second lookup was both awkward and racey (tag could advance
	// between resolve and ensure).
	Digest string

	// PullPolicy is the configured pull_policy ("missing"|"always"|"never",
	// validated at config load) carried through to EnsureImage. Empty/local
	// paths treat it as a no-op.
	PullPolicy string
}

ResolvedImage represents the result of resolving an image name. Either Path (ext4 already exists locally) or DockerRef (needs pull + conversion) is set.

type SSHAuthConfig added in v0.7.0

type SSHAuthConfig struct {
	// Mode is off | warn | enforce (default off). off accepts all keys
	// (legacy); warn logs would-deny attempts but accepts; enforce rejects
	// keys not in the allowlist.
	Mode string `yaml:"mode,omitempty"`
	// AuthorizedKeys are inline OpenSSH authorized_keys lines.
	AuthorizedKeys []string `yaml:"authorized_keys,omitempty"`
	// AuthorizedKeysFile is a path to an authorized_keys-format file.
	AuthorizedKeysFile string `yaml:"authorized_keys_file,omitempty"`
	// GitHubUsers seeds the allowlist from https://github.com/<user>.keys,
	// cached to disk and failing closed to the last-known-good cache.
	GitHubUsers []string `yaml:"github_users,omitempty"`
	// GitHubRefresh is how often to re-fetch GitHub keys (default 1h).
	GitHubRefresh Duration `yaml:"github_refresh,omitempty"`
	// MaxAuthTries caps public-key attempts per connection (0 = shed default 10).
	// Raise it for clients whose agent holds many keys (1Password, Secretive) so
	// the allowlisted key is tried before the server gives up.
	MaxAuthTries int `yaml:"max_auth_tries,omitempty"`
}

SSHAuthConfig configures the SSH public-key allowlist. Identity comes from the offered key (the username still selects the shed), GitHub-style.

type SSHHostKeyResponse

type SSHHostKeyResponse struct {
	HostKey string `json:"host_key"`
}

SSHHostKeyResponse is returned by GET /api/ssh-host-key.

type ServerConfig

type ServerConfig struct {
	Name     string `yaml:"name"`
	HTTPPort int    `yaml:"http_port"`
	SSHPort  int    `yaml:"ssh_port"`

	// Network surface. All optional. TrustedProxy defaults to false, which
	// disables RealIP (see below) — the intended security default, not the
	// legacy always-on RealIP.
	//
	// BindAddress is the interface every listener (HTTP, HTTPS, SSH) binds. It
	// defaults to loopback (127.0.0.1) — shed is a local-development tool by
	// default. Set a routable address (a LAN/tailnet IP, "0.0.0.0"/"*" for all
	// IPv4, or "::" for all interfaces) to reach it off-box. In open mode that
	// also requires AllowInsecureExposure, since open mode has no transport
	// security; token mode (TLS + tokens) needs no acknowledgment.
	BindAddress string `yaml:"bind_address,omitempty"`
	// AllowInsecureExposure acknowledges binding an open-mode (plaintext,
	// possibly unauthenticated) server to a non-loopback interface. Required to
	// start such a server; ignored in token mode. Prefer auth.mode: token.
	AllowInsecureExposure bool `yaml:"allow_insecure_exposure,omitempty"`
	// TrustedProxy enables chi's RealIP middleware, which trusts the
	// client-supplied X-Forwarded-For. Only safe behind a proxy that
	// overwrites that header; the default (false) uses the real TCP peer
	// so an attacker can't forge a source IP to evade per-IP rate limits
	// or poison audit logs.
	TrustedProxy bool `yaml:"trusted_proxy,omitempty"`

	// HTTPSPort, when >0, starts an HTTPS listener (bound to bind_address)
	// serving the same public router as the plain HTTP listener, presenting
	// a self-signed cert that clients pin by fingerprint. 0 (default) = no
	// HTTPS; plain HTTP only (legacy, unchanged).
	HTTPSPort int `yaml:"https_port,omitempty"`
	// TLSNames are extra hostnames/IPs added as SANs in the generated cert
	// so hostname verification (curl --cacert, browsers) passes for each
	// advertised address. localhost + 127.0.0.1 + ::1 are always included.
	TLSNames []string `yaml:"tls_names,omitempty"`
	// TLSCertFile / TLSKeyFile override where the cert + key are persisted.
	// Empty (default) places them next to the SSH host key.
	TLSCertFile string `yaml:"tls_cert_file,omitempty"`
	TLSKeyFile  string `yaml:"tls_key_file,omitempty"`

	// Mounts are host directories mounted into every shed (e.g. ~/.ssh,
	// ~/.config/gh). This was previously named "credentials".
	Mounts map[string]MountConfig `yaml:"mounts"`
	// Credentials is the deprecated alias for Mounts. When "mounts" is unset
	// it is used as a fallback (see LoadServerConfigFromPath). Remove in a
	// future release.
	Credentials map[string]MountConfig `yaml:"credentials"`
	EnvFile     string                 `yaml:"env_file"`
	LogLevel    string                 `yaml:"log_level"`
	Terminal    *terminal.Config       `yaml:"terminal"`

	// DefaultBackend specifies the backend type: "vz", "firecracker", or "detect".
	// When set to "detect", the backend is chosen based on the platform
	// (vz on macOS/arm64, firecracker on linux).
	DefaultBackend string `yaml:"default_backend,omitempty"`

	// Firecracker contains Firecracker-specific configuration
	Firecracker *FirecrackerConfig `yaml:"firecracker,omitempty"`

	// VZ contains Apple Virtualization.framework-specific configuration (macOS only)
	VZ *VZConfig `yaml:"vz,omitempty"`

	// Extensions configures which extensions the agent should enable in VMs.
	Extensions *ExtensionsConfig `yaml:"extensions,omitempty"`

	// Git configures git-related behaviour for in-VM clones, including
	// the SSH known_hosts content seeded before `git clone` runs.
	Git *GitConfig `yaml:"git,omitempty"`

	// Egress configures optional Level-1 (audit-first) egress control.
	// Default (nil / enabled:false) preserves unrestricted networking.
	Egress *EgressConfig `yaml:"egress,omitempty"`

	// Auth configures optional authentication. Default (nil) preserves the
	// legacy accept-all behavior.
	Auth *AuthConfig `yaml:"auth,omitempty"`

	// Loaded environment variables (not from YAML)
	EnvVars map[string]string `yaml:"-"`
}

ServerConfig represents the server-side configuration.

func DefaultServerConfig

func DefaultServerConfig() *ServerConfig

DefaultServerConfig returns a ServerConfig with default values.

func LoadServerConfig

func LoadServerConfig() (*ServerConfig, error)

LoadServerConfig loads server configuration from standard locations. It checks in order: ./server.yaml, ~/.config/shed/server.yaml, /etc/shed/server.yaml

func LoadServerConfigForCLI

func LoadServerConfigForCLI(path string) (*ServerConfig, error)

LoadServerConfigForCLI loads server config the same way as LoadServerConfigFromPath but skips the host/backend OS coupling validation. Used by CLI commands like `shed image push --local` and `shed image build` that read the OCI store via the backend's config block but never actually start a VM. For example, the publish-images.yaml workflow runs on a Linux runner, builds VZ images via `--target shed-vz-*`, and pushes them via `shed image push --local -c <config-with-vz-block>`. The strict validator rejects that combination ("vz backend is only supported on macOS") even though we're never going to boot a VM there.

CALLERS THAT START A VM (or accept arbitrary HTTP traffic that will start a VM) MUST use LoadServerConfigFromPath instead — the OS coupling check matters for them.

func LoadServerConfigFromPath

func LoadServerConfigFromPath(path string) (*ServerConfig, error)

LoadServerConfigFromPath loads server configuration from a specific path. If path is empty, it searches standard locations.

func (*ServerConfig) ActiveDefaultImage added in v0.6.3

func (c *ServerConfig) ActiveDefaultImage() string

ActiveDefaultImage returns the resolved default_image for the configured default_backend (after ${shed.version} expansion / version synthesis at load), or "" if the active backend block is absent or has no default.

func (*ServerConfig) AuthEnforced added in v0.8.1

func (c *ServerConfig) AuthEnforced() bool

AuthEnforced reports whether the server enforces a non-open auth posture — token or mtls. SSH allowlist enforce, TLS-only serving (no plain HTTP), the https_port default, and non-loopback bind without an explicit acknowledgment all key off this combined predicate: both modes share the same network/SSH shape and differ only in how the client authenticates.

func (*ServerConfig) AuthModeValue added in v0.8.1

func (c *ServerConfig) AuthModeValue() string

AuthModeValue returns the effective auth.mode string, defaulting to AuthModeOpen when auth is unset. Config load guarantees this is one of open, token, or mtls by the time the server starts (normalizeAuthMode resolves the deprecated "secure" alias and validateAuth rejects anything else), so callers — e.g. the /api/info handler — can report it directly.

func (*ServerConfig) EffectiveSSHAuth added in v0.7.1

func (c *ServerConfig) EffectiveSSHAuth() *SSHAuthConfig

EffectiveSSHAuth returns the SSH auth config to build the allowlist from. In an enforced mode (token or mtls) the mode is forced to enforce (key sources still come from the configured auth.ssh block); otherwise the configured block is used verbatim.

func (*ServerConfig) HTTPAuthEnforced added in v0.7.1

func (c *ServerConfig) HTTPAuthEnforced() bool

HTTPAuthEnforced reports whether the HTTP API requires an authenticated caller at all. It is the gate on the auth middleware, not a statement about WHICH credential: token mode requires a scoped bearer token, mtls mode requires a scoped client certificate, and only open mode passes traffic through unauthenticated. The middleware branches on TokenMode/MTLSMode after this gate says "enforce something".

It is deliberately identical to AuthEnforced today. The two are kept apart because they answer different questions — "does HTTP require a credential" versus "is the network/SSH posture hardened" — and a future mode could answer them differently.

func (*ServerConfig) HTTPListenAddr added in v0.7.0

func (c *ServerConfig) HTTPListenAddr() string

HTTPListenAddr returns the bind address for the plain-HTTP listener, honoring bind_address (default loopback). The plain-HTTP listener is served only in open mode (see PlainHTTPEnabled); token mode is TLS-only and starts no plain-HTTP listener, so this is not consulted there.

func (*ServerConfig) HTTPSEnabled added in v0.7.0

func (c *ServerConfig) HTTPSEnabled() bool

HTTPSEnabled reports whether the HTTPS listener is configured.

func (*ServerConfig) HTTPSListenAddr added in v0.7.0

func (c *ServerConfig) HTTPSListenAddr() string

HTTPSListenAddr returns the bind address for the HTTPS listener (sharing bind_address with the other listeners), or "" when HTTPS is disabled.

func (*ServerConfig) MTLSMode added in v0.8.1

func (c *ServerConfig) MTLSMode() bool

MTLSMode reports whether the server runs in mtls mode (auth.mode: mtls): the client credential is a short-lived certificate issued over the SSH bootstrap channel rather than a bearer token, verified at the TLS handshake and re-validated on every HTTP request. It shares every other token-mode network/SSH invariant — see AuthEnforced, the combined predicate most call sites should use when they only care that auth is on.

func (*ServerConfig) PlainHTTPEnabled added in v0.7.2

func (c *ServerConfig) PlainHTTPEnabled() bool

PlainHTTPEnabled reports whether the main plain-HTTP listener should be served. False in an enforced mode (token or mtls; TLS-only — the plain listener is not started).

func (*ServerConfig) PreflightAuth added in v0.8.1

func (c *ServerConfig) PreflightAuth() error

PreflightAuth gates enforced-mode startup: it refuses to start when auth.mode: token or mtls is set without any SSH key source (github_users / authorized_keys / authorized_keys_file). Both modes enforce the SSH allowlist, so an empty allowlist would lock everyone out; mtls additionally needs a key source to identify which client gets a certificate issued over the bootstrap channel. Inert in open mode.

func (*ServerConfig) SSHAuth added in v0.7.0

func (c *ServerConfig) SSHAuth() *SSHAuthConfig

SSHAuth returns the SSH auth config, or nil when unset.

func (*ServerConfig) SSHListenAddr added in v0.7.0

func (c *ServerConfig) SSHListenAddr() string

SSHListenAddr returns the bind address for the SSH listener.

func (*ServerConfig) TokenMode added in v0.8.1

func (c *ServerConfig) TokenMode() bool

TokenMode reports whether the server runs in token mode (auth.mode: token): SSH allowlist enforce + HTTP bearer-token enforce + TLS-only (no plain HTTP listener faces clients). The default (open) preserves the legacy accept-all tailnet/LAN posture.

func (*ServerConfig) TokenTTL added in v0.7.1

func (c *ServerConfig) TokenTTL() time.Duration

TokenTTL is the lifetime of a bootstrap-minted HTTP token (auth.token_ttl), defaulting to DefaultTokenTTL when unset.

func (*ServerConfig) Validate

func (c *ServerConfig) Validate() error

Validate checks that the configuration is valid.

func (*ServerConfig) ValidateNoHostCoupling

func (c *ServerConfig) ValidateNoHostCoupling() error

ValidateNoHostCoupling runs the parts of Validate that aren't tied to the host OS/arch. Used by CLI commands that only need to read the OCI store (image build, image push --local), where the strict "vz backend only on macOS" / "firecracker backend only on linux" checks would block cross-platform image operations from CI runners or developers cross-publishing images. Callers that actually start a VM MUST call Validate instead.

type ServerEntry

type ServerEntry struct {
	Host     string    `yaml:"host"`
	HTTPPort int       `yaml:"http_port,omitempty"`
	SSHPort  int       `yaml:"ssh_port"`
	AddedAt  time.Time `yaml:"added_at"`
	// ControlToken is the bearer token sent on control-plane HTTP requests
	// (CLI, desktop). CredentialsToken is sent by the host-agent for the
	// credential bus. Both optional; empty when the server isn't token-gated.
	ControlToken     string `yaml:"control_token,omitempty"`
	CredentialsToken string `yaml:"credentials_token,omitempty"`
	// ControlTokenExpiresAt is when ControlToken (bootstrap-minted, short-TTL)
	// expires, so the CLI can transparently re-mint before/after expiry. Zero
	// for a legacy static token or an open server.
	ControlTokenExpiresAt time.Time `yaml:"control_token_expires_at,omitempty"`
	// APIURL, when set, overrides the scheme+host+port for the control plane
	// (e.g. https://host:8443). Empty = plain http://Host:HTTPPort (legacy).
	APIURL string `yaml:"api_url,omitempty"`
	// TLSCertFingerprint pins the server's self-signed TLS cert as
	// "sha256:<hex>", captured at `shed server add`. When set, the client
	// verifies the presented cert against it (no CA needed).
	TLSCertFingerprint string `yaml:"tls_cert_fingerprint,omitempty"`
	// AuthMode records the credential shape the server issued at the last
	// bootstrap: AuthModeToken (a bearer token) or AuthModeMTLS (a client
	// certificate).
	//
	// ABSENT MEANS TOKEN. Every entry written before client-certificate support
	// existed has no auth_mode key, and those are all token/open servers — so an
	// empty value must never be read as "unknown, go find out". It is a CACHE of
	// what the server last said, not a setting: a bootstrap that comes back in
	// the other mode rewrites it (see the mode-flip path in cmd/shed/client.go),
	// which is what lets an operator switch a server's auth.mode without every
	// client needing to be re-added.
	AuthMode string `yaml:"auth_mode,omitempty"`
	// ClientCertFile / ClientKeyFile locate this entry's client certificate and
	// its private key under the creds dir (see ServerCredsDir). Paths rather
	// than inline PEM: the key must live in a 0600 file of its own, not inside a
	// config the user hand-edits, copies between machines, and pastes into
	// issues.
	ClientCertFile string `yaml:"client_cert_file,omitempty"`
	ClientKeyFile  string `yaml:"client_key_file,omitempty"`
	// ClientCertExpiresAt is when ClientCertFile's certificate expires, so the
	// client can re-enroll before a request races expiry — the mtls counterpart
	// of ControlTokenExpiresAt. Cached here so the proactive check costs no file
	// read; the certificate itself remains the authority.
	ClientCertExpiresAt time.Time `yaml:"client_cert_expires_at,omitempty"`
}

ServerEntry represents a configured server.

func (*ServerEntry) BaseURL added in v0.7.0

func (e *ServerEntry) BaseURL() string

BaseURL returns the control-plane base URL for the entry: APIURL when set (it carries scheme+host+port), else the legacy plain http://Host:HTTPPort.

The host:port is joined with net.JoinHostPort, not printf: Host may be an IPv6 literal ("::1"), and "http://::1:8080" is not a URL any client can parse. The bracketed form is what every http.Client, url.Parse, and SSH-first open-mode add downstream of this needs.

func (*ServerEntry) IsMTLS added in v0.8.1

func (e *ServerEntry) IsMTLS() bool

IsMTLS reports whether this entry last bootstrapped a client certificate.

func (*ServerEntry) NeedsEnrollment added in v0.8.1

func (e *ServerEntry) NeedsEnrollment() bool

NeedsEnrollment reports whether this entry holds NO credential it could present to a server that demands one, and has enough information (host + ssh port) to obtain one over the SSH `_bootstrap` channel.

It exists because "no credential recorded" is ambiguous in a stored entry and the two readings need opposite handling:

  • an OPEN server legitimately has no credential, and must never pay an SSH round-trip to discover that. It is plain HTTP, so UsesTLS is false.
  • a SECURE server's entry that lost its credential fields must enroll. That is a routine condition during an upgrade, not a corner case: a pre-mtls client that loads and re-saves config.yaml silently drops every key its ServerEntry struct does not know (auth_mode, client_cert_file, client_key_file, client_cert_expires_at), leaving exactly this shape — an https entry with nothing to present. Without enrollment such an entry fails forever, because the client has no credential to be rejected and so never reaches its reactive re-mint.

A legacy static token (a control_token with no expiry) is NOT this case: it has something to present and is deliberately never re-minted.

func (*ServerEntry) UsesTLS added in v0.8.1

func (e *ServerEntry) UsesTLS() bool

UsesTLS reports whether this entry's control plane is HTTPS — which, for a shed-server, means the server enforces auth (token or mtls). Open mode serves plain HTTP and has no HTTPS listener at all, so this is the one durable signal in a stored entry that separates "this server wants a credential" from "this server wants nothing".

Either marker alone is enough: `shed server add` writes both an https api_url and a tls_cert_fingerprint for a secure server, but an entry is user-editable and a half-filled one still points at a listener that will demand a credential.

type ServerInfo

type ServerInfo struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	SSHPort int    `json:"ssh_port"`
	// HTTPPort is the plain-HTTP port (open mode). Omitted in token mode, which
	// serves no plain HTTP — clients use the HTTPS endpoint there.
	HTTPPort int    `json:"http_port,omitempty"`
	Backend  string `json:"backend"`
	// AuthMode is the server's auth.mode, so `shed server add` knows whether
	// to bootstrap an HTTP token (or, in mtls mode, a client certificate)
	// over SSH. WIRE CONTRACT: on /api/info token mode travels as the legacy
	// "secure" spelling — released clients gate their bootstrap on that exact
	// string (LegacyWireAuthMode) — and the decode boundary normalizes it back
	// to "token" (NormalizeAuthMode, applied in the CLI's GetInfo), so
	// in-process consumers only ever see "open", "token", or "mtls".
	// Reported on the unauthenticated /api/info in open/token mode — the mode
	// is observable from behavior anyway.
	AuthMode string `json:"auth_mode,omitempty"`
	// DefaultImage is the resolved default_image for the active backend
	// (after ${shed.version} expansion / version synthesis at load). Exposed
	// so clients can see which image a `shed create` without --image will
	// use — useful when the ref is synthesized and never written in config.
	DefaultImage string `json:"default_image,omitempty"`

	// HTTPSPort is the pinned-TLS listener port in token mode (auth.mode:
	// token), so a client adding a token-mode server can learn the TLS
	// endpoint. 0/omitted in open mode (no HTTPS listener).
	HTTPSPort int `json:"https_port,omitempty"`

	// CAFingerprint is the "sha256:<hex>" pin of the internal CA that signs
	// client certificates, reported only in mtls mode. It is operator
	// visibility, not a trust anchor: a client never needs it (it authenticates
	// the SERVER by the tls_cert_fingerprint pin and proves itself with the cert
	// the SSH bootstrap handed it), but an operator comparing two servers, or
	// confirming a CA rotation landed, does. Omitted in open/token mode.
	CAFingerprint string `json:"ca_fingerprint,omitempty"`
	// CANotAfter is the client CA's expiry (RFC 3339), reported only in mtls
	// mode. Rotating the CA invalidates every issued client certificate
	// fleet-wide and must be scheduled, so the deadline is worth surfacing
	// somewhere a human or a monitor can read it. Omitted in open/token mode.
	//
	// A string rather than time.Time so the field can be omitted when unset —
	// encoding/json's omitempty does not apply to struct values.
	CANotAfter string `json:"ca_not_after,omitempty"`

	// Features advertises server capability tokens (e.g. "overview",
	// "rc-enrich") for endpoint discovery, so a client learns which endpoints
	// and behaviors this server supports without probing each one. The same set
	// is mirrored in the GET /api/overview server block. The token list is owned
	// by internal/api (serverFeatures); older clients decode it as an empty slice.
	Features []string `json:"features,omitempty"`
}

ServerInfo is returned by GET /api/info.

type Session

type Session struct {
	Name        string    `json:"name"`
	ShedName    string    `json:"shed_name"`
	ServerName  string    `json:"server_name,omitempty"`
	CreatedAt   time.Time `json:"created_at"`
	Attached    bool      `json:"attached"`
	WindowCount int       `json:"window_count,omitempty"`
	// RC carries Remote Control Session Convention metadata for "rc-*" sessions.
	// The server populates it by exec'ing the in-shed shed-ext-rc binary over the
	// guest agent channel and merging by tmux name (GET /api/sessions and
	// GET /api/sheds/{name}/sessions, unless ?rc=0). It is nil for non-RC sessions
	// and for rc-* rows on a shed whose enrichment degraded (a warnings entry is
	// added in that case).
	RC *SessionRC `json:"rc,omitempty"`
}

Session represents a tmux session within a shed container.

type SessionRC added in v0.7.5

type SessionRC struct {
	Kind        string `json:"kind,omitempty"`
	State       string `json:"state,omitempty"`
	Managed     bool   `json:"managed"`
	DisplayName string `json:"display_name,omitempty"`
	URL         string `json:"url,omitempty"`
	CreatedBy   string `json:"created_by,omitempty"`
	// Lane is the session's current lane (contract v2): "tui" or "structured",
	// projected straight from rc.Session.Lane. omitempty here (unlike the always-
	// present wire field) is deliberate: a shed running a pre-lane guest binary
	// yields an empty string, and a client reading an ABSENT lane must treat it as
	// "tui" — the same old-payload rule rc.Session documents at its own field.
	Lane string `json:"lane,omitempty"`
	// Live-activity dimension (Phase C), projected from the rc DTO so listings can
	// carry it once a hub is running. Absent when the hub is not running / the kind
	// is unsupported. Activity is one of working|needs_input|idle|unknown; ActivityAt
	// is RFC3339; LastMessage is a sanitized ≤200-rune preview.
	Activity    string `json:"activity,omitempty"`
	ActivityAt  string `json:"activity_at,omitempty"`
	LastMessage string `json:"last_message,omitempty"`
}

SessionRC holds the RC Session Convention fields surfaced for an "rc-*" session (a subset of shed-ext-rc's neutral DTO, for display). Managed is false for legacy/unmanaged rc-* sessions.

type SessionsResponse

type SessionsResponse struct {
	Sessions []Session `json:"sessions"`
	Warnings []string  `json:"warnings,omitempty"`
}

SessionsResponse is returned by GET /api/sheds/{name}/sessions and GET /api/sessions.

type Shed

type Shed struct {
	Name        string    `json:"name" yaml:"name"`
	Status      string    `json:"status" yaml:"status"`
	CreatedAt   time.Time `json:"created_at" yaml:"created_at"`
	Repo        string    `json:"repo,omitempty" yaml:"repo,omitempty"`
	ContainerID string    `json:"container_id" yaml:"container_id"`
	Backend     string    `json:"backend,omitempty" yaml:"backend,omitempty"`
	IPAddress   string    `json:"ip_address,omitempty" yaml:"ip_address,omitempty"`
	CPUs        int       `json:"cpus,omitempty" yaml:"cpus,omitempty"`
	MemoryMB    int       `json:"memory_mb,omitempty" yaml:"memory_mb,omitempty"`
	PID         int       `json:"pid,omitempty" yaml:"pid,omitempty"`
	RootfsPath  string    `json:"rootfs_path,omitempty" yaml:"rootfs_path,omitempty"`
	// ProjectMounts are host directories mounted under the home directory
	// (--local-dir / --add-dir), each at /home/shed/<basename>.
	ProjectMounts []MountConfig `json:"project_mounts,omitempty" yaml:"project_mounts,omitempty"`
	// LandingDir is the directory interactive logins land in (a project
	// subdirectory of the home directory, or the home directory itself).
	LandingDir  string `json:"landing_dir,omitempty" yaml:"landing_dir,omitempty"`
	Image       string `json:"image,omitempty" yaml:"image,omitempty"`
	ImageDigest string `json:"image_digest,omitempty" yaml:"image_digest,omitempty"` // pinned manifest digest (sha256:...)
	// FromSnapshot records the snapshot name this shed was spawned from (immediate parent only).
	FromSnapshot string                         `json:"from_snapshot,omitempty" yaml:"from_snapshot,omitempty"`
	LastHealthy  *time.Time                     `json:"last_healthy,omitempty" yaml:"last_healthy,omitempty"` // last heartbeat from agent (VM backends only)
	StartedAt    *time.Time                     `json:"started_at,omitempty" yaml:"started_at,omitempty"`     // agent boot time from heartbeat (VM backends only)
	Extensions   map[string]ExtensionHealthInfo `json:"extensions,omitempty" yaml:"extensions,omitempty"`     // per-extension health (VM backends only)
	// Egress* track Level-1 egress-control state (set when egress is enabled
	// and the shed is assigned a non-empty profile list). EgressPort is the
	// per-shed proxy listener port — stable across stop/start because the
	// guest's injected HTTP_PROXY is baked into the persistent upper.
	EgressProfiles []string `json:"egress_profiles,omitempty" yaml:"egress_profiles,omitempty"`
	EgressPort     int      `json:"egress_port,omitempty" yaml:"egress_port,omitempty"`
	// EgressToken is the per-shed proxy-auth token binding the port to this
	// shed. It is a secret: never serialized to API/CLI responses (json:"-").
	// It lives durably in the per-backend on-disk metadata, not here.
	EgressToken string `json:"-" yaml:"-"`
}

Shed represents a development environment container.

type ShedCache

type ShedCache struct {
	Server    string    `yaml:"server"`
	Status    string    `yaml:"status"`
	UpdatedAt time.Time `yaml:"updated_at"`
}

ShedCache caches the location of a shed.

type ShedDiskEntry

type ShedDiskEntry struct {
	Name       string      `json:"name"`
	Status     string      `json:"status"`
	Image      string      `json:"image,omitempty"`
	Rootfs     FileEntry   `json:"rootfs"`
	ConsoleLog *FileEntry  `json:"console_log,omitempty"` // nil for Firecracker
	OtherFiles []FileEntry `json:"other_files,omitempty"`
	Total      DiskSize    `json:"total"`
}

ShedDiskEntry describes one shed's per-instance disk footprint.

type ShedsResponse

type ShedsResponse struct {
	Sheds []Shed `json:"sheds"`
}

ShedsResponse is returned by GET /api/sheds.

type SkippedItem

type SkippedItem struct {
	Kind   string `json:"kind"`
	Name   string `json:"name,omitempty"`
	Path   string `json:"path,omitempty"`
	Reason string `json:"reason"`
}

SkippedItem is an entity the prune pass inspected but left alone, with a short reason. Examples: running shed; stopped but too recent; lock held by an in-flight conversion; malformed metadata.

type Snapshot

type Snapshot struct {
	// Version is the snapshot schema version (current: SnapshotSchemaVersion).
	Version int `json:"version"`

	// Name is the unique snapshot identifier within a server.
	Name string `json:"name"`

	// Backend is "vz" or "firecracker"; only matching backends can spawn from this snapshot.
	Backend string `json:"backend"`

	// SourceShed is the shed this snapshot was created from. May reference a deleted shed.
	SourceShed string `json:"source_shed,omitempty"`

	// SourceImage is the image variant the source shed was created from (provenance hint).
	SourceImage string `json:"source_image,omitempty"`

	// SourceLocalDirs are the host directories the source shed mounted
	// (--local-dir / --add-dir); hint only, not bound at spawn.
	SourceLocalDirs []string `json:"source_local_dirs,omitempty"`

	// Comment is an optional user-supplied note attached at create time.
	Comment string `json:"comment,omitempty"`

	// CreatedAt is when the snapshot was captured.
	CreatedAt time.Time `json:"created_at"`

	// SizeBytes is the apparent (logical) size of the snapshot rootfs.
	SizeBytes int64 `json:"size_bytes,omitempty"`

	// LowerDigest is the digest of the lower (base) image the source shed
	// was created from, in the form "sha256:...". Snapshots count toward
	// the lower's refcount: pruning a digest pinned by a snapshot is
	// refused. Empty for snapshots created before schema v2.
	LowerDigest string `json:"lower_digest,omitempty"`

	// LowerCached reports whether the lower digest's blob is currently
	// installed in the local image store. Computed at read time, never
	// persisted (the on-disk value is recomputed on each load). When
	// false, `shed create --from-snapshot` will fail until the lower
	// image is pulled or rebuilt; surfaced by `shed snapshot info`.
	LowerCached bool `json:"lower_cached,omitempty"`
}

Snapshot represents a captured rootfs that can be used to spawn new sheds.

type SnapshotCreateRequest

type SnapshotCreateRequest struct {
	Name       string `json:"name"`
	SourceShed string `json:"source_shed"`
	Comment    string `json:"comment,omitempty"`
}

SnapshotCreateRequest is the request body for POST /api/snapshots.

type SnapshotCreateResponse

type SnapshotCreateResponse struct {
	Snapshot *Snapshot `json:"snapshot"`
	Warnings []string  `json:"warnings,omitempty"`
}

SnapshotCreateResponse is returned by POST /api/snapshots. It wraps the created snapshot together with any non-fatal warnings emitted during the operation (e.g., source shed used --local-dir so workspace contents are not captured). Wire format is intentionally distinct from the Snapshot type so warnings can grow without disturbing snapshot.json on disk.

type SnapshotDiskEntry

type SnapshotDiskEntry struct {
	Name       string      `json:"name"`
	SourceShed string      `json:"source_shed,omitempty"`
	Rootfs     FileEntry   `json:"rootfs"`
	OtherFiles []FileEntry `json:"other_files,omitempty"`
	Total      DiskSize    `json:"total"`
}

SnapshotDiskEntry describes one snapshot's disk footprint for `shed system df`. OtherFiles holds metadata sidecars (snapshot.json) so callers can sum the total footprint without hardcoding a per-snapshot file count.

type SnapshotsResponse

type SnapshotsResponse struct {
	Snapshots []Snapshot `json:"snapshots"`
}

SnapshotsResponse is returned by GET /api/snapshots.

type StagedClientCredentials added in v0.8.1

type StagedClientCredentials = creds.Staged

StagedClientCredentials is a client certificate + key written to disk but not yet adopted by the server it belongs to — the transaction handle that lets `shed server add` order the config save before the destructive half of the credential update. See sdk/creds.Staged.

func StageClientCredentials added in v0.8.1

func StageClientCredentials(name string, certPEM, keyPEM []byte) (*StagedClientCredentials, error)

StageClientCredentials writes an issued certificate + key into the staging area for the named server, WITHOUT touching whatever that server currently has. Call Commit to adopt the pair, or Discard to throw it away.

type SystemDFResponse

type SystemDFResponse struct {
	Servers []DiskUsageOrError `json:"servers"`
}

SystemDFResponse is the client-side aggregation of per-server df results produced by `shed system df --all`. Never returned by the API directly.

type SystemPruneResponse

type SystemPruneResponse struct {
	Servers []PruneReportOrError `json:"servers"`
}

SystemPruneResponse is the client-side aggregation of per-server prune results from `shed system prune --all`. Never returned by the API directly.

type UserProfileStore added in v0.7.5

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

UserProfileStore is the runtime, user-editable egress-profile store — a second source of named profiles alongside the read-only server.yaml ones. Each profile is one whole-document <name>.yaml file, CRUD'd via the API/CLI. It is the FIRST writer into the egress resolution read path (server.yaml is immutable after load), so List/Get return deep copies and all access is RWMutex-guarded.

func OpenUserProfileStore added in v0.7.5

func OpenUserProfileStore(dir string) (*UserProfileStore, error)

OpenUserProfileStore loads every <name>.yaml under dir into memory. It FAILS HARD on a malformed file or an invalid profile spec — a bad runtime policy file must not be silently skipped (a profile the user thought they had, gone after a restart) nor produce a half-valid effective policy.

func (*UserProfileStore) Delete added in v0.7.5

func (s *UserProfileStore) Delete(name string) error

Delete removes a user profile (file + map). Returns an error if it doesn't exist.

func (*UserProfileStore) Get added in v0.7.5

func (s *UserProfileStore) Get(name string) (EgressProfile, bool)

Get returns a deep copy of one profile.

func (*UserProfileStore) List added in v0.7.5

func (s *UserProfileStore) List() map[string]EgressProfile

List returns a deep copy of every user profile (map + Allow/Deny slices cloned) so a caller can't mutate store state or race a concurrent Put. nil receiver → nil (lets callers pass store.List() unconditionally).

func (*UserProfileStore) Names added in v0.7.5

func (s *UserProfileStore) Names() []string

Names returns the user-profile names, sorted (deterministic CLI/UX output).

func (*UserProfileStore) Put added in v0.7.5

func (s *UserProfileStore) Put(name string, p EgressProfile) error

Put validates then atomically writes a profile (<name>.yaml, temp+rename) and updates the in-memory map. On a validation or write error the map is unchanged. Config-name collisions are the API layer's job (the store has no config view).

type VZConfig

type VZConfig struct {
	// VfkitPath is the path to the vfkit binary
	VfkitPath string `yaml:"vfkit_path"`

	// KernelPath is the path to the decompressed Linux kernel
	KernelPath string `yaml:"kernel_path"`

	// InitrdPath is the path to the initial RAM disk image
	InitrdPath string `yaml:"initrd_path"`

	// DefaultImage is the Docker ref (or local rootfs path) used for new
	// sheds when no --image is given. Docker refs are resolved by their
	// io.shed.source-ref identity and pulled per PullPolicy on first use.
	DefaultImage string `yaml:"default_image"`

	// ImageAliases maps short alias names to Docker refs (or paths) for
	// convenience with: shed create mydev --image <alias>. Aliases resolve
	// to the underlying ref; image listings always show the resolved ref.
	ImageAliases map[string]string `yaml:"image_aliases,omitempty"`

	// PullPolicy controls cache-vs-pull at create: "missing" (default —
	// use the cached ref, pull only if absent), "always" (always pull),
	// or "never" (error if not cached). Ignored for local-path images.
	PullPolicy string `yaml:"pull_policy,omitempty"`

	// PullConcurrency caps how many image blobs (layers + kernel/initrd/
	// erofs) download in parallel during a registry pull. Defaults to
	// DefaultPullConcurrency; must be >= 1 (1 == serial).
	PullConcurrency int `yaml:"pull_concurrency,omitempty"`

	// ImagesDir is the directory for the content-addressed image store.
	ImagesDir string `yaml:"images_dir,omitempty"`

	// InstanceDir is the directory for instance data
	InstanceDir string `yaml:"instance_dir"`

	// SnapshotsDir is the directory where shed snapshots are stored.
	SnapshotsDir string `yaml:"snapshots_dir,omitempty"`

	// UppersDir is the directory where per-shed writable upper layers
	// (sparse ext4 files) are stored.
	UppersDir string `yaml:"uppers_dir,omitempty"`

	// UpperSizeDefault is the default logical size of the per-shed
	// writable upper. Accepted units: G (GB) and M (MB). Range 1G-100G.
	UpperSizeDefault string `yaml:"upper_size_default,omitempty"`

	// SocketDir is the directory for vsock Unix sockets.
	// NOTE: This path must not contain spaces. vfkit URL-encodes socket paths,
	// turning spaces into %20, which causes connection failures.
	SocketDir string `yaml:"socket_dir"`

	// DefaultCPUs is the default number of vCPUs for new VMs
	DefaultCPUs int `yaml:"default_cpus"`

	// DefaultMemoryMB is the default memory in MB for new VMs
	DefaultMemoryMB int `yaml:"default_memory_mb"`

	// DefaultDiskGB is the default disk size in GB for new VMs
	DefaultDiskGB int `yaml:"default_disk_gb"`

	// ConsolePort is the vsock port for console/exec connections
	ConsolePort uint32 `yaml:"console_port"`

	// NotifyPort is the vsock port for the message channel (health checks, plugins, credentials)
	NotifyPort uint32 `yaml:"notify_port"`

	// TCPProxyPort is the vsock port for the TCP proxy (used by DialService to reach VM services)
	TCPProxyPort uint32 `yaml:"tcp_proxy_port"`

	// StartTimeout is the timeout for VM startup
	StartTimeout Duration `yaml:"start_timeout"`

	// StopTimeout is the timeout for graceful VM shutdown
	StopTimeout Duration `yaml:"stop_timeout"`

	// GuestMTU, when non-zero, forces the guest's primary interface MTU
	// instead of auto-detecting the host's egress path MTU at VM start.
	// 0 (the default) means auto-detect: behind a reduced-MTU path (e.g. a
	// VPN/overlay) the guest is lowered to match; otherwise it stays at
	// 1500. Set this only to pin a value when detection misses. Validated to
	// [MinGuestMTU, MaxGuestMTU] when non-zero.
	GuestMTU int `yaml:"guest_mtu,omitempty"`
}

VZConfig contains Apple Virtualization.framework-specific configuration.

func DefaultVZConfig

func DefaultVZConfig() *VZConfig

DefaultVZConfig returns a VZConfig with default values.

Cross-backend alignment (DO NOT drift between this and DefaultFirecrackerConfig without an explicit reason):

  • DefaultCPUs / DefaultMemoryMB / DefaultDiskGB: same physical resource shape per shed on both backends. A user moving a shed between platforms should see identical resource sizing by default.
  • ConsolePort (1024) / NotifyPort (1026) / TCPProxyPort (1028): the agent's vsock contract. Same on both so the same shed-agent binary speaks to both backends without per-platform port plumbing. TCPProxyPort is aligned across backends as of the hub-parity change — both backends route DialService through the guest agent's TCP proxy on this port.
  • StopTimeout (10 s): the budget for the shutdown-hook + sync + graceful-stop sequence. Same on both because the in-guest work (sync, hook execution) is identical regardless of VMM.

Intentionally divergent from FC:

  • StartTimeout (60 s vs FC 30 s): historical VZ create wall time was ~5.9 s (pre-Phase-2 in-guest mkfs.ext4 on the vfkit virtio-blk write path, which is ~20× slower than Firecracker's per §0 of the runtime-opt doc). 60 s gave ~10× headroom for that worst case. As of v0.5.5 warm VZ create is ~1.6 s, so 60 s is generous; the value stands to absorb cold-state and overloaded-host variance without surprising the operator.
  • VfkitPath: VZ-only (vfkit is the macOS VMM); Firecracker equivalent is invoked directly by the binary at FirecrackerPath (set elsewhere).

func (*VZConfig) GetDefaultImage added in v0.6.0

func (c *VZConfig) GetDefaultImage() string

GetDefaultImage implements vmimage.ImageConfig.

func (*VZConfig) GetExtractKernel

func (c *VZConfig) GetExtractKernel() bool

GetExtractKernel implements vmimage.ImageConfig.

func (*VZConfig) GetImageAliases added in v0.6.0

func (c *VZConfig) GetImageAliases() map[string]string

GetImageAliases implements vmimage.ImageConfig.

func (*VZConfig) GetImagesDir

func (c *VZConfig) GetImagesDir() string

GetImagesDir implements vmimage.ImageConfig.

func (*VZConfig) GetNeedsInitrd

func (c *VZConfig) GetNeedsInitrd() bool

GetNeedsInitrd implements vmimage.ImageConfig.

func (*VZConfig) GetPlatform

func (c *VZConfig) GetPlatform() string

GetPlatform implements vmimage.ImageConfig.

func (*VZConfig) GetPullConcurrency added in v0.6.2

func (c *VZConfig) GetPullConcurrency() int

GetPullConcurrency implements vmimage.ImageConfig.

func (*VZConfig) GetPullPolicy added in v0.6.0

func (c *VZConfig) GetPullPolicy() string

GetPullPolicy implements vmimage.ImageConfig.

func (*VZConfig) ResolveBaseRootfs

func (c *VZConfig) ResolveBaseRootfs() (ResolvedImage, error)

ResolveBaseRootfs resolves the default image (used when no --image is given).

func (*VZConfig) ResolveImage

func (c *VZConfig) ResolveImage(image string) (ResolvedImage, error)

ResolveImage resolves an image selector to a local path or Docker ref.

func (*VZConfig) Validate

func (c *VZConfig) Validate() error

Validate checks that the VZ configuration is valid.

Jump to

Keyboard shortcuts

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