Quellcode durchsuchen

fix(backup): name Telegram backups after webDomain/IP instead of x-ui

The bot's ServerService is a separate instance whose mutex-guarded LastStatus is never populated (only RefreshStatus fills it, which the bot never calls), so backupHost's public-IP fallback never fired and bot backups collapsed to x-ui when no webDomain was set.

Resolve the public IP directly via a new mutex-guarded resolvePublicIPs helper (extracted from GetStatus and shared with it) so the bot path gets a real address. Panel downloads keep using the browser request host; the Telegram bot falls back to webDomain then public IP.
MHSanaei vor 14 Stunden
Ursprung
Commit
a5e865c109
1 geänderte Dateien mit 65 neuen und 53 gelöschten Zeilen
  1. 65 53
      internal/web/service/server.go

+ 65 - 53
internal/web/service/server.go

@@ -375,6 +375,53 @@ func getPublicIP(url string) string {
 	return ipString
 }
 
+var publicIPv4Services = []string{
+	"https://api4.ipify.org",
+	"https://ipv4.icanhazip.com",
+	"https://v4.api.ipinfo.io/ip",
+	"https://ipv4.myexternalip.com/raw",
+	"https://4.ident.me",
+	"https://check-host.net/ip",
+}
+
+var publicIPv6Services = []string{
+	"https://api6.ipify.org",
+	"https://ipv6.icanhazip.com",
+	"https://v6.api.ipinfo.io/ip",
+	"https://ipv6.myexternalip.com/raw",
+	"https://6.ident.me",
+}
+
+// resolvePublicIPs caches the public IPv4/IPv6 addresses on first use. Guarded
+// by s.mu because the bot's ServerService may call it from sendBackup while a
+// status report runs concurrently.
+func (s *ServerService) resolvePublicIPs() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	if s.cachedIPv4 == "" {
+		for _, ip4Service := range publicIPv4Services {
+			s.cachedIPv4 = getPublicIP(ip4Service)
+			if s.cachedIPv4 != "N/A" {
+				break
+			}
+		}
+	}
+
+	if s.cachedIPv6 == "" && !s.noIPv6 {
+		for _, ip6Service := range publicIPv6Services {
+			s.cachedIPv6 = getPublicIP(ip6Service)
+			if s.cachedIPv6 != "N/A" {
+				break
+			}
+		}
+	}
+
+	if s.cachedIPv6 == "N/A" {
+		s.noIPv6 = true
+	}
+}
+
 func (s *ServerService) GetStatus(lastStatus *Status) *Status {
 	now := time.Now()
 	status := &Status{
@@ -536,45 +583,7 @@ func (s *ServerService) GetStatus(lastStatus *Status) *Status {
 		logger.Warning("get udp connections failed:", err)
 	}
 
-	// IP fetching with caching
-	showIp4ServiceLists := []string{
-		"https://api4.ipify.org",
-		"https://ipv4.icanhazip.com",
-		"https://v4.api.ipinfo.io/ip",
-		"https://ipv4.myexternalip.com/raw",
-		"https://4.ident.me",
-		"https://check-host.net/ip",
-	}
-	showIp6ServiceLists := []string{
-		"https://api6.ipify.org",
-		"https://ipv6.icanhazip.com",
-		"https://v6.api.ipinfo.io/ip",
-		"https://ipv6.myexternalip.com/raw",
-		"https://6.ident.me",
-	}
-
-	if s.cachedIPv4 == "" {
-		for _, ip4Service := range showIp4ServiceLists {
-			s.cachedIPv4 = getPublicIP(ip4Service)
-			if s.cachedIPv4 != "N/A" {
-				break
-			}
-		}
-	}
-
-	if s.cachedIPv6 == "" && !s.noIPv6 {
-		for _, ip6Service := range showIp6ServiceLists {
-			s.cachedIPv6 = getPublicIP(ip6Service)
-			if s.cachedIPv6 != "N/A" {
-				break
-			}
-		}
-	}
-
-	if s.cachedIPv6 == "N/A" {
-		s.noIPv6 = true
-	}
-
+	s.resolvePublicIPs()
 	status.PublicIP.IPv4 = s.cachedIPv4
 	status.PublicIP.IPv6 = s.cachedIPv6
 
@@ -1282,11 +1291,12 @@ func (s *ServerService) GetDb() ([]byte, error) {
 
 // BackupFilename returns the filename for a database backup, named after the
 // panel's address so a downloaded or Telegram-sent backup identifies the server
-// it came from. requestHost is the browser's address (the getDb handler passes
-// c.Request.Host, matching the host shown in the panel title); it is preferred
-// when present, otherwise the configured web domain and then the server's public
-// IP are used. The extension is .dump on PostgreSQL and .db on SQLite; the base
-// falls back to "x-ui" when no address is known.
+// it came from. requestHost is the browser's address: the getDb handler passes
+// c.Request.Host so a panel download is named after whatever address the user
+// reached the panel with, no Listen Domain needed. The Telegram bot has no
+// request and passes "", falling back to the configured Listen Domain (webDomain)
+// and then the public IP. The extension is .dump on PostgreSQL and .db on SQLite;
+// the base falls back to "x-ui" when no address is known.
 func (s *ServerService) BackupFilename(requestHost string) string {
 	ext := ".db"
 	if database.IsPostgres() {
@@ -1296,9 +1306,12 @@ func (s *ServerService) BackupFilename(requestHost string) string {
 }
 
 // backupHost picks the address used to name backup files: the browser's request
-// host (port stripped, the same value the panel title shows) when available,
-// otherwise the configured web domain and then the cached public IP (IPv4 before
-// IPv6), reduced to safe filename characters.
+// host (port stripped) when available, otherwise the configured Listen Domain
+// (webDomain) and then the resolved public IP (IPv4 before IPv6), reduced to safe
+// filename characters. The public IP is resolved directly rather than read from
+// LastStatus so callers whose ServerService never runs the status ticker —
+// notably the Telegram bot — still get a real address instead of the "x-ui"
+// fallback.
 func (s *ServerService) backupHost(requestHost string) string {
 	host := extractHostname(strings.TrimSpace(requestHost))
 	if host == "" {
@@ -1307,12 +1320,11 @@ func (s *ServerService) backupHost(requestHost string) string {
 		}
 	}
 	if host == "" {
-		if st := s.LastStatus(); st != nil {
-			if ip := st.PublicIP.IPv4; ip != "" && ip != "N/A" {
-				host = ip
-			} else if ip := st.PublicIP.IPv6; ip != "" && ip != "N/A" {
-				host = ip
-			}
+		s.resolvePublicIPs()
+		if ip := s.cachedIPv4; ip != "" && ip != "N/A" {
+			host = ip
+		} else if ip := s.cachedIPv6; ip != "" && ip != "N/A" {
+			host = ip
 		}
 	}
 	return sanitizeBackupHost(host)