Implement bd daemons restart command
Adds restart subcommand to bd daemons that gracefully stops a daemon and starts a new one in the same workspace. Features: - Accepts workspace path or PID as target - Graceful shutdown via RPC with SIGTERM fallback - Starts new daemon with exec.Cmd in correct workspace directory - Prefers workspace-local bd binary if present - Supports --search and --json flags - Proper error handling and user feedback Closes bd-3ee2c7e9 (bd daemons command epic) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
@@ -195,9 +196,107 @@ var daemonsRestartCmd = &cobra.Command{
|
|||||||
Stops the daemon gracefully, then starts a new one.`,
|
Stops the daemon gracefully, then starts a new one.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
fmt.Fprintf(os.Stderr, "Error: restart not yet implemented\n")
|
target := args[0]
|
||||||
fmt.Fprintf(os.Stderr, "Use 'bd daemons stop <target>' then 'bd daemon' to restart manually\n")
|
searchRoots, _ := cmd.Flags().GetStringSlice("search")
|
||||||
os.Exit(1)
|
// Use global jsonOutput set by PersistentPreRun
|
||||||
|
|
||||||
|
// Discover daemons
|
||||||
|
daemons, err := daemon.DiscoverDaemons(searchRoots)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error discovering daemons: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the target daemon
|
||||||
|
var targetDaemon *daemon.DaemonInfo
|
||||||
|
for _, d := range daemons {
|
||||||
|
if d.WorkspacePath == target || fmt.Sprintf("%d", d.PID) == target {
|
||||||
|
targetDaemon = &d
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetDaemon == nil {
|
||||||
|
if jsonOutput {
|
||||||
|
outputJSON(map[string]string{"error": "daemon not found"})
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: daemon not found for %s\n", target)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace := targetDaemon.WorkspacePath
|
||||||
|
|
||||||
|
// Stop the daemon
|
||||||
|
if !jsonOutput {
|
||||||
|
fmt.Printf("Stopping daemon for workspace: %s (PID %d)\n", workspace, targetDaemon.PID)
|
||||||
|
}
|
||||||
|
if err := daemon.StopDaemon(*targetDaemon); err != nil {
|
||||||
|
if jsonOutput {
|
||||||
|
outputJSON(map[string]string{"error": err.Error()})
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error stopping daemon: %v\n", err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a moment for cleanup
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
|
||||||
|
// Start a new daemon by executing 'bd daemon' in the workspace directory
|
||||||
|
if !jsonOutput {
|
||||||
|
fmt.Printf("Starting new daemon for workspace: %s\n", workspace)
|
||||||
|
}
|
||||||
|
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
if jsonOutput {
|
||||||
|
outputJSON(map[string]string{"error": fmt.Sprintf("cannot resolve executable: %v", err)})
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: cannot resolve executable: %v\n", err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if workspace-local bd binary exists (preferred)
|
||||||
|
localBd := filepath.Join(workspace, "bd")
|
||||||
|
_, localErr := os.Stat(localBd)
|
||||||
|
|
||||||
|
bdPath := exe
|
||||||
|
if localErr == nil {
|
||||||
|
// Use local bd binary if it exists
|
||||||
|
bdPath = localBd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use bd daemon command with proper working directory
|
||||||
|
// The daemon will fork itself into the background
|
||||||
|
daemonCmd := &exec.Cmd{
|
||||||
|
Path: bdPath,
|
||||||
|
Args: []string{bdPath, "daemon"},
|
||||||
|
Dir: workspace,
|
||||||
|
Env: os.Environ(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := daemonCmd.Start(); err != nil {
|
||||||
|
if jsonOutput {
|
||||||
|
outputJSON(map[string]string{"error": fmt.Sprintf("failed to start daemon: %v", err)})
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error starting daemon: %v\n", err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't wait for daemon to exit (it will fork and continue in background)
|
||||||
|
go func() { _ = daemonCmd.Wait() }()
|
||||||
|
|
||||||
|
if jsonOutput {
|
||||||
|
outputJSON(map[string]interface{}{
|
||||||
|
"workspace": workspace,
|
||||||
|
"action": "restarted",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Successfully restarted daemon for workspace: %s\n", workspace)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,5 +661,6 @@ func init() {
|
|||||||
daemonsKillallCmd.Flags().Bool("force", false, "Use SIGKILL immediately if graceful shutdown fails")
|
daemonsKillallCmd.Flags().Bool("force", false, "Use SIGKILL immediately if graceful shutdown fails")
|
||||||
|
|
||||||
// Flags for restart command
|
// Flags for restart command
|
||||||
|
daemonsRestartCmd.Flags().StringSlice("search", nil, "Directories to search for daemons (default: home, /tmp, cwd)")
|
||||||
daemonsRestartCmd.Flags().Bool("json", false, "Output in JSON format")
|
daemonsRestartCmd.Flags().Bool("json", false, "Output in JSON format")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user