Various changes

This commit is contained in:
Uber Veng
2026-05-21 21:12:31 +07:00
parent 533df4a476
commit 56f08e36e6
10 changed files with 1010 additions and 618 deletions

View File

@@ -1,5 +1,7 @@
use crate::error::{AppError, Result};
use serde_json::Value;
#[cfg(unix)]
use libc;
use std::process::Stdio;
use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
@@ -29,6 +31,13 @@ pub enum StructuredCommand {
percent: u8,
message: Option<String>,
},
/// Run an interactive program that needs the real terminal.
/// The client suspends ratatui, inherits stdin/stdout/stderr, waits for
/// the process to exit, then restores the TUI.
/// The script receives the exit code as the response.
Exec {
shell: String,
},
}
#[derive(Debug)]
@@ -57,6 +66,7 @@ pub fn spawn(
UnboundedReceiver<StructuredCommand>,
tokio::sync::oneshot::Receiver<i32>,
UnboundedSender<String>,
tokio::sync::oneshot::Sender<()>, // kill signal
)> {
let mut child = Command::new("bash")
.arg("-c")
@@ -66,6 +76,9 @@ pub fn spawn(
.stderr(Stdio::piped())
.spawn()?;
// Capture the PID before moving child into tasks (needed for kill).
let pid = child.id();
let stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();
@@ -74,6 +87,7 @@ pub fn spawn(
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
let (finished_tx, finished_rx) = tokio::sync::oneshot::channel();
let (reply_tx, mut reply_rx) = mpsc::unbounded_channel::<String>();
let (kill_tx, kill_rx) = tokio::sync::oneshot::channel::<()>();
// Чтение stdout
let output_tx_clone = output_tx.clone();
@@ -120,14 +134,27 @@ pub fn spawn(
}
});
// Ожидание завершения
// Wait for child exit normally.
tokio::spawn(async move {
let status = child.wait().await;
let code = status.map(|s| s.code().unwrap_or(1)).unwrap_or(1);
let _ = finished_tx.send(code);
});
Ok((output_rx, cmd_rx, finished_rx, reply_tx))
// Kill-watcher: when kill_tx fires, send SIGTERM to the entire process
// group so bash AND all its children (mysqldump, php, etc.) are killed.
tokio::spawn(async move {
if kill_rx.await.is_ok() {
if let Some(pid) = pid {
#[cfg(unix)]
unsafe {
libc::kill(-(pid as libc::pid_t), libc::SIGTERM);
}
}
}
});
Ok((output_rx, cmd_rx, finished_rx, reply_tx, kill_tx))
}
fn parse_command(json_str: &str, cmd_tx: UnboundedSender<StructuredCommand>) -> Result<()> {
@@ -176,6 +203,10 @@ fn parse_command(json_str: &str, cmd_tx: UnboundedSender<StructuredCommand>) ->
let message = v["message"].as_str().map(String::from);
StructuredCommand::Progress { percent, message }
}
"exec" => {
let shell = v["shell"].as_str().unwrap_or("").to_string();
StructuredCommand::Exec { shell }
}
_ => return Err(AppError::Protocol(format!("Unknown command type: {}", typ))),
};