Documentation
¶
Index ¶
- Constants
- func CanonicalSubscriptionSourceManifest(manifest SubscriptionSourceManifestV1) ([]byte, string, error)
- func SnapshotKey(pluginID, subscriptionID string) string
- func SubscriptionSourceVersion(canonical []byte) string
- func TraceLevelAtLeast(have, want TraceLevel) bool
- func ValidTraceLevel(l TraceLevel) bool
- type APIError
- type APIErrorResponse
- type AgentConfig
- type AgentDebugBatch
- type AgentDebugConfig
- type AgentDebugPolicy
- type AgentLaunchConfig
- type AgentUpdatePolicy
- type Approval
- type AuditEvent
- type ConnRecord
- type ConnRecordKey
- type DDNSProfile
- type DNSDeployment
- type DNSRecord
- type DNSZone
- type GeoRouting
- type Group
- type GroupNetPolicy
- type GroupNetRule
- type GroupSelector
- type GuardInterface
- type GuardListener
- type GuardNodeReality
- type GuardPortRange
- type GuardRule
- type GuardZone
- type HopPath
- type HostFacts
- type KVEntry
- type LogBatch
- type LogLine
- type LogSource
- type MachineProfile
- type MachineVendor
- type Metrics
- type Monitor
- type MonitorResult
- type NFTInputs
- type NetEndpoint
- type NetPolicy
- type NetRule
- type Node
- type NodeGeo
- type NodeGuardBinding
- type NodeIPConfig
- type NodeInventory
- type NotifyChannel
- type NotifyRule
- type OIDCIdentity
- type OIDCProvider
- type PluginInstallation
- type ProxyInbound
- type ProxyNodeProfile
- type ProxyUsageSnapshot
- type ProxyUser
- type SecurityGroup
- type ShareSource
- type SingBoxInventory
- type SingBoxNode
- type StaticObject
- type StorageAccessToken
- type StorageBinding
- type StorageBucket
- type SubscriptionShare
- type SubscriptionSnapshot
- type SubscriptionSourceManifestEdge
- type SubscriptionSourceManifestEndpoint
- type SubscriptionSourceManifestEntry
- type SubscriptionSourceManifestIdentity
- type SubscriptionSourceManifestTerminal
- type SubscriptionSourceManifestV1
- type Task
- type TaskLease
- type TaskResult
- type TerminalEvent
- type TerminalInput
- type TerminalSession
- type Token
- type TraceAgentConfig
- type TraceAgentSession
- type TraceBatch
- type TraceFilter
- type TraceLevel
- type TraceLine
- type TraceMarker
- type TracePolicy
- type TraceSession
- type TunnelIngress
- type TunnelProfile
- type User
- type WGExternalPeer
- type WGMembership
- type WGNetwork
- type WorkerScript
Constants ¶
const ( TaskQueued = "queued" TaskLeased = "leased" TaskFinished = "finished" TaskFailed = "failed" // TaskCancelled is an operator-cancelled task. Only a queued task can be // cancelled (a leased task is already running on the agent and cannot be // reliably stopped from the server); it is then never leased. TaskCancelled = "cancelled" TerminalPending = "pending" TerminalOpen = "open" TerminalClosed = "closed" TerminalFailed = "failed" ApprovalPending = "pending" ApprovalApproved = "approved" ApprovalRejected = "rejected" ApprovalApplied = "applied" PluginStatusVerified = "verified" PluginStatusInstalled = "installed" PluginStatusActive = "active" PluginStatusDisabled = "disabled" )
const ( NodeIPModeAuto = "auto" // static override if set, else resolver probe NodeIPModeStatic = "static" // use the operator-provided static IPs only NodeIPModeResolver = "resolver" // always probe the resolvers, ignore static NodeIPModeScript = "script" // run an operator-provided script on the agent )
const ( RenewalCycleMonthly = "monthly" RenewalCycleQuarterly = "quarterly" RenewalCycleSemiannual = "semiannual" RenewalCycleAnnual = "annual" RenewalCycleCustomDays = "custom_days" )
const ( DNSEngineCoreDNS = "coredns" DNSExposureMesh = "mesh" DNSExposurePublic = "public" DNSZoneForward = "forward" DNSZoneStatic = "static" DNSZoneBlock = "block" DNSStatusPending = "pending" DNSStatusApplying = "applying" DNSStatusRunning = "running" DNSStatusFailed = "failed" DNSStatusDisabled = "disabled" )
const ( GeoRoutingStrategyGeoIP = "geoip" GeoRoutingStrategyAllHealthy = "all-healthy" )
const ( NetRuleAllow = "allow" NetRuleDeny = "deny" NetDirEgress = "egress" NetDirIngress = "ingress" NetProtoTCP = "tcp" NetProtoUDP = "udp" NetProtoAny = "any" NetRefNode = "node" NetRefCIDR = "cidr" NetRefDomain = "domain" NetRefAny = "any" // NetRefGroup is a group-scoped remote. It is an authoring-layer kind only: // the server expands it to one concrete node ref per resolved group member // before compilation, so the per-node nft compiler never sees a group ref. NetRefGroup = "group" )
const ( // NetRefZone is a guard-zone-scoped remote (design-13). Like NetRefGroup it // is an authoring-layer kind: the server resolves the zone to concrete // interface/CIDR matches before the per-node nft compiler runs. NetRefZone = "zone" GuardProtoICMP = "icmp" GuardProtoICMPv6 = "icmpv6" // Builtin guard-zone ids. "public" resolves to the node's public-facing // interface, "loopback" is the always-accepted lo scaffold, and overlay // zones ("wireguard", "tailscale") resolve to the node's discovered or // managed overlay interfaces/CIDRs. GuardZonePublic = "public" GuardZoneLoopback = "loopback" GuardZoneWireGuard = "wireguard" GuardZoneTailscale = "tailscale" )
const ( // WireGuard topology modes (design-13 §5.3). Mesh is today's implicit // behavior; hub-and-spoke and custom are additive. WGTopologyMesh = "mesh" WGTopologyHubSpoke = "hub-and-spoke" WGTopologyCustom = "custom" WGRoleHub = "hub" WGRoleSpoke = "spoke" WGRolePeer = "peer" WGDefaultListenPort = 51820 WGDefaultKeepalive = 25 )
const ( ProxyCoreSingbox = "sing-box" ProxyCoreXray = "xray" ProxyProtocolVLESS = "vless" ProxyProtocolVMess = "vmess" ProxyProtocolTrojan = "trojan" ProxyProtocolShadowsocks = "shadowsocks" ProxyProtocolHysteria2 = "hysteria2" ProxyTransportTCP = "tcp" ProxyTransportWS = "ws" ProxyTransportGRPC = "grpc" ProxyTransportHTTP2 = "http2" ProxyTransportQUIC = "quic" ProxySecurityNone = "none" ProxySecurityTLS = "tls" ProxySecurityReality = "reality" ProxyUserStatusActive = "active" ProxyUserStatusExpired = "expired" ProxyUserStatusOverQuota = "over_quota" ProxyUserStatusDisabled = "disabled" ProxyUsageCollectorStatusOK = "ok" ProxyUsageCollectorStatusError = "error" )
const ( APIErrorBadRequest = "bad_request" APIErrorForbidden = "forbidden" APIErrorNotFound = "not_found" APIErrorMethodNotAllowed = "method_not_allowed" APIErrorRateLimited = "rate_limited" APIErrorBadGateway = "bad_gateway" APIErrorInternal = "internal_error" APIErrorRequestFailed = "request_failed" APIErrorCapabilityDenied = "capability_denied" APIErrorApprovalStale = "approval_stale" APIErrorAgentUpdateNoop = "agent_update_noop" APIErrorMFARequired = "mfa_required" APIErrorInvalidNodeToken = "invalid_node_token" APIErrorInvalidTaskLease = "invalid_task_lease" APIErrorTaskOutputLimitExceeded = "task_output_limit_exceeded" )
const ( StorageKindKV = "kv" StorageKindStatic = "static" StorageAccessAdmin = "admin" StorageAccessRead = "read" StorageAccessWrite = "write" )
const ( DDNSProviderCloudflare = "cloudflare" DDNSProviderWebhook = "webhook" )
const ( MonitorTypeTCP = "tcp" MonitorTypeHTTP = "http" MonitorTypeICMP = "icmp" )
const ( ShareSourceCoreProxyUser = "core.proxy_user" // the share's token and never owns the route. ShareSourcePlugin = "plugin" )
const ( SubscriptionSourceManifestSchemaV1 = "lattice.subscription-source-manifest.v1" SubscriptionSourceRendererV1 = "vpn-core-graph-v1" MaxSubscriptionSourceRoots = 2_048 MaxSubscriptionSourceVisits = 10_000 MaxSubscriptionURIBytes = 4 << 10 MaxSubscriptionSourceManifestBytes = 1 << 20 MaxSubscriptionRawBytes = 1 << 20 MaxSubscriptionResponseBytes = 4 << 20 )
const ( TraceSessionRunning = "running" TraceSessionExpired = "expired" TraceSessionStopped = "stopped" )
Trace session states.
const ( CloseEOF = "eof" // upload/download finished CloseCanceled = "canceled" // closed, no error (trace level) CloseReset = "reset" // connection reset by peer CloseTimeout = "timeout" // i/o timeout CloseDialFailed = "dial_failed" // open connection to ... using outbound/... CloseAuthFailed = "auth_failed" // process connection from ... (no user is known) CloseHandshakeFailed = "handshake_failed" // TLS handshake / upload handshake CloseUDPIdle = "udp_idle" // packet upload closed, no error CloseCoreRestart = "core_restart" // core generation changed under an open connection // CloseUnknown is an honest gap: the stream ended, or the id never produced // a terminal line. It must never be rendered as a clean close. CloseUnknown = "unknown" )
How a connection ended. Derived from the last line sing-box emitted for it; see SINGBOX-TRACE-DESIGN.md section 4.6 for the mapping.
const ( UserKindManaged = "managed" // u_<16hex>, reversible to a Lattice user UserKindLegacy = "legacy" // a free-text operator label on a legacy ProxyUser UserKindDiscovered = "discovered" // a third-party adopted node's named user UserKindUnnamed = "unnamed" // sing-box logged an index, not a name // UserKindUnobserved means the identity was never delivered for this // connection, which is different from sing-box declining to name a user. // // The case that matters is multiplexing: a VLESS mux transport // authenticates the user on the OUTER connection, and sing-box then mints a // fresh log id for every inner stream. The inner streams begin at routing // or outbound, so no user-bearing line ever carries their id, and the Clash // API does not serialise the user either. The evidence does not exist under // that id, so calling it unnamed would blame sing-box for something it was // never asked. It also covers a collector that started mid-connection. UserKindUnobserved = "unobserved" UserKindUnresolved = "unresolved" // a name that no lookup could place, yet )
How confidently a user was attributed to a connection.
const ( HopConfidenceExact = "exact" // identity carried through the chain (carry_identity) HopConfidenceInferred = "inferred" // one candidate matched dst + window + declared edge HopConfidenceAmbiguous = "ambiguous" // several candidates matched; Candidates lists them HopConfidenceNone = "none" // no downstream record matched )
How a hop path was joined across machines.
const ( MarkerCoreRestart = "core_restart" // sing-box restarted; Count is connections swept MarkerConfigApply = "config_apply" // an approved apply landed on the node MarkerSubFetch = "sub_fetch" // a subscription URL was fetched by a user MarkerSession = "session" // a trace session started or ended )
TraceMarkerKind classifies the events drawn on the trace timeline. Each comes from a signal Lattice already records; none is new instrumentation.
const (
ApprovalStaleAgentUpdatePolicyChanged = "agent_update_policy_changed"
)
SubscriptionShareSchemaVersion is the current record shape. Readers must tolerate a higher value rather than refuse the record: migrations are additive only, so a newer record is readable, and Extra preserves what this version does not name.
const SubscriptionSnapshotSchemaVersion = 2
SubscriptionSnapshotSchemaVersion is the current record shape.
Variables ¶
This section is empty.
Functions ¶
func CanonicalSubscriptionSourceManifest ¶ added in v0.2.19
func CanonicalSubscriptionSourceManifest(manifest SubscriptionSourceManifestV1) ([]byte, string, error)
func SnapshotKey ¶ added in v0.2.19
SnapshotKey is the storage key for one subscription's snapshot.
func SubscriptionSourceVersion ¶ added in v0.2.19
func TraceLevelAtLeast ¶ added in v0.2.19
func TraceLevelAtLeast(have, want TraceLevel) bool
TraceLevelAtLeast reports whether have is at least as verbose as want.
func ValidTraceLevel ¶ added in v0.2.19
func ValidTraceLevel(l TraceLevel) bool
ValidTraceLevel reports whether l is one of the three known levels. Callers fail closed on false rather than defaulting, because silently collecting at the wrong verbosity is both a cost and a privacy surprise.
Types ¶
type APIError ¶ added in v0.2.0
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
RequestID string `json:"request_id"`
}
APIError is the stable machine-readable error shape shared by server, dashboard, agents and plugins. Messages are user-facing; callers should make authorization and retry decisions from Code.
type APIErrorResponse ¶ added in v0.2.0
type APIErrorResponse struct {
Error APIError `json:"error"`
}
type AgentConfig ¶ added in v0.2.1
type AgentConfig struct {
Debug AgentDebugConfig `json:"debug"`
// LatticeIdentityUUID is the server-minted, stable node identity used by
// line discovery metadata and cross-node line graph resolution. Empty means
// the server has not minted one yet; old agents ignore this field.
LatticeIdentityUUID string `json:"lattice_identity_uuid,omitempty"`
// TerminalTransport is the server's per-node override for the agent terminal
// transport: "poll" or "stream". Empty means "no override" — the agent keeps
// its startup -terminal-transport / LATTICE_TERMINAL_TRANSPORT value. This is
// the rollout lever for promoting streaming per node without a redeploy; it
// affects only sessions opened after the change, never in-flight ones.
TerminalTransport string `json:"terminal_transport,omitempty"`
// IPConfig is the server's per-node override for public-IP discovery. nil
// means "no override" — the agent keeps its startup -ip-mode/-ip-resolvers
// flags. Old agents that do not know this field ignore it safely.
IPConfig *NodeIPConfig `json:"ip_config,omitempty"`
}
type AgentDebugBatch ¶ added in v0.2.1
type AgentDebugBatch struct {
NodeID string `json:"node_id"`
Lines []string `json:"lines"`
CapturedAt time.Time `json:"captured_at"`
}
AgentDebugBatch carries locally emitted agent diagnostics to the server log store when server-side collection is enabled for the node.
type AgentDebugConfig ¶ added in v0.2.1
type AgentDebugConfig struct {
Enabled bool `json:"enabled"`
Collect bool `json:"collect"`
MaxLineBytes int `json:"max_line_bytes,omitempty"`
MaxBatchLines int `json:"max_batch_lines,omitempty"`
}
AgentDebugConfig is the runtime policy an agent polls from the server. It is intentionally small and non-secret so older agents can ignore it safely.
type AgentDebugPolicy ¶ added in v0.2.1
type AgentDebugPolicy struct {
Enabled bool `json:"enabled"`
Collect bool `json:"collect"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
AgentDebugPolicy is the operator-owned diagnostic mode for a node-agent. When Enabled is true the agent emits verbose non-secret diagnostics locally on the node. Collect controls whether those diagnostics are also shipped back to the server log store. Server-managed debug collection defaults to true when enabled, but operators may keep local node debug output without central collection by setting Collect=false.
type AgentLaunchConfig ¶ added in v0.2.10
type AgentLaunchConfig struct {
AllowExec bool `json:"allow_exec"`
AllowRootExec bool `json:"allow_root_exec"`
NoExec bool `json:"no_exec"`
AllowTerminal bool `json:"allow_terminal"`
TerminalTransport string `json:"terminal_transport,omitempty"`
SSHAlerts bool `json:"ssh_alerts"`
SingBoxDiscover bool `json:"singbox_discover"`
SingBoxBin string `json:"singbox_bin,omitempty"`
ProxyUsageFile string `json:"proxy_usage_file,omitempty"`
ProxyUsageURL string `json:"proxy_usage_url,omitempty"`
ProxyUsageXrayAPI string `json:"proxy_usage_xray_api,omitempty"`
ProxyUsageXrayBin string `json:"proxy_usage_xray_bin,omitempty"`
ProxyUsageXrayPattern string `json:"proxy_usage_xray_pattern,omitempty"`
// SingBoxStatsAPI is the loopback host:port of the sing-box experimental
// stats API (ADR-004); empty disables the singbox-stats collector.
SingBoxStatsAPI string `json:"singbox_stats_api,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
AgentLaunchConfig mirrors lattice-agent startup flags/env vars used by the dashboard's enroll/reconfigure command generator. Runtime overrides that already-running agents poll remain in AgentConfig instead.
type AgentUpdatePolicy ¶ added in v0.2.0
type AgentUpdatePolicy struct {
NodeID string `json:"node_id"`
Enabled bool `json:"enabled"`
AutoPlan bool `json:"auto_plan"`
TargetVersion string `json:"target_version"`
BinaryURL string `json:"binary_url"`
SHA256 string `json:"sha256"`
InstallPath string `json:"install_path"`
ServiceName string `json:"service_name"`
LastPlannedVersion string `json:"last_planned_version,omitempty"`
LastPlannedAt time.Time `json:"last_planned_at,omitempty"`
LastAppliedVersion string `json:"last_applied_version,omitempty"`
LastAppliedAt time.Time `json:"last_applied_at,omitempty"`
LastError string `json:"last_error,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
AgentUpdatePolicy is a server-owned node-agent update intent. It carries no secrets: operators provide a public HTTPS binary URL plus the expected SHA-256 digest, and the server turns that into a reviewed, plan-hash-bound update task. AutoPlan never mutates a node directly; it only creates a pending approval when the node reports a different AgentVersion and no equivalent pending/approved update is already open.
type Approval ¶
type Approval struct {
ID string `json:"id"`
NodeID string `json:"node_id"`
Plugin string `json:"plugin"`
Action string `json:"action"`
Plan string `json:"plan"`
Status string `json:"status"`
Reason string `json:"reason,omitempty"`
Stale bool `json:"stale,omitempty"`
StaleCode string `json:"stale_code,omitempty"`
ActorID string `json:"actor_id"`
ApprovedBy string `json:"approved_by,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Operation binding (spec §9.3). When a plugin's plan-effect method produces a
// host-risk plan, the approval records the exact code and inputs that produced it,
// so execution can refuse to run against anything that has since changed. These are
// typed columns rather than values buried in Plan: each is compared to live state at
// execute time, and each is what the operator's review is accountable for.
//
// All omitempty, so an approval that is not a plugin operation (nft, dns, agent
// update) serializes exactly as before.
PluginVersion string `json:"plugin_version,omitempty"`
ArtifactDigest string `json:"artifact_digest,omitempty"`
Service string `json:"service,omitempty"`
Method string `json:"method,omitempty"`
// RequestSHA256 hashes the request that produced the plan. Re-planning with
// different inputs yields a different approval; an approval cannot be replayed
// against a request the operator never saw.
RequestSHA256 string `json:"request_sha256,omitempty"`
// Targets is the full node set the operation would touch. NodeID stays as the first
// target so the existing per-node approval machinery keeps working unchanged.
Targets []string `json:"targets,omitempty"`
}
type AuditEvent ¶
type AuditEvent struct {
ID string `json:"id"`
At time.Time `json:"at"`
ActorID string `json:"actor_id"`
TokenID string `json:"token_id"`
NodeID string `json:"node_id"`
Action string `json:"action"`
Scope string `json:"scope"`
Decision string `json:"decision"`
Reason string `json:"reason"`
CorrelationID string `json:"correlation_id"`
Metadata map[string]string `json:"metadata,omitempty"`
}
type ConnRecord ¶ added in v0.2.19
type ConnRecord struct {
// Identity of where it happened.
NodeID string `json:"node_id"`
LineUUID string `json:"line_uuid,omitempty"`
LineHashID string `json:"line_hash_id,omitempty"`
InboundTag string `json:"inbound_tag,omitempty"`
InboundType string `json:"inbound_type,omitempty"`
// Identity of who. UserName is what sing-box logged; UserID is the Lattice
// user it was reversed to, empty unless UserKind is managed.
UserName string `json:"user_name,omitempty"`
UserID string `json:"user_id,omitempty"`
UserKind string `json:"user_kind,omitempty"`
// The connection itself. LogID is sing-box's per-connection id: uint32 from
// rand, unique enough within a process-lifetime window but NOT globally, so
// it is a join key only together with NodeID and CoreGeneration.
LogID uint32 `json:"log_id"`
Network string `json:"network,omitempty"` // tcp | udp
SrcIP string `json:"src_ip,omitempty"`
SrcPort int `json:"src_port,omitempty"`
DstHost string `json:"dst_host,omitempty"` // as logged: hostname or ip
DstIP string `json:"dst_ip,omitempty"` // resolved, when DNS lines were seen
DstPort int `json:"dst_port,omitempty"`
// What the router decided.
SniffedProtocol string `json:"sniffed_protocol,omitempty"`
SniffedDomain string `json:"sniffed_domain,omitempty"`
RuleIndex int `json:"rule_index,omitempty"`
RuleText string `json:"rule_text,omitempty"`
OutboundTag string `json:"outbound_tag,omitempty"`
OutboundType string `json:"outbound_type,omitempty"`
ChainEdgeUUID string `json:"chain_edge_uuid,omitempty"`
// Lifecycle.
StartedAt time.Time `json:"started_at"`
EndedAt time.Time `json:"ended_at,omitzero"`
// DurationMS comes from sing-box's own elapsed counter on the final line,
// which is more trustworthy than subtracting two agent-side timestamps.
DurationMS int64 `json:"duration_ms,omitempty"`
// Open marks a periodic snapshot of a still-running connection. Snapshots
// replace each other; only the final record has Open false.
Open bool `json:"open,omitempty"`
// Bytes, and whether they were ever actually measured. A short connection
// dies between /connections samples and is never counted: BytesKnown false
// means "we did not see", not "zero".
Upload int64 `json:"upload,omitempty"`
Download int64 `json:"download,omitempty"`
BytesKnown bool `json:"bytes_known,omitempty"`
// How it ended, and whether it looked stuck. StalledAt is set when a
// connection older than the stall floor went quiet in both directions;
// cleared if it resumes. sing-box says nothing about a stalled TCP stream,
// so this heuristic is the only signal there is.
CloseReason string `json:"close_reason,omitempty"`
CloseError string `json:"close_error,omitempty"`
StalledAt time.Time `json:"stalled_at,omitzero"`
// CoreGeneration changes when sing-box restarts. Records from a previous
// generation that never closed are swept to CloseCoreRestart, and the count
// is the blast radius of that restart.
CoreGeneration uint64 `json:"core_generation,omitempty"`
// SessionIDs are the trace sessions that captured this connection.
SessionIDs []string `json:"session_ids,omitempty"`
// HopPathID groups records stitched across machines.
HopPathID string `json:"hop_path_id,omitempty"`
}
ConnRecord is one sing-box connection as assembled on the node. It is the central object of the whole feature: the row an operator filters, sorts, and opens.
type ConnRecordKey ¶ added in v0.2.19
type ConnRecordKey struct {
NodeID string `json:"node_id"`
CoreGeneration uint64 `json:"core_generation"`
LogID uint32 `json:"log_id"`
StartedAt time.Time `json:"started_at,omitzero"`
}
ConnRecordKey identifies one ConnRecord.
StartedAt is part of the identity, not decoration. sing-box's log id is rand.Uint32, so one core generation on one node can reuse it; the assembler deliberately splits those into two connections and the store's primary key keeps both. A key without StartedAt collapses them again wherever it is used, so a lookup returns whichever the query happened to order first and a hop view can walk into the wrong connection entirely.
func KeyOf ¶ added in v0.2.19
func KeyOf(r ConnRecord) ConnRecordKey
KeyOf builds the full identity of a record.
type DDNSProfile ¶ added in v0.2.0
type DDNSProfile struct {
ID string `json:"id"`
Name string `json:"name"`
NodeID string `json:"node_id"`
Provider string `json:"provider"`
Domains []string `json:"domains"`
EnableIPv4 bool `json:"enable_ipv4"`
EnableIPv6 bool `json:"enable_ipv6"`
MaxRetries int `json:"max_retries"`
TTL int `json:"ttl"`
// IntervalSeconds is how long the server waits between attempts for this
// profile. A residential address wants minutes; a datacenter one that has
// held the same IP for a year does not, and the interval is also what
// spaces out retries when a provider is rejecting the write. Zero means the
// server default.
IntervalSeconds int `json:"interval_seconds,omitempty"`
// Cloudflare provider
CFAPIToken string `json:"cf_api_token,omitempty"`
// Webhook provider. Body/URL support the templates #ip#, #domain#, #type#.
WebhookURL string `json:"webhook_url,omitempty"`
WebhookMethod string `json:"webhook_method,omitempty"`
WebhookBody string `json:"webhook_body,omitempty"`
WebhookHeaders string `json:"webhook_headers,omitempty"`
// Status (updated by the server after each run).
LastIPv4 string `json:"last_ipv4,omitempty"`
LastIPv6 string `json:"last_ipv6,omitempty"`
LastRunAt time.Time `json:"last_run_at,omitempty"`
LastError string `json:"last_error,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
DDNSProfile describes how a node's public IP should be published to DNS. It is bound to a node; when that node's observed public IP changes, the bound profiles' records are updated.
type DNSDeployment ¶ added in v0.2.0
type DNSDeployment struct {
ID string `json:"id"`
Name string `json:"name"`
NodeID string `json:"node_id"`
Engine string `json:"engine"`
ListenPort int `json:"listen_port"`
EnableUDP bool `json:"enable_udp"`
EnableTCP bool `json:"enable_tcp"`
Exposure string `json:"exposure"`
Zones []DNSZone `json:"zones"`
Hostname string `json:"hostname,omitempty"`
PublishIPv4 bool `json:"publish_ipv4"`
PublishIPv6 bool `json:"publish_ipv6"`
RecordTTL int `json:"record_ttl,omitempty"`
CFAPIToken string `json:"cf_api_token,omitempty"`
DDNSProfileID string `json:"ddns_profile_id,omitempty"`
Status string `json:"status"`
EngineVersion string `json:"engine_version,omitempty"`
LastIPv4 string `json:"last_ipv4,omitempty"`
LastIPv6 string `json:"last_ipv6,omitempty"`
LastAppliedAt time.Time `json:"last_applied_at,omitempty"`
LastError string `json:"last_error,omitempty"`
LastPublishedAt time.Time `json:"last_published_at,omitempty"`
LastPublishError string `json:"last_publish_error,omitempty"`
Disabled bool `json:"disabled,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
DNSDeployment is the control-plane intent for a self-hosted DNS service on a node. CFAPIToken is a server-side secret and must never appear in read views or agent payloads.
type DNSRecord ¶ added in v0.2.0
type DNSRecord struct {
Name string `json:"name"`
Type string `json:"type"`
Value string `json:"value"`
TTL int `json:"ttl,omitempty"`
}
DNSRecord is a static authoritative record for a DNSZoneStatic zone.
type DNSZone ¶ added in v0.2.0
type DNSZone struct {
Suffix string `json:"suffix"`
Mode string `json:"mode"`
Upstreams []string `json:"upstreams,omitempty"`
Records []DNSRecord `json:"records,omitempty"`
}
DNSZone is one served block in a self-hosted resolver configuration. It is server-owned intent; the agent only receives the rendered, approved artifact.
type GeoRouting ¶ added in v0.2.0
type GeoRouting struct {
ID string `json:"id"`
Name string `json:"name"`
Hostname string `json:"hostname"` // the geo apex, e.g. dns.roobli.org
NodeIDs []string `json:"node_ids"` // participating targets (need NodeGeo + IP)
DNSNodeIDs []string `json:"dns_node_ids"` // authoritative DNS nodes (run self-host DNS)
TTL int `json:"ttl,omitempty"`
Strategy string `json:"strategy"` // "geoip" | "all-healthy"
GeoIPDBPath string `json:"geoip_db_path,omitempty"` // GeoLite2 path on the node
// Parent-zone NS delegation reuses the referenced DDNSProfile's CF token.
PublishNS bool `json:"publish_ns,omitempty"`
DDNSProfileID string `json:"ddns_profile_id,omitempty"`
LastRenderedSHA string `json:"last_rendered_sha,omitempty"`
Status string `json:"status,omitempty"`
LastAppliedAt time.Time `json:"last_applied_at,omitempty"`
LastDelegatedAt time.Time `json:"last_delegated_at,omitempty"`
LastError string `json:"last_error,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
GeoRouting answers one apex hostname (e.g. dns.roobli.org) with the nearest healthy participating node, served by Lattice's own DNS nodes (Design 06, Path B). It carries no secrets: the NS-delegation token is reused from the referenced DDNSProfile.
type Group ¶ added in v0.2.4
type Group struct {
ID string `json:"id"` // "grp_<ulid>"
Name string `json:"name"` // unique, display
Slug string `json:"slug"` // url/nft-safe, unique, immutable
Description string `json:"description,omitempty"`
Color string `json:"color"` // design-token name, never raw hex (CSP)
Icon string `json:"icon,omitempty"` // lucide icon name
ParentID string `json:"parent_id,omitempty"` // single parent; "" = root
Order int `json:"order"` // sort weight within the parent
Members []string `json:"members"` // explicit operator-pinned node IDs
Selector *GroupSelector `json:"selector,omitempty"` // dynamic smart-membership source
// LeaderID is the operator-designated group leader. It must be an explicit
// Member of the group (validated on upsert); empty means "no leader". This is
// the real, first-class field that replaces the old role-name heuristic used
// by the dashboard to mark a node as its group's leader.
LeaderID string `json:"leader_id,omitempty"`
System bool `json:"system,omitempty"` // built-in (e.g. "Ungrouped"); limited edits
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Group is a first-class fleet organization entity. Members are explicit, operator-pinned node IDs; Selector is a dynamic smart-membership source used by dashboard grouping and group policy planning. Because selector matches can change when node tags, role, or geo facts change, group-policy plans must surface selector impact and require fresh approval. Color is a design-token name (e.g. "sky", "violet"), never a raw hex value, so the dashboard stays CSP-safe. Slug is url/nft-safe, unique, and treated as immutable once assigned. ParentID gives a single-parent hierarchy ("" = root); Order is the operator-controlled sort weight within a parent.
type GroupNetPolicy ¶ added in v0.2.4
type GroupNetPolicy struct {
ID string `json:"id"` // "gnp_<ulid>"
ScopeGroupID string `json:"scope_group_id"` // applies to members of this group
Rules []GroupNetRule `json:"rules"`
Enabled bool `json:"enabled"`
Priority int `json:"priority"` // lower wins when a node is in 2+ scoped groups
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
GroupNetPolicy is a group-scoped authoring layer over the unchanged per-node NetPolicy engine. The server expands it into one NetPolicy per member of ScopeGroupID before compilation; the agent never receives it. Priority breaks ties when a node is a member of two or more scoped groups (lower wins).
type GroupNetRule ¶ added in v0.2.4
type GroupNetRule struct {
ID string `json:"id"`
Comment string `json:"comment,omitempty"`
Action string `json:"action"`
Direction string `json:"direction"`
Protocol string `json:"protocol"`
Ports []int `json:"ports,omitempty"`
Remote NetEndpoint `json:"remote"` // Kind may be NetRefGroup in addition to node|cidr|domain|any
Disabled bool `json:"disabled,omitempty"`
}
GroupNetRule mirrors NetRule but its Remote may additionally be a group ref (Remote.Kind == NetRefGroup with Remote.GroupID set). The server fans a group remote out to one node-ref rule per resolved remote member during expansion, so the compiled per-node rule set only ever contains node/cidr/domain/any remotes.
type GroupSelector ¶ added in v0.2.4
type GroupSelector struct {
MatchTagsAny []string `json:"match_tags_any,omitempty"`
MatchRoles []string `json:"match_roles,omitempty"`
MatchCountry []string `json:"match_country,omitempty"` // ISO-3166 alpha-2 codes
MatchContinent []string `json:"match_continent,omitempty"` // AS/EU/NA/SA/AF/OC/AN
}
GroupSelector is a dynamic "smart group" membership rule. Dashboard grouping uses it for previews, and group-policy planning uses the current match set when expanding policies into per-node nft plans. Each field is an OR-set; a node matches the selector when it satisfies any one of the populated criteria (tags-any / roles / country / continent).
type GuardInterface ¶ added in v0.2.17
type GuardInterface struct {
Name string `json:"name"`
Addresses []string `json:"addresses,omitempty"`
Up bool `json:"up,omitempty"`
}
GuardInterface is one network interface from a node reality report.
type GuardListener ¶ added in v0.2.17
type GuardListener struct {
Protocol string `json:"protocol"` // tcp | udp
Port int `json:"port"`
Address string `json:"address,omitempty"`
Process string `json:"process,omitempty"`
}
GuardListener is one listening socket from a node reality report.
type GuardNodeReality ¶ added in v0.2.17
type GuardNodeReality struct {
NodeID string `json:"node_id"`
Listeners []GuardListener `json:"listeners,omitempty"`
Interfaces []GuardInterface `json:"interfaces,omitempty"`
ManagedSHA string `json:"managed_sha,omitempty"`
ForeignTables []string `json:"foreign_tables,omitempty"`
NFTVersion string `json:"nft_version,omitempty"`
CollectedAt time.Time `json:"collected_at"`
}
GuardNodeReality is the agent-reported ground truth for reality-first authoring and drift detection (design-13 §4.3). It is low-trust input: it feeds suggestions, diffs, and display only — never silent policy.
type GuardPortRange ¶ added in v0.2.17
GuardPortRange is an inclusive L4 port range; From == To expresses a single port. Security-group rules use ranges instead of bare port lists so "9009-9013" stays one reviewable entry (design-13 §4.2).
type GuardRule ¶ added in v0.2.17
type GuardRule struct {
ID string `json:"id"`
Comment string `json:"comment,omitempty"`
Action string `json:"action"` // allow | deny
Direction string `json:"direction"` // ingress | egress
Protocol string `json:"protocol"` // tcp | udp | icmp | icmpv6 | any
Ports []GuardPortRange `json:"ports,omitempty"`
Remote NetEndpoint `json:"remote"` // Kind may additionally be NetRefZone
Log bool `json:"log,omitempty"`
Disabled bool `json:"disabled,omitempty"`
}
GuardRule is one ordered security-group rule. Empty Ports means all ports for the selected protocol. Log is an L2 render surface (design-13 §4.6): the field is part of the authoring contract now, but the compiler rejects it until renderer support lands.
type GuardZone ¶ added in v0.2.17
type GuardZone struct {
ID string `json:"id"`
Name string `json:"name"`
Builtin bool `json:"builtin,omitempty"`
Interfaces []string `json:"interfaces,omitempty"`
CIDRs []string `json:"cidrs,omitempty"`
Description string `json:"description,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
GuardZone is a named trust surface built from interface names and/or CIDRs. Builtin zones carry well-known ids (GuardZone* constants) and resolve per-node facts (public interface, overlay membership) at compile/view time; operator zones pin explicit interfaces/CIDRs. Zone references appear both as a rule remote (match scope) and as a node binding's trusted-zone list (full accept), reviewed like every other plan input. (design-13 §4.1)
type HopPath ¶ added in v0.2.19
type HopPath struct {
ID string `json:"id"`
Confidence string `json:"confidence"`
// RecordKeys are (node_id, core_generation, log_id) triples in hop order.
RecordKeys []ConnRecordKey `json:"record_keys"`
// Candidates is populated only when Confidence is ambiguous, so the operator
// sees what the stitcher could not choose between rather than a guess.
Candidates []ConnRecordKey `json:"candidates,omitempty"`
}
HopPath is a connection stitched across machines.
type HostFacts ¶ added in v0.2.0
type HostFacts struct {
Hostname string `json:"hostname,omitempty"`
OS string `json:"os,omitempty"`
Platform string `json:"platform,omitempty"`
PlatformVersion string `json:"platform_version,omitempty"`
KernelVersion string `json:"kernel_version,omitempty"`
Arch string `json:"arch,omitempty"`
CPUCores int `json:"cpu_cores,omitempty"`
CPUModel string `json:"cpu_model,omitempty"`
MemoryTotal uint64 `json:"memory_total,omitempty"`
SwapTotal uint64 `json:"swap_total,omitempty"`
Virtualization string `json:"virtualization,omitempty"`
BootTime time.Time `json:"boot_time,omitempty"`
ReportedAt time.Time `json:"reported_at,omitempty"`
}
HostFacts are auto-detected, slow-changing machine facts reported by the node-agent. They are advisory low-trust telemetry: useful for display, inventory and map planning, but never for authorization decisions.
type LogBatch ¶ added in v0.2.0
type LogBatch struct {
SourceID string `json:"source_id"`
Path string `json:"path"` // echoed for server cross-check vs the source record
RotID string `json:"rot_id"` // opaque per-file-incarnation id (inode/ctime)
FirstOff uint64 `json:"first_off"` // offset before the first line in this batch
LastOff uint64 `json:"last_off"` // offset after the last line (== agent checkpoint)
Dropped uint64 `json:"dropped"` // lines the agent dropped (backpressure) since last batch
Lines []string `json:"lines"` // raw lines, ordered, no trailing newline
CapturedAt time.Time `json:"captured_at"`
}
LogBatch is the agent -> server ingest envelope (one source per batch).
type LogLine ¶ added in v0.2.0
type LogLine struct {
SourceID string `json:"source_id"`
NodeID string `json:"node_id"`
Path string `json:"path"`
Seq uint64 `json:"seq"`
Offset uint64 `json:"offset"`
At time.Time `json:"at"`
Line string `json:"line"`
Truncated bool `json:"truncated,omitempty"`
}
LogLine is one ingested line as persisted/queried. Seq is the server-assigned monotonic per-source ingest sequence (the query cursor); Offset is the agent's byte offset after this line in the (rotation-scoped) source file.
type LogSource ¶ added in v0.2.0
type LogSource struct {
ID string `json:"id"`
Name string `json:"name"`
NodeID string `json:"node_id"`
Path string `json:"path"`
Enabled bool `json:"enabled"`
MaxLineBytes int `json:"max_line_bytes"` // truncate longer lines (server default 16384)
MaxBatchLines int `json:"max_batch_lines"` // agent batch cap (server default 500)
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
LogSource declares a file on a node whose appended lines are tailed by the assigned agent and shipped to the server. It is assignment-driven like Monitor: exactly one node owns a source (a path is node-local), identified by NodeID. LogSource carries no secrets.
type MachineProfile ¶ added in v0.2.0
type MachineProfile struct {
ID string `json:"id"`
NodeID string `json:"node_id"`
Label string `json:"label,omitempty"`
Vendor string `json:"vendor,omitempty"`
ConsoleURL string `json:"console_url,omitempty"`
DetailURL string `json:"detail_url,omitempty"`
Region string `json:"region,omitempty"`
Notes string `json:"notes,omitempty"`
PriceCents int64 `json:"price_cents,omitempty"`
Currency string `json:"currency,omitempty"`
PurchasedAt time.Time `json:"purchased_at,omitempty"`
RenewalCycle string `json:"renewal_cycle,omitempty"`
CycleDays int `json:"cycle_days,omitempty"`
NextRenewal time.Time `json:"next_renewal,omitempty"`
AutoRoll bool `json:"auto_roll"`
RemindDaysBefore []int `json:"remind_days_before,omitempty"`
RemindersEnabled bool `json:"reminders_enabled"`
LastRemindedKey string `json:"last_reminded_key,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
MachineProfile is operator-authored inventory, cost, and renewal metadata for a node. It is server-only control-plane state: it must never be sent to an agent or used by an agent. ConsoleURL and DetailURL may carry account-specific or signed links and are encrypted at rest by lattice-server.
type MachineVendor ¶ added in v0.2.17
type MachineVendor struct {
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url,omitempty"`
LogoURL string `json:"logo_url,omitempty"`
Description string `json:"description,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
MachineVendor is operator-authored metadata for a provider/vendor referenced by machine profiles. MachineProfile.Vendor remains the compatibility key; the server matches this record by Name so old profiles do not require migration.
type Metrics ¶
type Metrics struct {
CPUPercent float64 `json:"cpu_percent"`
Load1 float64 `json:"load1"`
Load5 float64 `json:"load5"`
Load15 float64 `json:"load15"`
MemoryUsed uint64 `json:"memory_used"`
MemoryTotal uint64 `json:"memory_total"`
DiskUsed uint64 `json:"disk_used"`
DiskTotal uint64 `json:"disk_total"`
NetRxBytes uint64 `json:"net_rx_bytes"`
NetTxBytes uint64 `json:"net_tx_bytes"`
// NetRxSpeed / NetTxSpeed are bytes-per-second rates the agent derives from
// the delta of the cumulative byte counters between two metrics cycles. The
// first cycle after agent start (no prior sample) and any counter reset both
// report 0. Dashboards read these for live bandwidth; the cumulative
// NetRxBytes/NetTxBytes remain the source of truth.
NetRxSpeed float64 `json:"net_rx_speed"`
NetTxSpeed float64 `json:"net_tx_speed"`
UptimeSeconds uint64 `json:"uptime_seconds"`
CollectedAt time.Time `json:"collected_at"`
}
type Monitor ¶ added in v0.2.0
type Monitor struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Target string `json:"target"`
IntervalSec int `json:"interval_sec"`
TimeoutSec int `json:"timeout_sec"`
AssignAll bool `json:"assign_all"`
NodeIDs []string `json:"node_ids,omitempty"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Monitor is a periodic reachability/latency probe executed by assigned agents. Targets are host:port for tcp/icmp probes and a URL for http probes. A monitor runs on every node when AssignAll is set, otherwise on the nodes in NodeIDs — this is how a group's members continuously measure their group leader.
type MonitorResult ¶ added in v0.2.0
type MonitorResult struct {
MonitorID string `json:"monitor_id"`
NodeID string `json:"node_id"`
At time.Time `json:"at"`
Success bool `json:"success"`
LatencyMs float64 `json:"latency_ms"`
Error string `json:"error,omitempty"`
}
MonitorResult is a single probe outcome reported by an agent.
type NFTInputs ¶ added in v0.2.0
type NFTInputs struct {
ID string `json:"id"`
NodeID string `json:"node_id"`
InterfaceName string `json:"interface_name,omitempty"`
WireGuardCIDR string `json:"wireguard_cidr,omitempty"`
PublicTCP []int `json:"public_tcp,omitempty"`
PublicUDP []int `json:"public_udp,omitempty"`
WireGuardTCP []int `json:"wireguard_tcp,omitempty"`
WireGuardUDP []int `json:"wireguard_udp,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
NFTInputs are the authoritative, server-owned baseline nftables inputs for a single node. Other core providers (DNS, per-node ACL, proxy cores) compose their required ports/rules into this shape before rendering the one lattice_guard table; they must not create competing nft tables.
type NetEndpoint ¶ added in v0.2.0
type NetEndpoint struct {
Kind string `json:"kind"`
NodeID string `json:"node_id,omitempty"`
CIDR string `json:"cidr,omitempty"`
Domain string `json:"domain,omitempty"`
GroupID string `json:"group_id,omitempty"` // set when Kind == NetRefGroup; resolved to node refs before compile
ZoneID string `json:"zone_id,omitempty"` // set when Kind == NetRefZone; resolved to iface/CIDR matches before compile
}
NetEndpoint describes the non-target side of a policy rule. Node refs are resolved by the server at validation/graph/compile time. Domain refs are egress-only and compile to named nft sets that the node refreshes through the agent's DNS updater; they are not accepted as ingress identity.
type NetPolicy ¶ added in v0.2.0
type NetPolicy struct {
ID string `json:"id"`
TargetNodeID string `json:"target_node_id"`
Rules []NetRule `json:"rules"`
Enabled bool `json:"enabled"`
// GroupDerived marks a per-node policy materialized from one or more
// GroupNetPolicy documents (server-side expansion). Manually-authored
// per-node policies leave it false. Materialization refuses to overwrite a
// manual policy, so the two authoring lanes never silently clobber.
GroupDerived bool `json:"group_derived,omitempty"`
LastPlanSHA string `json:"last_plan_sha,omitempty"`
LastAppliedAt time.Time `json:"last_applied_at,omitempty"`
LastError string `json:"last_error,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
NetPolicy is the per-node network intent document. It is control-plane state only: the agent does not receive it directly. A later iteration compiles this policy into nft with dead-man rollback.
type NetRule ¶ added in v0.2.0
type NetRule struct {
ID string `json:"id"`
Comment string `json:"comment,omitempty"`
Action string `json:"action"`
Direction string `json:"direction"`
Protocol string `json:"protocol"`
Ports []int `json:"ports,omitempty"`
Remote NetEndpoint `json:"remote"`
Disabled bool `json:"disabled,omitempty"`
}
NetRule is an ordered operator-authored L3/L4 policy rule evaluated on the target node. Empty Ports means all ports for the selected protocol.
type Node ¶
type Node struct {
ID string `json:"id"`
LatticeIdentityUUID string `json:"lattice_identity_uuid,omitempty"`
Name string `json:"name"`
Comment string `json:"comment,omitempty"`
TokenHash string `json:"token_hash"`
Tags []string `json:"tags"`
Role string `json:"role"`
// Inventory is operator-registered provenance/quality metadata (dashboard
// fleet inventory). Operator-owned; agents never report or overwrite it.
Inventory *NodeInventory `json:"inventory,omitempty"`
WireGuardIP string `json:"wireguard_ip"`
WireGuardPublicKey string `json:"wireguard_public_key,omitempty"`
WireGuardEndpoint string `json:"wireguard_endpoint,omitempty"`
WireGuardPort int `json:"wireguard_port,omitempty"`
PublicIP string `json:"public_ip"`
PublicIPv6 string `json:"public_ipv6,omitempty"`
// InternalIP / InternalIPv6 are the node's LAN/primary-interface addresses,
// reported by the agent. Informational (not geocoded); private ranges allowed.
InternalIP string `json:"internal_ip,omitempty"`
InternalIPv6 string `json:"internal_ipv6,omitempty"`
AgentVersion string `json:"agent_version"`
Online bool `json:"online"`
Disabled bool `json:"disabled,omitempty"`
// AgentSourceAllowlist optionally restricts node-agent bearer-token use to
// exact source IPs or CIDR prefixes as resolved by the server's client-IP
// trust policy. Empty means no source-address restriction.
AgentSourceAllowlist []string `json:"agent_source_allowlist,omitempty"`
// TokenLastUsedAt is coarse lifecycle telemetry for the node credential. It
// is updated only after successful bearer-token authentication and may be
// write-throttled by the server to avoid heartbeat write amplification.
TokenLastUsedAt time.Time `json:"token_last_used_at,omitempty"`
LastSeen time.Time `json:"last_seen"`
Metrics Metrics `json:"metrics"`
HostFacts HostFacts `json:"host_facts"`
Geo *NodeGeo `json:"geo,omitempty"`
AgentDebug AgentDebugPolicy `json:"agent_debug"`
// Trace is this node's sing-box trace collection policy. It sits beside
// AgentDebug rather than in its own map because it is node behaviour, not a
// separate resource: it is audited but never goes through the approval
// chain, since it changes what the agent subscribes to and never touches
// the node's own configuration.
Trace TracePolicy `json:"trace"`
// AgentLaunch is the last operator-authored installer/startup profile used
// to generate an enrollment or reconfigure command. It is advisory desired
// state, not proof of the currently running process flags.
AgentLaunch *AgentLaunchConfig `json:"agent_launch,omitempty"`
// TerminalTransport is the operator-owned per-node terminal transport: "poll"
// (default) or "stream". Empty is treated as the deployment default. It is the
// rollout lever for promoting the streaming terminal one node at a time; the
// agent reads it from its polled AgentConfig and applies it to new sessions.
TerminalTransport string `json:"terminal_transport,omitempty"`
// IPConfig is the operator-owned, per-node override for how the agent
// discovers its public IPs (mirrors the agent's -ip-mode/-ip-resolvers
// flags). nil means "no override" — the agent keeps its startup flags. It is
// pushed down through the polled AgentConfig.
IPConfig *NodeIPConfig `json:"ip_config,omitempty"`
// GroupIDs is the node's resolved group memberships. It is a server-computed,
// read-only convenience field (the union of every group whose explicit
// Members or display Selector resolves this node); it is never authored by a
// client and is not persisted as node intent. Tags/Role remain the underlying
// facts that selectors read.
GroupIDs []string `json:"group_ids,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type NodeGeo ¶ added in v0.2.0
type NodeGeo struct {
Country string `json:"country,omitempty"`
Region string `json:"region,omitempty"`
City string `json:"city,omitempty"`
Lat float64 `json:"lat,omitempty"`
Lon float64 `json:"lon,omitempty"`
IP string `json:"ip,omitempty"`
ASN int `json:"asn,omitempty"`
ASOrg string `json:"as_org,omitempty"`
Provider string `json:"provider,omitempty"`
Source string `json:"source,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
NodeGeo is map metadata for a node. Operator-entered values are authoritative; automatic GeoIP values are advisory and should not overwrite operator values unless an operator explicitly asks for that replacement.
type NodeGuardBinding ¶ added in v0.2.17
type NodeGuardBinding struct {
NodeID string `json:"node_id"`
GroupIDs []string `json:"group_ids"`
Overrides []GuardRule `json:"overrides,omitempty"`
ZoneIDs []string `json:"zone_ids,omitempty"`
Managed bool `json:"managed"`
Version int64 `json:"version"`
LastPlanSHA string `json:"last_plan_sha,omitempty"`
LastAppliedAt time.Time `json:"last_applied_at,omitempty"`
LastError string `json:"last_error,omitempty"`
AppliedTableSHA string `json:"applied_table_sha,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
NodeGuardBinding composes a node's effective firewall: base scaffold, then trusted zones, then Overrides, then attached groups in GroupIDs order. Managed=false marks an observe-only node the compiler must refuse to plan. The Last*/AppliedTableSHA fields carry apply state and the drift anchor the legacy NFTInputs record never had (design-13 §4.2).
type NodeIPConfig ¶ added in v0.2.5
type NodeIPConfig struct {
Mode string `json:"mode,omitempty"` // "" | auto | static | resolver | script
StaticIPv4 string `json:"static_ipv4,omitempty"`
StaticIPv6 string `json:"static_ipv6,omitempty"`
Resolvers []string `json:"resolvers,omitempty"` // IP-echo URLs; empty = agent defaults
Script string `json:"script,omitempty"` // server->agent only; redact from node views
ScriptSHA256 string `json:"script_sha256,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
NodeIPConfig is the operator-owned, per-node override for how the agent determines its public IPs. It mirrors the agent's -ip-mode / -ip-resolvers / -public-ip startup flags so the server can push a change without a redeploy. An empty Mode means "no override". Script discovery is high-trust node-local code: the server stores Script for the agent only, and read-facing node views should redact Script while keeping ScriptSHA256 for operator confirmation.
type NodeInventory ¶ added in v0.2.15
type NodeInventory struct {
// PurityPercent is the operator-assessed IP purity score, 0-100.
PurityPercent *int `json:"purity_percent,omitempty"`
// Quality is a coarse grade such as "high", "medium", "low" (free-form, <=64 chars).
Quality string `json:"quality,omitempty"`
// Notes is free-form inventory context (<=2048 chars, matching MachineProfile.Notes).
Notes string `json:"notes,omitempty"`
}
NodeInventory carries the operator's own assessment of a node's provenance quality — e.g. "98% pure, high quality" residential/ISP purity notes kept in the fleet inventory. Purely informational; nothing in the control plane branches on it.
type NotifyChannel ¶ added in v0.2.0
type NotifyChannel struct {
ID string `json:"id"`
Name string `json:"name"`
Kind string `json:"kind"`
Config map[string]string `json:"config,omitempty"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
NotifyChannel is a persisted notification destination. Config holds provider-specific fields (e.g. token, chat_id, webhook_url); its values are secret and never serialized back to clients.
type NotifyRule ¶ added in v0.2.1
type NotifyRule struct {
ID string `json:"id"`
Name string `json:"name"`
EventTypes []string `json:"event_types,omitempty"`
ChannelIDs []string `json:"channel_ids,omitempty"`
TitleTemplate string `json:"title_template,omitempty"`
BodyTemplate string `json:"body_template,omitempty"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
NotifyRule routes notification events to one or more destinations. EventTypes uses stable server event ids such as monitor.down or ssh.login; "*" matches all notification events. Templates are intentionally small string templates expanded by the server with event_type, title, and body variables.
type OIDCIdentity ¶ added in v0.2.0
type OIDCIdentity struct {
ProviderID string `json:"provider_id"`
Issuer string `json:"issuer"`
Subject string `json:"subject"`
UserID string `json:"user_id"`
Email string `json:"email,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
OIDCIdentity is the durable link between an external subject and a local user. Keyed in the store by (ProviderID, Subject): the trust anchor is the admin-vetted provider record, not the bare issuer string, so a second provider that happens to share an issuer cannot reuse another provider's links. Subject is the stable identifier; Email/Issuer are reference only.
type OIDCProvider ¶ added in v0.2.0
type OIDCProvider struct {
ID string `json:"id"`
DisplayName string `json:"display_name"`
Issuer string `json:"issuer"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret,omitempty"`
Scopes []string `json:"scopes,omitempty"`
AllowedDomains []string `json:"allowed_domains,omitempty"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
OIDCProvider is an admin-configured external identity provider for SSO login. ClientSecret is a secret-at-rest field (encrypted by the store boundary) and is never returned by the API.
type PluginInstallation ¶ added in v0.2.0
type PluginInstallation struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Version string `json:"version,omitempty"`
Entrypoint string `json:"entrypoint,omitempty"`
Publisher string `json:"publisher,omitempty"`
Capabilities []string `json:"capabilities"`
ArtifactSHA256 string `json:"artifact_sha256,omitempty"`
BundlePath string `json:"bundle_path,omitempty"`
Status string `json:"status"`
VerifiedAt time.Time `json:"verified_at,omitempty"`
InstalledAt time.Time `json:"installed_at,omitempty"`
ActivatedAt time.Time `json:"activated_at,omitempty"`
DisabledAt time.Time `json:"disabled_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
PluginInstallation is the persisted lifecycle record for a verified plugin bundle. It is intentionally metadata-only: artifacts and runtime handles stay outside the shared API model.
type ProxyInbound ¶ added in v0.2.0
type ProxyInbound struct {
ID string `json:"id"`
Name string `json:"name"`
Core string `json:"core"`
Protocol string `json:"protocol"`
Listen string `json:"listen,omitempty"`
Port int `json:"port"`
Transport string `json:"transport,omitempty"`
Path string `json:"path,omitempty"`
Host string `json:"host,omitempty"`
Security string `json:"security,omitempty"`
SNI string `json:"sni,omitempty"`
ALPN []string `json:"alpn,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
CertPath string `json:"cert_path,omitempty"`
KeyPath string `json:"key_path,omitempty"`
RealityPrivateKey string `json:"reality_private_key,omitempty"`
RealityPublicKey string `json:"reality_public_key,omitempty"`
RealityShortIDs []string `json:"reality_short_ids,omitempty"`
RealityDest string `json:"reality_dest,omitempty"`
SSMethod string `json:"ss_method,omitempty"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
ProxyInbound is the central protocol/transport template that the server renders into node-local sing-box/xray config. RealityPrivateKey is a server-side secret and must never appear in API views or agent payloads.
type ProxyNodeProfile ¶ added in v0.2.0
type ProxyNodeProfile struct {
ID string `json:"id"`
NodeID string `json:"node_id"`
Core string `json:"core"`
InboundIDs []string `json:"inbound_ids"`
Hostname string `json:"hostname,omitempty"`
ListenIP string `json:"listen_ip,omitempty"`
ConfigPath string `json:"config_path,omitempty"`
StatsAPI string `json:"stats_api,omitempty"`
// LogLevel is the sing-box log.level rendered into this node's config. It
// governs only what lands on disk. The trace subscription level is separate
// and independent (model.TracePolicy): sing-box emits to Clash API
// subscribers without applying log.level, which is what lets verbosity
// change without a restart. Empty renders the default.
LogLevel string `json:"log_level,omitempty"`
// ClashAPI is the loopback address of this node's sing-box Clash API, for
// example "127.0.0.1:9090". Empty means the API is not rendered. It must be
// a loopback address: the renderer rejects anything else, because this
// endpoint exposes live connection data and log lines.
ClashAPI string `json:"clash_api,omitempty"`
// ClashAPISecret is the bearer token for ClashAPI. It is rendered into the
// node config, which already carries user UUIDs and REALITY private keys and
// is handled as a node-scoped secret-bearing artifact. The agent reads it
// back out of the applied config on the node; it is never shipped to the
// agent by any other path.
ClashAPISecret string `json:"clash_api_secret,omitempty"`
AppliedSHA256 string `json:"applied_sha256,omitempty"`
LastApplyAt time.Time `json:"last_apply_at,omitempty"`
LastError string `json:"last_error,omitempty"`
// Usage collector health is agent-reported and server persisted for
// operator visibility. It is not client-editable policy and must not affect
// the server's monotonic usage accounting.
UsageCollectorSource string `json:"usage_collector_source,omitempty"`
UsageCollectorStatus string `json:"usage_collector_status,omitempty"`
UsageCollectorCheckedAt time.Time `json:"usage_collector_checked_at,omitempty"`
UsageCollectorLastOKAt time.Time `json:"usage_collector_last_ok_at,omitempty"`
UsageCollectorLastError string `json:"usage_collector_last_error,omitempty"`
UsageCollectorLastErrorAt time.Time `json:"usage_collector_last_error_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
ProxyNodeProfile binds the central proxy model to a single node. One profile exists per node; it is the unit rendered into a reviewed plan/apply task.
type ProxyUsageSnapshot ¶ added in v0.2.0
type ProxyUsageSnapshot struct {
NodeID string `json:"node_id"`
At time.Time `json:"at"`
CoreUptimeSec uint64 `json:"core_uptime_sec"`
UserBytes map[string]int64 `json:"user_bytes"`
// LineUserBytes optionally carries cumulative counters split by stable
// line_hash_id and proxy user id. It is additive to UserBytes so old
// collectors remain valid; if a collector sends only line_user_bytes, agents
// and servers may derive user_bytes by summing per-user line counters.
LineUserBytes map[string]map[string]int64 `json:"line_user_bytes,omitempty"`
// Collector fields describe this collection attempt. They let the agent
// report local collector errors without overwriting the previous accounting
// baseline on the server.
CollectorSource string `json:"collector_source,omitempty"` // file | http | future core transport
CollectorStatus string `json:"collector_status,omitempty"` // ok | error
CollectorError string `json:"collector_error,omitempty"`
CollectorCheckedAt time.Time `json:"collector_checked_at,omitempty"`
}
ProxyUsageSnapshot is the last node-reported accounting snapshot. The server later diffs successive snapshots to advance ProxyUser.UsedBytes monotonically.
type ProxyUser ¶ added in v0.2.0
type ProxyUser struct {
ID string `json:"id"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
UUID string `json:"uuid,omitempty"`
Password string `json:"password,omitempty"`
SubToken string `json:"sub_token,omitempty"`
InboundIDs []string `json:"inbound_ids,omitempty"`
TrafficLimitBytes int64 `json:"traffic_limit_bytes,omitempty"`
ExpiresAt time.Time `json:"expires_at,omitempty"`
UsedBytes int64 `json:"used_bytes"`
LastSeenAt time.Time `json:"last_seen_at,omitempty"`
Status string `json:"status"`
// Server-managed notification cursors. They prevent repeated quota/expiry
// alerts after the operator has already been notified for the current limit
// or expiry date.
LastQuotaNotifiedKey string `json:"last_quota_notified_key,omitempty"`
LastExpiryNotifiedKey string `json:"last_expiry_notified_key,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
ProxyUser is a central subscriber identity. UUID, Password and SubToken are bearer credential material; they are encrypted at rest and only surfaced through one-time/admin rotation flows, never through list/read views.
type SecurityGroup ¶ added in v0.2.17
type SecurityGroup struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Rules []GuardRule `json:"rules"`
Version int64 `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
SecurityGroup is a named, reusable, ordered rule set attachable to any number of nodes through NodeGuardBinding. Version implements optimistic concurrency on upserts so two operators cannot silently clobber each other (design-13 §4.2, closing the NFTInputs gap).
type ShareSource ¶ added in v0.2.19
type ShareSource struct {
}
ShareSource names where a share's content comes from. Exactly one kind is set, and the fields that do not belong to that kind stay empty.
type SingBoxInventory ¶ added in v0.2.7
type SingBoxInventory struct {
NodeID string `json:"node_id"`
At time.Time `json:"at"`
CoreVersion string `json:"core_version,omitempty"`
Nodes []SingBoxNode `json:"nodes"`
// Network is how this node is reached: nat | direct. Declared by the node
// itself, since whether a provider sits in front of it is not something the
// control plane can infer from the outside.
Network string `json:"network,omitempty"`
// ProviderEdge is the hostname the outside world actually connects to when a
// provider forwards into this node. It is not the node's own public host: a
// relay elsewhere in the fleet points its outbound at the provider edge, so
// without it a chain into a NAT node cannot be matched back to the line it
// lands on. Declared by the node, for the same reason Network is.
ProviderEdge string `json:"provider_edge,omitempty"`
Status string `json:"status,omitempty"` // ok | error
Error string `json:"error,omitempty"`
}
SingBoxInventory is the latest snapshot of the sing-box nodes discovered on one machine. It is reported by the agent (read-only `sb --json list`) and held in memory on the server as a live mirror — it is re-reported each interval and is not persisted (a restart simply waits for the next report). Status/Error let a node report a discovery failure (e.g. sb not installed) without a stale list.
type SingBoxNode ¶ added in v0.2.7
type SingBoxNode struct {
Name string `json:"name"`
LineID string `json:"line_id,omitempty"`
LineUUID string `json:"line_uuid,omitempty"` // design-15 D1: control-plane line identity from the sidecar
NodeIdentityUUID string `json:"node_identity_uuid,omitempty"`
Protocol string `json:"protocol,omitempty"`
Network string `json:"network,omitempty"`
Address string `json:"address,omitempty"`
Port string `json:"port,omitempty"`
SNI string `json:"sni,omitempty"`
Host string `json:"host,omitempty"`
ListenHost string `json:"listen_host,omitempty"`
// PublicPort is the port the outside world reaches this inbound on, when it
// differs from the port sing-box listens on. A NAT node behind a provider
// edge is the case that needs it: the box listens on 488 while the world
// dials 50100. Empty means the listen port is also the public one.
//
// It comes from a node-owned endpoints file rather than from the running
// config, because no amount of reading sing-box can reveal a mapping that
// lives in someone else's router.
PublicPort string `json:"public_port,omitempty"`
OutboundRef string `json:"outbound_ref,omitempty"`
OutboundServer string `json:"outbound_server,omitempty"`
OutboundPort string `json:"outbound_port,omitempty"`
OutboundType string `json:"outbound_type,omitempty"`
// DownstreamLineUUID is the declared chain edge target (design-15 §6), read
// from the sidecar chain block; empty when the line is single-exit or the
// edge is only inferable from outbound host/port.
DownstreamLineUUID string `json:"downstream_line_uuid,omitempty"`
UserCount int `json:"user_count,omitempty"`
UserKnown bool `json:"user_known,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
PublicKey string `json:"public_key,omitempty"`
}
SingBoxNode is one inbound discovered on a node by reading its on-box sing-box management state (the 233boy `sb --json list` output). It is the adoption-model view of a proxy that exists on the machine but is NOT (necessarily) managed by Lattice's own proxy store — the bridge that lets the dashboard see proxies on machines provisioned out-of-band. Secret-free: share_url already encodes the connection without exposing additional server-side material.
type StaticObject ¶
type StorageAccessToken ¶ added in v0.2.1
type StorageAccessToken struct {
ID string `json:"id"`
Name string `json:"name"`
TokenHash string `json:"token_hash,omitempty"`
Kind string `json:"kind"`
Access string `json:"access"`
Buckets []string `json:"buckets,omitempty"`
RevokedAt time.Time `json:"revoked_at,omitempty"`
LastUsedAt time.Time `json:"last_used_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type StorageBinding ¶ added in v0.2.1
type StorageBinding struct {
ID string `json:"id"`
Kind string `json:"kind"`
Bucket string `json:"bucket"`
Hostname string `json:"hostname"`
PathPrefix string `json:"path_prefix,omitempty"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type StorageBucket ¶ added in v0.2.1
type StorageBucket struct {
ID string `json:"id"`
Kind string `json:"kind"`
Name string `json:"name"`
DisplayName string `json:"display_name,omitempty"`
Description string `json:"description,omitempty"`
IndexDocument string `json:"index_document,omitempty"`
NotFoundDocument string `json:"not_found_document,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type SubscriptionShare ¶ added in v0.2.19
type SubscriptionShare struct {
// rollback cannot silently delete data this version cannot interpret.
Extra map[string]json.RawMessage `json:"-"`
}
SubscriptionShare is one publicly reachable subscription URL. Token is the only secret; Slug is a label that reaches reverse-proxy logs and client screenshots and is never relied on for authorization.
func (SubscriptionShare) MarshalJSON ¶ added in v0.2.19
func (s SubscriptionShare) MarshalJSON() ([]byte, error)
MarshalJSON re-emits the unknown fields alongside the named ones. A known field always wins over a same-named Extra entry: the caller's edit must not be shadowed by whatever an older decode happened to stash.
func (*SubscriptionShare) UnmarshalJSON ¶ added in v0.2.19
func (s *SubscriptionShare) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the named fields and keeps everything else in Extra.
type SubscriptionSnapshot ¶ added in v0.2.19
type SubscriptionSnapshot struct {
SchemaVersion int `json:"schema_version"`
// PluginID and SubscriptionID together identify what this is a snapshot of.
PluginID string `json:"plugin_id"`
SubscriptionID string `json:"subscription_id"`
// Raw is the provider's response body exactly as the plugin received it.
Raw string `json:"raw"`
// SourceVersion identifies the exact secret-free source state used to render
// Raw. SourceManifest is its canonical, map-free provenance document. Neither
// field may contain credentials or rendered subscription URIs.
SourceVersion string `json:"source_version,omitempty"`
SourceManifest json.RawMessage `json:"source_manifest,omitempty"`
// Userinfo is the provider's Subscription-Userinfo header, passed through to
// the client so its traffic display stays truthful. Empty when the provider
// sent none.
Userinfo string `json:"userinfo,omitempty"`
FetchedAt time.Time `json:"fetched_at"`
// FetchError records why the most recent refresh failed while this snapshot
// stayed in service. A snapshot with a non-empty FetchError is still served -
// that is the point - but the operator needs to see that it is stale.
FetchError string `json:"fetch_error,omitempty"`
LastAttemptAt time.Time `json:"last_attempt_at,omitempty"`
// Stale is explicit in v2. It is independent of FetchError so public serving
// code never has to infer whether preserved last-good content is current.
Stale bool `json:"stale"`
// contains filtered or unexported fields
}
SubscriptionSnapshot is the last content a plugin successfully fetched for one subscription, held by the CORE.
The core owns it because a plugin has nowhere durable to keep it: it cannot reach bolt, and its runtime working directory is deleted when the runner stops it. Raw is therefore an opaque blob from the core's point of view - it is stored, handed back on the next render, and never interpreted.
This record is what keeps clients served when a provider goes down or rotates its URL, so losing it means subscriptions go dark. It is durable, not a cache.
func (SubscriptionSnapshot) Clone ¶ added in v0.2.19
func (s SubscriptionSnapshot) Clone() SubscriptionSnapshot
func (SubscriptionSnapshot) NeedsRewrite ¶ added in v0.2.19
func (s SubscriptionSnapshot) NeedsRewrite() bool
func (SubscriptionSnapshot) PersistedSchemaVersion ¶ added in v0.2.19
func (s SubscriptionSnapshot) PersistedSchemaVersion() int
func (*SubscriptionSnapshot) UnmarshalJSON ¶ added in v0.2.19
func (s *SubscriptionSnapshot) UnmarshalJSON(raw []byte) error
UnmarshalJSON performs the only supported in-memory schema migration. The durable store remains responsible for staging and atomically rewriting v1 records after every record has decrypted and validated.
type SubscriptionSourceManifestEdge ¶ added in v0.2.19
type SubscriptionSourceManifestEndpoint ¶ added in v0.2.19
type SubscriptionSourceManifestEndpoint struct {
LineUUID string `json:"line_uuid"`
NodeID string `json:"node_id"`
Label string `json:"label"`
Host string `json:"host"`
Port int `json:"port"`
SNI string `json:"sni"`
Fingerprint string `json:"fingerprint"`
ALPN []string `json:"alpn"`
PublicKey string `json:"public_key"`
ShortID string `json:"short_id"`
Flow string `json:"flow"`
}
type SubscriptionSourceManifestEntry ¶ added in v0.2.19
type SubscriptionSourceManifestEntry struct {
Root string `json:"root"`
Endpoint SubscriptionSourceManifestEndpoint `json:"endpoint"`
Path []SubscriptionSourceManifestEdge `json:"path"`
Terminal SubscriptionSourceManifestTerminal `json:"terminal"`
}
type SubscriptionSourceManifestIdentity ¶ added in v0.2.19
type SubscriptionSourceManifestTerminal ¶ added in v0.2.19
type SubscriptionSourceManifestV1 ¶ added in v0.2.19
type SubscriptionSourceManifestV1 struct {
Schema string `json:"schema"`
Renderer string `json:"renderer"`
Identity SubscriptionSourceManifestIdentity `json:"identity"`
EntryRoots []string `json:"entry_roots"`
Entries []SubscriptionSourceManifestEntry `json:"entries"`
}
func DecodeSubscriptionSourceManifest ¶ added in v0.2.19
func DecodeSubscriptionSourceManifest(raw []byte) (SubscriptionSourceManifestV1, error)
func (SubscriptionSourceManifestV1) Clone ¶ added in v0.2.19
func (m SubscriptionSourceManifestV1) Clone() SubscriptionSourceManifestV1
func (SubscriptionSourceManifestV1) Validate ¶ added in v0.2.19
func (m SubscriptionSourceManifestV1) Validate() error
type Task ¶
type Task struct {
ID string `json:"id"`
ApprovalID string `json:"approval_id,omitempty"`
ActorID string `json:"actor_id"`
TokenID string `json:"token_id"`
Targets []string `json:"targets"`
Interpreter string `json:"interpreter"`
Script string `json:"script"`
TimeoutSec int `json:"timeout_sec"`
OutputLimit int `json:"output_limit"`
Status string `json:"status"`
LeaseID string `json:"lease_id,omitempty"`
LeasedBy string `json:"leased_by,omitempty"`
TargetLeases map[string]TaskLease `json:"target_leases,omitempty"`
RerunOfTaskID string `json:"rerun_of_task_id,omitempty"`
RerunOfNodeID string `json:"rerun_of_node_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
StartedAt time.Time `json:"started_at,omitempty"`
FinishedAt time.Time `json:"finished_at,omitempty"`
}
type TaskResult ¶
type TaskResult struct {
TaskID string `json:"task_id"`
LeaseID string `json:"lease_id,omitempty"`
NodeID string `json:"node_id"`
ExitCode int `json:"exit_code"`
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
Error string `json:"error"`
StartedAt time.Time `json:"started_at"`
FinishedAt time.Time `json:"finished_at"`
}
type TerminalEvent ¶ added in v0.2.3
type TerminalInput ¶ added in v0.2.3
type TerminalSession ¶ added in v0.2.3
type TerminalSession struct {
ID string `json:"id"`
NodeID string `json:"node_id"`
ActorID string `json:"actor_id,omitempty"`
TokenID string `json:"token_id,omitempty"`
Shell string `json:"shell,omitempty"`
Cols int `json:"cols,omitempty"`
Rows int `json:"rows,omitempty"`
Status string `json:"status"`
Error string `json:"error,omitempty"`
BytesIn int64 `json:"bytes_in,omitempty"`
BytesOut int64 `json:"bytes_out,omitempty"`
CreatedAt time.Time `json:"created_at"`
OpenedAt time.Time `json:"opened_at,omitempty"`
ClosedAt time.Time `json:"closed_at,omitempty"`
LastSeen time.Time `json:"last_seen,omitempty"`
}
type Token ¶
type Token struct {
ID string `json:"id"`
Name string `json:"name"`
TokenHash string `json:"token_hash"`
ActorID string `json:"actor_id"`
Scopes []string `json:"scopes"`
ServerAllowlist []string `json:"server_allowlist"`
RevokedAt time.Time `json:"revoked_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type TraceAgentConfig ¶ added in v0.2.19
type TraceAgentConfig struct {
Policy TracePolicy `json:"policy"`
Sessions []TraceAgentSession `json:"sessions,omitempty"`
// ServerTime lets the agent bound clock skew when enforcing TTLs.
ServerTime time.Time `json:"server_time,omitzero"`
// RawSourceID is the virtual log source that receives the node's ordinary
// sing-box lines, the ones no capture session asked for. They go to the
// existing bounded log store rather than the trace store, so the Logs view
// keeps working and there is parser evidence to look at after the fact
// instead of only assembled records. Empty means the server does not want
// raw lines from this node.
RawSourceID string `json:"raw_source_id,omitempty"`
}
TraceAgentConfig is what an agent polls: its node policy plus the sessions whose filters touch this node, already expanded by the server into predicates this node can evaluate locally.
type TraceAgentSession ¶ added in v0.2.19
type TraceAgentSession struct {
ID string `json:"id"`
Level TraceLevel `json:"level"`
ExpiresAt time.Time `json:"expires_at"`
// UserNames are sing-box inbound user names (u_<16hex>, or a legacy label).
UserNames []string `json:"user_names,omitempty"`
// InboundTags are sing-box inbound tags on THIS node.
InboundTags []string `json:"inbound_tags,omitempty"`
DstPatterns []string `json:"dst_patterns,omitempty"`
}
TraceAgentSession is one session as the agent sees it: the server has already turned user ids into the u_<hex> names this node actually renders, and line uuids into inbound tags.
func (TraceAgentSession) MatchesAll ¶ added in v0.2.19
func (s TraceAgentSession) MatchesAll() bool
MatchesAll reports whether this session captures everything on the node.
type TraceBatch ¶ added in v0.2.19
type TraceBatch struct {
NodeID string `json:"node_id"`
Records []ConnRecord `json:"records,omitempty"`
Lines []TraceLine `json:"lines,omitempty"`
// CoreGeneration and CoreStartedAt let the server detect a restart even if
// it missed the records that the restart swept.
CoreGeneration uint64 `json:"core_generation,omitempty"`
CoreStartedAt time.Time `json:"core_started_at,omitzero"`
// Dropped counts what the agent discarded under budget since the last batch.
Dropped uint64 `json:"dropped,omitempty"`
// Unparsed counts lines the parser could not read. A rising number means
// sing-box changed its format, which is the failure mode most likely to be
// mistaken for "nothing is happening".
Unparsed uint64 `json:"unparsed,omitempty"`
CapturedAt time.Time `json:"captured_at"`
}
TraceBatch is the agent to server envelope.
func (TraceBatch) SourceLooksBare ¶ added in v0.2.19
func (b TraceBatch) SourceLooksBare() bool
SourceLooksBare reports whether this value is the zero batch. The agent sends {node_id, batch}; a client that posts a bare TraceBatch instead still authenticates, because TraceBatch has its own NodeID, and then decodes into an empty Batch that the server would accept with 200 OK and zero records. Nothing would ever surface that. A batch that legitimately carries only counters still sets CapturedAt, so this stays false for it.
type TraceFilter ¶ added in v0.2.19
type TraceFilter struct {
UserIDs []string `json:"user_ids,omitempty"`
LineUUIDs []string `json:"line_uuids,omitempty"`
NodeIDs []string `json:"node_ids,omitempty"`
// DstPatterns are case-insensitive substrings matched against the
// destination host. Not globs: a substring is what an operator types.
DstPatterns []string `json:"dst_patterns,omitempty"`
}
TraceFilter selects what a session captures. Empty fields mean "no constraint on this dimension"; a filter with every field empty matches the whole node, which is legal but is what the budget exists to survive.
func (TraceFilter) IsEmpty ¶ added in v0.2.19
func (f TraceFilter) IsEmpty() bool
IsEmpty reports whether the filter constrains nothing.
type TraceLevel ¶ added in v0.2.19
type TraceLevel string
TraceLevel is the verbosity of a sing-box log subscription. It is NOT the node's log.level (which stays at whatever the config says and governs only what lands on disk).
const ( // TraceLevelInfo carries connection accept, authenticated user, outbound, // and close. It is enough to assemble a ConnRecord and is the always-on // default. TraceLevelInfo TraceLevel = "info" // TraceLevelDebug adds rule matches, sniffing results, and DNS. TraceLevelDebug TraceLevel = "debug" // TraceLevelTrace adds the cancel-path close lines. TraceLevelTrace TraceLevel = "trace" )
type TraceLine ¶ added in v0.2.19
type TraceLine struct {
SessionID string `json:"session_id"`
NodeID string `json:"node_id"`
Seq uint64 `json:"seq"`
At time.Time `json:"at"`
Level string `json:"level"`
LogID uint32 `json:"log_id,omitempty"`
Tag string `json:"tag,omitempty"`
Message string `json:"message"`
// Raw is the payload exactly as sing-box sent it, kept so a parser bug can
// never destroy the evidence it failed to read.
Raw string `json:"raw,omitempty"`
}
TraceLine is one raw sing-box log line kept because a session asked for it. Unlabelled lines do not come here; they stay in the existing logstore.
type TraceMarker ¶ added in v0.2.19
type TraceMarker struct {
Kind string `json:"kind"`
At time.Time `json:"at"`
NodeID string `json:"node_id,omitempty"`
UserID string `json:"user_id,omitempty"`
Title string `json:"title"`
Detail string `json:"detail,omitempty"`
// Count is the blast radius where one applies: connections closed by a
// restart, nodes touched by an apply.
Count int `json:"count,omitempty"`
CorrelationID string `json:"correlation_id,omitempty"`
}
TraceMarker is one event on the timeline.
type TracePolicy ¶ added in v0.2.19
type TracePolicy struct {
NodeID string `json:"node_id"`
Enabled bool `json:"enabled"`
// Level is the floor for this node. The agent subscribes at the max of this
// and every running session's level.
Level TraceLevel `json:"level"`
// BudgetLinesPerSec caps ingest for this node. Over budget the agent drops
// oldest and counts; the count is reported and displayed, never hidden.
BudgetLinesPerSec int `json:"budget_lines_per_sec"`
// ClashAPIAddr is the loopback address of the node's sing-box Clash API,
// e.g. "127.0.0.1:9090". Never a routable address.
ClashAPIAddr string `json:"clash_api_addr,omitempty"`
// SecretPath is where the agent reads the Clash API secret on the node. The
// secret itself never travels to the server and is never stored here.
SecretPath string `json:"secret_path,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitzero"`
// LastCoreGeneration and LastCoreStartedAt are the newest sing-box process
// instance this node reported. A change is a restart, and recording it here
// means a restart on an idle node is still visible even though it swept no
// connections.
LastCoreGeneration uint64 `json:"last_core_generation,omitempty"`
LastCoreStartedAt time.Time `json:"last_core_started_at,omitzero"`
}
TracePolicy is the per-node collection policy: the always-on floor. It is an agent behaviour setting, not a node config change, so it is audited but does not go through the approval chain.
type TraceSession ¶ added in v0.2.19
type TraceSession struct {
ID string `json:"id"`
Name string `json:"name"`
Filter TraceFilter `json:"filter"`
Level TraceLevel `json:"level"`
StartedAt time.Time `json:"started_at"`
ExpiresAt time.Time `json:"expires_at"`
EndedAt time.Time `json:"ended_at,omitzero"`
State string `json:"state"`
StartedBy string `json:"started_by"`
CorrelationID string `json:"correlation_id,omitempty"`
// Counters, reported back for display. Dropped is as load-bearing as Lines:
// a capture that silently lost lines reads as a quiet network.
Lines uint64 `json:"lines"`
Records uint64 `json:"records"`
Dropped uint64 `json:"dropped"`
}
TraceSession is a time-boxed capture. The TTL is enforced on the agent as well as the server, so a session still ends if the control plane goes away mid-capture. That is deliberate: an unbounded trace left running on a node is a privacy and disk problem nobody would notice.
type TunnelIngress ¶ added in v0.2.0
type TunnelIngress struct {
Hostname string `json:"hostname"`
Service string `json:"service"`
Path string `json:"path,omitempty"`
}
TunnelIngress maps a public hostname to a node-local service for a Cloudflare Tunnel. Service is a cloudflared service URL, e.g. http://localhost:8088, ssh://localhost:22, or the literal http_status:404.
type TunnelProfile ¶ added in v0.2.0
type TunnelProfile struct {
ID string `json:"id"`
Name string `json:"name"`
NodeID string `json:"node_id"`
TunnelID string `json:"tunnel_id"`
CredentialsFile string `json:"credentials_file,omitempty"`
Ingress []TunnelIngress `json:"ingress"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
TunnelProfile describes a Cloudflare Tunnel deployed on a node. Credentials are node-local (CredentialsFile path); the server only stores the topology.
type User ¶
type User struct {
ID string `json:"id"`
Username string `json:"username"`
PasswordHash string `json:"password_hash"`
Scopes []string `json:"scopes"`
// ServerAllowlist confines this operator to a set of nodes, exactly as the
// same field on Token confines an API caller. Empty means every node, which
// is what every existing account has and so preserves their access across
// the upgrade. "*" is accepted as an explicit spelling of the same thing.
//
// Without this a human account could only be narrowed by scope, never by
// node: the confinement machinery existed and was enforced everywhere, but
// only an API token could express it.
ServerAllowlist []string `json:"server_allowlist,omitempty"`
TOTPEnabled bool `json:"totp_enabled"`
TOTPSecret string `json:"totp_secret,omitempty"`
RecoveryCodeHashes []string `json:"recovery_code_hashes,omitempty"`
// LastTOTPStep is the highest RFC-6238 counter accepted for this user. A
// successful verification must present a strictly greater step, which makes
// each code single-use and prevents replay within the validity window.
LastTOTPStep uint64 `json:"last_totp_step,omitempty"`
// SecurityEpoch is bumped on password change, 2FA disable, or admin revoke.
// Sessions carry the epoch at which they were minted; a session whose epoch
// is older than the user's current epoch is rejected, so privilege-reducing
// events invalidate all existing sessions.
SecurityEpoch uint64 `json:"security_epoch,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type WGExternalPeer ¶ added in v0.2.17
type WGExternalPeer struct {
ID string `json:"id"`
NetworkID string `json:"network_id"`
Name string `json:"name"`
Address string `json:"address"`
PublicKey string `json:"public_key"`
AllowedIPs []string `json:"allowed_ips,omitempty"`
LastIssuedAt time.Time `json:"last_issued_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
WGExternalPeer is a non-node device (laptop, phone). Its private key is generated server-side, rendered once for the operator, and never persisted: only the public key and metadata survive. Regeneration mints a new key and invalidates the old config.
type WGMembership ¶ added in v0.2.17
type WGMembership struct {
NetworkID string `json:"network_id"`
NodeID string `json:"node_id"`
Address string `json:"address"`
Role string `json:"role"` // hub | spoke | peer
InterfaceName string `json:"interface_name,omitempty"`
ListenPort int `json:"listen_port,omitempty"`
// Endpoint is the peer dial address rendered into WireGuard configs. It must
// be host:port, with IPv6 hosts bracketed as in net.JoinHostPort.
Endpoint string `json:"endpoint,omitempty"`
Keepalive int `json:"keepalive,omitempty"`
MTU int `json:"mtu,omitempty"`
ExtraAllowedIPs []string `json:"extra_allowed_ips,omitempty"`
Version int64 `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
WGMembership binds a node into a network. Address is server-allocated from the network CIDR and always pinned to a host route when it becomes a peer's AllowedIPs, so a member can never claim another member's address. ExtraAllowedIPs are additive routes a hub advertises (a LAN CIDR, or 0.0.0.0/0 for an exit node); they are reviewed in the plan like any other mutation and may never widen another member's pinned address.
type WGNetwork ¶ added in v0.2.17
type WGNetwork struct {
ID string `json:"id"`
Name string `json:"name"`
CIDR string `json:"cidr"`
Topology string `json:"topology"` // mesh | hub-and-spoke | custom
ListenPort int `json:"listen_port,omitempty"`
Keepalive int `json:"keepalive,omitempty"`
MTU int `json:"mtu,omitempty"`
DNS []string `json:"dns,omitempty"`
Description string `json:"description,omitempty"`
Version int64 `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
WGNetwork is a named WireGuard network. A fleet may run several; a node may belong to more than one (one interface per membership).