Documentation
¶
Index ¶
- Constants
- type AuditHeadShippingOptions
- type BuildInfo
- type KnownCapability
- type Line
- type LineBinding
- type LineGroup
- type NodeProfileRuntime
- type Options
- type Server
- type SubscriptionSummary
- type UsageByNode
- type UsageByUser
- type UsageCollector
- type UsageCollectorRuntime
- type UsageRow
- type VpnCredential
- type VpnUser
Constants ¶
const ( // ReachabilityOnline means the node is beating within the stale threshold. ReachabilityOnline = "online" // ReachabilityOffline means the node has reported before and has since gone // quiet. LastSeen says when it was last heard from. ReachabilityOffline = "offline" // ReachabilityNever means the node has never reported. CreatedAt says how // long it has been waiting, which is the number worth showing. ReachabilityNever = "never" )
Reachability is what the control plane actually knows about a node's contact history. model.Node.Online is a bool, so it collapses two situations an operator has to tell apart: a node that has been reporting and stopped, and a node that has never reported at all.
The second is not a corner case. handleEnrollNode writes the node record with neither LastSeen nor Online set, so every node is in it from enrollment until its first beat. Rendering that as "offline" tells the operator something broke, when what actually happened is that the agent was never installed or has never reached the control plane. Those have different fixes.
This is derived at view time rather than stored. The inputs already exist and already move together on the heartbeat path; a stored copy would be a second source of truth with nothing keeping it in step.
const ( NodeStatusNeverReported = "never_reported" NodeStatusOnline = "online" NodeStatusDegraded = "degraded" NodeStatusOffline = "offline" NodeStatusDisabled = "disabled" )
Node status is the one word the console renders for a node. It is derived here, at view time, so the nodes list, node detail, the overview KPIs, the map and the terminal picker all read the same rule at the same instant; the same machine used to read offline on one page and degraded on another because each page derived its own word from Online, LastSeen and metrics.
Online and Reachability stay on the wire for consumers that have not moved. Both read the stored Online flag as it is, so in the window described below, between a beat going stale and the liveness sweep flipping that flag, they still say online while Status has already moved to offline. Status is the derived answer and the one to trust; anything that counts, groups or filters nodes derives from it rather than from Online.
Precedence, first match wins:
disabled Disabled is set. An operator switched the node off, and
that outranks whatever the agent is or is not doing.
never_reported LastSeen is zero. The record has existed since enrollment
and no report has ever arrived. Since is CreatedAt.
offline Online is false, or the last report is older than
nodeOfflineThreshold. The second clause covers the window
between a beat going stale and the liveness sweep flipping
the flag. Since is LastSeen, the last contact.
degraded In contact, but a probe the control plane holds fresh
evidence for says something on the node is broken; see
nodeDegradations for the inputs. Since is the earliest
problem's start.
online In contact within the threshold and nothing proven broken.
Since is OnlineSince, omitted for records written before
that field existed.
const ( ApprovalWaitTaskFailed = "task_failed" ApprovalWaitTaskRunning = "task_running" ApprovalWaitAwaitingConfirmation = "awaiting_confirmation" ApprovalWaitVersionMismatch = "version_mismatch" ApprovalWaitTaskFinished = "task_finished" ApprovalWaitPlanSuperseded = "plan_superseded" ApprovalWaitNodeUnknown = "node_unknown" ApprovalWaitNodeDisabled = "node_disabled" ApprovalWaitNodeNeverReported = "node_never_reported" ApprovalWaitNodeOffline = "node_offline" ApprovalWaitCapabilityExcluded = "capability_excluded" ApprovalWaitTaskExecutionDisabled = "task_execution_disabled" ApprovalWaitNotQueued = "not_queued" ApprovalWaitTaskQueued = "task_queued" )
Why an approved approval has not applied.
An approval whose status is "approved" has left the operator's hands and has not reached the node. That is a legitimate state and the console used to render it as a bare word, so the one item that could never clear looked identical to the ones that were about to. The page said Pending 0 next to a non-empty inbox and an operator reasonably read that as "everything ran".
The explanation cannot be derived by the console. Whether an apply task was ever queued is not on the approvals wire at all, the node's contact state lives behind a second endpoint, and the capability gate and the fleet kill switch are server state with no client-visible projection. Correlating three endpoints to produce a sentence that may still be wrong is worse than saying nothing, so the control plane answers instead.
The vocabulary is deliberately borrowed. Node reasons carry the exact status word node_status.go derives (disabled, never_reported, offline) with its "since" instant, so the sentence on this page and the word on the Nodes page cannot disagree about the same machine.
Precedence, first match wins. Evidence of what already happened outranks a prediction of what will not:
task_failed An apply task ran and ended failed or cancelled.
task_running An apply task is leased right now. Nothing is wrong.
awaiting_confirmation The apply task of an agent update finished and the
node has not reported the target version yet. The
approval is applied the moment it does; nothing is
wrong. Only while the node is reporting: a silent node
falls through to the codes below.
version_mismatch The apply task of an agent update finished, the node
has been reporting since well past the scheduled
restart, and it still runs a version that is not the
target. The restart did not take.
task_finished An apply task finished and the approval was never
marked applied. The control plane does not know why;
it says so rather than picking a story.
plan_superseded A newer approval exists for the same node, plugin and
action. This plan is dead whatever the node does.
node_unknown The target node record is gone.
node_disabled An operator switched the node off.
node_never_reported The node was enrolled and has never reported.
node_offline The node has stopped reporting.
capability_excluded The plugin's capability is excluded for this node, so
queueing an apply is refused.
task_execution_disabled The fleet kill switch is on; nothing leases.
not_queued Approved without queueing an apply, and nothing since
has queued one. Nothing will run on its own.
task_queued Queued, node reporting, waiting for the next poll.
The ordinary case, and the only one that clears itself.
const ( EventServiceDown = "service.down" EventServiceRecovered = "service.recovered" )
Typed notification kinds for service liveness (design-19), declared beside their emitter per the convention: new callers say what the event is, so a reworded title can never silently change who receives it.
const ( // EventSSHCompromiseSuspected fires on a successful login from a source // that failed first. It is the only pressure-derived event that notifies. EventSSHCompromiseSuspected = "ssh.compromise_suspected" // EventSSHPressureWindow is recorded, never notified. EventSSHPressureWindow = "ssh.pressure_window" )
SSH authentication pressure: what the fleet reports, and the one thing worth waking someone for.
Alerting on "you are being brute-forced" is the wrong design. Every public node in this fleet is being brute-forced, one of them 8449 times in a day, so that alert fires constantly, gets muted, and is muted on the day it matters.
The signal that earns an alert is a SUCCESS from a source that had been failing. That is a possible compromise and it needs a person now. The volume itself is posture: it belongs in an audit trail and a report, where it informs a decision made at leisure.
So this file records every window and notifies for almost none of them.
const (
NodeStatusUnknown = "unknown"
)
GET /api/nodes/status-history?days=7[&node_id=]: each readable node's online record over the window, summarised here so the table, the card and the detail strip read one rule, as node_status.go does for the current word.
Three states: online, offline and unknown. Unknown is what nobody observed, which is the time before a node's first recorded edge and the time the control plane itself was down (the rows the store writes under NodeStatusServerID at start). A node's rows are transitions, so its state at any instant is the last row at or before it; the initial state is the one at the window start, and every later row in the window is echoed back with the cause that wrote it, control-plane edges included, so a strip is drawn by walking events from initial with nothing else to know.
Episodes count offline stretches that begin in the window plus one for a window that opens offline. A control-plane gap pauses a stretch rather than ending it: the node was offline on both sides, and the gap says nothing. The opening state is the observed one, so a window that opens inside a gap opens unknown, and the stretch the restart then reveals was already under way rather than one that opened here.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuditHeadShippingOptions ¶ added in v0.2.0
type AuditHeadShippingOptions struct {
URL string
BearerToken string
Interval time.Duration
HTTPClient *http.Client
}
AuditHeadShippingOptions configures automated off-box custody for the verified audit WAL head. Empty URL disables shipping.
type BuildInfo ¶ added in v0.2.0
type BuildInfo struct {
ServerVersion string `json:"server_version"`
ServerCommit string `json:"server_commit"`
ServerDate string `json:"server_date"`
DashboardRef string `json:"dashboard_ref,omitempty"`
DashboardBuilt string `json:"dashboard_built,omitempty"`
TaskExecutionDisabled bool `json:"task_execution_disabled,omitempty"`
}
type KnownCapability ¶ added in v0.2.3
type KnownCapability struct {
ID string `json:"id"`
Enforced bool `json:"enforced"`
Mutates bool `json:"mutates"`
// Derived is whether this capability can answer for a node that has no
// explicit enrolment. Enforcing one that cannot refuses the whole fleet on
// the first request, so the console has to be able to say so.
Derived bool `json:"derived"`
}
KnownCapability describes one declared capability to the console.
Enforced and Mutates are both exported because they answer different questions an operator asks: whether a decision here currently bites, and whether this capability is one that changes the machine (which is what makes it opt-in in the first place). Without Enforced the console would render fourteen inert capabilities beside the one live one and imply they all matter equally.
type Line ¶ added in v0.2.0
type Line struct {
ID string `json:"id"` // == LineHashID (stable handle)
LineHashID string `json:"line_hash_id"` // stable across re-probes; see lineHash / stableLineHandle
LineID string `json:"line_id,omitempty"`
NodeID string `json:"node_id"`
NodeIdentityUUID string `json:"node_identity_uuid,omitempty"`
LineUUID string `json:"line_uuid,omitempty"` // design-15 D1: durable control-plane identity (vpnmeta/lineuuid)
DownstreamLineUUID string `json:"downstream_line_uuid,omitempty"` // design-15 §6: declared chain edge target
Core string `json:"core"` // sing-box | xray | mihomo
Source string `json:"source"` // managed | discovered | imported
Managed bool `json:"managed"` // under Lattice config management
Name string `json:"name"`
Tag string `json:"tag,omitempty"`
// InboundTags is every sing-box inbound tag the node reported for the conf
// file this line stands for. Tag above is that file's name, which equals the
// inbound tag only by the helper script's convention; a hand-written file, or
// one holding a relay pair, carries tags of its own. Traffic counters and
// connection records arrive keyed by the core's real inbound tags, so these
// are what those joins have to run against. Empty when the node's sing-box
// helper predates the field, which leaves the convention in force and every
// existing join unchanged.
InboundTags []string `json:"inbound_tags,omitempty"`
// NamedUsers and UnnamedUsers count the credentials on this line's conf file
// that the node can and cannot count individually. sing-box builds its stats
// user allowlist by name, so an unnamed credential never gets a per-user
// counter and its traffic stays inside the inbound total. That is a
// permanent property of the config rather than a failed attribution, and the
// two are worth telling apart on screen. Both zero means the node did not
// report, which is not the same as a line with no credentials.
NamedUsers int `json:"named_users,omitempty"`
UnnamedUsers int `json:"unnamed_users,omitempty"`
Type string `json:"type,omitempty"` // protocol
Transport string `json:"transport,omitempty"`
Security string `json:"security,omitempty"`
ListenHost string `json:"listen_host,omitempty"`
ListenPort int `json:"listen_port,omitempty"`
PublicHost string `json:"public_host,omitempty"`
// PublicPort is where the outside actually reaches this line, when that
// differs from ListenPort. Declared by the node, because a mapping that
// lives in a provider's router cannot be read from the config here. Zero
// means the listen port is also the public one.
PublicPort int `json:"public_port,omitempty"`
// ProviderEdge is the hostname a provider forwards into this node from. A
// relay names it as its outbound server, so it is the only host under which
// a chain into a NAT node can be matched back to the line that ends it.
ProviderEdge string `json:"provider_edge,omitempty"`
Domain string `json:"domain,omitempty"`
OutboundRef string `json:"outbound_ref,omitempty"` // direct | <host/tag> | "" unknown
OutboundServer string `json:"outbound_server,omitempty"` // downstream server host the outbound routes to
OutboundPort int `json:"outbound_port,omitempty"` // downstream server port the outbound routes to
JumpEdges []string `json:"jump_edges,omitempty"` // line_hash_ids this line relays to
// DeclaredJumpEdges is the subset of JumpEdges resolved from the sidecar's
// declared downstream_line_uuid (design-15 §6), not inferred from outbound
// host/port — the UI badges these as orchestrated edges.
DeclaredJumpEdges []string `json:"declared_jump_edges,omitempty"`
// design-17: a line backed by a server-owned managed-line definition (the
// overlay) carries the definition's state. The join is by line_hash_id —
// the compiler pre-computes the hash discovery will assign, so a
// rediscovered applied line lands on its definition exactly.
Overlay bool `json:"overlay,omitempty"`
OverlayStatus string `json:"overlay_status,omitempty"` // planned | applied | failed
OverlayUser string `json:"overlay_user,omitempty"`
UserCount int `json:"user_count"`
UserKnown bool `json:"user_known"` // false ⇒ discovered line, count not yet inspected
Status string `json:"status,omitempty"` // ok | pending | error | stale
LastError string `json:"last_error,omitempty"`
// design-19: what the service is doing, as opposed to what the config
// says. Status above answers "does the configuration check out";
// ServiceState answers "is anything actually running and holding this
// line's port". They must never be merged back into one field: their
// disagreement is exactly the incident signal.
ServiceState string `json:"service_state,omitempty"` // running | down | restarting | unknown
ServiceCheckedAt time.Time `json:"service_checked_at,omitempty"`
// ServiceNote is the probe's own account of why the state is not
// "running": the refused candidate and the rule it failed, or the command
// that could not run. Empty when the service is proven running, so a
// consumer that prints it prints only what needs a hand.
ServiceNote string `json:"service_note,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"` // sing-box `_lattice` block (future enrich)
}
Line is the unified, node-grouped view of a proxy "line" — an inbound/endpoint regardless of origin: a Lattice-managed inbound rendered onto a node, or a proxy discovered on-box via `sb --json list`. It replaces the split between the old managed Inbounds view and the Discovered view (design-12). It is a DERIVED, read-model type computed on demand from the proxy store + live discovery inventory; it is not persisted and is never sent to the agent (so it lives in the server package, not the shared SDK). Secret-free: it carries only connection-shape metadata, never private keys or passwords.
type LineBinding ¶ added in v0.2.0
type LineBinding struct {
LineHashID string `json:"line_hash_id"`
Enabled bool `json:"enabled"`
FlowOverride string `json:"flow_override,omitempty"`
}
LineBinding attaches a user to a specific Line (by its stable line_hash_id).
type LineGroup ¶ added in v0.2.0
type LineGroup struct {
NodeID string `json:"node_id"`
NodeName string `json:"node_name,omitempty"`
Lines []Line `json:"lines"`
}
LineGroup is the set of lines on one node — the unit the dashboard renders.
type NodeProfileRuntime ¶ added in v0.2.0
type NodeProfileRuntime struct {
NodeID string `json:"node_id"`
NodeName string `json:"node_name,omitempty"`
Managed bool `json:"managed"` // a Lattice ProxyNodeProfile exists
Origin string `json:"origin,omitempty"` // managed | discovered, when a profile exists
Core string `json:"core,omitempty"`
CoreVersion string `json:"core_version,omitempty"`
ConfigPath string `json:"config_path,omitempty"`
StatsAPI string `json:"stats_api,omitempty"`
Applied bool `json:"applied"`
LastApplyAt string `json:"last_apply_at,omitempty"`
LastError string `json:"last_error,omitempty"`
InboundCount int `json:"inbound_count"`
DiscoveredCount int `json:"discovered_count"`
DiscoveryStatus string `json:"discovery_status,omitempty"`
DiscoveryError string `json:"discovery_error,omitempty"`
DiscoveredAt string `json:"discovered_at,omitempty"`
Collector *UsageCollectorRuntime `json:"collector,omitempty"`
Capabilities []string `json:"capabilities"`
}
NodeProfileRuntime is the vpn-core per-node runtime view (design-12 S4): the operator-facing "is this node under vpn-core management, what core/version is on it, is the config applied, is the collector healthy, what was last probed" page. It is a DERIVED read-model unioning the Lattice ProxyNodeProfile (managed config + apply/collector status) with the live discovered SingBoxInventory (core version + discovered node count + discovery status). A node appears if it has either.
type Options ¶
type Options struct {
// SubscriptionDecoy shapes the response every non-servable public
// subscription request receives. Its zero value is an empty-bodied 404, which
// a reverse proxy can replace with its own error page so /sub/<anything> and
// an unknown path are indistinguishable.
SubscriptionDecoy subscriptionDecoyOptions
Store *store.Store
WebFS fs.FS
// LogStore is the dedicated bounded log-line database (logs.db). Nil disables
// the log-ingestion feature: its endpoints return 503 and agents are told to
// tail nothing. Injected by main (opened beside the state file with the same
// cipher), mirroring Store.
LogStore *logstore.Store
// TraceStore is the sing-box connection trace database (trace.db). Nil
// disables tracing: its endpoints return 503 and agents are told to collect
// nothing. Injected by main beside LogStore with the same cipher.
TraceStore *tracestore.Store
AdminUsername string
AdminPassword string
Build BuildInfo
SecureCookies bool
// TrustProxy enables reading the client address from proxy headers
// (CF-Connecting-IP, then X-Forwarded-For). Only enable when the server
// sits behind a trusted reverse proxy / Cloudflare; otherwise clients can
// spoof the header and evade per-IP rate limiting.
TrustProxy bool
// RequireTOTP forces interactive user sessions to enable TOTP before they can
// use non-setup APIs. Existing password/SSO login still issues a session so
// the operator can enroll, but withAuth gates every other route until TOTP is
// active. Bearer PATs are not interactive sessions and are unaffected.
RequireTOTP bool
// PluginDir is the root directory of installed plugin bundles. Empty disables
// plugin loading entirely.
PluginDir string
// PluginBundleCacheDir is the private content-addressed extraction cache for
// signed manifest v2 archives. V2 plugins fail closed when it is empty.
PluginBundleCacheDir string
// PluginTrust is the operator policy used to verify plugin signatures at load
// time. The zero value is fail-closed: host-risk plugins require a trusted
// publisher signature.
PluginTrust plugin.TrustPolicy
// PluginRuntimeDir, when set, enables the Tier-2 system runner: each system
// plugin gets a confined 0700 working dir under this path and its verified
// artifact is executed there (design-08). Empty keeps the noop runner (broker
// armed, artifact NOT executed) — the deliberate default.
PluginRuntimeDir string
// PluginRuntimeEnv is the explicit environment-variable allowlist forwarded
// to Tier-2 system plugins. Empty means plugins receive only the runner's
// fixed safe PATH/HOME/TMPDIR.
PluginRuntimeEnv []string
// PluginRuntimePool controls persistent stdio-json-v2 worker capacity and
// retirement. Nil uses the system runner's safe host defaults.
PluginRuntimePool *plugin.SystemPoolConfig
// PublicURL is the externally-reachable base URL of this server (scheme +
// host, no trailing slash), used to build the OIDC redirect URL. Required
// for SSO login; empty disables the OIDC start/callback flow. It is also the
// canonical source for the WebAuthn relying-party ID and origin (RPID = host,
// RPOrigin = scheme://host[:port]); passkeys fail closed when it is empty.
PublicURL string
// MetricsToken enables the /metrics endpoint when non-empty. The endpoint
// accepts only Authorization: Bearer <token>; empty keeps it hidden so public
// deployments do not expose fleet runtime counters by default.
MetricsToken string
// CoreDNSBinary optionally pins the CoreDNS executable that self-host DNS
// apply scripts may install. Empty preserves the fail-closed precondition
// that coredns already exists on the node.
CoreDNSBinary selfdns.CoreDNSBinarySource
// GeoResolver maps node public IPs to advisory coordinates for the Fleet Map.
// Nil keeps automatic lookup disabled; manual NodeGeo remains available.
GeoResolver geoip.Resolver
// AgentReleaseRepo is the trusted GitHub repository used by the simplified
// node-agent update UX. Empty uses LatticeNet/lattice-node-agent.
AgentReleaseRepo string
// AuditHeadShipping periodically POSTs the verified, locally anchored audit
// WAL head to an operator-controlled HTTPS endpoint. Empty URL disables it.
AuditHeadShipping AuditHeadShippingOptions
// TaskExecutionDisabled is a server-side fleet kill switch. When true, new
// tasks are not queued and agents receive no task leases. Already leased
// task results are still accepted so in-flight work can report terminal state.
TaskExecutionDisabled bool
// ApprovalAutoRules is the operator's JSON-encoded auto-approve policy list
// (LATTICE_APPROVAL_AUTO_RULES). Empty keeps approvals fully manual — the
// default. Malformed input is logged and ignored, never fatal.
ApprovalAutoRules string
// RenewalReminderInterval controls the machine-renewal reminder scheduler.
// Zero uses the production default. DisableRenewalScheduler is intended for
// tests that need full control over reminder evaluation.
RenewalReminderInterval time.Duration
DisableRenewalScheduler bool
Logger *log.Logger
}
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
func (*Server) Close ¶ added in v0.2.3
Close stops runtime-owned subprocesses and waits for their transports to be reaped before the server process exits.
func (*Server) KnownCapabilities ¶ added in v0.2.3
func (s *Server) KnownCapabilities() []KnownCapability
KnownCapabilities lists every capability this fleet has, compiled-in and plugin-provided, with the enforcement each one currently has.
type SubscriptionSummary ¶ added in v0.2.0
type SubscriptionSummary struct {
UserID string `json:"user_id"`
Email string `json:"email,omitempty"`
Enabled bool `json:"enabled"`
Eligible bool `json:"eligible"` // enabled AND not expired
HasSubToken bool `json:"has_sub_token"`
BindingCount int `json:"binding_count"`
CredentialCount int `json:"credential_count"`
ExpiresAt string `json:"expires_at,omitempty"`
}
SubscriptionSummary is the producer-side, per-identity subscription state (design-12 S5). The locked boundary: vpn-core PRODUCES the source (identities, credentials, line bindings, and a sub token). A separately installed plugin may consume that source and publish it. This read-model intentionally exposes only subscription STATE (eligibility, binding/credential counts, whether a sub token exists) — NOT the raw sub token or rendered links, which remain with the legacy /sub substrate and any independently installed publisher plugin.
type UsageByNode ¶ added in v0.2.0
type UsageByUser ¶ added in v0.2.0
type UsageByUser struct {
UserID string `json:"user_id"`
Email string `json:"email,omitempty"`
UsedBytes int64 `json:"used_bytes"`
QuotaBytes int64 `json:"quota_bytes,omitempty"`
Status string `json:"status,omitempty"`
LastSeen string `json:"last_seen,omitempty"`
}
Usage is the vpn-core 3-D usage read-model (design-12 S3). It presents traffic from two operator-facing dimensions — by user and by node — plus the per-(node, user) breakdown. It is DERIVED on demand from the existing accounting substrate: ProxyUser.UsedBytes (monotonic per-user total) and the latest ProxyUsageSnapshot per node (raw per-(node,user) counters), mapped onto VpnUser identities.
S3b line-aware collectors populate line_user_bytes in ProxyUsageSnapshot; older collectors continue to produce aggregate rows with line_hash_id empty.
type UsageCollector ¶ added in v0.2.0
type UsageCollectorRuntime ¶ added in v0.2.0
type VpnCredential ¶ added in v0.2.0
type VpnCredential struct {
Protocol string `json:"protocol"` // vless|vmess|trojan|shadowsocks|hysteria2|tuic|anytls
UUID string `json:"uuid,omitempty"` // vless/vmess/tuic
Password string `json:"password,omitempty"` // trojan/shadowsocks/hysteria2/anytls
Flow string `json:"flow,omitempty"` // vless xtls flow
Method string `json:"method,omitempty"` // shadowsocks cipher
Security string `json:"security,omitempty"` // vmess security
}
VpnCredential is one per-protocol credential. Only the fields relevant to the protocol are populated. uuid/password are secret material.
type VpnUser ¶ added in v0.2.0
type VpnUser struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name,omitempty"`
Enabled bool `json:"enabled"`
Credentials []VpnCredential `json:"credentials"`
Bindings []LineBinding `json:"bindings"`
SubID string `json:"sub_id,omitempty"`
QuotaBytes int64 `json:"quota_bytes,omitempty"`
// QuotaPeriod is none (lifetime, the default) or monthly; QuotaResetDay is
// the day of month (1..28) a monthly period starts. Period usage is summed
// from the user-day rows, never stored.
QuotaPeriod string `json:"quota_period,omitempty"`
QuotaResetDay int `json:"quota_reset_day,omitempty"`
ExpiresAt time.Time `json:"expires_at,omitempty"`
Group string `json:"group,omitempty"`
Comment string `json:"comment,omitempty"`
SubscriptionGeneration uint64 `json:"-"`
// MigratedFromProxyUser records the legacy ProxyUser this identity was derived
// from, so the migration is idempotent and the subscription substrate is traceable.
MigratedFromProxyUser string `json:"migrated_from_proxy_user,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
VpnUser is the vpn-core identity model (design-12 S2): one human/account identity that carries a credential SET (per protocol) and is bound to many Lines. It is OWNED by the vpn-core plugin — persisted in the plugin's durable KV bucket (plugin:latticenet.vpn-core), not the SDK-typed store — so it is genuinely plugin-owned data with no SDK release coupling. It is additive: the legacy model.ProxyUser stays as the subscription-render + usage-accounting substrate this slice; VpnUsers are derived from ProxyUsers by an idempotent migration.
Credential secrets (uuid/password) are NEVER returned through the read RPC; the gateway-facing views are redacted (see vpnUserView).
Source Files
¶
- audit_head_shipper.go
- audit_throttle.go
- audit_wal_verifier.go
- authz_nodeset.go
- capabilities.go
- linemeta.go
- linemeta_sync.go
- lines.go
- lines_cache.go
- lineusers.go
- managedline.go
- node_reachability.go
- node_status.go
- plugin_host.go
- plugin_pool_telemetry.go
- profiles.go
- publishing.go
- server.go
- server_agent_artifacts.go
- server_agent_auth_cache.go
- server_agent_control.go
- server_agent_debug.go
- server_agent_install_script.go
- server_agent_update.go
- server_approval_policy.go
- server_approval_reason.go
- server_approval_waiting.go
- server_dns.go
- server_dns_external.go
- server_geo.go
- server_georouting.go
- server_group_policy.go
- server_groups.go
- server_inventory.go
- server_linechain.go
- server_lines_runtime.go
- server_logs.go
- server_monitor_tls.go
- server_netguard.go
- server_netguard_reality.go
- server_netpolicy.go
- server_network_plugins.go
- server_nft.go
- server_node_delete.go
- server_node_status_history.go
- server_nodes_dup.go
- server_notify_webhook.go
- server_oidc.go
- server_plugin_assets.go
- server_plugin_grants.go
- server_plugin_invoke.go
- server_plugin_operation.go
- server_plugin_trust.go
- server_proxy.go
- server_proxy_enforce.go
- server_proxy_notify.go
- server_singbox_discover.go
- server_singbox_liveness.go
- server_singbox_manage.go
- server_ssh_pressure.go
- server_sshguard.go
- server_sshguard_knock.go
- server_sshguard_status.go
- server_stepup.go
- server_storage.go
- server_subscription_share.go
- server_subscription_share_api.go
- server_substore_plugins.go
- server_terminal.go
- server_terminal_stream.go
- server_trace.go
- server_trace_attr.go
- server_trace_timeline.go
- server_users.go
- server_views.go
- server_vpncore.go
- server_webauthn.go
- storage_verify_cache.go
- subscription_cache.go
- subscription_compose.go
- subscription_decoy.go
- subscription_graph_authority.go
- subscription_options.go
- subscription_refresh.go
- subscription_share_export.go
- subscription_ua.go
- subscriptions.go
- substore_sync.go
- usage.go
- usage_attribution.go
- usage_history.go
- vpnusers.go