B5 rehearsal fix: fragment tag = panel row tag

The rehearsal's inbound-level totals never moved: the agent
reports sing-box tags (inbound-<port>) while the master's agent
traffic handler matches inbound rows by their panel tag — which
the B1 model renamed to the n<node>-in- scheme, so the lookup
silently matched nothing (per-user totals, keyed by email, were
unaffected; production on V1 keeps the old tag form and is not
hit until the V2 migration itself).

The converter now takes the row's panel tag as the fragment tag
(migrator passes it through), making one identity serve the DB
row, the generated config, core stats and traffic reports. Bare
fixtures without a panel tag keep the historical form.
This commit is contained in:
Muse
2026-10-23 02:15:36 +00:00
parent c610ab88ea
commit 05c164dc8f
3 changed files with 24 additions and 4 deletions
+1
View File
@@ -437,6 +437,7 @@ func convertOneInbound(tx *gorm.DB, ib *model.Inbound, rep *singboxMigrationRepo
Listen: ib.Listen,
Port: ib.Port,
Protocol: string(ib.Protocol),
Tag: ib.Tag,
DisableFlow: ib.DisableFlow,
Settings: json.RawMessage(ib.Settings),
StreamSettings: json.RawMessage(ib.StreamSettings),
+21 -1
View File
@@ -50,6 +50,11 @@ type Inbound struct {
Listen string
Port int
Protocol string
// Tag is the inbound row's panel tag; it becomes the fragment's
// sing-box tag so core stats and agent traffic reports key
// inbound totals by the panel's own identity (see fragmentTag).
// Empty keeps the historical inbound-<port> form.
Tag string
DisableFlow bool // xray DisableInboundFlow switch value for this inbound
Settings json.RawMessage
StreamSettings json.RawMessage
@@ -335,12 +340,27 @@ func (c *conv) finishDisposition() {
c.res.Disposition = "direct"
}
// fragmentTag is the fragment's sing-box tag: the inbound row's
// panel tag, so core stats and the agent's traffic reports key
// inbound totals by the same identity the panel stores (the
// master's agent traffic handler matches reports by row tag —
// with the historical inbound-<port> form those lookups silently
// never matched once panel tags moved to the n<node>-in- scheme;
// ITEM6 B5 rehearsal). Rows without a panel tag (bare converter
// fixtures) keep the historical form.
func (c *conv) fragmentTag() string {
if c.in.Tag != "" {
return c.in.Tag
}
return fmt.Sprintf("inbound-%d", c.in.Port)
}
// base builds the common fragment head. WireGuard endpoints and tun
// inbounds do not take listen/listen_port and build their own head.
func (c *conv) base(typ string) map[string]any {
frag := map[string]any{
"type": typ,
"tag": fmt.Sprintf("inbound-%d", c.in.Port),
"tag": c.fragmentTag(),
}
if c.in.Listen != "" {
frag["listen"] = c.in.Listen
+2 -3
View File
@@ -2,7 +2,6 @@ package singbox
import (
"encoding/json"
"fmt"
"net"
"strconv"
)
@@ -315,7 +314,7 @@ func convertMasquerade(mz map[string]any) map[string]any {
func (c *conv) convertWireguard() error {
frag := map[string]any{
"type": "wireguard",
"tag": fmt.Sprintf("inbound-%d", c.in.Port),
"tag": c.fragmentTag(),
}
c.res.Endpoint = true
if c.in.Port != 0 {
@@ -385,7 +384,7 @@ func deriveWireguardAddress(settings map[string]any) string {
func (c *conv) convertTun() error {
frag := map[string]any{
"type": "tun",
"tag": fmt.Sprintf("inbound-%d", c.in.Port),
"tag": c.fragmentTag(),
}
if name := gStr(c.set, "name"); name != "" {
frag["interface_name"] = name