Help us shape the future of Go! Take the 2026 Go Developer Survey

runtime

package
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Oct 28, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// SchemaVersion is the version of the runtime.json shape, the file the
	// daemon writes for the CLI. It is independent of the public view schema
	// (inspect.SchemaVersion), so a change to the public views never makes a
	// running daemon's snapshot unreadable to a newer CLI. Readers accept a
	// snapshot of this version and ignore fields they do not know: a daemon
	// may add fields without raising it, and raises it only for a shape an
	// older reader cannot read.
	SchemaVersion = 1

	StatusExact            = "exact"
	StatusMissing          = "missing"
	StatusMalformed        = "malformed"
	StatusUnreadable       = "unreadable"
	StatusStale            = "stale"
	StatusPartial          = "partial"
	StatusPIDMismatch      = "pid_mismatch"
	StatusRegistryMismatch = "registry_mismatch"

	ServiceRuntimeRunning = "running"
	ServiceRuntimeFailed  = "failed"

	FunnelStateNotRequested      = "not_requested"
	FunnelStateRequestedUnknown  = "requested_unverified"
	FunnelStateActive            = "active"
	FunnelStateCapabilityMissing = "capability_missing"
	FunnelStateListenFailed      = "listen_failed"
	FunnelStateStartTimeout      = "startup_timeout"
)
View Source
const (
	OwnershipSchemaVersion = 2
)

Variables

This section is empty.

Functions

func AdoptOwnedNode

func AdoptOwnedNode(path, serviceName, nodeID string, recordedAt time.Time, retire bool) error

AdoptOwnedNode records a one-time reviewed proof for an exact device match. Orphans are retired immediately as explicit cleanup authorization; services still present in registry.json receive active proof without retired_at.

func CurrentRegistryFingerprint

func CurrentRegistryFingerprint(path string) (string, error)

CurrentRegistryFingerprint fingerprints registry.json at path the way the daemon does, for comparison with the fingerprint in runtime.json.

func MarkOwnedNodeIDsRetired

func MarkOwnedNodeIDsRetired(path string, nodeIDs []string, retiredAt time.Time) error

MarkOwnedNodeIDsRetired records that remove reviewed the exact ownership rows before unregistering their service. Rows remain until remote deletion succeeds or proves the device already absent.

func RecordOwnedNode

func RecordOwnedNode(path, serviceName, nodeID string, recordedAt time.Time) error

RecordOwnedNode idempotently records exact NodeID ownership for a service.

func RegistryFingerprint

func RegistryFingerprint(reg *registry.Registry, issues []registry.ServiceIssue) (string, error)

RegistryFingerprint is the one derivation of the registry fingerprint: the daemon writes it into runtime.json and the CLI compares against it. Its input is every service entry registry.LoadForRuntime decodes, in file order, the valid ones and the ones isolated as per-service issues alike. The daemon passes what it loaded; the CLI calls CurrentRegistryFingerprint. A fingerprint over only the valid entries on one side would call a snapshot stale for as long as any service has an issue, which says nothing about whether the daemon applied the latest registry.

func Remove

func Remove(path string) error

func RemoveOwnedNodeIDs

func RemoveOwnedNodeIDs(path string, nodeIDs []string) error

RemoveOwnedNodeIDs forgets only ownership IDs that remote reconciliation has proved deleted or already absent.

func RemoveOwnedNodeIDsContext

func RemoveOwnedNodeIDsContext(ctx context.Context, path string, nodeIDs []string) error

func RemoveServiceNodeState

func RemoveServiceNodeState(configDir, name string) error

RemoveServiceNodeState deletes the tsnet state directory a service kept inside configDir.

configDir is a parameter rather than something this function reads from the environment, and the callers derive it from the registry path they just wrote. That is what keeps the deletion inside the same config directory as the decision that authorised it: a caller operating on one registry while config.Dir() resolves to another would otherwise delete a live service's node identity out of an unrelated installation, and the deletion would look entirely normal from inside that caller.

The name is validated as a registry service name before anything is removed. Callers reach this with a name that came out of the ownership ledger or off a command line, and a registry service name is a DNS label -- no separator, no dot segment, no empty string -- so the validation is what keeps a malformed or hostile name from turning a RemoveAll of one directory into a RemoveAll of the nodes directory itself, or of something beside it.

It is deliberately not a decision about *whether* removing the state is safe. That decision needs facts this package does not have (was the remote node actually deleted, is a tsnet server still holding the directory), so it stays with the callers that have them.

func Save

func Save(path string, snapshot Snapshot) error

func ServiceNodeStateConfigDir

func ServiceNodeStateConfigDir(registryPath string) string

ServiceNodeStateConfigDir maps a registry path to the config directory that owns it. Registry and node state are siblings by construction, so this is the one derivation both callers use.

func TryRemoveOwnedNodesIfUnchanged

func TryRemoveOwnedNodesIfUnchanged(path string, oldNodes []OwnedNode) (bool, error)

TryRemoveOwnedNodesIfUnchanged clears only the exact rows that were proved resolved before local cleanup. A newly recorded identity with the same ID must survive a writer arriving after the final state deletion.

func TryUpdateOwnership

func TryUpdateOwnership(path string, update func(*OwnershipLedger) (bool, error)) (bool, error)

TryUpdateOwnership updates the ledger atomically without waiting for a writer. A busy lock leaves both the local state and its retry evidence unchanged.

func TryWithLockedOwnership

func TryWithLockedOwnership(path string, fn func(OwnershipLedger) error) (bool, error)

TryWithLockedOwnership reports false on contention without invoking fn.

func UpdateOwnership

func UpdateOwnership(path string, update func(*OwnershipLedger) (changed bool, err error)) error

UpdateOwnership loads the ledger under its lock and hands it to update, saving it afterwards when update reports a change. What update reads, and any action it takes on that basis, happens before any other writer can change the ledger.

func WithLockedOwnership

func WithLockedOwnership(path string, fn func(OwnershipLedger) error) error

WithLockedOwnership keeps a read and its local-state action atomic with RecordOwnedNode and RemoveOwnedNodeIDs. The callback must not write the ownership ledger or acquire this lock again.

func WithOwnershipLock

func WithOwnershipLock(path string, fn func() error) error

WithOwnershipLock runs fn while holding the ledger's lock, the lock every ledger writer in this package takes in every process. A decision fn makes cannot be overtaken by a RecordOwnedNode, MarkOwnedNodeIDsRetired or RemoveOwnedNodeIDs. The lock is not reentrant: fn must not call those writers, or UpdateOwnership.

Types

type ExpectedRuntime

type ExpectedRuntime struct {
	DaemonPID                  int
	DaemonStartedAtLowerBound  time.Time
	CurrentRegistryFingerprint string
}

type Freshness

type Freshness struct {
	Status  string `json:"status"`
	Code    string `json:"code,omitempty"`
	Exact   bool   `json:"exact"`
	Message string `json:"message,omitempty"`
}

func Classify

func Classify(snapshot *Snapshot, loadErr error, expected ExpectedRuntime) Freshness

type GatewayRouteState added in v0.2.0

type GatewayRouteState struct {
	HealthChecked *time.Time `json:"health_checked,omitempty"`
	App           string     `json:"app"`
	Port          int        `json:"port"`
	URL           string     `json:"url,omitempty"`
	State         string     `json:"state"`
	Reason        string     `json:"reason,omitempty"`
	HostLabel     string     `json:"host_label"`
	Health        string     `json:"health,omitempty"`
	OfflineSince  *time.Time `json:"offline_since,omitempty"`
}

type GatewayState added in v0.2.0

type GatewayState struct {
	Dropped         uint64                `json:"dropped"`
	Enabled         bool                  `json:"enabled"`
	Persistent      bool                  `json:"persistent"`
	Tagged          bool                  `json:"tagged"`
	NodeID          string                `json:"node_id,omitempty"`
	OwnershipProven bool                  `json:"ownership_proven"`
	IdentityChanged bool                  `json:"identity_changed,omitempty"`
	Ports           registry.GatewayPorts `json:"ports"`
	Routes          []GatewayRouteState   `json:"routes"`
}

type OwnedNode

type OwnedNode struct {
	ServiceName string     `json:"service_name"`
	NodeID      string     `json:"node_id"`
	RecordedAt  time.Time  `json:"recorded_at"`
	RetiredAt   *time.Time `json:"retired_at,omitempty"`
}

OwnedNode is durable proof that a concrete Tailscale StableNodeID was observed from a TSLink-managed tsnet node for ServiceName.

func RetireServiceNodes

func RetireServiceNodes(path, serviceName string, retiredAt time.Time, commit func() error) ([]OwnedNode, error)

RetireServiceNodes records the removal of serviceName in the same step as the removal itself. Holding the ledger lock, it marks every row of the service retired and then runs commit, the registry write that unregisters the service; when commit fails the ledger is put back exactly as it was. It returns the service's rows as they were before retirement.

The rows are the ones the ledger holds at that moment, not a snapshot taken before the caller waited for the registry lock: RecordOwnedNode takes the same ledger lock, so a node that enrolled for the service while the removal waited is retired with the others instead of keeping a row without retired_at, which would withhold its device deletion for good.

func RetireServiceNodesContext

func RetireServiceNodesContext(ctx context.Context, path, serviceName string, retiredAt time.Time, commit func() error) ([]OwnedNode, error)

type OwnershipLedger

type OwnershipLedger struct {
	SchemaVersion int         `json:"schema_version"`
	Nodes         []OwnedNode `json:"nodes"`
}

func LoadOwnership

func LoadOwnership(path string) (OwnershipLedger, error)

LoadOwnership reads the durable ownership ledger. An absent or empty file is an empty ledger; malformed data returns an actionable coded error so callers can disable deletion without taking down service availability.

func PreviewAdoptOwnedNode

func PreviewAdoptOwnedNode(path, serviceName, nodeID string, recordedAt time.Time, retire bool) (OwnershipLedger, error)

PreviewAdoptOwnedNode returns the exact proof that adoption would persist, without writing the ledger. retire is true only for a service absent from the registry, allowing cleanup dry-run to preview the corresponding deletion.

type PortalState

type PortalState struct {
	Message         string        `json:"message,omitempty"`
	UnavailableApps []string      `json:"unavailable_apps,omitempty"`
	Gateway         *GatewayState `json:"gateway,omitempty"`
	Enabled         bool          `json:"enabled"`
	Hostname        string        `json:"hostname,omitempty"`
	State           string        `json:"state"`
	URL             string        `json:"url,omitempty"`
	Error           string        `json:"error,omitempty"`
}

PortalState is runtime evidence for the independent Tailnet portal node.

type ServiceError

type ServiceError struct {
	Code      string                     `json:"code"`
	Message   string                     `json:"message"`
	Next      []string                   `json:"next,omitempty"`
	Provision *registry.ProvisionOutcome `json:"provision,omitempty"`
}

ServiceError is stable, actionable failure data persisted for agent consumers.

type ServiceSnapshot

type ServiceSnapshot struct {
	Health   health.State          `json:"health"`
	NodeKey  health.Expiry         `json:"node_key"`
	Warnings []inspect.WarningView `json:"warnings,omitempty"`

	Name            string               `json:"name"`
	Type            string               `json:"type"`
	NodeID          string               `json:"node_id,omitempty"`
	RuntimeState    string               `json:"runtime_state"`
	Endpoint        inspect.EndpointView `json:"endpoint"`
	Exposure        inspect.ExposureView `json:"exposure"`
	FunnelRequested bool                 `json:"funnel_requested"`
	FunnelActive    bool                 `json:"funnel_active"`
	FunnelState     string               `json:"funnel_state"`
	Error           *ServiceError        `json:"error,omitempty"`
	CertDomains     []string             `json:"cert_domains,omitempty"`
}

type ServiceState

type ServiceState struct {
	Health       health.State
	NodeKey      health.Expiry
	Warnings     []inspect.WarningView
	Service      registry.Service
	NodeID       string
	RuntimeHost  string
	RuntimeState string
	FunnelState  string
	Error        *ServiceError
	CertDomains  []string
}

type Snapshot

type Snapshot struct {
	AccessLog           *accesslog.Health `json:"access_log,omitempty"`
	Portal              PortalState       `json:"portal"`
	Alerts              health.AlertsView `json:"alerts"`
	SchemaVersion       SnapshotVersion   `json:"schema_version"`
	DaemonPID           int               `json:"daemon_pid"`
	DaemonStartedAt     time.Time         `json:"daemon_started_at"`
	RegistryFingerprint string            `json:"registry_fingerprint"`
	UpdatedAt           time.Time         `json:"updated_at"`
	Partial             bool              `json:"partial,omitempty"`
	GlobalError         *ServiceError     `json:"global_error,omitempty"`
	Services            []ServiceSnapshot `json:"services"`
}

func Load

func Load(path string) (*Snapshot, error)

func NewPartialSnapshot

func NewPartialSnapshot(daemonPID int, daemonStartedAt time.Time, registryFingerprint string, updatedAt time.Time, states []ServiceState) Snapshot

NewPartialSnapshot returns non-authoritative progress evidence produced while a registry synchronization is still starting services. Consumers may use services present in it as positive evidence, but absence is not evidence that any other registered service is down or missing.

func NewSnapshot

func NewSnapshot(daemonPID int, daemonStartedAt time.Time, registryFingerprint string, updatedAt time.Time, states []ServiceState) Snapshot

type SnapshotError

type SnapshotError struct {
	Status string
	Code   string
	Err    error
}

func (*SnapshotError) Error

func (e *SnapshotError) Error() string

func (*SnapshotError) StableCode

func (e *SnapshotError) StableCode() string

func (*SnapshotError) Unwrap

func (e *SnapshotError) Unwrap() error

type SnapshotVersion

type SnapshotVersion int

SnapshotVersion is runtime.json's schema_version. It reads the string that builds before the integer version wrote as version 1.

func (*SnapshotVersion) UnmarshalJSON

func (v *SnapshotVersion) UnmarshalJSON(data []byte) error

Jump to

Keyboard shortcuts

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