From b78f5f021130f46f87bc19495bd768c2e373045f Mon Sep 17 00:00:00 2001 From: A386official Date: Sat, 28 Feb 2026 09:35:26 +0000 Subject: [PATCH] fix(cli): start lifetime modules before use in CLI commands The CLI subcommands (imap-acct, creds) load and configure modules from the config file but never call Start() on them. For storage.imapsql this leaves the Back field nil, so EnableUpdatePipe() dereferences a nil UpdateManager and panics with SIGSEGV. Call c.Lifetime.StartAll() in getCfgBlockModule() after module lookup so that backends are fully initialized before the CLI uses them. Also wire up c.Lifetime.StopAll() on error paths so modules are cleaned up properly when the type assertion fails. Fixes #815 --- internal/cli/ctl/moduleinit.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/cli/ctl/moduleinit.go b/internal/cli/ctl/moduleinit.go index 1129a516..ea05e098 100644 --- a/internal/cli/ctl/moduleinit.go +++ b/internal/cli/ctl/moduleinit.go @@ -78,17 +78,26 @@ func getCfgBlockModule(ctx *cli.Context) (*container.C, module.Module, error) { return nil, nil, err } + // Start all lifetime modules so that backends (e.g. storage.imapsql) + // are fully initialized before the CLI attempts to use them. + // Without this, fields like imapsql.Back remain nil and calls such + // as EnableUpdatePipe dereference a nil pointer. + if err := c.Lifetime.StartAll(); err != nil { + return nil, nil, err + } + return c, mod, nil } func openStorage(ctx *cli.Context) (module.Storage, error) { - _, mod, err := getCfgBlockModule(ctx) + c, mod, err := getCfgBlockModule(ctx) if err != nil { return nil, err } storage, ok := mod.(module.Storage) if !ok { + c.Lifetime.StopAll() return nil, cli.Exit(fmt.Sprintf("Error: configuration block %s is not an IMAP storage", ctx.String("cfg-block")), 2) } @@ -104,13 +113,14 @@ func openStorage(ctx *cli.Context) (module.Storage, error) { } func openUserDB(ctx *cli.Context) (module.PlainUserDB, error) { - _, mod, err := getCfgBlockModule(ctx) + c, mod, err := getCfgBlockModule(ctx) if err != nil { return nil, err } userDB, ok := mod.(module.PlainUserDB) if !ok { + c.Lifetime.StopAll() return nil, cli.Exit(fmt.Sprintf("Error: configuration block %s is not a local credentials store", ctx.String("cfg-block")), 2) }