refactor: use nginx -s reload instead of systemd

- doc: add comment
This commit is contained in:
2026-08-17 10:10:14 +08:00
parent 79f18fcdea
commit f536987a7e
62 changed files with 2216 additions and 598 deletions
+35 -5
View File
@@ -1,4 +1,6 @@
// Package daemonserver accepts local CLI requests over the frozen Unix Socket.
// Package daemonserver 通过固定的本地 Unix Socket 接受命令行客户端请求,并将其分派给事务编排器执行。
// 服务端负责监听并校验请求、把请求映射到后端更新或重启动作、将执行进度流式写回客户端,
// 最终返回携带事务终态与错误信息的结果。
package daemonserver
import (
@@ -20,22 +22,36 @@ import (
"yms-daemon/internal/transaction"
)
// maximumRequestBytes 单次请求体的最大字节数,超过该上限的请求会被拒绝,以防内存被异常输入耗尽。
const maximumRequestBytes = 1 << 20
// backendUpdater 抽象了后端更新与重启的具体实现,是 Server 与事务编排器之间的解耦接口。
// 它由 backendupdate 包在运行时注入,便于在测试中用轻量替身替换。
type backendUpdater interface {
// UpdateRepack 使用重新打包的 ZIP 制品执行后端更新,file 为制品绝对路径。
UpdateRepack(context.Context, string, backendupdate.ProgressReporter) (transaction.Transaction, error)
// UpdateNativeJAR 使用原生后端 JAR 文件执行后端更新,file 为制品绝对路径。
UpdateNativeJAR(context.Context, string, backendupdate.ProgressReporter) (transaction.Transaction, error)
// UpdateContainerImage 使用容器镜像执行后端更新,imageReference 为镜像引用,startLog 控制是否输出启动日志。
UpdateContainerImage(context.Context, string, bool, backendupdate.ProgressReporter) (transaction.Transaction, error)
// Restart 重启后端服务而不更换制品。
Restart(context.Context, backendupdate.ProgressReporter) (transaction.Transaction, error)
}
// Server owns the local Unix Socket and dispatches requests to the transaction orchestrator.
// Server 持有本地 Unix Socket 并把收到的请求分派给事务编排器。
// 它负责连接的生命周期管理、请求解码与校验、执行进度回写以及最终结果的返回。
type Server struct {
// socketPath 守护进程监听的 Unix Socket 绝对路径。
socketPath string
updater backendUpdater
logger *slog.Logger
// updater 后端更新与重启动作的实际执行者。
updater backendUpdater
// logger 用于记录服务运行与请求处理过程中的日志。
logger *slog.Logger
}
// New 构造一个 Server 实例并校验入参。
// socketPath 必须是规范化的绝对路径,updater 不能为空,logger 为空时回退为默认日志器。
// 当路径非法或 updater 为空时返回 error。
func New(socketPath string, updater backendUpdater, logger *slog.Logger) (*Server, error) {
if !filepath.IsAbs(socketPath) || filepath.Clean(socketPath) != socketPath {
return nil, errors.New("daemon Unix Socket path must be a clean absolute path")
@@ -49,7 +65,10 @@ func New(socketPath string, updater backendUpdater, logger *slog.Logger) (*Serve
return &Server{socketPath: socketPath, updater: updater, logger: logger}, nil
}
// Serve listens until ctx is canceled. Each accepted request owns its response connection.
// Serve 监听 Unix Socket 直到 ctx 被取消,返回前会等待所有连接处理完成并清理 Socket 文件。
// 它先准备 Socket 目录并移除残留的旧 Socket,然后以 0o600 权限监听;
// 每个被接受的连接都由独立的 goroutine 处理,连接在响应写完后由 handle 关闭。
// 当监听或准备 Socket 路径失败时返回 errorctx 取消后正常返回 nil。
func (s *Server) Serve(ctx context.Context) error {
if err := prepareSocketPath(s.socketPath); err != nil {
return err
@@ -90,6 +109,11 @@ func (s *Server) Serve(ctx context.Context) error {
}
}
// handle 处理单个已接受的连接:读取并校验请求、执行对应操作、把进度与结果写回客户端。
// 它限制请求读取时限为 10 秒,仅接受 service 为 backend 的请求;
// 对于 update 操作会按 inputType 分派到不同更新实现,对 restart 操作校验其不携带多余输入。
// 执行期间产生的进度通过 report 回调实时写回,一旦写回失败便停止后续进度发送,
// 最终无论成功失败都会写入一条 ResponseResult 响应并记录日志。
func (s *Server) handle(ctx context.Context, connection net.Conn) {
defer connection.Close()
_ = connection.SetReadDeadline(time.Now().Add(10 * time.Second))
@@ -168,10 +192,14 @@ func (s *Server) handle(ctx context.Context, connection net.Conn) {
}
}
// writeResponse 把一条响应以换行分隔的 JSON 编码写入连接,失败时返回底层写入错误。
func (s *Server) writeResponse(connection net.Conn, response daemonapi.Response) error {
return json.NewEncoder(connection).Encode(response)
}
// decodeRequest 从 reader 中解码并校验一条客户端请求。
// 它限制请求体大小不超过 maximumRequestBytes,禁止未知字段,并要求请求只能包含一个 JSON 值,
// 且所有字符串字段不得带有前后空白字符;任一条不满足时返回 error。
func decodeRequest(reader io.Reader) (daemonapi.Request, error) {
limited := io.LimitReader(reader, maximumRequestBytes+1)
decoder := json.NewDecoder(limited)
@@ -193,6 +221,8 @@ func decodeRequest(reader io.Reader) (daemonapi.Request, error) {
return request, nil
}
// prepareSocketPath 在监听前准备 Socket 路径:创建父目录,若目标位置已被占用则校验其是否为 Socket,
// 若是残留 Socket 则移除,若被非 Socket 文件占用则返回 error。
func prepareSocketPath(socketPath string) error {
if err := os.MkdirAll(filepath.Dir(socketPath), 0o755); err != nil {
return fmt.Errorf("create daemon Unix Socket directory: %w", err)
+28 -3
View File
@@ -17,6 +17,8 @@ import (
"yms-daemon/internal/transaction"
)
// TestServerAcceptsBackendUpdateThroughUnixSocket 验证服务端能通过 Unix Socket 接收后端 ZIP 更新请求,
// 正确分派到更新器,并向客户端返回事务终态以及流式的更新进度事件。
func TestServerAcceptsBackendUpdateThroughUnixSocket(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{record: transaction.Transaction{ID: "transaction-01", State: transaction.StateCommitted}}
@@ -58,6 +60,8 @@ func TestServerAcceptsBackendUpdateThroughUnixSocket(t *testing.T) {
}
}
// TestServerAcceptsDirectNativeJARThroughUnixSocket 验证服务端能接收原生后端 JAR 直接更新请求,
// 并将制品路径与输入类型正确分派到更新器。
func TestServerAcceptsDirectNativeJARThroughUnixSocket(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{record: transaction.Transaction{ID: "transaction-direct-01", State: transaction.StateCommitted}}
@@ -84,6 +88,8 @@ func TestServerAcceptsDirectNativeJARThroughUnixSocket(t *testing.T) {
}
}
// TestServerAcceptsContainerImageThroughUnixSocket 验证服务端能接收容器镜像更新请求,
// 并把镜像引用作为制品路径分派到更新器。
func TestServerAcceptsContainerImageThroughUnixSocket(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{record: transaction.Transaction{ID: "transaction-container-01", State: transaction.StateCommitted}}
@@ -107,6 +113,8 @@ func TestServerAcceptsContainerImageThroughUnixSocket(t *testing.T) {
}
}
// TestServerReturnsTransactionFailure 验证更新器返回错误时,服务端会在最终结果中携带事务标识、
// 回滚终态以及错误信息,客户端据此返回错误。
func TestServerReturnsTransactionFailure(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{
@@ -130,6 +138,8 @@ func TestServerReturnsTransactionFailure(t *testing.T) {
}
}
// TestServerAcceptsBackendRestartThroughUnixSocket 验证服务端能接收后端重启请求,
// 并返回事务终态与流式的重启进度事件。
func TestServerAcceptsBackendRestartThroughUnixSocket(t *testing.T) {
socketPath := shortSocketPath(t)
updater := &fakeUpdater{record: transaction.Transaction{ID: "transaction-restart-01", State: transaction.StateCommitted}}
@@ -158,6 +168,8 @@ func TestServerAcceptsBackendRestartThroughUnixSocket(t *testing.T) {
}
}
// shortSocketPath 创建一个短目录并返回其下的 daemon.sock 路径,用于规避 Unix Socket 路径长度上限。
// 目录会在测试结束时自动清理。
func shortSocketPath(t *testing.T) string {
t.Helper()
directory, err := os.MkdirTemp("", "yd-")
@@ -168,6 +180,9 @@ func shortSocketPath(t *testing.T) string {
return filepath.Join(directory, "daemon.sock")
}
// waitForSocket 轮询等待 Unix Socket 文件出现,用于在测试中同步服务端就绪状态。
// 若服务端在 Socket 创建前就退出且原因为权限不足则跳过测试,否则视为失败;
// 超过 3 秒仍未见 Socket 也视为失败。
func waitForSocket(t *testing.T, socketPath string, serveResult <-chan error) {
t.Helper()
deadline := time.Now().Add(3 * time.Second)
@@ -189,14 +204,21 @@ func waitForSocket(t *testing.T, socketPath string, serveResult <-chan error) {
t.Fatalf("daemon Unix Socket was not created: %s", socketPath)
}
// fakeUpdater backendUpdater 接口的测试替身,记录被分派的参数并返回预设结果。
type fakeUpdater struct {
record transaction.Transaction
err error
file string
// record 各更新方法返回的预设事务。
record transaction.Transaction
// err 各更新方法返回的预设错误。
err error
// file 记录最近一次分派收到的制品路径或镜像引用。
file string
// inputType 记录最近一次分派收到的输入类型。
inputType string
// operation 记录最近一次分派收到的操作类型。
operation string
}
// UpdateRepack 记录分派参数并上报一条更新进度,返回预设事务与错误。
func (u *fakeUpdater) UpdateRepack(_ context.Context, file string, report backendupdate.ProgressReporter) (transaction.Transaction, error) {
u.file = file
u.inputType = daemonapi.InputTypeRepackZIP
@@ -207,6 +229,7 @@ func (u *fakeUpdater) UpdateRepack(_ context.Context, file string, report backen
return u.record, u.err
}
// UpdateNativeJAR 记录分派参数并上报一条更新进度,返回预设事务与错误。
func (u *fakeUpdater) UpdateNativeJAR(_ context.Context, file string, report backendupdate.ProgressReporter) (transaction.Transaction, error) {
u.file = file
u.inputType = daemonapi.InputTypeNativeJAR
@@ -217,6 +240,7 @@ func (u *fakeUpdater) UpdateNativeJAR(_ context.Context, file string, report bac
return u.record, u.err
}
// UpdateContainerImage 记录分派参数并上报一条更新进度,返回预设事务与错误。
func (u *fakeUpdater) UpdateContainerImage(_ context.Context, imageReference string, _ bool, report backendupdate.ProgressReporter) (transaction.Transaction, error) {
u.file = imageReference
u.inputType = daemonapi.InputTypeContainerImage
@@ -227,6 +251,7 @@ func (u *fakeUpdater) UpdateContainerImage(_ context.Context, imageReference str
return u.record, u.err
}
// Restart 记录重启操作并上报一条重启进度,返回预设事务与错误。
func (u *fakeUpdater) Restart(_ context.Context, report backendupdate.ProgressReporter) (transaction.Transaction, error) {
u.operation = daemonapi.OperationRestart
if report != nil {