feat(agent): SIGHUP 重读节点本地配置并热重渲染,不踢在线用户
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,23 @@ func main() {
|
|||||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
|
// SIGHUP 重读节点本地配置(acl.json / warp.json)并重渲染。走 sing-box 的 SIGHUP
|
||||||
|
// 热重载路径,在线用户不掉线;重启 agent 则会冷启 sing-box,把所有人踢下线。
|
||||||
|
hup := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(hup, syscall.SIGHUP)
|
||||||
|
defer signal.Stop(hup)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-hup:
|
||||||
|
log.Printf("[pangolin-agent] SIGHUP: re-reading node-local config (acl.json/warp.json)")
|
||||||
|
agent.SingBox().Refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
log.Printf("[pangolin-agent] starting (control-plane=%s, version=%s)", cfg.ControlPlaneAddr, cfg.AgentVersion)
|
log.Printf("[pangolin-agent] starting (control-plane=%s, version=%s)", cfg.ControlPlaneAddr, cfg.AgentVersion)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
if err := agent.Run(ctx); err != nil {
|
if err := agent.Run(ctx); err != nil {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package agentd
|
package agentd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1"
|
agentv1 "github.com/wangjia/pangolin/server/internal/pb/agentv1"
|
||||||
)
|
)
|
||||||
@@ -439,6 +441,32 @@ func TestACL_DeletedFileKeepsInMemoryLastGood(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh() 必须能触发一次重渲染(经 debounce 循环),用于「编辑 acl.json 后
|
||||||
|
// systemctl reload pangolin-agent」而不必重启 agent(重启会冷启 sing-box 踢人)。
|
||||||
|
func TestSingBoxRefresh_TriggersRender(t *testing.T) {
|
||||||
|
cfg := testConfig(t)
|
||||||
|
writeACL(t, cfg.ACLConfigPath, validACL)
|
||||||
|
fr := &fakeRestarter{}
|
||||||
|
sb := NewSingBox(cfg, fr)
|
||||||
|
sb.ApplyConfig(sampleSnapshot(&agentv1.Credential{DpUUID: "aaaa", Protocol: agentv1.ProtocolBoth}), true)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
go sb.Run(ctx)
|
||||||
|
|
||||||
|
// 等首次渲染落地(ApplyConfig 已 markDirty)
|
||||||
|
eventually(t, 2*time.Second, func() bool {
|
||||||
|
_, err := os.Stat(cfg.SingboxConfigPath)
|
||||||
|
return err == nil
|
||||||
|
}, "首次渲染写出配置")
|
||||||
|
|
||||||
|
// 断言 reloadCount 而非 count:首次渲染已冷启动过(started=true),此后的重渲染
|
||||||
|
// 一律走 Reload(SIGHUP 热重载),Restart 计数不会再增加。断言错计数器会假失败。
|
||||||
|
before := fr.reloadCount()
|
||||||
|
sb.Refresh()
|
||||||
|
eventually(t, 2*time.Second, func() bool { return fr.reloadCount() > before }, "Refresh 触发了热重载")
|
||||||
|
}
|
||||||
|
|
||||||
// 新 agent 实例(模拟进程重启) + 被删的 acl.json → 磁盘 last-good 兜底,规则仍在。
|
// 新 agent 实例(模拟进程重启) + 被删的 acl.json → 磁盘 last-good 兜底,规则仍在。
|
||||||
func TestACL_ColdStartAfterDeletedFileFallsBackToDisk(t *testing.T) {
|
func TestACL_ColdStartAfterDeletedFileFallsBackToDisk(t *testing.T) {
|
||||||
cfg := testConfig(t)
|
cfg := testConfig(t)
|
||||||
|
|||||||
@@ -446,6 +446,11 @@ func (s *SingBox) markDirty() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh 请求一次重渲染。供 agent 收到 SIGHUP 时调用,使编辑节点本地配置文件
|
||||||
|
// (acl.json / warp.json)后无需重启进程即可生效 —— 重启 agent 会让 sing-box 走
|
||||||
|
// 冷启动(Restart),把全部在线用户踢下线。
|
||||||
|
func (s *SingBox) Refresh() { s.markDirty() }
|
||||||
|
|
||||||
// Run drives the debounce loop: it coalesces bursts of changes within
|
// Run drives the debounce loop: it coalesces bursts of changes within
|
||||||
// DebounceWindow into a single write+restart. It blocks until ctx is cancelled.
|
// DebounceWindow into a single write+restart. It blocks until ctx is cancelled.
|
||||||
func (s *SingBox) Run(ctx context.Context) {
|
func (s *SingBox) Run(ctx context.Context) {
|
||||||
|
|||||||
Reference in New Issue
Block a user