install

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 8, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Overview

Package install owns the durable authority and orchestration contracts for the guided single-host installer. It deliberately does not import terminal presentation or deployment-manifest packages.

Index

Constants

View Source
const (
	ExecutionBundleEstimateBytes = int64(11 << 30)
	MinimumWorkspaceBytes        = int64(50 << 30)
	MinimumBackingReserveBytes   = int64(16 << 30)
	MinimumObjectStoreBytes      = int64(4 << 30)
	MinimumControlBackingBytes   = MinimumBackingReserveBytes
	MinimumDeploymentBytes       = ExecutionBundleEstimateBytes
	MinimumFilesystemImageBytes  = ExecutionBundleEstimateBytes + MinimumWorkspaceBytes + (4 << 30)
	MinimumHostMemoryBytes       = int64(12 << 30)
	HostMemoryReserveBytes       = int64(4 << 30)
	MinimumHostCPUCount          = 6
	HostCPUReserveMillis         = int64(2000)
	DurableCodingCPUMillis       = int64(4000)
	DurableCodingMemoryBytes     = int64(8 << 30)
)
View Source
const (
	HostFactsSchema = "secondbox.install.host-facts/v1"
	PlanSchema      = "secondbox.install.plan/v1"
	ReceiptSchema   = "secondbox.install.receipt/v1"
)

Variables

Functions

func Canonical

func Canonical(value any) ([]byte, error)

func Digest

func Digest(content []byte) string

func HasBlockingFindings

func HasBlockingFindings(facts HostFacts) bool

func HostFactsDigest

func HostFactsDigest(facts HostFacts) (string, error)

func MaterializeRelease

MaterializeRelease installs only bytes named by an independently verified release. The artifact directory is published with one rename after all allowlist, hash, signature, component, platform, and rootfs checks succeed.

func MountUnit

func MountUnit(imagePath, workspacePath string) string

func NewOperationID

func NewOperationID() (string, error)

func PlanDigest

func PlanDigest(plan InstallPlan) (string, error)

func ReadOperation

func ReadOperation(string, int) (InstallPlan, InstallReceipt, error)

func RenderPlanReview

func RenderPlanReview(plan InstallPlan) string

func RenderPreflight

func RenderPreflight(facts HostFacts) string

func ValidateCLIConfig

func ValidateCLIConfig(plan InstallPlan) error

ValidateCLIConfig checks the protected stored authority without contacting the control plane or exposing the token through arguments or diagnostics.

func ValidateComposeProjectEvidence

func ValidateComposeProjectEvidence(InstallReceipt, string) error

func ValidatePlannedPath

func ValidatePlannedPath(PlannedPath) error

func ValidateRecordedResources

func ValidateRecordedResources(InstallPlan, InstallReceipt) error

func WriteAccepted

func WriteAccepted(directory string, plan InstallPlan, receipt InstallReceipt) (string, string, error)

func WriteSupportBundle

func WriteSupportBundle(output string, plan InstallPlan, receipt InstallReceipt, evidence map[string][]byte) error

WriteSupportBundle writes bounded, redacted installer evidence. Callers may add only already-sanitized operational projections; secret files and Workspace contents are never opened for collection.

Types

type CLIPlan

type CLIPlan struct {
	ConfigPath string `json:"configPath"`
	TenantRef  string `json:"tenantRef"`
	SubjectRef string `json:"subjectRef"`
}

type CandidateReleaseMaterializer

type CandidateReleaseMaterializer struct {
	Directory  string
	Output     io.Writer
	Diagnostic io.Writer
}

CandidateReleaseMaterializer uses only exact images already loaded by the qualification controller and exact staged release objects. It never contacts a registry or release server before publication.

func (CandidateReleaseMaterializer) ExtractMicroVMImage

func (executor CandidateReleaseMaterializer) ExtractMicroVMImage(ctx context.Context, reference, target string) error

func (CandidateReleaseMaterializer) Fetch

func (executor CandidateReleaseMaterializer) Fetch(ctx context.Context, location string) ([]byte, error)

func (CandidateReleaseMaterializer) PullImage

func (executor CandidateReleaseMaterializer) PullImage(ctx context.Context, reference string) error

type CapacityPlan

type CapacityPlan struct {
	MaxSandboxes           int64            `json:"maxSandboxes"`
	MaxCPUMillis           int64            `json:"maxCpuMillis"`
	MaxMemoryBytes         int64            `json:"maxMemoryBytes"`
	MaxWorkspaceBytes      int64            `json:"maxWorkspaceBytes"`
	ConcurrentStarts       int64            `json:"concurrentStarts"`
	ConcurrentOperations   int64            `json:"concurrentOperations"`
	StoragePressurePercent int64            `json:"storagePressurePercent"`
	SubjectQuotas          map[string]int64 `json:"subjectQuotas"`
}

type ClockProbe

type ClockProbe interface{ Now() time.Time }

type CommandResult

type CommandResult struct {
	Stdout string
	Stderr string
}

type CreatedResource

type CreatedResource struct {
	ID       string       `json:"id"`
	Kind     ResourceKind `json:"kind"`
	Path     string       `json:"path,omitempty"`
	Class    PathClass    `json:"class,omitempty"`
	Stage    Stage        `json:"stage"`
	Mode     uint32       `json:"mode,omitempty"`
	OwnerUID int64        `json:"ownerUid,omitempty"`
	OwnerGID int64        `json:"ownerGid,omitempty"`
	Digest   string       `json:"digest,omitempty"`
	Identity string       `json:"identity,omitempty"`
}

func LoginCLI

func LoginCLI(ctx context.Context, plan InstallPlan, httpClient *http.Client) ([]CreatedResource, error)

LoginCLI verifies the generated platform authority, then writes the invoking user's ordinary CLI configuration without ever putting the token in process arguments, the receipt, or diagnostic output.

type DeviceFact

type DeviceFact struct {
	Path           string `json:"path"`
	Identity       string `json:"identity"`
	Filesystem     string `json:"filesystem,omitempty"`
	SizeBytes      int64  `json:"sizeBytes"`
	AvailableBytes int64  `json:"availableBytes"`
	Mountpoint     string `json:"mountpoint,omitempty"`
}

type FailureClass

type FailureClass string
const (
	FailureBlocked     FailureClass = "blocked"
	FailureNeedsAction FailureClass = "needs_action"
	FailureRetryable   FailureClass = "retryable"
	FailureInternal    FailureClass = "internal"
)

type FilesystemProbe

type FilesystemProbe interface {
	ReadFile(string) ([]byte, error)
	Lstat(string) (fs.FileInfo, error)
	StatFS(string) (available, total int64, err error)
	OpenReadWrite(string) error
}

type Finding

type Finding struct {
	ID      string       `json:"id"`
	Class   FindingClass `json:"class"`
	Summary string       `json:"summary"`
	Detail  string       `json:"detail,omitempty"`
	Remedy  string       `json:"remedy,omitempty"`
}

type FindingClass

type FindingClass string
const (
	FindingPass        FindingClass = "pass"
	FindingWarning     FindingClass = "warning"
	FindingRemediable  FindingClass = "remediable"
	FindingNeedsAction FindingClass = "needs_action"
	FindingBlocked     FindingClass = "blocked"
)

type HostApplyDependencies

type HostApplyDependencies struct {
	Executor       HostApplyExecutor
	PersistReceipt func(InstallReceipt) error
	Now            func() time.Time
}

type HostApplyExecutor

type HostApplyExecutor interface {
	EffectiveUID() int
	Revalidate(context.Context, InstallPlan, InstallReceipt) error
	CreateDirectory(PlannedPath) error
	AllocateFilesystemImage(PlannedPath, int64) error
	FormatBtrfs(context.Context, string, string) error
	WriteMountUnit(PlannedPath, string) error
	EnableMountUnit(context.Context, string) error
	SecureMountedWorkspace(PlannedPath) error
	ProveReflinkIsolation(string) (string, error)
	RemoveEmpty(CreatedResource) (bool, error)
}

type HostFacts

type HostFacts struct {
	SchemaVersion      string            `json:"schemaVersion"`
	ObservedAt         time.Time         `json:"observedAt"`
	HostIdentity       string            `json:"hostIdentity"`
	OS                 string            `json:"os"`
	Architecture       string            `json:"architecture"`
	InvokingUID        int64             `json:"invokingUid"`
	InvokingGID        int64             `json:"invokingGid"`
	KernelVersion      string            `json:"kernelVersion"`
	SystemdVersion     string            `json:"systemdVersion,omitempty"`
	DockerVersion      string            `json:"dockerVersion,omitempty"`
	ComposeVersion     string            `json:"composeVersion,omitempty"`
	CgroupVersion      int               `json:"cgroupVersion"`
	CgroupControllers  []string          `json:"cgroupControllers"`
	CPUCount           int               `json:"cpuCount"`
	MemoryBytes        int64             `json:"memoryBytes"`
	Virtualization     string            `json:"virtualization,omitempty"`
	BtrfsSupported     bool              `json:"btrfsSupported"`
	KVMAccessible      bool              `json:"kvmAccessible"`
	TUNAccessible      bool              `json:"tunAccessible"`
	Devices            []DeviceFact      `json:"devices"`
	ListeningPorts     []PortFact        `json:"listeningPorts"`
	Routes             []RouteFact       `json:"routes"`
	DNSUpstreams       []string          `json:"dnsUpstreams"`
	AssignedUIDs       []int64           `json:"assignedUIDs"`
	ReservedIDRanges   []UIDRange        `json:"reservedIdRanges"`
	CandidateUIDRanges []UIDRange        `json:"candidateUIDRanges"`
	Utilities          map[string]string `json:"utilities"`
	Findings           []Finding         `json:"findings"`
}

func DecodeHostFacts

func DecodeHostFacts(content []byte) (HostFacts, error)

func Preflight

func Preflight(ctx context.Context, probes PreflightProbes) (HostFacts, error)

func (HostFacts) Validate

func (facts HostFacts) Validate() error

type InstallPlan

type InstallPlan struct {
	SchemaVersion                string         `json:"schemaVersion"`
	OperationID                  string         `json:"operationId"`
	CreatedAt                    time.Time      `json:"createdAt"`
	HostFacts                    HostFacts      `json:"hostFacts"`
	HostFactsDigest              string         `json:"hostFactsDigest"`
	Release                      ReleasePlan    `json:"release"`
	Storage                      StoragePlan    `json:"storage"`
	Capacity                     CapacityPlan   `json:"capacity"`
	Network                      NetworkPlan    `json:"network"`
	CLI                          CLIPlan        `json:"cli"`
	Paths                        []PlannedPath  `json:"paths"`
	SecretTargets                []SecretTarget `json:"secretTargets"`
	GeneratedAuthorityCategories []string       `json:"generatedAuthorityCategories"`
	StandardBundles              []string       `json:"standardBundles"`
	RetentionSeconds             int64          `json:"retentionSeconds"`
	PrivilegedActions            []string       `json:"privilegedActions"`
}

func DecodePlan

func DecodePlan(content []byte) (InstallPlan, error)

func ProposePlan

func ProposePlan(facts HostFacts, input ProposalInput) (InstallPlan, error)

func ReadPlan

func ReadPlan(path string) (InstallPlan, []byte, error)

func (InstallPlan) Validate

func (plan InstallPlan) Validate() error

type InstallReceipt

type InstallReceipt struct {
	SchemaVersion       string            `json:"schemaVersion"`
	OperationID         string            `json:"operationId"`
	PlanDigest          string            `json:"planDigest"`
	HostIdentity        string            `json:"hostIdentity"`
	Status              OperationStatus   `json:"status"`
	FailureClass        FailureClass      `json:"failureClass,omitempty"`
	FailureStage        Stage             `json:"failureStage,omitempty"`
	CompletedStages     []StageRecord     `json:"completedStages"`
	CreatedResources    []CreatedResource `json:"createdResources"`
	PendingResourceIDs  []string          `json:"pendingResourceIds"`
	RemovedResourceIDs  []string          `json:"removedResourceIds"`
	CompletedPurgeSteps []string          `json:"completedPurgeSteps"`
	UpdatedAt           time.Time         `json:"updatedAt"`
}

func ApplyAcceptedHost

func ApplyAcceptedHost(ctx context.Context, directory, expectedDigest string, callerUID int, executor HostApplyExecutor, now func() time.Time) (result InstallReceipt, resultErr error)

func ApplyHost

func ApplyHost(ctx context.Context, plan InstallPlan, receipt InstallReceipt, dependencies HostApplyDependencies) (InstallReceipt, error)

func DecodeReceipt

func DecodeReceipt(content []byte, plan InstallPlan) (InstallReceipt, error)

func NewReceipt

func NewReceipt(plan InstallPlan, now time.Time) (InstallReceipt, error)

func PurgeAcceptedHost

func PurgeAcceptedHost(context.Context, string, string, int, func() time.Time) (InstallReceipt, error)

func (*InstallReceipt) AppendResource

func (receipt *InstallReceipt) AppendResource(resource CreatedResource) error

func (*InstallReceipt) BeginResource

func (receipt *InstallReceipt) BeginResource(id string, now time.Time) error

func (*InstallReceipt) CompletePurgeStep

func (receipt *InstallReceipt) CompletePurgeStep(step string, now time.Time) error

func (*InstallReceipt) CompleteResource

func (receipt *InstallReceipt) CompleteResource(resource CreatedResource, now time.Time) error

func (*InstallReceipt) CompleteStage

func (receipt *InstallReceipt) CompleteStage(stage Stage, now time.Time, evidence map[string]string) error

func (*InstallReceipt) Fail

func (receipt *InstallReceipt) Fail(stage Stage, class FailureClass, now time.Time) error

Fail records a resumable stage failure without inventing a completed stage.

func (*InstallReceipt) MarkPurged

func (receipt *InstallReceipt) MarkPurged(now time.Time) error

func (*InstallReceipt) MarkPurging

func (receipt *InstallReceipt) MarkPurging(now time.Time) error

func (*InstallReceipt) MarkResourceRemoved

func (receipt *InstallReceipt) MarkResourceRemoved(id string, now time.Time) error

func (*InstallReceipt) MarkUninstalled

func (receipt *InstallReceipt) MarkUninstalled(now time.Time) error

func (*InstallReceipt) MarkUninstalling

func (receipt *InstallReceipt) MarkUninstalling(now time.Time) error

func (*InstallReceipt) RecoverSucceeded

func (receipt *InstallReceipt) RecoverSucceeded(now time.Time) error

RecoverSucceeded clears a transient post-install health failure after the complete installation has passed readiness again. It does not invent or repeat any completed stage.

func (*InstallReceipt) RestoreSucceeded

func (receipt *InstallReceipt) RestoreSucceeded(now time.Time) error

func (InstallReceipt) Validate

func (receipt InstallReceipt) Validate(planDigest, hostIdentity, operationID string) error

type NetworkOverrides

type NetworkOverrides struct {
	APIPort                int
	RunnerPort             int
	DataPlanePort          int
	DatabasePort           int
	ObjectStorePort        int
	ObjectStoreConsolePort int
	GuestCIDR              string
	TAPPrefix              string
	CgroupParent           string
	DNSUpstream            string
	JailerUID              UIDRange
}

type NetworkPlan

type NetworkPlan struct {
	APIAddress                string            `json:"apiAddress"`
	RunnerAddress             string            `json:"runnerAddress"`
	DataPlaneAddress          string            `json:"dataPlaneAddress"`
	DatabaseAddress           string            `json:"databaseAddress"`
	ObjectStoreAddress        string            `json:"objectStoreAddress"`
	ObjectStoreConsoleAddress string            `json:"objectStoreConsoleAddress"`
	GuestBridgeCIDR           string            `json:"guestBridgeCidr"`
	TAPPrefix                 string            `json:"tapPrefix"`
	CgroupParent              string            `json:"cgroupParent"`
	JailerUIDRange            UIDRange          `json:"jailerUidRange"`
	DNSUpstream               string            `json:"dnsUpstream"`
	Gateways                  map[string]string `json:"gateways"`
}

type NetworkProbe

type NetworkProbe interface {
	LookupHost(context.Context, string) ([]string, error)
	Head(context.Context, string) (int, error)
}

type OperationLock

type OperationLock struct{}

func AcquireLock

func AcquireLock(string) (*OperationLock, error)

func (*OperationLock) Close

func (*OperationLock) Close() error

type OperationStatus

type OperationStatus string
const (
	OperationPlanned      OperationStatus = "planned"
	OperationRunning      OperationStatus = "running"
	OperationFailed       OperationStatus = "failed"
	OperationSucceeded    OperationStatus = "succeeded"
	OperationUninstalling OperationStatus = "uninstalling"
	OperationUninstalled  OperationStatus = "uninstalled"
	OperationPurging      OperationStatus = "purging"
	OperationPurged       OperationStatus = "purged"
)

type PathClass

type PathClass string
const (
	PathUserDeployment    PathClass = "user_deployment"
	PathInstallerHost     PathClass = "installer_host"
	PathExistingWorkspace PathClass = "existing_workspace"
	PathFilesystemImage   PathClass = "filesystem_image"
)

type PlannedPath

type PlannedPath struct {
	Name         string       `json:"name"`
	Path         string       `json:"path"`
	Class        PathClass    `json:"class"`
	Kind         ResourceKind `json:"kind"`
	Mode         uint32       `json:"mode"`
	OwnerUID     int64        `json:"ownerUid"`
	OwnerGID     int64        `json:"ownerGid"`
	RequiresSudo bool         `json:"requiresSudo"`
	Create       bool         `json:"create"`
}

type PortFact

type PortFact struct {
	Address  string `json:"address"`
	Port     int    `json:"port"`
	Protocol string `json:"protocol"`
	Process  string `json:"process,omitempty"`
}

type PreflightProbes

type PreflightProbes struct {
	Filesystem   FilesystemProbe
	Process      ProcessProbe
	Network      NetworkProbe
	Clock        ClockProbe
	Users        UserProbe
	LookupEnv    func(string) (string, bool)
	OS           string
	Architecture string
	CPUCount     int
	InvokingUID  int64
	InvokingGID  int64
}

func SystemPreflightProbes

func SystemPreflightProbes() PreflightProbes

type ProcessProbe

type ProcessProbe interface {
	Run(context.Context, string, ...string) (CommandResult, error)
	LookPath(string) (string, error)
}

type ProposalInput

type ProposalInput struct {
	OperationID              string
	CreatedAt                time.Time
	DeploymentDirectory      string
	BinaryDirectory          string
	CLIConfigPath            string
	CLITenantRef             string
	CLISubjectRef            string
	BackingAvailableBytes    int64
	DeploymentAvailableBytes int64
	Release                  ReleasePlan
	StorageChoice            StorageChoice
	ExistingMountpoint       string
	FilesystemImageBytes     int64
	NetworkOverrides         NetworkOverrides
	StandardBundles          []string
	RetentionSeconds         int64
}

type ReleaseMaterializeDependencies

type ReleaseMaterializeDependencies struct {
	Executor       ReleaseMaterializeExecutor
	PersistReceipt func(InstallReceipt) error
	Now            func() time.Time
}

type ReleaseMaterializeExecutor

type ReleaseMaterializeExecutor interface {
	PullImage(context.Context, string) error
	ExtractMicroVMImage(context.Context, string, string) error
	Fetch(context.Context, string) ([]byte, error)
}

type ReleasePlan

type ReleasePlan struct {
	Version                string            `json:"version"`
	ArtifactManifestURL    string            `json:"artifactManifestUrl"`
	ArtifactManifestDigest string            `json:"artifactManifestDigest"`
	SigningKeyFingerprint  string            `json:"signingKeyFingerprint"`
	Images                 map[string]string `json:"images"`
	BinaryDigests          map[string]string `json:"binaryDigests"`
	ExpectedDownloadBytes  int64             `json:"expectedDownloadBytes"`
}

type ResourceKind

type ResourceKind string
const (
	ResourceDirectory       ResourceKind = "directory"
	ResourceFile            ResourceKind = "file"
	ResourceFilesystemImage ResourceKind = "filesystem_image"
	ResourceMountUnit       ResourceKind = "mount_unit"
	ResourceBinary          ResourceKind = "binary"
	ResourceComposeProject  ResourceKind = "compose_project"
)

type RouteFact

type RouteFact struct {
	Destination string `json:"destination"`
	Interface   string `json:"interface"`
	Gateway     string `json:"gateway,omitempty"`
}

type SecretTarget

type SecretTarget struct {
	Category string `json:"category"`
	Path     string `json:"path"`
}

type Stage

type Stage string
const (
	StagePreflight              Stage = "preflight"
	StagePlanAccepted           Stage = "plan_accepted"
	StageHostApply              Stage = "host_apply"
	StageReleaseVerified        Stage = "release_verified"
	StageAssetsMaterialized     Stage = "assets_materialized"
	StageDeploymentMaterialized Stage = "deployment_materialized"
	StageRunnerEnrolled         Stage = "runner_enrolled"
	StageComposeStarted         Stage = "compose_started"
	StageCLILogin               Stage = "cli_login"
	StageReadiness              Stage = "readiness"
	StageSmokeExecution         Stage = "smoke_execution"
)

type StageRecord

type StageRecord struct {
	Stage       Stage             `json:"stage"`
	CompletedAt time.Time         `json:"completedAt"`
	Evidence    map[string]string `json:"evidence"`
}

type StorageChoice

type StorageChoice string
const (
	StorageExistingMount StorageChoice = "existing_mount"
	StorageBtrfsImage    StorageChoice = "btrfs_image"
)

type StorageOption

type StorageOption struct {
	Choice         StorageChoice
	Label          string
	Mountpoint     string
	DeviceIdentity string
	Filesystem     string
	AvailableBytes int64
}

func StorageOptions

func StorageOptions(facts HostFacts, backingAvailableBytes, releaseDownloadBytes int64) []StorageOption

type StoragePlan

type StoragePlan struct {
	Choice                 StorageChoice `json:"choice"`
	WorkspacePath          string        `json:"workspacePath"`
	ExistingDeviceIdentity string        `json:"existingDeviceIdentity,omitempty"`
	FilesystemImagePath    string        `json:"filesystemImagePath,omitempty"`
	ImageSizeBytes         int64         `json:"imageSizeBytes,omitempty"`
	MountUnitPath          string        `json:"mountUnitPath,omitempty"`
}

type SystemHostApplyExecutor

type SystemHostApplyExecutor struct{ CallerUID int }

func (SystemHostApplyExecutor) AllocateFilesystemImage

func (SystemHostApplyExecutor) AllocateFilesystemImage(PlannedPath, int64) error

func (SystemHostApplyExecutor) CreateDirectory

func (SystemHostApplyExecutor) CreateDirectory(PlannedPath) error

func (SystemHostApplyExecutor) EffectiveUID

func (SystemHostApplyExecutor) EffectiveUID() int

func (SystemHostApplyExecutor) EnableMountUnit

func (SystemHostApplyExecutor) FormatBtrfs

func (SystemHostApplyExecutor) ProveReflinkIsolation

func (SystemHostApplyExecutor) ProveReflinkIsolation(string) (string, error)

func (SystemHostApplyExecutor) RemoveEmpty

func (SystemHostApplyExecutor) Revalidate

func (SystemHostApplyExecutor) SecureMountedWorkspace

func (SystemHostApplyExecutor) SecureMountedWorkspace(PlannedPath) error

func (SystemHostApplyExecutor) WriteMountUnit

type SystemReleaseMaterializer

type SystemReleaseMaterializer struct {
	Output     io.Writer
	Diagnostic io.Writer
	HTTPClient *http.Client
}

SystemReleaseMaterializer executes the narrow Docker and HTTPS operations required by MaterializeRelease while forwarding bounded command output.

func (SystemReleaseMaterializer) ExtractMicroVMImage

func (executor SystemReleaseMaterializer) ExtractMicroVMImage(ctx context.Context, reference, target string) (resultErr error)

func (SystemReleaseMaterializer) Fetch

func (executor SystemReleaseMaterializer) Fetch(ctx context.Context, location string) ([]byte, error)

func (SystemReleaseMaterializer) PullImage

func (executor SystemReleaseMaterializer) PullImage(ctx context.Context, reference string) error

type UIDRange

type UIDRange struct {
	Start int64 `json:"start"`
	Count int64 `json:"count"`
}

type UserProbe

type UserProbe interface {
	AssignedUIDs() (map[int64]bool, error)
}

type UserRangeProbe

type UserRangeProbe interface {
	ReservedIDRanges() ([]UIDRange, error)
}

type VerifiedArtifact

type VerifiedArtifact struct {
	SigningPublicKeyPEM []byte
	SigningKeyID        string
	ManifestDigest      string
}

func VerifyArtifactDirectory

func VerifyArtifactDirectory(directory string, release releasecontract.ArtifactManifest) (VerifiedArtifact, error)

Jump to

Keyboard shortcuts

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