package cli import ( "fmt" "os" "os/exec" "os/signal" "syscall" "github.com/neutrino2211/at-rund/internal/atproto" "github.com/neutrino2211/at-rund/internal/config" "github.com/neutrino2211/at-rund/internal/server" "github.com/neutrino2211/at-rund/internal/vm" "github.com/spf13/cobra" ) var ( servePort int serveDev bool serveIsolation string ) var serveCmd = &cobra.Command{ Use: "serve", Short: "Start the at-rund server", Long: `Starts the HTTP server and initializes the execution backend.`, RunE: runServe, } func init() { serveCmd.Flags().IntVarP(&servePort, "port", "p", 0, "port to listen on (default: from config)") serveCmd.Flags().BoolVar(&serveDev, "dev", false, "enable development mode (alias for --isolation=none)") serveCmd.Flags().StringVar(&serveIsolation, "isolation", "", "isolation backend: auto, none, container, firecracker") } func kvmAvailable() bool { _, err := os.Stat("/dev/kvm") return err == nil } func dockerAvailable() bool { cmd := exec.Command("docker", "info") return cmd.Run() == nil } func podmanAvailable() bool { cmd := exec.Command("podman", "info") return cmd.Run() == nil } func detectIsolation() string { if kvmAvailable() { return "firecracker" } if dockerAvailable() || podmanAvailable() { return "container" } return "none" } func runServe(cmd *cobra.Command, args []string) error { // Load config cfg, err := config.Load(cfgFile) if err != nil { return fmt.Errorf("failed to load config: %w", err) } // Override with flags if servePort != 0 { cfg.Port = servePort } if serveDev { cfg.Isolation = "none" } if serveIsolation != "" { cfg.Isolation = serveIsolation } // Auto-detect isolation if set to auto or empty if cfg.Isolation == "" || cfg.Isolation == "auto" { cfg.Isolation = detectIsolation() } fmt.Printf("at-rund starting...\n") fmt.Printf(" Port: %d\n", cfg.Port) fmt.Printf(" Isolation: %s\n", cfg.Isolation) if cfg.DID != "" { fmt.Printf(" DID: %s\n", cfg.DID) } // Initialize executor based on isolation mode var executor vm.Executor flakePath := vm.GetFlakePath() switch cfg.Isolation { case "none": fmt.Println(" Executor: Nix (direct execution, no isolation)") executor, err = vm.NewNixPool(flakePath) if err != nil { fmt.Println("\n Nix is required. Install it with:") fmt.Println(" curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install") fmt.Println("") return fmt.Errorf("failed to initialize Nix executor: %w", err) } case "container": fmt.Println(" Executor: Container (namespace isolation)") executor, err = vm.NewContainerPool(flakePath) if err != nil { fmt.Println("\n Docker or Podman is required for container isolation.") fmt.Println(" Install Docker: https://docs.docker.com/engine/install/") fmt.Println(" Or use --isolation=none for dev mode") fmt.Println("") return fmt.Errorf("failed to initialize Container executor: %w", err) } case "firecracker": fmt.Println(" Executor: Firecracker (VM isolation)") if !kvmAvailable() { return fmt.Errorf("firecracker requires KVM (/dev/kvm not accessible)") } executor, err = vm.NewFirecrackerPool(nil) if err != nil { return fmt.Errorf("failed to initialize Firecracker pool: %w", err) } default: return fmt.Errorf("unknown isolation mode: %s (valid: auto, none, container, firecracker)", cfg.Isolation) } defer executor.Shutdown() // Pre-warm if configured if cfg.Pool.PreWarm > 0 { fmt.Printf(" Pre-warming runtimes...\n") if err := executor.Warm(cfg.Pool.PreWarm); err != nil { fmt.Printf(" Warning: failed to pre-warm: %v\n", err) } } // Initialize bundle client bundleClient := atproto.NewBundleClient(cfg.BundlesDir(), cfg.DevMode) fmt.Println(" Bundle client: ready") // Start HTTP server srv := server.New(cfg, executor, bundleClient) // Handle shutdown gracefully sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) errCh := make(chan error, 1) go func() { fmt.Printf("\nListening on http://localhost:%d\n", cfg.Port) errCh <- srv.ListenAndServe() }() select { case err := <-errCh: return err case sig := <-sigCh: fmt.Printf("\nReceived %s, shutting down...\n", sig) executor.Drain() return srv.Shutdown() } }