diff options
author | yuzu <yuzu@b9215c17-b818-4693-b096-d1e41a411fef> | 2025-06-04 04:45:58 +0000 |
---|---|---|
committer | yuzu <yuzu@b9215c17-b818-4693-b096-d1e41a411fef> | 2025-06-04 04:45:58 +0000 |
commit | 8880b0afd01bd9afb4a47a76d90e8b90e5cbff1e (patch) | |
tree | 96ee3e699a64ca44644cc53e19904444deb051a4 | |
parent | 288842e0621eb3e07a0790ba328727939c480884 (diff) | |
download | salaryman-8880b0afd01bd9afb4a47a76d90e8b90e5cbff1e.tar.gz salaryman-8880b0afd01bd9afb4a47a76d90e8b90e5cbff1e.tar.bz2 salaryman-8880b0afd01bd9afb4a47a76d90e8b90e5cbff1e.zip |
Work on Service::stdout() and Service::stderr()
git-svn-id: svn+ssh://diminuette.aengel.lesbianunix.dev/salaryman/trunk@7 b9215c17-b818-4693-b096-d1e41a411fef
-rw-r--r-- | salaryman.toml | 8 | ||||
-rw-r--r-- | src/main.rs | 149 | ||||
-rw-r--r-- | src/model.rs | 170 |
3 files changed, 249 insertions, 78 deletions
diff --git a/salaryman.toml b/salaryman.toml index b09acd4..b0d600b 100644 --- a/salaryman.toml +++ b/salaryman.toml @@ -4,12 +4,12 @@ port = 8080 [[service]] name = "minecraft" command = "java" -args = "-jar minecraft_server.jar" -directory = "/minecraft/data" -autostart = false +args = "-jar minecraft_server.jar nogui" +directory = "/home/yuzu/minecraft" +autostart = true [[service]] name = "derpcraft" command = "java" args = "-jar minecraft_server.jar" -autostart = true +autostart = false diff --git a/src/main.rs b/src/main.rs index 1804ca9..f4e4b1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,53 +1,52 @@ +pub mod model; + use clap::Parser; use serde::{Deserialize, Serialize}; use tokio::fs::read_to_string; -use tokio::fs::write; use std::{ - fs::canonicalize, - io::Read, - net::{IpAddr, Ipv4Addr, Ipv6Addr}, + net::IpAddr, path::PathBuf, - process::{Child, Command, Stdio}, + sync::Arc, }; -#[derive(Parser)] +use crate::model::{Service, ServiceConf}; + +#[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { - #[arg(short, long, value_name = "FILE")] - config: Option<PathBuf>, - #[arg(short, long, value_name = "ADDR")] - address: Option<IpAddr>, - #[arg(short, long, value_name = "PORT")] - port: Option<u16>, -} - -#[derive(Serialize, Deserialize, Debug)] -struct Service { - name: String, - command: String, - args: Option<String>, - directory: Option<PathBuf>, - autostart: bool, -} -impl Service { - fn new() -> Self { - Self { - name: String::new(), - command: String::new(), - args: None, - directory: None, - autostart: false, - } - } + #[arg( + short, + long, + value_name = "FILE", + help = "config file override", + default_value = "salaryman.toml" + )] + config: PathBuf, + #[arg( + short, + long, + value_name = "ADDR", + help = "IP address to bind API to", + default_value = "127.0.0.1" + )] + address: IpAddr, + #[arg( + short, + long, + value_name = "PORT", + help = "TCP Port to bind API to", + default_value = "3080" + )] + port: u16, } #[derive(Serialize, Deserialize, Debug)] struct Config { address: Option<IpAddr>, port: Option<u16>, - service: Vec<Service>, + service: Vec<ServiceConf>, } impl Config { fn new() -> Self { @@ -59,32 +58,7 @@ impl Config { } } -fn exec( - image: &str, - args: Vec<&str>, - dir: Option<PathBuf>, -) -> Result<Child, Box<dyn std::error::Error>> { - if let Some(cwd) = dir { - let child = Command::new(image) - .args(args) - .current_dir(canonicalize(cwd)?) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - Ok(child) - } else { - let child = Command::new(image) - .args(args) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; - Ok(child) - } -} - -async fn load_config(file: PathBuf) -> Config { +async fn load_config(file: &PathBuf) -> Config { let s: String = match read_to_string(file).await { Ok(s) => s, Err(_) => String::new(), @@ -98,20 +72,47 @@ async fn load_config(file: PathBuf) -> Config { #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let args = Args::parse(); - let conf: Config = load_config(PathBuf::from("salaryman.toml")).await; - - println!("{conf:?}"); - - /* - let mut child = exec("java", vec!["-jar", "minecraft_server.jar"], None)?; - std::thread::sleep(std::time::Duration::from_secs(60)); - let mut buf: [u8; 512] = [0; 512]; - let mut ebuf: [u8; 512] = [0; 512]; - child.stdout.as_mut().unwrap().read(&mut buf[..])?; - child.stderr.as_mut().unwrap().read(&mut ebuf[..])?; - println!("{}", String::from_utf8_lossy(&buf)); - println!("{}", String::from_utf8_lossy(&ebuf)); - child.kill()?; - */ + let conf: Config = load_config(&args.config).await; + let mut services: Vec<Service> = Vec::new(); + for i in 0..conf.service.len() { + services.push(Service::from_conf(conf.service[i].clone())); + if conf.service[i].autostart { + services[i].start()?; + } + } + for i in 0..services.len() { + println!("loop -1"); + if let Ok(s) = services[i].stdout() { + for line in s.lines() { + println!("STDOUT :: [{}] :: {}", services[i].config.name, line); + } + } + if let Ok(s) = services[i].stderr() { + for line in s.lines() { + println!("STDERR :: [{}] :: {}", services[i].config.name, line); + } + } + } + for e in 0..100 { + println!("loop {e}"); + for i in 0..services.len() { + if let Ok(s) = services[i].stdout() { + for line in s.lines() { + println!("STDOUT :: [{}] :: {}", services[i].config.name, line); + } + } + if let Ok(s) = services[i].stderr() { + for line in s.lines() { + println!("STDERR :: [{}] :: {}", services[i].config.name, line); + } + } + }; + } + for mut service in services { + match service.stop() { + Ok(_) => println!("lol it was killed"), + Err(_) => println!("it either didn't exist, or failed to kill"), + } + } Ok(()) } diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..52408fc --- /dev/null +++ b/src/model.rs @@ -0,0 +1,170 @@ +use serde::{Deserialize, Serialize}; + +use std::{ + fs::canonicalize, + io::{Read, BufRead, BufReader}, + path::PathBuf, + process::{Child, Command, Stdio}, + sync::{Arc, Mutex}, +}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ServiceConf { + pub name: String, + command: String, + args: Option<String>, + directory: Option<PathBuf>, + pub autostart: bool, +} +impl ServiceConf { + pub fn new() -> Self { + Self { + name: String::new(), + command: String::new(), + args: None, + directory: None, + autostart: false, + } + } + pub fn from_parts( + name: String, + command: String, + args: Option<String>, + directory: Option<PathBuf>, + autostart: bool, + ) -> Self { + Self { + name, + command, + args, + directory, + autostart, + } + } +} + +#[derive(Debug)] +pub struct Service { + pub config: ServiceConf, + process: Option<Mutex<Child>>, +} +impl Service { + pub fn new() -> Self { + Self { + config: ServiceConf::new(), + process: None, + } + } + pub fn from_conf(config: ServiceConf) -> Self { + Self { + config, + process: None, + } + } + pub fn start(&mut self) -> Result<(), Box<dyn std::error::Error>> { + if self.process.is_some() { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + "process already exists!", + ))); + } + let command: &str = &self.config.command.as_str(); + let args: Vec<String> = if let Some(a) = &self.config.args { + let mut v: Vec<String> = Vec::new(); + for arg in a.split_whitespace() { + v.push(String::from(arg)); + } + v + } else { + Vec::new() + }; + if let Some(cwd) = &self.config.directory { + let child = Command::new(command) + .args(args) + .current_dir(canonicalize(cwd)?) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + self.process = Some(Mutex::new(child)); + } else { + let child = Command::new(command) + .args(args) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + self.process = Some(Mutex::new(child)); + }; + Ok(()) + } + pub fn stop(&mut self) -> Result<(), Box<dyn std::error::Error>> { + if let Some(process) = &self.process { + if let Ok(mut process) = process.lock() { + process.kill()?; + } else { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::ResourceBusy, + "cannot acquire lock", + ))) + } + } else { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::NotFound, + "process already exists!", + ))); + }; + self.process = None; + Ok(()) + } + //TODO: this function needs to fork and do message passing via mpsc channels. + pub fn stdout(&mut self) -> Result<String, Box<dyn std::error::Error>> { + if let Some(process) = &mut self.process { + if let Ok(mut process) = process.lock() { + if let Some(stdout) = process.stdout.as_mut() { + let reader = BufReader::new(stdout); + Ok(String::from(reader.lines().filter_map(|line| line.ok()).collect::<String>())) + } else { + Err(Box::new(std::io::Error::new( + std::io::ErrorKind::ResourceBusy, + "cannot acquire stdout", + ))) + } + } else { + Err(Box::new(std::io::Error::new( + std::io::ErrorKind::ResourceBusy, + "cannot acquire lock", + ))) + } + } else { + Err(Box::new(std::io::Error::new( + std::io::ErrorKind::NotFound, + "is the process started?", + ))) + } + } + //TODO: this function needs to fork and do message passing via mpsc channels. + //TODO: this function needs to use a bufreader instead + pub fn stderr(&mut self) -> Result<String, Box<dyn std::error::Error>> { + if let Some(process) = &mut self.process { + if let Ok(mut process) = process.lock() { + let mut s = String::new(); + if let Some(stderr) = process.stderr.as_mut() { + stderr.read_to_string(&mut s)?; + } + Ok(s) + } else { + Err(Box::new(std::io::Error::new( + std::io::ErrorKind::ResourceBusy, + "cannot acquire lock", + ))) + } + } else { + Err(Box::new(std::io::Error::new( + std::io::ErrorKind::NotFound, + "is the process started?", + ))) + } + } +} + |