sspanel

package
v0.1.7 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: GPL-3.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type APIClient

type APIClient struct {
	APIHost     string
	NodeID      int
	Key         string
	NodeType    string
	EnableVless bool
	VlessFlow   string
	SpeedLimit  float64
	DeviceLimit int
	// PanelType selects the node-payload dialect: SSpanel means the traditional
	// 6-segment `server` string, Rigel means custom_config. See GetNodeInfo.
	PanelType string

	LocalRuleList    []api.DetectRule
	LastReportOnline map[int]int
	// contains filtered or unexported fields
}

APIClient create a api client to the panel.

func New

func New(apiConfig *api.Config) *APIClient

New create api instance

TWO clients, split by whether a re-send is safe (doc/14 C-08):

  • client — the three GET pull endpoints. Read-only, so resty's automatic retry is free reliability and keeps its stock semantics (including "do not retry a body that failed to unmarshal").
  • postClient — every report. Retry DISABLED. These change panel state and neither panel deduplicates: the traditional one does `u += ...` with nothing to key on, and Rigel's uniqueness key contains the server-side SECOND, which a re-send arriving a client-timeout later can never collide with. The failure we cannot tell apart on the wire — "the panel committed and the response was lost" — is exactly the one where a re-send charges the customer twice.

Two clients rather than one with a method-aware RetryCondition, because registering any condition REPLACES resty's default rule instead of narrowing it (retry.go:125-132 assigns rather than ORs), which silently turned the no-retry marking on unmarshal failures back on.

func (*APIClient) CancelInFlight added in v0.1.0

func (c *APIClient) CancelInFlight()

CancelInFlight aborts every request currently waiting on the network. Implements api.InFlightCanceler.

Without it, a Controller shutting down had to WAIT OUT whatever the sync loop was doing, and the fixed 30s cap is off by more than an order of magnitude against the real worst case: three retrying GETs (4 attempts × the 30s default timeout) plus five single-shot POSTs is ~8.5 minutes. So Close's "wait for the loop to stop" was not a guarantee at all whenever the panel was slow — which is exactly when a shutdown happens (doc/14 C-15).

It cancels the CURRENT requests and then installs a fresh context, rather than killing the client for good. Shutdown is not done talking to the panel when it calls this: the final traffic settlement and the access-log final flush both POST afterwards (doc/14 C-07, panel/panel.go stopAll ordering). A permanently poisoned client would fail both — silently reintroducing the whole-period loss C-07 exists to prevent, on every graceful stop. What keeps a straggler cycle from doing damage after this is the Controller's generation fence, not a dead client.

func (*APIClient) Debug

func (c *APIClient) Debug()

Debug set the client debug for client

func (*APIClient) Describe

func (c *APIClient) Describe() api.ClientInfo

Describe return a description of the client

func (*APIClient) GetNodeInfo

func (c *APIClient) GetNodeInfo() (nodeInfo *api.NodeInfo, err error)

GetNodeInfo will pull NodeInfo Config from ssPanel

func (*APIClient) GetNodeRule

func (c *APIClient) GetNodeRule() ([]api.DetectRule, error)

GetNodeRule will pull the audit rule form ssPanel

func (*APIClient) GetUserList

func (c *APIClient) GetUserList() (UserList []api.UserInfo, err error)

GetUserList will pull user form ssPanel

func (*APIClient) ParseMieruNodeResponse

func (c *APIClient) ParseMieruNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error)

ParseMieruNodeResponse parses a traditional-panel Mieru node (doc/13 T7, and the wulala panel plan §3.2). The traditional panel has no custom_config, so Mieru's parameters ride the same `k=v|k=v` extra segment every other legacy node type uses:

<addr>;<listen port>;0;;;server=<addr>|mieru_transport=TCP|mieru_ports=2090-2099|…

Slots 3 and 4 are empty — Mieru has neither a stream transport nor TLS (it carries its own XChaCha20, keyed off username+password).

| extra key | meaning | |------------------------|--------------------------------------------| | mieru_transport | "TCP" (default) or "UDP" | | mieru_ports | port range, e.g. "2090-2099"; wins over | | | the segment-1 port | | mieru_pattern | base64 traffic_pattern | | mieru_user_hint | 1 → user_hint_is_mandatory | | outside_port | subscription port; NOT the listener |

per-user credentials come from /mod_mu/users: username "u{uid}" (built by the translation layer, doc/13 §3.4) + password = passwd.

func (*APIClient) ParseNativeCustomConfig

func (c *APIClient) ParseNativeCustomConfig(nodeInfoResponse *NodeInfoResponse, body map[string]json.RawMessage) (*api.NodeInfo, error)

ParseNativeCustomConfig converts a sing-box-native `custom_config` into a NodeInfo carrying the body opaquely. Returns an error when `type` is missing or unknown — better to fail the sync loudly than to start the wrong inbound.

func (*APIClient) ParseSSNodeResponse

func (c *APIClient) ParseSSNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error)

ParseSSNodeResponse parse the response for the given node info format

func (*APIClient) ParseSSPanelNodeInfo

func (c *APIClient) ParseSSPanelNodeInfo(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error)

ParseSSPanelNodeInfo parses a version >= 2021.11 `custom_config`.

Two dialects arrive here (doc/13 §2.2, §3.1):

XrayR-flat      offset_port_node / network / security / enable_vless / …
                → parsed field-by-field below (unchanged behaviour).
sing-box-native listen_port(+s) and inbound-shaped keys (Rigel's
                Hysteria2/TUIC/AnyTLS/Mieru/TrustTunnel nodes)
                → handed to ParseNativeCustomConfig, which keeps the body
                  opaque for the base fork to unmarshal.

Dispatch is on the payload's SHAPE, not the version: Rigel emits both dialects under version="2021.11" depending on the protocol.

func (*APIClient) ParseSSPluginNodeResponse

func (c *APIClient) ParseSSPluginNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error)

ParseSSPluginNodeResponse parse the response for the given node info format

func (*APIClient) ParseTrojanNodeResponse

func (c *APIClient) ParseTrojanNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error)

ParseTrojanNodeResponse parse the response for the given node info format

func (*APIClient) ParseUserListResponse

func (c *APIClient) ParseUserListResponse(userInfoResponse *[]UserResponse) ([]api.UserInfo, error)

ParseUserListResponse parse the response for the given node info format. Return regularized *[]api.UserInfo → []api.UserInfo (doc/10 §2.2).

func (*APIClient) ParseV2rayNodeResponse

func (c *APIClient) ParseV2rayNodeResponse(nodeInfoResponse *NodeInfoResponse) (*api.NodeInfo, error)

ParseV2rayNodeResponse parses the legacy (SSpanel < 2021.11) 6-segment server string (doc/13 §2.8, T2).

Beyond XrayR v0.9.4 this now reads the protocol-selecting keys the traditional panel already ships but upstream discarded — `enable_vless`, `flow`, `enable_reality`, `servername`, `reality_short_id` — so ONE binary with ONE config.yml can serve VMess-TCP, VMess-WS-TLS and VLESS-Vision-REALITY nodes side by side instead of having the protocol pinned globally by NodeType / EnableVless / VlessFlow. Panel values win; the local config.yml stays the fallback (and remains the ONLY source of the REALITY private key — it must never enter the server string, which is also rendered into user subscriptions; see doc/13 §2.8 and the merge in panel.applyLocalReality, T3).

Port offset (doc/13 §2.7): segment 1 is the port the node LISTENS on; `outside_port` is the port clients dial (the panel rewrites subscriptions to it, Tools.php:538-544) with NAT/forwarding in between. It is deliberately not applied to the listener — only surfaced on NodeInfo for logging/diagnostics.

func (*APIClient) ReportAccessLog

func (c *APIClient) ReportAccessLog(entries []api.AccessLogEntry) error

ReportAccessLog reports a batch of access log entries. Implements api.AccessLogReporter; same endpoint base/auth as the other reporters.

func (*APIClient) ReportIllegal

func (c *APIClient) ReportIllegal(detectResultList []api.DetectResult) error

ReportIllegal reports the user illegal behaviors

func (*APIClient) ReportNodeOnlineUsers

func (c *APIClient) ReportNodeOnlineUsers(onlineUserList []api.OnlineUser) error

ReportNodeOnlineUsers reports online user ip

func (*APIClient) ReportNodeStatus

func (c *APIClient) ReportNodeStatus(nodeStatus *api.NodeStatus) (err error)

ReportNodeStatus reports the node status to the ssPanel

func (*APIClient) ReportUserTraffic

func (c *APIClient) ReportUserTraffic(userTraffic []api.UserTraffic, windowID string) error

ReportUserTraffic reports the user traffic.

windowID identifies the accounting period this payload belongs to, and is STABLE across any re-send of the same period. It is sent to Rigel only, as api.HeaderReportID — the traditional panel has no dedup to hang it on and would just ignore it (doc/10 §7.1, doc/14 C-08).

This is the cross-project half of the double-billing fix: node-side we simply stopped re-sending POSTs, which costs a period on every lost report. Once Rigel keys its user_traffic_logs uniqueness on (node, windowID) instead of (node, server-side second), a re-send becomes provably free and the retry can come back — see doc/10 §7.1.

type CustomConfig

type CustomConfig struct {
	OffsetPortNode string          `json:"offset_port_node"`
	Host           string          `json:"host"`
	Method         string          `json:"method"`
	TLS            string          `json:"tls"`
	EnableVless    string          `json:"enable_vless"`
	Network        string          `json:"network"`
	Security       string          `json:"security"`
	Path           string          `json:"path"`
	VerifyCert     bool            `json:"verify_cert"`
	Obfs           string          `json:"obfs"`
	Header         json.RawMessage `json:"header"`
	AllowInsecure  string          `json:"allow_insecure"`
	Servicename    string          `json:"servicename"`
	EnableXtls     string          `json:"enable_xtls"`
	Flow           string          `json:"flow"`
	EnableREALITY  bool            `json:"enable_reality"`
	RealityOpts    *REALITYConfig  `json:"reality-opts"`
}

type IllegalItem

type IllegalItem struct {
	ID  int `json:"list_id"`
	UID int `json:"user_id"`
}

type NodeInfoResponse

type NodeInfoResponse struct {
	Group           int             `json:"node_group"`
	Class           int             `json:"node_class"`
	SpeedLimit      float64         `json:"node_speedlimit"`
	TrafficRate     float64         `json:"traffic_rate"`
	Sort            int             `json:"sort"`
	RawServerString string          `json:"server"`
	Type            string          `json:"type"`
	CustomConfig    json.RawMessage `json:"custom_config"`
	Version         string          `json:"version"`
}

NodeInfoResponse is the response of node

type OnlineUser

type OnlineUser struct {
	UID int    `json:"user_id"`
	IP  string `json:"ip"`
}

OnlineUser is the data structure of online user

type PostData

type PostData struct {
	Data interface{} `json:"data"`
}

PostData is the data structure of post data

type REALITYConfig

type REALITYConfig struct {
	Dest             string   `json:"dest,omitempty"`
	ProxyProtocolVer uint64   `json:"proxy_protocol_ver,omitempty"`
	ServerNames      []string `json:"server_names,omitempty"`
	PrivateKey       string   `json:"private_key,omitempty"`
	MinClientVer     string   `json:"min_client_ver,omitempty"`
	MaxClientVer     string   `json:"max_client_ver,omitempty"`
	MaxTimeDiff      uint64   `json:"max_time_diff,omitempty"`
	ShortIds         []string `json:"short_ids,omitempty"`
}

type Response

type Response struct {
	Ret  uint            `json:"ret"`
	Data json.RawMessage `json:"data"`
}

Response is the common response

type RuleItem

type RuleItem struct {
	ID      int    `json:"id"`
	Content string `json:"regex"`
}

type SystemLoad

type SystemLoad struct {
	Uptime string `json:"uptime"`
	Load   string `json:"load"`
}

SystemLoad is the data structure of system load

type UserResponse

type UserResponse struct {
	ID          int     `json:"id"`
	Email       string  `json:"email"`
	Passwd      string  `json:"passwd"`
	Port        uint32  `json:"port"`
	Method      string  `json:"method"`
	SpeedLimit  float64 `json:"node_speedlimit"`
	DeviceLimit int     `json:"node_iplimit"`
	UUID        string  `json:"uuid"`
	AliveIP     int     `json:"alive_ip"`
}

UserResponse is the response of user

type UserTraffic

type UserTraffic struct {
	UID      int   `json:"user_id"`
	Upload   int64 `json:"u"`
	Download int64 `json:"d"`
}

UserTraffic is the data structure of traffic

Jump to

Keyboard shortcuts

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