package restore import ( "context" "fmt" "strings" "time" ) // startDocker ensures Docker is running and starts compose stacks func (p *Pipeline) startDocker(ctx context.Context) error { // Ensure Docker is enabled and running if err := p.remoteCmd(ctx, "systemctl enable docker"); err != nil { return fmt.Errorf("failed to enable docker: %w", err) } if err := p.remoteCmd(ctx, "systemctl start docker"); err != nil { return fmt.Errorf("failed to start docker: %w", err) } // Wait for Docker to be ready for i := 0; i < 30; i++ { if err := p.remoteCmd(ctx, "docker info > /dev/null 2>&1"); err == nil { break } time.Sleep(time.Second) } // Find and start docker-compose stacks findCmd := "find /opt -name 'docker-compose.yml' -o -name 'docker-compose.yaml' -o -name 'compose.yml' -o -name 'compose.yaml' 2>/dev/null | head -20" output, err := p.remoteCmdOutput(ctx, findCmd) if err != nil || strings.TrimSpace(output) == "" { if p.Verbose { fmt.Println(" No docker-compose files found") } return nil } composeFiles := strings.Split(strings.TrimSpace(output), "\n") for _, file := range composeFiles { if file == "" { continue } // Get directory containing compose file dir := file[:strings.LastIndex(file, "/")] if p.Verbose { fmt.Printf(" Starting compose stack in %s\n", dir) } // Try docker compose (v2) first, fall back to docker-compose (v1) startCmd := fmt.Sprintf("cd %s && (docker compose up -d 2>/dev/null || docker-compose up -d)", dir) if err := p.remoteCmd(ctx, startCmd); err != nil { if p.Verbose { fmt.Printf(" Warning: failed to start stack in %s: %v\n", dir, err) } // Don't fail on individual stack failures } } return nil } // runHealth performs health verification func (p *Pipeline) runHealth(ctx context.Context) error { checks := []struct { name string cmd string require bool }{ {"SSH accessible", "echo ok", true}, {"Docker running", "docker info > /dev/null 2>&1 && echo ok", true}, {"Network connectivity", "ping -c 1 8.8.8.8 > /dev/null 2>&1 && echo ok", false}, {"DNS resolution", "host google.com > /dev/null 2>&1 && echo ok", false}, } var failures []string for _, check := range checks { output, err := p.remoteCmdOutput(ctx, check.cmd) success := err == nil && strings.TrimSpace(output) == "ok" status := "✓" if !success { status = "✗" if check.require { failures = append(failures, check.name) } } if p.Verbose { fmt.Printf(" %s %s\n", status, check.name) } } if len(failures) > 0 { return fmt.Errorf("required health checks failed: %v", failures) } return nil }