diff --git a/internal/app/app.go b/internal/app/app.go index 15031aa..5979ad5 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -70,7 +70,9 @@ func New(cfg config.Config, staticFS fs.FS, migrationsDir string) (*App, error) } logger.Info("会话密钥已就绪", "module", "SYSTEM", "source", secretSource) - updaterToken, updaterTokenSource, err := updater.EnsureToken(cfg.UpdaterToken, cfg.UpdaterTokenFile) + // updater Token 只给「一键升级」sidecar 用;原生版与飞牛 Docker 版都显式把 socket 置空, + // 此时不能去写 /run/havline-updater:原生版以非 root 运行,权限不足会让应用直接起不来。 + updaterToken, updaterTokenSource, err := updater.EnsureTokenIfEnabled(cfg.UpdaterSocket, cfg.UpdaterToken, cfg.UpdaterTokenFile) if err != nil { return nil, err } diff --git a/internal/updater/token.go b/internal/updater/token.go index 254dc8a..e6b6b9f 100644 --- a/internal/updater/token.go +++ b/internal/updater/token.go @@ -67,6 +67,20 @@ func EnsureToken(token, tokenFile string) (string, string, error) { return value, path, nil } +// EnsureTokenIfEnabled 只在 updater 启用(socket 非空)时准备共享 Token。 +// +// updater Token 只服务于「一键升级」sidecar,而 sidecar 与主程序是通过共享卷里的 +// Token 文件 + Unix Socket 对接的。原生版(飞牛 Native)与飞牛 Docker 版都显式把 +// socket 置空表示「不启用升级」,此时根本不需要这个文件: +// 原生版以非 root 用户(havline)运行,去写 /run/havline-updater 会因权限不足报错, +// 而启动期的这个错误会让整个应用起不来。 +func EnsureTokenIfEnabled(socketPath, token, tokenFile string) (string, string, error) { + if strings.TrimSpace(socketPath) == "" { + return "", "", nil + } + return EnsureToken(token, tokenFile) +} + // WaitForToken 等待主程序在共享卷中创建 Token 文件;环境变量已设置时立即返回。 func WaitForToken(token, tokenFile string, timeout time.Duration) (string, string, error) { if timeout <= 0 { diff --git a/internal/updater/token_test.go b/internal/updater/token_test.go index bf74eb7..4a89742 100644 --- a/internal/updater/token_test.go +++ b/internal/updater/token_test.go @@ -1,6 +1,7 @@ package updater import ( + "errors" "os" "path/filepath" "testing" @@ -105,3 +106,36 @@ func TestWaitForTokenTimesOut(t *testing.T) { t.Fatal("文件不存在时应等待超时") } } + +// updater 未启用(socket 为空)时不能去碰 Token 文件: +// 原生版以非 root 运行,写 /run/havline-updater 会失败并拖垮整个启动。 +func TestEnsureTokenIfEnabledSkipsWhenSocketDisabled(t *testing.T) { + tokenFile := filepath.Join(t.TempDir(), "nested", "updater.token") + + token, source, err := EnsureTokenIfEnabled("", "", tokenFile) + if err != nil { + t.Fatalf("socket 未启用时不应报错:%v", err) + } + if token != "" || source != "" { + t.Fatalf("socket 未启用时应返回空 Token,实际 token=%q source=%q", token, source) + } + if _, statErr := os.Stat(tokenFile); !errors.Is(statErr, os.ErrNotExist) { + t.Fatalf("socket 未启用时不应创建 Token 文件(statErr=%v)", statErr) + } +} + +// 对照组:updater 启用(socket 非空)时仍照旧生成并落盘。 +func TestEnsureTokenIfEnabledCreatesWhenSocketSet(t *testing.T) { + tokenFile := filepath.Join(t.TempDir(), "nested", "updater.token") + + token, source, err := EnsureTokenIfEnabled("/run/havline-updater/updater.sock", "", tokenFile) + if err != nil { + t.Fatalf("socket 启用时应生成 Token:%v", err) + } + if len(token) != 64 || source != tokenFile { + t.Fatalf("生成的 Token 不正确,实际 token=%q source=%q", token, source) + } + if _, statErr := os.Stat(tokenFile); statErr != nil { + t.Fatalf("socket 启用时应写入 Token 文件:%v", statErr) + } +}