Documentation
¶
Index ¶
- Constants
- func NormalizePanelType(s string) (canonical string, legacy, ok bool)
- type API
- type AccessLogEntry
- type AccessLogReporter
- type ClientInfo
- type Config
- type DetectResult
- type DetectRule
- type Dialect
- type InFlightCanceler
- type NameServer
- type NodeInfo
- type NodeStatus
- type OnlineUser
- type REALITYConfig
- type UserInfo
- type UserTraffic
Constants ¶
const ( UserNotModified = "users not modified" NodeNotModified = "node not modified" RuleNotModified = "rules not modified" )
Panel GET responses that hit an unchanged ETag surface these sentinel errors.
const ( // PanelSSpanel — the traditional SSpanel. Ships nodes as the 6-segment // `server` string, and has no dedup on any of its POST endpoints. PanelSSpanel = "SSpanel" // PanelRigel — Rigel. Ships nodes as `custom_config` (either the // XrayR-flat shape or a sing-box-native inbound body, chosen per node by // its protocol — that split stays payload-driven, see // sspanel.ParseSSPanelNodeInfo). PanelRigel = "Rigel" // PanelPhoenixLegacy is what PanelRigel was called before the panel was // renamed Phoenix → Rigel. Still accepted: this value lives in config.yml // on every already-deployed node, and a panel-side rename must not require // touching them in the same maintenance window. NormalizePanelType folds it // onto PanelRigel; nothing downstream compares against it. PanelPhoenixLegacy = "Phoenix" )
Panel types accepted in config.yml's Nodes[].PanelType. Both speak the mod_mu wire protocol; what differs is the node payload each one sends and, consequently, how safe it is to re-send a POST (doc/09 §5.1, doc/14 C-08).
const HeaderReportID = "X-Sing2-Report-Id"
HeaderReportID carries the accounting-period id on a traffic report, so a panel can drop a re-send without dropping a genuinely new report. Sent to Rigel only (doc/10 §7.1, doc/14 C-08); the traditional panel has no dedup to hang it on.
const HeaderReportIDAck = "X-Sing2-Report-Id-Ack"
HeaderReportIDAck is the panel's answer: present on a traffic response means this panel really did key its dedup on the HeaderReportID we sent, so a re-send of that id is provably free.
It exists to make a deploy-order mistake impossible rather than merely documented. The node re-sends a failed traffic report ONLY because it believes the panel dedups; a node pointed at a Rigel that has not run the migration yet would otherwise double-bill on every retry — the exact bug this mechanism closes. No ack, no retry (doc/10 §7.1).
Variables ¶
This section is empty.
Functions ¶
func NormalizePanelType ¶ added in v0.1.7
NormalizePanelType folds an operator-written Nodes[].PanelType onto the canonical constant. Returns legacy=true when the value was an accepted but deprecated spelling (so the caller can warn once), and ok=false when it is not a recognised panel type at all.
Canonicalising once, up front, is what keeps the alias from leaking: every downstream comparison stays a plain `== PanelRigel`, with no second spelling to remember at each site.
Types ¶
type API ¶
type API interface {
GetNodeInfo() (*NodeInfo, error)
GetUserList() ([]UserInfo, error)
GetNodeRule() ([]DetectRule, error)
// ReportUserTraffic carries reset-then-report semantics (doc/10 §7.1).
// windowID names the accounting period and MUST be identical on any re-send
// of the same payload, so a panel that dedups on it can drop a duplicate
// without dropping a genuinely new report (doc/14 C-08).
ReportUserTraffic(userTraffic []UserTraffic, windowID string) error
ReportNodeOnlineUsers(onlineUsers []OnlineUser) error // deduped online IPs (T8)
ReportIllegal(detectResults []DetectResult) error // SSpanel /mod_mu/users/detectlog (T6)
ReportNodeStatus(nodeStatus *NodeStatus) error
Describe() ClientInfo
Debug()
}
API is the panel client interface. The three GET endpoints (GetNodeInfo/GetUserList/GetNodeRule) support ETag incremental sync and return the NodeNotModified/UserNotModified/RuleNotModified sentinel errors on HTTP 304. AccessLog reporting is a separate optional interface (AccessLogReporter, api/accesslog.go), discovered by type assertion.
type AccessLogEntry ¶
type AccessLogEntry struct {
UserID *int `json:"user_id"`
Email *string `json:"email"`
Ts int64 `json:"ts"`
SrcIP string `json:"src_ip"`
SrcPort *int `json:"src_port"`
Action string `json:"action"` // accepted / rejected
Network *string `json:"network"`
DestHost *string `json:"dest_host"`
DestPort *int `json:"dest_port"`
RejectReason *string `json:"reject_reason"`
}
AccessLogEntry is a single access-log record reported to the panel. Nullable fields use pointers so they marshal to JSON null when absent (doc/10 §3.9).
type AccessLogReporter ¶
type AccessLogReporter interface {
ReportAccessLog(entries []AccessLogEntry) error
}
AccessLogReporter is an optional interface implemented by panel API clients that support access-log ingestion (mod_mu / sspanel-compatible). The billing/accesslog manager type-asserts each node's API client against it; clients that don't implement it are skipped (doc/10 §2.2, §6.4; doc/11 §13).
type ClientInfo ¶
ClientInfo is returned by an API client's Describe() (doc/10 §3.7).
type Config ¶
type Config struct {
// PanelType is NOT read from the ApiConfig block — it lives one level up at
// Nodes[].PanelType and the panel layer copies it in. Declared here so the
// api client can see it without widening every constructor.
PanelType string `mapstructure:"-"`
APIHost string `mapstructure:"ApiHost"`
NodeID int `mapstructure:"NodeID"`
Key string `mapstructure:"ApiKey"`
NodeType string `mapstructure:"NodeType"`
EnableVless bool `mapstructure:"EnableVless"`
VlessFlow string `mapstructure:"VlessFlow"`
Timeout int `mapstructure:"Timeout"`
SpeedLimit float64 `mapstructure:"SpeedLimit"`
DeviceLimit int `mapstructure:"DeviceLimit"`
RuleListPath string `mapstructure:"RuleListPath"`
// DisableCustomConfig is XrayR compatibility: parsed, warned about, never
// applied (2026-07-29). It used to force the legacy `server`-string parser
// on a panel that also offered custom_config. PanelType now decides that
// outright — SSpanel is always the server string, Rigel is always
// custom_config — so there is nothing left for it to switch.
DisableCustomConfig bool `mapstructure:"DisableCustomConfig"`
}
Config is the per-node panel API config (doc/09 §5, mapstructure keys match config.yml). Panel scope is SSpanel/mod_mu only (2026-07-22 decision).
type DetectResult ¶
DetectResult is one audit-rule hit (doc/10 §3.6).
type DetectRule ¶
DetectRule is a compiled audit regex (doc/10 §3.6). ID -1 marks a local rule (never reported upstream).
type Dialect ¶
type Dialect uint8
Dialect identifies which panel wire format produced a NodeInfo (doc/13 §3.1). It selects the translation path: the two flat dialects share the field-by-field translator, while DialectSingboxNative carries an opaque sing-box inbound body that is handed to the base fork's own unmarshaler.
const ( // DialectLegacy — traditional SSpanel (< 2021.11), 6-segment `server` string. // Zero value, so an un-tagged NodeInfo keeps the historical behaviour. DialectLegacy Dialect = iota // DialectXrayRFlat — SSpanel >= 2021.11 with XrayR's flat `custom_config` // (offset_port_node / network / security / enable_vless / reality-opts). DialectXrayRFlat // DialectSingboxNative — `custom_config` shaped like a sing-box inbound // (Rigel). Carried opaquely in NativeRaw; see NativeType. DialectSingboxNative )
type InFlightCanceler ¶ added in v0.1.0
type InFlightCanceler interface {
CancelInFlight()
}
InFlightCanceler aborts panel requests that are already waiting on the network. Optional, discovered by type assertion like AccessLogReporter — the method set of API itself stays as doc/10 §2.2 froze it.
It exists so shutdown does not have to WAIT OUT a slow panel. A sync cycle's worst case is minutes (retrying GETs at the API timeout, then the reports), while the shutdown wait is necessarily short, so without cancellation the wait is not a guarantee at all — and everything built on it (the pre-DelNode settlement, the teardown ordering) quietly stops holding exactly when the panel is sick, which is when shutdowns happen (doc/14 C-15).
type NameServer ¶
type NameServer struct {
Address string
Port int
Domains []string // 命中域名 → 走此上游
ExpectIPs []string // 期望 IP 段(分流)
}
NameServer is the neutral DNS upstream (doc/10 §3.8), replacing xray's conf.NameServerConfig. The translation layer emits option.DNSServerOptions.
type NodeInfo ¶
type NodeInfo struct {
// 方言
Dialect Dialect
// NativeType is the sing-box inbound type ("vless", "hysteria2", …) resolved
// from custom_config.type. Only set for DialectSingboxNative.
NativeType string
// NativeRaw is the panel's custom_config with the panel-only keys removed
// (`type`, `client`) — i.e. the inbound body verbatim. Only set for
// DialectSingboxNative. The translation layer merges tag/listen/users into it
// and lets the base fork unmarshal the result, so every field the fork
// understands works without a per-field mapping here (doc/13 §2.3).
NativeRaw json.RawMessage
// 身份
NodeType string
NodeID int
Port uint32 // 节点实际监听端口
// OutsidePort 是「订阅下发端口」——客户端实际拨号的端口,与 Port 之间由
// 运维在主机上做 NAT/端口转发(传统 SSpanel 的 `outside_port`,doc/13 §2.7)。
// 0 表示未设置(等同于 Port)。**不参与监听**,仅用于日志/诊断:面板自己已把
// 订阅端口改写好了(Tools.php:538-544),节点端如果拿它去 listen 反而会错。
OutsidePort uint32
// 限速 / 协议
SpeedLimit uint64
AlterID uint16 // vmess
EnableVless bool
VlessFlow string
CypherMethod string // shadowsocks
Method string // ss method
ServerKey string // ss2022
// 传输层
TransportProtocol string // tcp/ws/grpc/h2/...
FakeType string // tcp header 伪装
Host string // ws/h2 host
Path string // ws/h2 path
ServiceName string // gRPC serviceName
Authority string // gRPC/h2
Header json.RawMessage
Headers map[string]string
HttpHeaders map[string][]string // 原 map[string]*conf.StringList
AcceptProxyProtocol bool
// ProxyProtoLoopbackPort is set by core/singbox, not by any panel: when
// Sing2 owns the public port for PROXY protocol, the base inbound has to
// move somewhere else, and both the translation layer and the relay need to
// agree on where. 0 means the inbound keeps the real port (native mode).
ProxyProtoLoopbackPort uint16
// ListenIP is the address the inbound binds, taken from the node's
// ControllerConfig.ListenIP — node-local, never from a panel, and injected
// by the controller the same way the REALITY private key and the Mieru
// user-hint override are (see Controller.applyLocalListen).
//
// Empty means 0.0.0.0, which is what it has always been. That matters: the
// value is IPv4-only, so a node that must also serve IPv6 has to say "::"
// explicitly. Until 2026-07-30 the whole field was hardcoded and no
// configuration could reach it, which made IPv6 unreachable on every
// protocol except Mieru — mieru binds a wildcard of its own and never
// consults this (doc/14 C-09).
ListenIP string
EnableTFO bool
// TLS
EnableTLS bool
RejectUnknownSni bool
// REALITY(统一到嵌套结构,去扁平重复)
EnableREALITY bool
REALITYConfig *REALITYConfig
// DNS(中立化)
NameServers []NameServer // 原 []*conf.NameServerConfig
// Mieru 专有(传统 SSpanel 无 custom_config,只能走 server 串的 extra 段;
// Rigel 走 native 方言时这些字段不使用,参数直接在 NativeRaw 里)。
MieruListenPorts []string // listen_ports,端口段如 "2090-2099";非空则取代 Port
MieruTrafficPat string // traffic_pattern(base64)
MieruUserHint bool // user_hint_is_mandatory
// 嗅探 / 杂项
EnableSniffing bool
RouteOnly bool
Flow string
Security string
Key string
}
NodeInfo is the neutralized node descriptor (doc/10 §3.1): xray conf types removed (HttpHeaders → map[string][]string, DNS → []NameServer) and REALITY params collapsed into the nested *REALITYConfig (no flat duplicates).
Two shapes share this struct. The flat dialects populate the individual fields below; DialectSingboxNative populates only the identity fields plus NativeType/NativeRaw and leaves the rest zero (doc/13 §3.2).
type NodeStatus ¶
NodeStatus is periodic node load reported to the panel (doc/10 §3.7).
type OnlineUser ¶
OnlineUser is a user's currently-connected source IP (doc/10 §3.4).
type REALITYConfig ¶
type REALITYConfig struct {
Dest string
ProxyProtocolVer uint64
ServerNames []string
PrivateKey string // 敏感
MinClientVer string
MaxClientVer string
MaxTimeDiff uint64
ShortIds []string
}
REALITYConfig carries REALITY handshake params (doc/10 §3.2).
type UserInfo ¶
type UserInfo struct {
UID int
Email string // SSpanel 面板直接下发,无需合成
UUID string // vmess/vless id
Passwd string // trojan/ss 密码(敏感)
Port uint32 // 单端口多用户面板用
AlterID uint16
Method string // ss method
SpeedLimit uint64 // per-user 限速 (Bps)
DeviceLimit int // per-user 设备上限
}
UserInfo is a single panel user (doc/10 §3.3). No standalone join-key field: NodeTag + Email + UID are joined downstream into "NodeTag|email|uid".