mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2026-09-06 17:08:49 +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>
62 lines
1.4 KiB
Go
62 lines
1.4 KiB
Go
package clustersync
|
|
|
|
import (
|
|
"context"
|
|
"runtime"
|
|
"sync"
|
|
|
|
"github.com/uozi-tech/cosy/logger"
|
|
)
|
|
|
|
// item is one unit of work replicated to a node.
|
|
type item struct {
|
|
kind Kind
|
|
name string
|
|
push func(ctx context.Context, node nodeRef) error
|
|
}
|
|
|
|
// run pushes every item to every node. Nodes are processed concurrently while a
|
|
// single node receives its items sequentially, which keeps the remote reload
|
|
// order predictable and avoids hammering a node with parallel writes.
|
|
func run(ctx context.Context, nodes []nodeRef, items []item) *Summary {
|
|
results := &collector{}
|
|
if len(nodes) == 0 || len(items) == 0 {
|
|
return results.summary()
|
|
}
|
|
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(len(nodes))
|
|
|
|
for _, node := range nodes {
|
|
go func(node nodeRef) {
|
|
defer func() {
|
|
if err := recover(); err != nil {
|
|
buf := make([]byte, 1024)
|
|
runtime.Stack(buf, false)
|
|
logger.Errorf("%s\n%s", err, buf)
|
|
}
|
|
}()
|
|
defer wg.Done()
|
|
|
|
for _, current := range items {
|
|
if ctx.Err() != nil {
|
|
results.fail(node, current.kind, current.name, ctx.Err())
|
|
continue
|
|
}
|
|
|
|
if err := current.push(ctx, node); err != nil {
|
|
logger.Errorf("cluster sync %s %s to %s: %v", current.kind, current.name, node.name, err)
|
|
results.fail(node, current.kind, current.name, err)
|
|
continue
|
|
}
|
|
|
|
results.ok(node, current.kind, current.name)
|
|
}
|
|
}(node)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
return results.summary()
|
|
}
|