mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-28 00:24:19 +00:00
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.
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user