Skip to content

Commit 4766a4f

Browse files
steveyeggeclaude
andcommitted
refactor: remove embedded Dolt server code entirely
Remove all embedded Dolt server spawning, watchdog auto-recovery, and process management code. Beads now requires a standalone dolt sql-server (per Tim Sehn's recommendation). The embedded code caused port-hijacking chaos where watchdog loops respawned rogue servers from .beads/dolt/. Deleted (6 implementation files): - internal/storage/dolt/server.go (process spawning) - internal/storage/dolt/watchdog.go (auto-restart loop) - internal/storage/dolt/procattr_{unix,windows}.go - internal/storage/dolt/process_{unix,windows}.go Deleted (8 test files): - server_test.go, server_integration_test.go, server_dependency_test.go - watchdog_test.go, nested_query_test.go - hub_spokes_integration_test.go, peer_sync_integration_test.go - cmd/bd/test_dolt_server_test.go Simplified: - store.go: removed watchdog fields and start/stop calls - cmd/bd/dolt.go: removed bd dolt start/stop commands - doctor/federation.go: replaced GetRunningServerPID with TCP check Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a7e1203 commit 4766a4f

20 files changed

Lines changed: 469 additions & 4204 deletions

cmd/bd/doctor/federation.go

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,10 @@ func CheckFederationRemotesAPI(path string) DoctorCheck {
5151

5252
// Check if server PID file exists (indicates server mode might be running)
5353
pidFile := filepath.Join(doltPath, "dolt-server.pid")
54-
serverPID := dolt.GetRunningServerPID(doltPath)
54+
_, pidFileErr := os.Stat(pidFile)
55+
serverRunning := pidFileErr == nil
5556

56-
if serverPID == 0 {
57+
if !serverRunning {
5758
// No server running - check if we have remotes configured
5859
ctx := context.Background()
5960
store, err := dolt.New(ctx, &dolt.Config{Path: doltPath, ReadOnly: true, Database: doltDatabaseName(beadsDir)})
@@ -90,7 +91,7 @@ func CheckFederationRemotesAPI(path string) DoctorCheck {
9091

9192
// Server is running - check if remotesapi port is accessible
9293
// Default remotesapi port is 8080
93-
remotesAPIPort := dolt.DefaultRemotesAPIPort
94+
remotesAPIPort := 8080 // default dolt remotesapi port
9495
host := "127.0.0.1"
9596

9697
addr := net.JoinHostPort(host, fmt.Sprintf("%d", remotesAPIPort))
@@ -100,7 +101,7 @@ func CheckFederationRemotesAPI(path string) DoctorCheck {
100101
Name: "Federation remotesapi",
101102
Status: StatusError,
102103
Message: fmt.Sprintf("remotesapi port %d not accessible", remotesAPIPort),
103-
Detail: fmt.Sprintf("Server PID %d found in %s but port unreachable: %v", serverPID, pidFile, err),
104+
Detail: fmt.Sprintf("PID file %s exists but port unreachable: %v", pidFile, err),
104105
Fix: "Check if dolt sql-server is running with --remotesapi-port flag",
105106
Category: CategoryFederation,
106107
}
@@ -441,25 +442,24 @@ func CheckDoltServerModeMismatch(path string) DoctorCheck {
441442
}
442443
}
443444

444-
// Check for server PID file
445-
serverPID := dolt.GetRunningServerPID(doltPath)
445+
// Check if server is reachable by trying to connect
446+
cfg, _ := configfile.Load(beadsDir)
447+
serverReachable := false
448+
if cfg != nil {
449+
host := cfg.GetDoltServerHost()
450+
port := cfg.GetDoltServerPort()
451+
addr := net.JoinHostPort(host, fmt.Sprintf("%d", port))
452+
conn, err := net.DialTimeout("tcp", addr, 2*time.Second)
453+
if err == nil {
454+
_ = conn.Close()
455+
serverReachable = true
456+
}
457+
}
446458

447459
// Open storage to check for remotes
448460
ctx := context.Background()
449461
store, err := dolt.New(ctx, &dolt.Config{Path: doltPath, ReadOnly: true, Database: doltDatabaseName(beadsDir)})
450462
if err != nil {
451-
// If we can't open the store, check if there's a lock file indicating embedded mode
452-
lockFile := filepath.Join(doltPath, ".dolt", "lock")
453-
if _, lockErr := os.Stat(lockFile); lockErr == nil && serverPID == 0 {
454-
return DoctorCheck{
455-
Name: "Dolt Mode",
456-
Status: StatusWarning,
457-
Message: "Embedded mode with lock file",
458-
Detail: "Another process may be using the database in embedded mode",
459-
Fix: "Close other bd processes or switch to server mode",
460-
Category: CategoryFederation,
461-
}
462-
}
463463
return DoctorCheck{
464464
Name: "Dolt Mode",
465465
Status: StatusWarning,
@@ -491,22 +491,22 @@ func CheckDoltServerModeMismatch(path string) DoctorCheck {
491491
}
492492

493493
// Determine expected vs actual mode
494-
if peerCount > 0 && serverPID == 0 {
494+
if peerCount > 0 && !serverReachable {
495495
return DoctorCheck{
496496
Name: "Dolt Mode",
497497
Status: StatusWarning,
498-
Message: fmt.Sprintf("Embedded mode with %d peers configured", peerCount),
499-
Detail: "Federation with peers requires server mode for multi-writer support",
500-
Fix: "Switch to server mode: gt dolt start && bd config set dolt.mode server",
498+
Message: fmt.Sprintf("Server not reachable with %d peers configured", peerCount),
499+
Detail: "Federation with peers requires a running dolt sql-server",
500+
Fix: "Start dolt sql-server manually",
501501
Category: CategoryFederation,
502502
}
503503
}
504504

505-
if serverPID > 0 {
505+
if serverReachable {
506506
return DoctorCheck{
507507
Name: "Dolt Mode",
508508
Status: StatusOK,
509-
Message: fmt.Sprintf("Server mode (PID %d)", serverPID),
509+
Message: "Server mode (connected)",
510510
Detail: fmt.Sprintf("%d peers configured", peerCount),
511511
Category: CategoryFederation,
512512
}

cmd/bd/dolt.go

Lines changed: 0 additions & 208 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/steveyegge/beads/internal/beads"
1515
"github.com/steveyegge/beads/internal/config"
1616
"github.com/steveyegge/beads/internal/configfile"
17-
dolt "github.com/steveyegge/beads/internal/storage/dolt"
1817
"github.com/steveyegge/beads/internal/ui"
1918
)
2019

@@ -30,8 +29,6 @@ Commands:
3029
bd dolt show Show current Dolt configuration with connection test
3130
bd dolt set <k> <v> Set a configuration value
3231
bd dolt test Test server connection
33-
bd dolt start Start a Dolt SQL server (background process)
34-
bd dolt stop Stop the running Dolt SQL server
3532
bd dolt commit Commit pending changes
3633
bd dolt push Push commits to Dolt remote
3734
bd dolt pull Pull commits from Dolt remote
@@ -46,8 +43,6 @@ Flags for 'bd dolt set':
4643
--update-config Also write to config.yaml for team-wide defaults
4744
4845
Examples:
49-
bd dolt start Start server with configured settings
50-
bd dolt stop Stop the running server
5146
bd dolt set database myproject
5247
bd dolt set host 192.168.1.100 --update-config
5348
bd dolt test`,
@@ -102,35 +97,6 @@ Use this before switching to server mode to ensure the server is running.`,
10297
},
10398
}
10499

105-
var doltStartCmd = &cobra.Command{
106-
Use: "start",
107-
Short: "Start a Dolt SQL server using configured settings",
108-
Long: `Start a Dolt SQL server as a background process.
109-
110-
Uses the host, port, and user from your Dolt configuration (see 'bd dolt show').
111-
The server runs in the background and persists after bd exits.
112-
113-
Configuration sources (priority order):
114-
1. Environment variables (BEADS_DOLT_*)
115-
2. metadata.json (bd dolt set)
116-
3. config.yaml (team defaults)`,
117-
Run: func(cmd *cobra.Command, args []string) {
118-
startDoltServer()
119-
},
120-
}
121-
122-
var doltStopCmd = &cobra.Command{
123-
Use: "stop",
124-
Short: "Stop the running Dolt SQL server",
125-
Long: `Stop the Dolt SQL server started by 'bd dolt start'.
126-
127-
Sends a graceful shutdown signal (SIGTERM). If the server doesn't stop
128-
within 10 seconds, it is forcefully terminated.`,
129-
Run: func(cmd *cobra.Command, args []string) {
130-
stopDoltServer()
131-
},
132-
}
133-
134100
var doltPushCmd = &cobra.Command{
135101
Use: "push",
136102
Short: "Push commits to Dolt remote",
@@ -246,8 +212,6 @@ func init() {
246212
doltCmd.AddCommand(doltShowCmd)
247213
doltCmd.AddCommand(doltSetCmd)
248214
doltCmd.AddCommand(doltTestCmd)
249-
doltCmd.AddCommand(doltStartCmd)
250-
doltCmd.AddCommand(doltStopCmd)
251215
doltCmd.AddCommand(doltCommitCmd)
252216
doltCmd.AddCommand(doltPushCmd)
253217
doltCmd.AddCommand(doltPullCmd)
@@ -483,178 +447,6 @@ func testServerConnection(cfg *configfile.Config) bool {
483447
return true
484448
}
485449

486-
func startDoltServer() {
487-
beadsDir := beads.FindBeadsDir()
488-
if beadsDir == "" {
489-
fmt.Fprintf(os.Stderr, "Error: not in a beads repository (no .beads directory found)\n")
490-
os.Exit(1)
491-
}
492-
493-
cfg, err := configfile.Load(beadsDir)
494-
if err != nil {
495-
fmt.Fprintf(os.Stderr, "Error loading config: %v\n", err)
496-
os.Exit(1)
497-
}
498-
if cfg == nil {
499-
cfg = configfile.DefaultConfig()
500-
}
501-
502-
dataDir := filepath.Join(beadsDir, "dolt")
503-
logFile := filepath.Join(beadsDir, "dolt-server.log")
504-
host := cfg.GetDoltServerHost()
505-
port := cfg.GetDoltServerPort()
506-
user := cfg.GetDoltServerUser()
507-
database := cfg.GetDoltDatabase()
508-
509-
// Check if server is already running
510-
if pid := dolt.GetRunningServerPID(dataDir); pid > 0 {
511-
if jsonOutput {
512-
outputJSON(map[string]interface{}{
513-
"error": "server_already_running",
514-
"message": fmt.Sprintf("Dolt server already running (PID %d)", pid),
515-
"pid": pid,
516-
"host": host,
517-
"port": port,
518-
})
519-
} else {
520-
fmt.Fprintf(os.Stderr, "Error: Dolt server already running (PID %d)\n", pid)
521-
fmt.Fprintf(os.Stderr, "Stop it first: bd dolt stop\n")
522-
}
523-
os.Exit(1)
524-
}
525-
526-
// Check if data directory exists
527-
if _, err := os.Stat(dataDir); os.IsNotExist(err) {
528-
if jsonOutput {
529-
outputJSON(map[string]interface{}{
530-
"error": "data_dir_not_found",
531-
"message": fmt.Sprintf("Dolt data directory not found: %s", dataDir),
532-
})
533-
} else {
534-
fmt.Fprintf(os.Stderr, "Error: Dolt data directory not found: %s\n", dataDir)
535-
fmt.Fprintf(os.Stderr, "Run 'bd init' first to create the database.\n")
536-
}
537-
os.Exit(1)
538-
}
539-
540-
if !jsonOutput {
541-
fmt.Println("Starting Dolt SQL server...")
542-
fmt.Printf(" Host: %s\n", host)
543-
fmt.Printf(" Port: %d\n", port)
544-
fmt.Printf(" User: %s\n", user)
545-
fmt.Printf(" Database: %s\n", database)
546-
fmt.Printf(" Data dir: %s\n", dataDir)
547-
fmt.Printf(" Log file: %s\n", logFile)
548-
fmt.Println()
549-
fmt.Print("Waiting for server to accept connections...")
550-
}
551-
552-
server := dolt.NewServer(dolt.ServerConfig{
553-
DataDir: dataDir,
554-
SQLPort: port,
555-
Host: host,
556-
LogFile: logFile,
557-
User: user,
558-
DisableRemotesAPI: true, // remotesapi only needed for federation
559-
})
560-
561-
if err := server.Start(context.Background()); err != nil {
562-
if jsonOutput {
563-
outputJSON(map[string]interface{}{
564-
"error": "start_failed",
565-
"message": err.Error(),
566-
})
567-
} else {
568-
fmt.Println() // finish the "Waiting..." line
569-
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
570-
fmt.Fprintf(os.Stderr, "Check the log file for details: %s\n", logFile)
571-
}
572-
os.Exit(1)
573-
}
574-
if !jsonOutput {
575-
fmt.Println() // finish the "Waiting..." line
576-
}
577-
578-
pid := dolt.GetRunningServerPID(dataDir)
579-
580-
if jsonOutput {
581-
outputJSON(map[string]interface{}{
582-
"status": "started",
583-
"pid": pid,
584-
"host": host,
585-
"port": port,
586-
"user": user,
587-
"database": database,
588-
"data_dir": dataDir,
589-
"log_file": logFile,
590-
})
591-
return
592-
}
593-
594-
fmt.Printf(" %s\n", ui.RenderPass(fmt.Sprintf("✓ Server started (PID %d)", pid)))
595-
fmt.Println()
596-
fmt.Println("To stop the server:")
597-
fmt.Println(" bd dolt stop")
598-
}
599-
600-
func stopDoltServer() {
601-
beadsDir := beads.FindBeadsDir()
602-
if beadsDir == "" {
603-
fmt.Fprintf(os.Stderr, "Error: not in a beads repository (no .beads directory found)\n")
604-
os.Exit(1)
605-
}
606-
607-
dataDir := filepath.Join(beadsDir, "dolt")
608-
609-
pid := dolt.GetRunningServerPID(dataDir)
610-
if pid == 0 {
611-
if jsonOutput {
612-
outputJSON(map[string]interface{}{
613-
"status": "not_running",
614-
"message": "No Dolt server is running",
615-
})
616-
} else {
617-
fmt.Println("No Dolt server is running.")
618-
}
619-
return
620-
}
621-
622-
if !jsonOutput {
623-
fmt.Printf("Stopping Dolt SQL server (PID %d)...\n", pid)
624-
}
625-
626-
if err := dolt.StopServerByPID(pid); err != nil {
627-
if jsonOutput {
628-
outputJSON(map[string]interface{}{
629-
"error": "stop_failed",
630-
"message": err.Error(),
631-
"pid": pid,
632-
})
633-
} else {
634-
fmt.Fprintf(os.Stderr, "Error stopping server: %v\n", err)
635-
}
636-
os.Exit(1)
637-
}
638-
639-
// Clean up PID file
640-
pidFile := filepath.Join(dataDir, "dolt-server.pid")
641-
_ = os.Remove(pidFile)
642-
643-
if jsonOutput {
644-
outputJSON(map[string]interface{}{
645-
"status": "stopped",
646-
"pid": pid,
647-
})
648-
return
649-
}
650-
651-
fmt.Printf("%s\n", ui.RenderPass("✓ Server stopped"))
652-
653-
fmt.Println()
654-
fmt.Println("Note: bd commands require a running server.")
655-
fmt.Println("Restart with: bd dolt start")
656-
}
657-
658450
// logDoltConfigChange appends an audit entry to .beads/dolt-config.log.
659451
// Includes the beadsDir path for debugging worktree config pollution (bd-la2cl).
660452
func logDoltConfigChange(beadsDir, key, value string) {

0 commit comments

Comments
 (0)