about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authoryuzu <yuzu@b9215c17-b818-4693-b096-d1e41a411fef>2025-06-04 04:45:58 +0000
committeryuzu <yuzu@b9215c17-b818-4693-b096-d1e41a411fef>2025-06-04 04:45:58 +0000
commit8880b0afd01bd9afb4a47a76d90e8b90e5cbff1e (patch)
tree96ee3e699a64ca44644cc53e19904444deb051a4
parent288842e0621eb3e07a0790ba328727939c480884 (diff)
downloadsalaryman-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.toml8
-rw-r--r--src/main.rs149
-rw-r--r--src/model.rs170
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?",
+            )))
+        }
+    }
+}
+