commit 138803713a5a13d02a475111f5f3bc4ce933f835 Author: gaofei Date: Mon Jan 13 17:08:11 2025 +0800 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d01bd1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8546926 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "pmr" +version = "0.1.0" +edition = "2021" +authors = ["Your Name "] +description = "A process manager in Rust" +license = "MIT" + +[dependencies] +clap = { version = "4.0", features = ["derive"] } +sysinfo = "0.33.1" +tabled = "0.17.0" +serde = { version = "1.0", features = ["derive"] } +dirs = "5.0" +serde_json = "1.0" +crossterm = "0.28.1" +once_cell = "1.18.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..68c593e --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Process Manager in Rust + +## Usage +Just like pm2 + +```bash +pmr start --name [name] [program] -- [args] +pmr list/ls +pmr stop [id;name] +pmr stop [id;name] +pmr restart [id;name] +pmr delete/rm [idname] +``` \ No newline at end of file diff --git a/instruction.md b/instruction.md new file mode 100644 index 0000000..0401118 --- /dev/null +++ b/instruction.md @@ -0,0 +1,50 @@ +# AI Instruction + +1. +在当前文件夹下创建rust工程,编写 pmr 命令行工具,用于守护进程的管理: +1. 支持windowns、Linux、MacOS +2. 支持命令直接启动,例如 pmr start --name [别名] [程序名] -- [参数] +3. 支持配置文件启动,例如 pmr start --config config.json: +{ + "name": "test", + "program": "python", + "args": ["-m", "test"] +} +4. 支持查看进程列表,例如 pmr list(alias命令:ls),在终端显示列表,例如┌────┬───────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐ +│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │ +├────┼───────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤ +│ 0 │ naive │ default │ N/A │ fork │ 1492097 │ 22h │ 39 │ online │ 0% │ 8.1mb │ root │ disabled │ +│ 1 │ naive-http │ default │ N/A │ fork │ 1492102 │ 22h │ 9 │ online │ 0% │ 8.3mb │ root │ disabled │ +└────┴───────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘ + +5. +每次执行pmr命令,检查用户目录下是否有.pmr 文件夹,如果没有,则创建,并在文件夹下创建dump.json文件,用于管理使用pmr命令启动的进程,例如: +[ + { + "id": 1, + "name": "test", + "program": "python", + "args": ["-m", "test"] + }, + { + "id": 2, + "name": "test2", + "program": "python", + "args": ["-m", "test2"] + } +] +修改list(ls)命令,添加参数 --system,用于显示系统所有进程,如果不添加该参数,只显示在~/.pmr/dump.json中的进程 +增加stop命令,用于关闭指定进程,例如pmr stop 1,关闭~/.pmr/dump.json中id为1的进程,或者pmr stop test,关闭~/.pmr/dump.json中name为test的进程 + +6. +验证start命令成功执行notepad程序后,相关信息是否写入dump.json,并且执行stop命令能正常结束进程 + +7.继续修改pmr start命令,使得以下几种方式均可成功: +pmr start pmr_id // 启动存在于dump.json中的进程 +pmr start name // 启动存在于dump.json中的进程 +pmr start --config config.json // 从配置文件启动进程 +pmr start program_name // 从程序启动进程 + +8.增加pmr stop命令,用于关闭指定进程,例如pmr stop 1,关闭~/.pmr/dump.json中id为1的进程,或者pmr stop test,关闭~/.pmr/dump.json中name为test的进程 + +9.增加命令:restart, 例如 pmr restart 1,重启~/.pmr/dump.json中id为1的进程,或者pmr restart test,重启~/.pmr/dump.json中name为test的进程, pmr restart --config config.json, 从配置文件重启进程 \ No newline at end of file diff --git a/src/base/mod.rs b/src/base/mod.rs new file mode 100644 index 0000000..80fe812 --- /dev/null +++ b/src/base/mod.rs @@ -0,0 +1 @@ +pub mod process; diff --git a/src/base/process.rs b/src/base/process.rs new file mode 100644 index 0000000..8ca9d60 --- /dev/null +++ b/src/base/process.rs @@ -0,0 +1,12 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct PmrProcessInfo { + pub pmr_id: u32, // 自增ID + pub pid: u32, // 进程PID + pub name: String, + pub namespace: String, + pub status: String, + pub program: String, + pub args: Vec, +} diff --git a/src/commands/delete.rs b/src/commands/delete.rs new file mode 100644 index 0000000..a220d1e --- /dev/null +++ b/src/commands/delete.rs @@ -0,0 +1,57 @@ +use super::super::config::dump::DumpConfig; +use super::list::list_processes; +use super::stop::stop_process; + +pub fn delete_process(target: &str) { + let dump_config = DumpConfig::get_instance(); + match dump_config.list_processes() { + Ok(processes) => { + let mut found = false; + + // 首先尝试将target解析为pmr_id + if let Ok(pmr_id) = target.parse::() { + if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) { + // 如果进程还在运行,先停止它 + if process.pid > 0 { + stop_process(target, false); + } + + // 从配置中删除进程 + dump_config + .delete_process(pmr_id) + .expect("Failed to delete process from dump file"); + println!("Successfully deleted process '{}'", process.name); + found = true; + } + } + + // 如果不是pmr_id,尝试按name查找 + if !found { + if let Some(process) = processes.iter().find(|p| p.name == target) { + // 如果进程还在运行,先停止它 + if process.pid > 0 { + stop_process(&process.pmr_id.to_string(), false); + } + + // 从配置中删除进程 + dump_config + .delete_process(process.pmr_id) + .expect("Failed to delete process from dump file"); + println!("Successfully deleted process '{}'", process.name); + found = true; + } + } + + if found { + // 显示进程列表 + println!("\nCurrent process list:"); + list_processes(false); + } else { + eprintln!("No process found with id or name: {}", target); + } + } + Err(e) => { + eprintln!("Failed to read processes: {}", e); + } + } +} diff --git a/src/commands/list.rs b/src/commands/list.rs new file mode 100644 index 0000000..c161705 --- /dev/null +++ b/src/commands/list.rs @@ -0,0 +1,109 @@ +use crate::config::dump::DumpConfig; +use serde::{Deserialize, Serialize}; +use sysinfo::{System, Users}; +use tabled::{Table, Tabled}; + +#[derive(Serialize, Deserialize)] +pub struct PmrProcess { + pub pmr_id: u32, + pub pid: u32, + pub name: String, + pub namespace: String, + pub program: String, + pub args: Vec, + pub status: String, +} + +#[derive(Tabled)] +struct ProcessInfo { + id: String, + name: String, + namespace: String, + version: String, + pid: String, + uptime: String, + restarts: String, + status: String, + cpu: String, + mem: String, + user: String, +} + +pub fn read_pmr_processes() -> Vec { + let dump_config = DumpConfig::get_instance(); + match dump_config.list_processes() { + Ok(processes) => processes + .into_iter() + .map(|p| PmrProcess { + pmr_id: p.pmr_id, + pid: p.pid, + name: p.name, + namespace: p.namespace, + program: p.program, + args: p.args, + status: p.status, + }) + .collect(), + Err(e) => { + eprintln!("Failed to read processes: {}", e); + Vec::new() + } + } +} + +pub fn list_processes(system: bool) { + if system { + let mut sys = System::new_all(); + let users = Users::new_with_refreshed_list(); + sys.refresh_all(); + + let processes: Vec = sys + .processes() + .iter() + .map(|(&pid, process)| ProcessInfo { + id: "0".to_string(), + name: process.name().to_string_lossy().to_string(), + namespace: "default".to_string(), + version: "N/A".to_string(), + pid: pid.to_string(), + uptime: process.run_time().to_string(), + restarts: "0".to_string(), + status: process.status().to_string(), + cpu: format!("{:.1}%", process.cpu_usage()), + mem: format!("{:.1} MB", process.memory() as f64 / 1024.0 / 1024.0), + user: process + .user_id() + .and_then(|uid| users.get_user_by_id(uid)) + .map_or("N/A".to_string(), |u| u.name().to_string()), + }) + .collect(); + + let table = Table::new(processes).to_string(); + println!("{}", table); + } else { + let pmr_processes = read_pmr_processes(); + let processes: Vec = pmr_processes + .iter() + .map(|p| ProcessInfo { + id: p.pmr_id.to_string(), + name: p.name.clone(), + namespace: "pmr".to_string(), + version: "N/A".to_string(), + pid: p.pid.to_string(), + uptime: "N/A".to_string(), + restarts: "0".to_string(), + status: if p.pid > 0 { + "running".to_string() + } else { + "stopped".to_string() + }, + cpu: "N/A".to_string(), + mem: "N/A".to_string(), + user: "N/A".to_string(), + }) + .collect(); + + let table = Table::new(processes).to_string(); + println!("{}", table); + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..7439e90 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,5 @@ +pub mod delete; +pub mod list; +pub mod restart; +pub mod start; +pub mod stop; diff --git a/src/commands/restart.rs b/src/commands/restart.rs new file mode 100644 index 0000000..97aaea8 --- /dev/null +++ b/src/commands/restart.rs @@ -0,0 +1,64 @@ +use super::super::base::process::PmrProcessInfo; +use super::super::config::dump::DumpConfig; +use super::list::list_processes; +use super::start::start_process; +use super::stop::stop_process; +use std::path::PathBuf; + +pub fn restart_process(config: Option, target: Option, args: Vec) { + let dump_config = DumpConfig::get_instance(); + + // 如果指定了target,先检查是否是已存在的进程 + if let Some(ref target_str) = target { + if let Ok(processes) = dump_config.list_processes() { + // 尝试将target解析为pmr_id + if let Ok(pmr_id) = target_str.parse::() { + if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) { + restart_existing_process(process); + return; + } + } + + // 按名称查找进程 + if let Some(process) = processes.iter().find(|p| p.name == *target_str) { + restart_existing_process(process); + return; + } + } + } + + // 如果不是重启已存在的进程,就当作普通的启动处理 + start_process(config, None, target, args); +} + +fn restart_existing_process(process: &PmrProcessInfo) { + println!("正在重启进程 '{}'...", process.name); + + // 先停止进程 + stop_process(&process.pmr_id.to_string(), false); + + // 重新启动进程 + let mut cmd = std::process::Command::new(&process.program); + cmd.args(&process.args) + .stdout(std::process::Stdio::inherit()) + .stderr(std::process::Stdio::inherit()); + + match cmd.spawn() { + Ok(child) => { + let pid = child.id(); + println!("进程 '{}' 重启成功,新 PID: {}", process.name, pid); + + let dump_config = DumpConfig::get_instance(); + dump_config + .update_process_status(process.pmr_id, pid, "running".to_string()) + .expect("无法更新进程状态"); + + // 显示进程列表 + println!("\n当前进程列表:"); + list_processes(false); + } + Err(e) => { + eprintln!("重启进程 '{}' 失败: {}", process.name, e); + } + } +} diff --git a/src/commands/start.rs b/src/commands/start.rs new file mode 100644 index 0000000..39e902b --- /dev/null +++ b/src/commands/start.rs @@ -0,0 +1,157 @@ +use super::super::base::process::PmrProcessInfo; +use super::super::config::dump::DumpConfig; +use super::list::list_processes; +use serde::{Deserialize, Serialize}; +use std::fs::File; +use std::io::Read; +use std::path::PathBuf; +use std::process::{Command, Stdio}; + +#[derive(Deserialize, Serialize)] +struct Config { + name: String, + program: String, + args: Vec, +} + +pub fn start_process(config: Option, name: Option, target: Option, args: Vec) { + let dump_config = DumpConfig::get_instance(); + + // 如果指定了target,先检查是否是已存在的进程 + if let Some(ref target_str) = target { + if let Ok(processes) = dump_config.list_processes() { + // 尝试将target解析为pmr_id + if let Ok(pmr_id) = target_str.parse::() { + if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) { + start_existing_process(process); + return; + } + } + + // 按名称查找进程 + if let Some(process) = processes.iter().find(|p| p.name == *target_str) { + start_existing_process(process); + return; + } + } + } + + // 检查是否已存在同名进程 + let process_name = name.clone().unwrap_or_else(|| { + target.clone().unwrap_or_else(|| { + if let Some(ref config_path) = config { + let mut file = File::open(config_path).expect("Failed to open config file"); + let mut contents = String::new(); + file.read_to_string(&mut contents).expect("Failed to read config file"); + let config: Config = serde_json::from_str(&contents).expect("Failed to parse config file"); + config.name + } else { + "unnamed".to_string() + } + }) + }); + + if let Ok(processes) = dump_config.list_processes() { + if let Some(_existing) = processes.iter().find(|p| p.name == process_name) { + println!("\n进程 '{}' 已经存在:", process_name); + list_processes(false); + return; + } + } + + if let Some(config_path) = config { + // 从配置文件启动 + let mut file = File::open(config_path).expect("无法打开配置文件"); + let mut contents = String::new(); + file.read_to_string(&mut contents) + .expect("无法读取配置文件"); + + let config: Config = serde_json::from_str(&contents).expect("无法解析配置文件"); + + let mut cmd = Command::new(&config.program); + cmd.args(&config.args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + + match cmd.spawn() { + Ok(child) => { + let pid = child.id(); + println!("启动进程 '{}' PID: {}", process_name, pid); + + dump_config + .add_process( + process_name, + "default".to_string(), + config.program, + pid, + "running".to_string(), + config.args, + ) + .expect("无法将进程添加到配置文件"); + } + Err(e) => { + eprintln!("启动进程失败: {}", e); + } + } + } else if let Some(target_program) = target { + // 直接启动程序 + let mut cmd = Command::new(&target_program); + cmd.args(&args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + + match cmd.spawn() { + Ok(child) => { + let pid = child.id(); + println!("启动进程 '{}' PID: {}", process_name, pid); + + dump_config + .add_process( + process_name, + "default".to_string(), + target_program, + pid, + "running".to_string(), + args, + ) + .expect("无法将进程添加到配置文件"); + } + Err(e) => { + eprintln!("启动进程失败: {}", e); + } + } + } else { + eprintln!("错误: 必须指定 --config 或 target"); + } +} + +fn start_existing_process(process: &PmrProcessInfo) { + if process.status == "running" { + println!("进程 '{}' 已经在运行中,PID: {}", process.name, process.pid); + return; + } + + let mut cmd = Command::new(&process.program); + cmd.args(&process.args) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()); + + match cmd.spawn() { + Ok(child) => { + let pid = child.id(); + println!("启动进程 '{}' PID: {}", process.name, pid); + + let dump_config = DumpConfig::get_instance(); + dump_config + .update_process_status(process.pmr_id, pid, "running".to_string()) + .expect("无法更新进程状态"); + + // 显示进程列表 + println!("\n当前进程列表:"); + list_processes(false); + } + Err(e) => { + eprintln!("启动进程 '{}' 失败: {}", process.name, e); + } + } +} diff --git a/src/commands/stop.rs b/src/commands/stop.rs new file mode 100644 index 0000000..eba9627 --- /dev/null +++ b/src/commands/stop.rs @@ -0,0 +1,83 @@ +use super::super::config::dump::DumpConfig; +use super::list::list_processes; +use std::process::Command; + +pub fn stop_process(target: &str, show_list: bool) { + let dump_config = DumpConfig::get_instance(); + match dump_config.list_processes() { + Ok(processes) => { + let mut found = false; + + // 首先尝试将target解析为pmr_id + if let Ok(pmr_id) = target.parse::() { + if let Some(process) = processes.iter().find(|p| p.pmr_id == pmr_id) { + if process.pid > 0 { + // 在Windows上使用taskkill命令终止进程 + let output = Command::new("taskkill") + .args(&["/PID", &process.pid.to_string(), "/F"]) + .output() + .expect("无法执行taskkill命令"); + + if output.status.success() { + println!("已停止进程 '{}' (PID: {})", process.name, process.pid); + dump_config + .update_process_status(process.pmr_id, 0, "stopped".to_string()) + .expect("无法更新进程状态"); + found = true; + } else { + eprintln!( + "停止进程失败 '{}' (PID: {}): {}", + process.name, + process.pid, + String::from_utf8_lossy(&output.stderr) + ); + } + } else { + println!("进程 '{}' 已经停止", process.name); + found = true; + } + } + } + + // 如果不是pmr_id,尝试按name查找 + if !found { + if let Some(process) = processes.iter().find(|p| p.name == target) { + if process.pid > 0 { + // 在Windows上使用taskkill命令终止进程 + let output = Command::new("taskkill") + .args(&["/PID", &process.pid.to_string(), "/F"]) + .output() + .expect("无法执行taskkill命令"); + + if output.status.success() { + println!("已停止进程 '{}' (PID: {})", process.name, process.pid); + dump_config + .update_process_status(process.pmr_id, 0, "stopped".to_string()) + .expect("无法更新进程状态"); + } else { + eprintln!( + "停止进程失败 '{}' (PID: {}): {}", + process.name, + process.pid, + String::from_utf8_lossy(&output.stderr) + ); + } + } else { + println!("进程 '{}' 已经停止", process.name); + } + } else { + eprintln!("未找到进程: {}", target); + } + } + + // 根据show_list参数决定是否显示进程列表 + if show_list { + println!("\n当前进程列表:"); + list_processes(false); + } + } + Err(e) => { + eprintln!("读取进程列表失败: {}", e); + } + } +} diff --git a/src/config/dump.rs b/src/config/dump.rs new file mode 100644 index 0000000..3b0397e --- /dev/null +++ b/src/config/dump.rs @@ -0,0 +1,102 @@ +use super::super::base::process::PmrProcessInfo; +use once_cell::sync::OnceCell; +use serde::{Deserialize, Serialize}; +use std::sync::Mutex; +use std::{fs, io, path::PathBuf}; + +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct DumpData { + processes: Vec, +} + +pub struct DumpConfig { + path: PathBuf, + data: Mutex, +} + +static INSTANCE: OnceCell = OnceCell::new(); + +impl DumpConfig { + fn new() -> io::Result { + let home_dir = dirs::home_dir() + .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Home directory not found"))?; + + let config_dir = home_dir.join(".pmr"); + if !config_dir.exists() { + fs::create_dir_all(&config_dir)?; + } + + let dump_file = config_dir.join("dump.json"); + let data = if dump_file.exists() { + let file_contents = fs::read_to_string(&dump_file)?; + serde_json::from_str(&file_contents)? + } else { + let initial_data = DumpData { + processes: Vec::new(), + }; + fs::write(&dump_file, serde_json::to_string_pretty(&initial_data)?)?; + initial_data + }; + + Ok(Self { + path: dump_file, + data: Mutex::new(data), + }) + } + + pub fn get_instance() -> &'static DumpConfig { + INSTANCE.get_or_init(|| Self::new().expect("Failed to initialize DumpConfig")) + } + + fn save_data(&self, data: &DumpData) -> io::Result<()> { + let json = serde_json::to_string_pretty(data)?; + fs::write(&self.path, json) + } + + pub fn add_process( + &self, + name: String, + namespace: String, + program: String, + pid: u32, + status: String, + args: Vec, + ) -> io::Result<()> { + let mut data = self.data.lock().unwrap(); + let new_id = data.processes.iter().map(|p| p.pmr_id).max().unwrap_or(0) + 1; + + data.processes.push(PmrProcessInfo { + pmr_id: new_id, + name, + namespace, + pid, + status, + program, + args, + }); + + self.save_data(&data) + } + + pub fn delete_process(&self, id: u32) -> io::Result<()> { + let mut data = self.data.lock().unwrap(); + data.processes.retain(|p| p.pmr_id != id); + self.save_data(&data) + } + + pub fn list_processes(&self) -> io::Result> { + let data = self.data.lock().unwrap(); + Ok(data.processes.clone()) + } + + pub fn update_process_status(&self, pmr_id: u32, pid: u32, status: String) -> io::Result<()> { + let mut data = self.data.lock().unwrap(); + if let Some(process) = data.processes.iter_mut().find(|p| p.pmr_id == pmr_id) { + process.pid = pid; + process.status = status; + self.save_data(&data) + } else { + Ok(()) + } + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..88f4251 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1 @@ +pub mod dump; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4cee434 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,125 @@ +use clap::{Parser, Subcommand}; +use std::path::PathBuf; +mod base; +mod commands; +mod config; +use commands::delete::delete_process; +use commands::list::list_processes; +use commands::restart::restart_process; +use commands::start::start_process; +use commands::stop::stop_process; +use config::dump::DumpConfig; + +fn config_init() -> std::io::Result<()> { + // 使用DumpConfig初始化配置 + let _ = DumpConfig::get_instance(); + Ok(()) +} + +#[derive(Parser)] +#[command(name = "pmr")] +#[command(about = "Process Manager in Rust", long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + /// Start a process + Start { + /// Config file path + #[arg(short, long)] + config: Option, + + /// Process name + #[arg(short, long)] + name: Option, + + /// Target (can be pmr_id, name, or program to run) + target: Option, + + /// Arguments for the program + #[arg(last = true)] + args: Vec, + }, + + /// List running processes + #[command(alias = "ls")] + List { + /// Show all system processes + #[arg(long)] + system: bool, + }, + + /// Delete a process + #[command(alias = "rm")] + Delete { + /// Process ID or name + target: String, + }, + + /// Stop a process + Stop { + /// Process ID or name + target: String, + }, + + /// Restart a process + Restart { + /// Config file path + #[arg(short, long)] + config: Option, + + /// Target (can be pmr_id, name, or program to run) + target: Option, + + /// Arguments for the program + #[arg(last = true)] + args: Vec, + }, +} + +fn main() { + if let Err(e) = config_init() { + eprintln!("Failed to initialize .pmr directory: {}", e); + return; + } + + let cli = Cli::parse(); + + match cli.command { + Commands::Start { + config, + name, + target, + args, + } => { + if config.is_none() && target.is_none() { + eprintln!("错误: 必须指定 --config 或 target"); + return; + } + start_process(config.clone(), name.clone(), target.clone(), args); + } + Commands::List { system } => { + list_processes(system); + } + Commands::Delete { target } => { + delete_process(&target); + } + Commands::Stop { target } => { + stop_process(&target, true); + } + Commands::Restart { + config, + target, + args, + } => { + if config.is_none() && target.is_none() { + eprintln!("错误: 必须指定 --config 或 target"); + return; + } + restart_process(config.clone(), target.clone(), args); + } + } +}