(&self, ctx: &AppContext)
| 31 | #[async_trait] |
| 32 | impl Command for ServerStartCommand { |
| 33 | async fn execute(&self, ctx: &AppContext) -> Result<(), Box<dyn Error>> { |
| 34 | let is_interactive = std::io::stdout().is_terminal(); |
| 35 | |
| 36 | if !is_interactive { |
| 37 | println!("Starting tsk server with {} worker(s)...", self.workers); |
| 38 | } |
| 39 | ctx.notification_client().set_sound_enabled(self.sound); |
| 40 | let docker_client: Arc<dyn DockerClient> = Arc::new( |
| 41 | DefaultDockerClient::new(&ctx.tsk_config().container_engine) |
| 42 | .map_err(|e| -> Box<dyn Error> { e.into() })?, |
| 43 | ); |
| 44 | |
| 45 | // Validate Docker connectivity before committing to start |
| 46 | docker_client.ping().await.map_err(|e| -> Box<dyn Error> { |
| 47 | format!("Cannot start server: Docker/Podman daemon is not reachable: {e}").into() |
| 48 | })?; |
| 49 | |
| 50 | // Create event channel for TUI mode |
| 51 | let (event_sender, event_receiver) = if is_interactive { |
| 52 | let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); |
| 53 | (Some(tx), Some(rx)) |
| 54 | } else { |
| 55 | (None, None) |
| 56 | }; |
| 57 | |
| 58 | let docker_client_for_tui = docker_client.clone(); |
| 59 | let server = TskServer::with_workers( |
| 60 | Arc::new(ctx.clone()), |
| 61 | docker_client, |
| 62 | self.workers, |
| 63 | self.quit, |
| 64 | event_sender, |
| 65 | ); |
| 66 | |
| 67 | // Setup signal handlers for graceful shutdown (SIGINT and SIGTERM) |
| 68 | let shutdown_signal = Arc::new(tokio::sync::Notify::new()); |
| 69 | let shutdown_signal_clone = shutdown_signal.clone(); |
| 70 | |
| 71 | tokio::spawn(async move { |
| 72 | let mut sigterm = |
| 73 | signal(SignalKind::terminate()).expect("Failed to listen for SIGTERM"); |
| 74 | tokio::select! { |
| 75 | _ = tokio::signal::ctrl_c() => {}, |
| 76 | _ = sigterm.recv() => {}, |
| 77 | } |
| 78 | shutdown_signal_clone.notify_one(); |
| 79 | }); |
| 80 | |
| 81 | if let Some(event_receiver) = event_receiver { |
| 82 | // TUI mode: run TUI alongside server |
| 83 | let tui_shutdown = shutdown_signal.clone(); |
| 84 | let storage = ctx.task_storage(); |
| 85 | let data_dir = ctx.tsk_env().data_dir().to_path_buf(); |
| 86 | let workers_total = self.workers as usize; |
| 87 | |
| 88 | tokio::select! { |
| 89 | result = server.run() => { |
| 90 | match result { |
nothing calls this directly
no test coverage detected