mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-09-03 07:24:52 +08:00
Deployment to other nodes was file by file, manual, and always validated against the local Nginx. One synchronization engine now backs every path. - Directory deployment: a directory carries its own deployment targets and every file below it inherits them, so a whole tree replicates in one request per node (#1559). - Node sync: one action pushes all configurations, sites and streams to the selected nodes, which is what a freshly added node needs (#1484). - Namespace replication: the namespace record is mirrored to its member nodes and synced sites and streams carry the namespace name, so every node groups them identically (#1744). - Automatic sync: a namespace can switch to the auto strategy with its own interval and a cron job re-pushes its content (#1582). - deploy_mode=remote no longer touches the local Nginx: no sites-enabled symlink, no local nginx -t and no local reload. The deployment intent moves to the database, and moving a site into a remote namespace detaches its leftover local symlink (#1505). The receiving side gains a batch config endpoint that writes every file and reloads once, with a per-file fallback for nodes that predate it. Disabling a site or stream became idempotent so converging a node reports no spurious failures, renaming only tests and reloads when the enabled tree actually changed, and stream delete now removes the stream record instead of a site one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
92 lines
2.4 KiB
Go
92 lines
2.4 KiB
Go
package cluster
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/0xJacky/Nginx-UI/internal/clustersync"
|
|
"github.com/0xJacky/Nginx-UI/model"
|
|
"github.com/0xJacky/Nginx-UI/query"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/spf13/cast"
|
|
"github.com/uozi-tech/cosy"
|
|
"gorm.io/gen/field"
|
|
)
|
|
|
|
// SyncNodes pushes the local configurations, sites and streams to the selected
|
|
// nodes. A freshly added node is brought up to date with a single request.
|
|
func SyncNodes(c *gin.Context) {
|
|
var json struct {
|
|
NodeIDs []uint64 `json:"node_ids" binding:"required"`
|
|
Configs *bool `json:"configs"`
|
|
Sites *bool `json:"sites"`
|
|
Streams *bool `json:"streams"`
|
|
Overwrite *bool `json:"overwrite"`
|
|
}
|
|
|
|
if !cosy.BindAndValid(c, &json) {
|
|
return
|
|
}
|
|
|
|
scope := clustersync.FullScope()
|
|
if json.Configs != nil {
|
|
scope.Configs = *json.Configs
|
|
}
|
|
if json.Sites != nil {
|
|
scope.Sites = *json.Sites
|
|
}
|
|
if json.Streams != nil {
|
|
scope.Streams = *json.Streams
|
|
}
|
|
if json.Overwrite != nil {
|
|
scope.Overwrite = *json.Overwrite
|
|
}
|
|
|
|
summary, err := clustersync.SyncNodes(c, json.NodeIDs, scope)
|
|
if err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, summary)
|
|
}
|
|
|
|
// SyncNamespace replicates a namespace and all of its sites and streams to every
|
|
// node of that namespace, so each node ends up with the same content.
|
|
func SyncNamespace(c *gin.Context) {
|
|
summary, err := clustersync.SyncNamespace(c, cast.ToUint64(c.Param("id")))
|
|
if err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, summary)
|
|
}
|
|
|
|
// UpsertNamespace is the receiving end of namespace replication. It creates the
|
|
// namespace when the node does not know it yet and keeps its local node
|
|
// membership and deploy mode untouched.
|
|
func UpsertNamespace(c *gin.Context) {
|
|
var json struct {
|
|
Name string `json:"name" binding:"required"`
|
|
PostSyncAction string `json:"post_sync_action" binding:"omitempty,oneof=none reload_nginx"`
|
|
UpstreamTestType string `json:"upstream_test_type" binding:"omitempty,oneof=local remote mirror"`
|
|
}
|
|
|
|
if !cosy.BindAndValid(c, &json) {
|
|
return
|
|
}
|
|
|
|
n := query.Namespace
|
|
namespace, err := n.Assign(field.Attrs(&model.Namespace{
|
|
Name: json.Name,
|
|
PostSyncAction: json.PostSyncAction,
|
|
UpstreamTestType: json.UpstreamTestType,
|
|
})).Where(n.Name.Eq(json.Name)).FirstOrCreate()
|
|
if err != nil {
|
|
cosy.ErrHandler(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, namespace)
|
|
}
|