diff --git a/backend/internal/httpapi/nodes.go b/backend/internal/httpapi/nodes.go index c51f8ec..ebd584a 100644 --- a/backend/internal/httpapi/nodes.go +++ b/backend/internal/httpapi/nodes.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "net" "net/http" "strconv" "time" @@ -216,17 +217,26 @@ type joinTokenResponse struct { } // controlPlaneAddr assembles the host:port a node agent must dial: the panel -// domain (live setting, falling back to the boot-time PANEL_DOMAIN) plus the -// agent listener's NODE_AGENT_PORT. Nil when either half is unknown. -func (s *Server) controlPlaneAddr(ctx context.Context) *string { +// domain (live setting, falling back to the boot-time PANEL_DOMAIN, falling back +// to the Host the admin is browsing the panel on right now - an IP-only +// deployment has no configured domain, but the browser's Host reaches this +// server by definition) plus the agent listener's NODE_AGENT_PORT. +func (s *Server) controlPlaneAddr(ctx context.Context, r *http.Request) *string { host := s.BootPanelDomain if settings, err := s.Store.GetSettings(ctx); err == nil && settings.PanelDomain != nil && *settings.PanelDomain != "" { host = *settings.PanelDomain } + if host == "" { + if h, _, err := net.SplitHostPort(r.Host); err == nil { + host = h + } else { + host = r.Host + } + } if host == "" || s.NodeAgentPort == "" { return nil } - addr := host + ":" + s.NodeAgentPort + addr := net.JoinHostPort(host, s.NodeAgentPort) return &addr } @@ -300,7 +310,7 @@ func (s *Server) handleGenerateJoinToken(w http.ResponseWriter, r *http.Request) Token: rawToken, ExpiresAt: expiresAt, Unlimited: req.Unlimited, - PanelAddr: s.controlPlaneAddr(ctx), + PanelAddr: s.controlPlaneAddr(ctx, r), }) } diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 5cf6c98..583a036 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -485,8 +485,9 @@ paths: nullable: true description: >- The exact host:port to give install-node.sh as the control-plane - address (panel domain + NODE_AGENT_PORT). Null when no panel - domain is configured. This is NOT the panel's web/HTTPS port. + address: the panel domain (falling back to the Host this request + was made on, for IP-only deployments) + NODE_AGENT_PORT. This is + NOT the panel's web/HTTPS port. example: panel.example.com:48443 "401": { $ref: "#/components/responses/Unauthorized" } "403": { $ref: "#/components/responses/Forbidden" }