fix(inbound): convert legacy externalProxy to hosts on import

An inbound exported from a build that predated the hosts table carries
its external proxies inline in streamSettings.externalProxy. The startup
migration that converts those to host rows runs once and is gated off
afterwards, so it never sees a freshly imported inbound, leaving its
external proxies stranded in streamSettings (never surfaced as Hosts).

Extract the migration's per-inbound conversion into a shared
database.CreateHostsFromExternalProxy and run it inside the AddInbound
transaction. No-op for inbounds without externalProxy (everything the
current UI builds), so it only fires on such imports.
This commit is contained in:
MHSanaei
2026-06-27 13:50:06 +02:00
parent 876d55f274
commit 39eb5baf42
3 changed files with 149 additions and 20 deletions
+36 -20
View File
@@ -183,32 +183,48 @@ func seedHostsFromExternalProxy() error {
return db.Transaction(func(tx *gorm.DB) error {
for _, inbound := range inbounds {
if strings.TrimSpace(inbound.StreamSettings) == "" {
continue
}
var stream map[string]any
if err := json.Unmarshal([]byte(inbound.StreamSettings), &stream); err != nil {
log.Printf("HostsFromExternalProxy: skip inbound %d (invalid stream json): %v", inbound.Id, err)
continue
}
eps, ok := stream["externalProxy"].([]any)
if !ok || len(eps) == 0 {
continue
}
for i, raw := range eps {
ep, ok := raw.(map[string]any)
if !ok {
continue
}
if err := tx.Create(externalProxyEntryToHost(inbound.Id, i, ep)).Error; err != nil {
return err
}
if _, err := CreateHostsFromExternalProxy(tx, inbound.Id, inbound.StreamSettings); err != nil {
return err
}
}
return tx.Create(&model.HistoryOfSeeders{SeederName: "HostsFromExternalProxy"}).Error
})
}
// CreateHostsFromExternalProxy parses a legacy streamSettings.externalProxy array
// and inserts one Host row per entry on tx, returning the number of rows created.
// It is the shared core of both the one-time seedHostsFromExternalProxy startup
// migration and the inbound-import path: an inbound exported from a build that
// predated the hosts table carries its external proxies inline in
// streamSettings.externalProxy, and the startup migration is gated off after its
// first run, so a freshly imported inbound must be converted here instead. Blank
// or malformed streamSettings, or one without externalProxy entries, is a no-op.
func CreateHostsFromExternalProxy(tx *gorm.DB, inboundId int, streamSettings string) (int, error) {
if strings.TrimSpace(streamSettings) == "" {
return 0, nil
}
var stream map[string]any
if err := json.Unmarshal([]byte(streamSettings), &stream); err != nil {
return 0, nil
}
eps, ok := stream["externalProxy"].([]any)
if !ok || len(eps) == 0 {
return 0, nil
}
created := 0
for i, raw := range eps {
ep, ok := raw.(map[string]any)
if !ok {
continue
}
if err := tx.Create(externalProxyEntryToHost(inboundId, i, ep)).Error; err != nil {
return created, err
}
created++
}
return created, nil
}
// externalProxyEntryToHost maps one legacy externalProxy entry onto a Host.
// forceTls (same|tls|none) maps straight to Security; an unknown value falls back
// to "same" (inherit). An empty remark gets a stable generated label so the row