diff options
Diffstat (limited to 'internal/cli/serve.go')
| -rw-r--r-- | internal/cli/serve.go | 88 |
1 files changed, 85 insertions, 3 deletions
diff --git a/internal/cli/serve.go b/internal/cli/serve.go index 94f0c5d..1753a64 100644 --- a/internal/cli/serve.go +++ b/internal/cli/serve.go @@ -1,6 +1,7 @@ package cli import ( + "bytes" "context" "fmt" "net/http" @@ -10,6 +11,7 @@ import ( "syscall" "time" + "github.com/BurntSushi/toml" "github.com/thepeterstone/claudomator/internal/api" "github.com/thepeterstone/claudomator/internal/executor" "github.com/thepeterstone/claudomator/internal/notify" @@ -44,6 +46,23 @@ func serve(addr string) error { return fmt.Errorf("creating dirs: %w", err) } + // Auto-generate VAPID keys if not configured. + if cfg.VAPIDPublicKey == "" || cfg.VAPIDPrivateKey == "" { + pub, priv, err := notify.GenerateVAPIDKeys() + if err != nil { + return fmt.Errorf("generating VAPID keys: %w", err) + } + cfg.VAPIDPublicKey = pub + cfg.VAPIDPrivateKey = priv + // Write new keys back to config file. + if cfgFile != "" { + if err := saveVAPIDToConfig(cfgFile, pub, priv); err != nil { + // Non-fatal: log but continue. + fmt.Fprintf(os.Stderr, "warning: failed to persist VAPID keys to %s: %v\n", cfgFile, err) + } + } + } + store, err := storage.Open(cfg.DBPath) if err != nil { return fmt.Errorf("opening db: %w", err) @@ -56,22 +75,24 @@ func serve(addr string) error { if len(addr) > 0 && addr[0] != ':' { apiURL = "http://" + addr } - + runners := map[string]executor.Runner{ "claude": &executor.ClaudeRunner{ BinaryPath: cfg.ClaudeBinaryPath, Logger: logger, LogDir: cfg.LogDir, APIURL: apiURL, + DropsDir: cfg.DropsDir, }, "gemini": &executor.GeminiRunner{ BinaryPath: cfg.GeminiBinaryPath, Logger: logger, LogDir: cfg.LogDir, APIURL: apiURL, + DropsDir: cfg.DropsDir, }, } - + pool := executor.NewPool(cfg.MaxConcurrent, runners, store, logger) if cfg.GeminiBinaryPath != "" { pool.Classifier = &executor.Classifier{GeminiBinaryPath: cfg.GeminiBinaryPath} @@ -81,9 +102,26 @@ func serve(addr string) error { pool.RecoverStaleBlocked() srv := api.NewServer(store, pool, logger, cfg.ClaudeBinaryPath, cfg.GeminiBinaryPath) + + // Configure notifiers: combine webhook (if set) with web push. + notifiers := []notify.Notifier{} if cfg.WebhookURL != "" { - srv.SetNotifier(notify.NewWebhookNotifier(cfg.WebhookURL, logger)) + notifiers = append(notifiers, notify.NewWebhookNotifier(cfg.WebhookURL, logger)) + } + webPushNotifier := ¬ify.WebPushNotifier{ + Store: store, + VAPIDPublicKey: cfg.VAPIDPublicKey, + VAPIDPrivateKey: cfg.VAPIDPrivateKey, + VAPIDEmail: cfg.VAPIDEmail, + Logger: logger, } + notifiers = append(notifiers, webPushNotifier) + srv.SetNotifier(notify.NewMultiNotifier(logger, notifiers...)) + + srv.SetVAPIDConfig(cfg.VAPIDPublicKey, cfg.VAPIDPrivateKey, cfg.VAPIDEmail) + srv.SetPushStore(store) + srv.SetDropsDir(cfg.DropsDir) + if cfg.WorkspaceRoot != "" { srv.SetWorkspaceRoot(cfg.WorkspaceRoot) } @@ -125,3 +163,47 @@ func serve(addr string) error { } return nil } + +// saveVAPIDToConfig appends VAPID key assignments to the config file. +// It reads the existing file (if any), then writes a complete TOML file with +// the new keys merged in. Uses toml encoder for correctness. +func saveVAPIDToConfig(path, pub, priv string) error { + existing := cfg // already loaded + + // Marshal the full config back including the new VAPID keys. + // We use a struct alias to only encode fields we want persisted. + type persistedConfig struct { + DataDir string `toml:"data_dir,omitempty"` + ClaudeBinaryPath string `toml:"claude_binary_path,omitempty"` + GeminiBinaryPath string `toml:"gemini_binary_path,omitempty"` + MaxConcurrent int `toml:"max_concurrent,omitempty"` + DefaultTimeout string `toml:"default_timeout,omitempty"` + ServerAddr string `toml:"server_addr,omitempty"` + WebhookURL string `toml:"webhook_url,omitempty"` + WorkspaceRoot string `toml:"workspace_root,omitempty"` + WebhookSecret string `toml:"webhook_secret,omitempty"` + VAPIDPublicKey string `toml:"vapid_public_key,omitempty"` + VAPIDPrivateKey string `toml:"vapid_private_key,omitempty"` + VAPIDEmail string `toml:"vapid_email,omitempty"` + } + pc := persistedConfig{ + DataDir: existing.DataDir, + ClaudeBinaryPath: existing.ClaudeBinaryPath, + GeminiBinaryPath: existing.GeminiBinaryPath, + MaxConcurrent: existing.MaxConcurrent, + DefaultTimeout: existing.DefaultTimeout, + ServerAddr: existing.ServerAddr, + WebhookURL: existing.WebhookURL, + WorkspaceRoot: existing.WorkspaceRoot, + WebhookSecret: existing.WebhookSecret, + VAPIDPublicKey: pub, + VAPIDPrivateKey: priv, + VAPIDEmail: existing.VAPIDEmail, + } + + var buf bytes.Buffer + if err := toml.NewEncoder(&buf).Encode(pc); err != nil { + return fmt.Errorf("encoding config: %w", err) + } + return os.WriteFile(path, buf.Bytes(), 0600) +} |
