P9(3): per-inbound forwarding module (amo-style form, dynamic egress compile)
Design as decided 2026-10-21 (replaces the raw-JSON egress editor idea): the forwarding form lives on the inbound, and the entry agent's egress entries are compiled from it on every config pull — never snapshotted. - model: Inbound.Forwarding (JSON text) via AutoMigrate; panel-side metadata only, never reaches xray itself - service/forwarding.go: ForwardingSpec (mode direct/forward; landing managed|manual), save-time validation (managed target must exist and be a SOCKS-family landing — the fork's SK5 inbound is 'mixed'; no self-forward; manual endpoint/protocol sanity), live resolution (managed: concrete listen IP wins, else hosting node's address; credentials from the target's first account), and the compiler that appends one socks/http outbound (tag fwd-<inboundId>) plus a prepended inboundTag rule per forwarding inbound - buildXrayConfig agent path: compile runs after applyAgentEgress, so entries ride on the live template (or stored egress override) and template edits keep flowing; unresolvable entries skip with a warning instead of failing the whole config - UpdateInbound's field-merge now carries Forwarding (its absence was caught by the smoke: adds worked, updates silently kept the old spec) - GET /panel/api/inbounds/forwardTargets: managed-landing candidates (id/remark/tag/listen/port/node name) — credentials never exposed - frontend: new Forwarding tab in the inbound form (mode, landing type, managed picker, manual endpoint with link import incl. base64 form), adapter round-trips the spec as a JSON string like settings; en/zh strings; generated API types refreshed Acceptance: unit tests for parse/validate/resolve/compile incl. the dynamic-recompile proof (template edit + spec edit both reflected), and an end-to-end smoke (real binary, two agent nodes, all inbound writes in browser form-urlencoded format): 22/22 PASS — see P9-REPORT.md.
This commit is contained in:
@@ -966,6 +966,9 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||
if err := normalizeInboundShareAddressStrict(inbound); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
if err := s.validateForwarding(inbound); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
|
||||
tag, err := s.resolveInboundTag(inbound, 0)
|
||||
if err != nil {
|
||||
@@ -1477,6 +1480,9 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
return inbound, false, err
|
||||
}
|
||||
inbound.SubSortIndex = normalizeSubSortIndex(inbound.SubSortIndex)
|
||||
if err := s.validateForwarding(inbound); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
|
||||
clients, err := s.GetClients(inbound)
|
||||
if err != nil {
|
||||
@@ -1642,6 +1648,7 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
oldInbound.Settings = inbound.Settings
|
||||
oldInbound.StreamSettings = inbound.StreamSettings
|
||||
oldInbound.Sniffing = inbound.Sniffing
|
||||
oldInbound.Forwarding = inbound.Forwarding
|
||||
if strings.TrimSpace(inbound.ShareAddrStrategy) == "" {
|
||||
normalizeInboundShareAddress(oldInbound)
|
||||
inbound.ShareAddrStrategy = oldInbound.ShareAddrStrategy
|
||||
|
||||
Reference in New Issue
Block a user