Files
3x-ui-agent/internal/database/model/agent_node.go
T
Rogue 5ca22ec340 probe-only node mode: master flag + agent gate + panel switch (V2.1.0 A)
agent_nodes gains probe_only; getConfig always serves the flag and withholds the sing-box payload (null config, empty hash) for flagged nodes while probe tasks, fast interval, stat iface and update intents ride as usual. The agent gates both core branches when the local agent.json preset or the master flag is on, retires a running core once (best-effort tail flush, switchover bookkeeping untouched so turning the flag off self-heals through the existing hash-current-but-not-running path), and makes the traffic cycle an explicit no-op. Panel: node create/update accept probeOnly, the node view overlays it from the binding, the node form gains a switch (carried into the generated install command), the list shows a probe-only tag in the core-version cell, and install-agent.sh --probe-only writes the preset, skips the core download/self-check and inherits the flag on no-arg reruns.
2026-10-23 11:54:35 +00:00

131 lines
6.8 KiB
Go

package model
// AgentNode binds a lightweight agent (token-registered, no web panel) to a
// 3x-ui node row and carries the per-agent egress configuration.
//
// Agents are the only writers of their own LastSeen; the panel owns everything
// else. Authentication uses only the SHA-256 hex of the token; since the
// next-core batch (item 19) the raw token is additionally kept recoverably
// in TokenPlain — an operator-accepted tradeoff so the node edit page can
// re-display the full install command.
type AgentNode struct {
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
TokenHash string `json:"-" gorm:"size:64;uniqueIndex"`
TokenPrefix string `json:"tokenPrefix" gorm:"size:16;index"`
// TokenPlain is a recoverable copy of the raw token: stored at
// issuance, and backfilled from the first authenticated agent request
// for rows created before this field existed (item 19). Existing
// values are never overwritten by requests; only issuance (create /
// panel-side re-issue) writes a fresh one. Never serialized.
TokenPlain string `json:"-" gorm:"type:text"`
Name string `json:"name"`
// NodeID is the 3x-ui node whose inbounds this agent serves. Nil means the
// token was issued but not yet bound to a node.
NodeID *int `json:"nodeId" gorm:"index"`
// Outbounds is the full xray "outbounds" JSON array pushed to this agent,
// replacing the template defaults. Empty means "use template defaults".
Outbounds string `json:"outbounds" gorm:"type:text"`
// RoutingRules is the full xray "routing.rules" JSON array pushed to this
// agent, replacing the template defaults. Empty means "use template defaults".
RoutingRules string `json:"routingRules" gorm:"type:text"`
// SingboxOutbounds / SingboxRoutingRules are the sing-box conversions
// of the two override columns above, produced by the B1 one-shot
// migration (ITEM6 §5.6). Entries that cannot be mapped stay only in
// the legacy columns and are listed in the migration report. The
// legacy columns stay the read source until B2.
SingboxOutbounds string `json:"-" gorm:"column:singbox_outbounds;type:text"`
SingboxRoutingRules string `json:"-" gorm:"column:singbox_routing_rules;type:text"`
LastSeen int64 `json:"lastSeen"`
CreatedAt int64 `json:"createdAt" gorm:"autoCreateTime:milli"`
UpdatedAt int64 `json:"updatedAt" gorm:"autoUpdateTime:milli"`
// Heartbeat telemetry (P13): the agent piggybacks host/runtime facts on
// its 60s heartbeat and the panel overlays them onto the node list row.
// All fields are best-effort: an agent that never reported telemetry has
// TelemetryAt == 0 and the UI keeps showing "-" for its row.
AgentVersion string `json:"agentVersion"`
XrayVersion string `json:"xrayVersion"`
// SingboxVersion is the agent's sing-box core version (V2.0.1 ②):
// reported on the same telemetry since ITEM6 B3, persisted only
// from V2.0.1 on — the node list shows it in place of the xray
// version for agents that report one.
SingboxVersion string `json:"singboxVersion"`
UptimeSecs uint64 `json:"uptimeSecs"`
LatencyMs int `json:"latencyMs"`
CpuPct float64 `json:"cpuPct"`
MemPct float64 `json:"memPct"`
IPv4 string `json:"ipv4"`
IPv6 string `json:"ipv6"`
// TelemetryAt is the unix-milli time of the last heartbeat that carried
// a telemetry object.
TelemetryAt int64 `json:"telemetryAt"`
// Probe snapshot (item 7, B1): the monitor-probe-style host Facts and
// latest Metrics the agent piggybacks on register + heartbeat. Only the
// latest values live here; history lands in B2's probe tables. Agents
// that never reported a probe block keep ProbeAt == 0 and zero values.
ProbeHostname string `json:"probeHostname"`
ProbeOS string `json:"probeOs"`
ProbeKernel string `json:"probeKernel"`
ProbeArch string `json:"probeArch"`
ProbeVirt string `json:"probeVirt"`
ProbeCPUName string `json:"probeCpuName"`
ProbeCPUCores uint32 `json:"probeCpuCores"`
ProbeBootID string `json:"probeBootId"`
ProbeIface string `json:"probeIface"`
ProbeLoad1 float32 `json:"probeLoad1"`
ProbeLoad5 float32 `json:"probeLoad5"`
ProbeLoad15 float32 `json:"probeLoad15"`
ProbeMemTotal uint64 `json:"probeMemTotal"`
ProbeMemUsed uint64 `json:"probeMemUsed"`
ProbeSwapTotal uint64 `json:"probeSwapTotal"`
ProbeSwapUsed uint64 `json:"probeSwapUsed"`
ProbeDiskTotal uint64 `json:"probeDiskTotal"`
ProbeDiskUsed uint64 `json:"probeDiskUsed"`
ProbeNetRxTot uint64 `json:"probeNetRxTotal"`
ProbeNetTxTot uint64 `json:"probeNetTxTotal"`
ProbeNetRx uint64 `json:"probeNetRx"`
ProbeNetTx uint64 `json:"probeNetTx"`
ProbeTCP uint32 `json:"probeTcp"`
ProbeUDP uint32 `json:"probeUdp"`
ProbeProcs uint32 `json:"probeProcs"`
ProbeCPU float32 `json:"probeCpu"`
// ProbeAt is the unix-milli time of the last heartbeat that carried a
// probe metrics block.
ProbeAt int64 `json:"probeAt"`
// Seen source addresses (public-address resolution): the source IP of
// the agent's own authenticated requests, as observed by the master —
// the CF-Connecting-IP header when the agent comes through Cloudflare,
// else the direct peer address. An agent behind 1:1 NAT reports only
// its private interface address in telemetry (its public address is
// not assigned to any local interface, so enumeration can never see
// it); the observed source address is the master's view of the
// agent's public side. Writes are change-only (see
// AgentController.captureSeenIP); SeenIPAt is the unix-milli time of
// the last change, not of the last request.
SeenIPv4 string `json:"seenIpv4"`
SeenIPv6 string `json:"seenIpv6"`
SeenIPAt int64 `json:"seenIpAt"`
// ProbeOnly marks the node as probe-only (V2.1.0): the master serves
// no core config for it and the agent runs no core at all — only the
// probe channels (heartbeat, fast report, latency tasks) stay live.
// Set from the panel's node form; the agent can also preset it in
// its local agent.json, either source turns the mode on.
ProbeOnly bool `json:"probeOnly"`
// One-click update (item 20): the master records an update intent here;
// the agent picks it up from its config poll, downloads + verifies +
// swaps its binary, restarts itself and reports the outcome back on
// the heartbeat. UpdateRequestedAt == 0 means no intent is pending.
UpdateRequestedAt int64 `json:"updateRequestedAt"`
UpdateTarget string `json:"updateTarget"` // A-form display target, e.g. "A1.0.1"
UpdateTag string `json:"updateTag"` // release tag to download, e.g. "V1.0.1"
// UpdateState: "" | pending | downloading | swapped | failed |
// current | success (success = new A version observed by the master).
UpdateState string `json:"updateState"`
UpdateError string `json:"updateError"`
UpdateStateAt int64 `json:"updateStateAt"`
}