fix: apply managed Nginx public entry without service restart
This commit is contained in:
1 parent
36d653b32b
commit
e4f1059880
11 files changed
+338
-33
No files matched your search
@@ -38,6 +38,26 @@ func authGatewayPort(publicBase string) (int, error) {
|
||||
return port, nil
|
||||
}
|
||||
|
||||
func checkAuthGatewayChange(oldBase, newBase string) error {
|
||||
if oldBase == "" || oldBase == newBase {
|
||||
return nil
|
||||
}
|
||||
port, err := authGatewayPort(oldBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if port != 0 {
|
||||
return &activeAuthEntry{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type activeAuthEntry struct{}
|
||||
|
||||
func (*activeAuthEntry) Error() string {
|
||||
return "运行中的 OIDC 专线仍使用旧公网地址,请先停止或迁移专线"
|
||||
}
|
||||
|
||||
type gatewayConflict struct{}
|
||||
|
||||
func (*gatewayConflict) Error() string {
|
||||
|
||||
@@ -29,6 +29,12 @@ func TestAuthGatewayPortFollowsRunningOIDCTunnel(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
check(19090)
|
||||
if err := checkAuthGatewayChange("https://vpn.example.com", "http://vpn.example.com"); err == nil {
|
||||
t.Fatal("active OIDC callback must block public entry change")
|
||||
}
|
||||
if err := checkAuthGatewayChange("https://vpn.example.com", "https://vpn.example.com"); err != nil {
|
||||
t.Fatalf("unchanged public entry should be allowed: %v", err)
|
||||
}
|
||||
if port, err := authGatewayPort("https://other.example.com"); err != nil || port != 0 {
|
||||
t.Fatalf("wrong domain routed: %d, %v", port, err)
|
||||
}
|
||||
@@ -36,4 +42,7 @@ func TestAuthGatewayPortFollowsRunningOIDCTunnel(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
check(0)
|
||||
if err := checkAuthGatewayChange("https://vpn.example.com", "http://vpn.example.com"); err != nil {
|
||||
t.Fatalf("stopped OIDC tunnel should allow change: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -26,14 +26,19 @@ func (s *Server) nginxSetupStatus(c *gin.Context) {
|
||||
|
||||
func (s *Server) activateNginx(c *gin.Context) {
|
||||
status := tlsservice.Status()
|
||||
if status.Mode != "nginx" || status.ConfiguredMode != "nginx" || status.RestartRequired || status.ConfiguredDomain == "" {
|
||||
c.JSON(http.StatusPreconditionFailed, gin.H{"error": "请先保存 Nginx 模式和公网入口,重启 opsx-vpn 后再启用站点"})
|
||||
if status.Mode != "nginx" || status.ConfiguredMode != "nginx" || status.ConfiguredDomain == "" {
|
||||
c.JSON(http.StatusPreconditionFailed, gin.H{"error": "请先启用 Nginx 模式并保存公网入口;切换入口模式需重启 opsx-vpn"})
|
||||
return
|
||||
}
|
||||
scheme := status.ConfiguredScheme
|
||||
if scheme == "" {
|
||||
scheme = "https"
|
||||
}
|
||||
authPort, err := authGatewayPort(status.ConfiguredPublicBase)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if !nginxgateway.Begin() {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "入口配置正在运行,请等待完成"})
|
||||
return
|
||||
@@ -54,11 +59,15 @@ func (s *Server) activateNginx(c *gin.Context) {
|
||||
write("info", "检查现有站点并启用 HTTP-01 验证。\n")
|
||||
}
|
||||
opts := nginxconfig.RenderOptions{Domain: status.ConfiguredDomain, Scheme: scheme, Port: status.ConfiguredPort}
|
||||
if err := nginxgateway.RunEntry(opts, s.router.Routes(), func(message string) { write("output", message) }); err != nil {
|
||||
if err := nginxgateway.RunEntryWithAuth(opts, s.router.Routes(), authPort, func(message string) { write("output", message) }); err != nil {
|
||||
store.Audit(c.GetString("username"), "nginx_activate", "failed", err.Error())
|
||||
write("error", err.Error())
|
||||
return
|
||||
}
|
||||
if err := tlsservice.AdoptNginxConfiguration(service.BaseDir()); err != nil {
|
||||
write("error", "站点已更新,但状态同步失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
store.Audit(c.GetString("username"), "nginx_activate", "completed", status.ConfiguredPublicBase)
|
||||
write("done", "站点和入口已就绪。")
|
||||
}
|
||||
@@ -157,13 +166,48 @@ func (s *Server) setTLSDomain(c *gin.Context) {
|
||||
if req.Port != nil {
|
||||
port = *req.Port
|
||||
}
|
||||
status := tlsservice.Status()
|
||||
newBase := tlsservice.PublicBaseURL(scheme, strings.ToLower(strings.TrimSpace(req.Domain)), port)
|
||||
if newBase == "" || port < 0 || port > 65535 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "公网域名、协议或端口无效"})
|
||||
return
|
||||
}
|
||||
// The Go listener cannot be rebound in place; Nginx owns its own sockets.
|
||||
if status.Mode == "nginx" && status.ConfiguredMode == "nginx" && nginxgateway.Stage() != "none" {
|
||||
s.gatewayMu.Lock()
|
||||
defer s.gatewayMu.Unlock()
|
||||
if err := checkAuthGatewayChange(status.PublicBase, newBase); err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
authPort, err := authGatewayPort(newBase)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
opts := nginxconfig.RenderOptions{Domain: req.Domain, Scheme: scheme, Port: port}
|
||||
err = nginxgateway.UpdateEntry(opts, status.ConfiguredDomain, s.router.Routes(), authPort, func() error {
|
||||
if err := tlsservice.ConfigurePublicEntry(service.BaseDir(), scheme, req.Domain, port); err != nil {
|
||||
return err
|
||||
}
|
||||
return tlsservice.AdoptNginxConfiguration(service.BaseDir())
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
store.Audit(c.GetString("username"), "tls_domain_applied", newBase, "Nginx 已重载")
|
||||
c.JSON(http.StatusOK, gin.H{"status": tlsservice.Status(), "restart_required": false, "applied": true})
|
||||
return
|
||||
}
|
||||
if err := tlsservice.ConfigurePublicEntry(service.BaseDir(), scheme, req.Domain, port); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
store.Audit(c.GetString("username"), "tls_domain_configured", tlsservice.PublicBaseURL(scheme, strings.ToLower(strings.TrimSpace(req.Domain)), port), "需重启后生效")
|
||||
status := tlsservice.Status()
|
||||
c.JSON(http.StatusOK, gin.H{"status": status, "restart_required": status.RestartRequired})
|
||||
status = tlsservice.Status()
|
||||
c.JSON(http.StatusOK, gin.H{"status": status, "restart_required": status.RestartRequired,
|
||||
"site_activation_required": status.Mode == "nginx" && status.ConfiguredMode == "nginx" && nginxgateway.Stage() == "none"})
|
||||
}
|
||||
|
||||
func (s *Server) uploadTLSCertificate(c *gin.Context) {
|
||||
|
||||
Reference in new issue
Block a user