From 672b6760208157f2e564d78aeff496344007d070 Mon Sep 17 00:00:00 2001 From: Niko Marmeladkov Date: Fri, 3 Jul 2026 15:33:01 +0300 Subject: [PATCH] multi-service commands: rss start/stop/restart/enable/disable mc-proxy mc-lobby supports space-separated service names in start, stop, restart, enable, disable commands. each service result returned on its own line. --- src/cli.rs | 10 ++--- src/daemon.rs | 113 ++++++++++++++++++++++++++++++-------------------- src/main.rs | 62 +++++++++++++++++---------- 3 files changed, 111 insertions(+), 74 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index e60f663..255d01c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -14,15 +14,15 @@ pub struct Cli { pub enum Commands { /// Start a service Start { - name: String, + names: Vec, }, /// Stop a service Stop { - name: String, + names: Vec, }, /// Restart a service Restart { - name: String, + names: Vec, }, /// Show service status Status { @@ -38,13 +38,13 @@ pub enum Commands { }, /// Enable auto-start for a service Enable { - name: String, + names: Vec, #[arg(long, help = "Start the service immediately")] now: bool, }, /// Disable auto-start for a service Disable { - name: String, + names: Vec, }, /// List all configured services List, diff --git a/src/daemon.rs b/src/daemon.rs index fc26a8c..76d7992 100644 --- a/src/daemon.rs +++ b/src/daemon.rs @@ -364,48 +364,60 @@ fn handle_client( let response = match line { l if l.starts_with("start ") => { - let name = &l[6..]; - if !config.service.contains_key(name) { - format!("error: service '{}' not found", name) - } else if procs.contains_key(name) { - format!("error: '{}' already running", name) - } else { - match spawn(name, &config.service[name]) { - Some(p) => { - let pid = p.pid; - procs.insert(name.to_string(), p); - format!("ok {}", pid) + let names: Vec<&str> = l[6..].split_whitespace().collect(); + let mut out = Vec::new(); + for name in &names { + if !config.service.contains_key(*name) { + out.push(format!("error: service '{}' not found", name)); + } else if procs.contains_key(*name) { + out.push(format!("error: '{}' already running", name)); + } else { + match spawn(name, &config.service[*name]) { + Some(p) => { + let pid = p.pid; + procs.insert(name.to_string(), p); + out.push(format!("ok {} pid={}", name, pid)); + } + None => out.push(format!("error: failed to spawn '{}'", name)), } - None => format!("error: failed to spawn '{}'", name), } } + out.join("\n") } l if l.starts_with("stop ") => { - let name = &l[5..]; - if !procs.contains_key(name) { - format!("error: '{}' not running", name) - } else { - stop(name, procs); - "ok".to_string() + let names: Vec<&str> = l[5..].split_whitespace().collect(); + let mut out = Vec::new(); + for name in &names { + if !procs.contains_key(*name) { + out.push(format!("error: '{}' not running", name)); + } else { + stop(name, procs); + out.push(format!("ok {}", name)); + } } + out.join("\n") } l if l.starts_with("restart ") => { - let name = &l[8..]; - if procs.contains_key(name) { - stop(name, procs); - } - if let Some(cfg) = config.service.get(name) { - match spawn(name, cfg) { - Some(p) => { - let pid = p.pid; - procs.insert(name.to_string(), p); - format!("ok {}", pid) - } - None => format!("error: failed to spawn '{}'", name), + let names: Vec<&str> = l[8..].split_whitespace().collect(); + let mut out = Vec::new(); + for name in &names { + if procs.contains_key(*name) { + stop(name, procs); + } + if let Some(cfg) = config.service.get(*name) { + match spawn(name, cfg) { + Some(p) => { + let pid = p.pid; + procs.insert(name.to_string(), p); + out.push(format!("ok {} pid={}", name, pid)); + } + None => out.push(format!("error: failed to spawn '{}'", name)), + } + } else { + out.push(format!("error: service '{}' not found", name)); } - } else { - format!("error: service '{}' not found", name) } + out.join("\n") } "list" | "list " => { let mut out = Vec::new(); @@ -446,30 +458,39 @@ fn handle_client( } l if l.starts_with("enable ") => { let rest = &l[7..]; - let (name, now) = if let Some(n) = rest.strip_suffix(" now") { + let (names_str, now) = if let Some(n) = rest.strip_suffix(" now") { (n, true) } else { (rest, false) }; - if !config.service.contains_key(name) { - format!("error: service '{}' not found", name) - } else if let Err(e) = crate::state::enable(name) { - format!("error: enable '{}': {}", name, e) - } else { - if now { - if !procs.contains_key(name) { - if let Some(p) = spawn(name, &config.service[name]) { - procs.insert(name.to_string(), p); + let names: Vec<&str> = names_str.split_whitespace().collect(); + let mut out = Vec::new(); + for name in &names { + if !config.service.contains_key(*name) { + out.push(format!("error: service '{}' not found", name)); + } else if let Err(e) = crate::state::enable(name) { + out.push(format!("error: enable '{}': {}", name, e)); + } else { + if now { + if !procs.contains_key(*name) { + if let Some(p) = spawn(name, &config.service[*name]) { + procs.insert(name.to_string(), p); + } } } + out.push(format!("ok {}", name)); } - "ok".to_string() } + out.join("\n") } l if l.starts_with("disable ") => { - let name = &l[8..]; - crate::state::disable(name); - "ok".to_string() + let names: Vec<&str> = l[8..].split_whitespace().collect(); + let mut out = Vec::new(); + for name in &names { + crate::state::disable(name); + out.push(format!("ok {}", name)); + } + out.join("\n") } _ => format!("error: unknown command '{}'", line), }; diff --git a/src/main.rs b/src/main.rs index 0c71770..d67581e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,14 +18,14 @@ fn main() { let config_path = resolve_config_path(cli.config.as_deref()); daemon::run(config_path); } - Commands::Start { ref name } => cmd_start(name), - Commands::Stop { ref name } => cmd_stop(name), - Commands::Restart { ref name } => cmd_restart(name), + Commands::Start { ref names } => cmd_start(names), + Commands::Stop { ref names } => cmd_stop(names), + Commands::Restart { ref names } => cmd_restart(names), Commands::Status { ref name } => cmd_status(name.as_deref()), Commands::List => cmd_list(), Commands::Logs { ref name, lines, follow } => cmd_logs(name, lines, follow), - Commands::Enable { ref name, now } => cmd_enable(name, now), - Commands::Disable { ref name } => cmd_disable(name), + Commands::Enable { ref names, now } => cmd_enable(names, now), + Commands::Disable { ref names } => cmd_disable(names), Commands::Supervise { .. } => { eprintln!("error: supervise is internal, use daemon"); std::process::exit(1); @@ -43,11 +43,12 @@ fn resolve_config_path(custom: Option<&str>) -> String { state::config_dir().join("config.toml").to_string_lossy().to_string() } -fn cmd_start(name: &str) { - match ipc::send_cmd(&format!("start {}", name)) { +fn cmd_start(names: &[String]) { + let cmd = format!("start {}", names.join(" ")); + match ipc::send_cmd(&cmd) { Ok(lines) => { - if let Some(first) = lines.first() { - println!("{}", first); + for line in lines { + println!("{}", line); } } Err(e) => { @@ -57,9 +58,14 @@ fn cmd_start(name: &str) { } } -fn cmd_stop(name: &str) { - match ipc::send_cmd(&format!("stop {}", name)) { - Ok(_) => {} +fn cmd_stop(names: &[String]) { + let cmd = format!("stop {}", names.join(" ")); + match ipc::send_cmd(&cmd) { + Ok(lines) => { + for line in lines { + println!("{}", line); + } + } Err(e) => { eprintln!("error: {}", e); std::process::exit(1); @@ -67,11 +73,12 @@ fn cmd_stop(name: &str) { } } -fn cmd_restart(name: &str) { - match ipc::send_cmd(&format!("restart {}", name)) { +fn cmd_restart(names: &[String]) { + let cmd = format!("restart {}", names.join(" ")); + match ipc::send_cmd(&cmd) { Ok(lines) => { - if let Some(first) = lines.first() { - println!("{}", first); + for line in lines { + println!("{}", line); } } Err(e) => { @@ -140,14 +147,18 @@ fn cmd_logs(name: &str, mut lines: Option, follow: bool) { } } -fn cmd_enable(name: &str, now: bool) { +fn cmd_enable(names: &[String], now: bool) { let cmd = if now { - format!("enable {} now", name) + format!("enable {} now", names.join(" ")) } else { - format!("enable {}", name) + format!("enable {}", names.join(" ")) }; match ipc::send_cmd(&cmd) { - Ok(_) => println!("enabled {}", name), + Ok(lines) => { + for line in lines { + println!("{}", line); + } + } Err(e) => { eprintln!("error: {}", e); std::process::exit(1); @@ -155,9 +166,14 @@ fn cmd_enable(name: &str, now: bool) { } } -fn cmd_disable(name: &str) { - match ipc::send_cmd(&format!("disable {}", name)) { - Ok(_) => println!("disabled {}", name), +fn cmd_disable(names: &[String]) { + let cmd = format!("disable {}", names.join(" ")); + match ipc::send_cmd(&cmd) { + Ok(lines) => { + for line in lines { + println!("{}", line); + } + } Err(e) => { eprintln!("error: {}", e); std::process::exit(1);