|
|
@@ -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)
|