From e67027f1c746f27b3412469663cde7df48c71c59 Mon Sep 17 00:00:00 2001 From: mbround18 <12646562+mbround18@users.noreply.github.com> Date: Thu, 4 Feb 2021 21:38:33 -0700 Subject: [PATCH 1/2] Fixed a few command executions and added a catch --- Cargo.lock | 75 ++++++++++++++++++++ Cargo.toml | 4 ++ scripts/entrypoint.sh | 5 +- src/cli.yaml | 15 ++++ src/commands/initialize.rs | 3 + src/commands/mod.rs | 1 + src/commands/start.rs | 139 +++++++++++++++++++------------------ src/commands/stop.rs | 32 ++++++--- src/logger.rs | 21 ++++++ src/main.rs | 32 ++++++--- src/utils/mod.rs | 28 ++++---- 11 files changed, 252 insertions(+), 103 deletions(-) create mode 100644 src/commands/initialize.rs create mode 100644 src/logger.rs diff --git a/Cargo.lock b/Cargo.lock index a3010e3c..89d54b5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,6 +141,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" + [[package]] name = "lazy_static" version = "1.4.0" @@ -159,12 +165,25 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +dependencies = [ + "cfg-if", +] + [[package]] name = "odin" version = "0.1.0" dependencies = [ "clap", "dialoguer", + "log", + "pad", + "serde", + "tinytemplate", "which", ] @@ -174,6 +193,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85" +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + [[package]] name = "ppv-lite86" version = "0.2.10" @@ -295,6 +323,43 @@ dependencies = [ "winapi", ] +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "strsim" version = "0.10.0" @@ -374,6 +439,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tinytemplate" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "unicode-segmentation" version = "1.7.1" diff --git a/Cargo.toml b/Cargo.toml index 94073339..a9002590 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,13 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +pad = "0.1" +log = "0.4.14" clap = { version = "3.0.0-beta.2", features = ["yaml"] } which = "4.0.2" dialoguer = "0.7.1" +tinytemplate = "1.1" +serde = { version = "1.0", features = ["derive"] } [profile.dev] opt-level = 0 diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 46e5e9ce..b3a35ac5 100644 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -37,9 +37,8 @@ cleanup() { exit } -trap cleanup INT TERM -trap cleanup EXIT +trap cleanup INT TERM EXIT while :; do - sleep 1s + sleep 1s done diff --git a/src/cli.yaml b/src/cli.yaml index 3818cdd0..4722c314 100644 --- a/src/cli.yaml +++ b/src/cli.yaml @@ -2,6 +2,19 @@ name: odin version: "0.1.0" author: mbround18 about: Installs and Runs Valheim +args: + - debug: + short: d + long: debug + multiple: true + help: Sets the logger to log debug events. + takes_value: false + - dry_run: + short: r + global: true + long: dry-run + about: Will output the commands that it intends to run. + subcommands: - install: about: Installs Valheim with steamcmd @@ -31,10 +44,12 @@ subcommands: about: Sets the world of the server, (Can be set with ENV variable WORLD) takes_value: true - password: + short: s long: password value_name: PASSWORD about: Sets the password of the server, (Can be set with ENV variable PASSWORD) takes_value: true + - stop: about: Stops Valheim version: "1.0" diff --git a/src/commands/initialize.rs b/src/commands/initialize.rs new file mode 100644 index 00000000..983c7ef7 --- /dev/null +++ b/src/commands/initialize.rs @@ -0,0 +1,3 @@ +// pub fn invoke(args: Option<&ArgMatches>) { +// let option +// } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 52d8299c..ab364af6 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,3 +1,4 @@ pub mod install; pub mod start; pub mod stop; +pub mod initialize; diff --git a/src/commands/start.rs b/src/commands/start.rs index d414d0fc..97c91be0 100644 --- a/src/commands/start.rs +++ b/src/commands/start.rs @@ -1,85 +1,92 @@ use crate::executable::{create_execution}; use std::process::{Stdio}; use clap::{ArgMatches}; -use std::{process}; -use crate::utils::{get_working_dir, get_variable}; -use std::path::Path; +use crate::utils::{get_variable, server_installed, get_working_dir}; use std::fs::{File}; use std::io::Write; +use log::{info, error}; +use tinytemplate::TinyTemplate; +use serde::Serialize; +#[derive(Serialize)] +struct Context { + command: String, + arguments: String +} -fn create_start_server_script(command: String, arguments: String) { - let source = &[ - "#!/usr/bin/env bash", - format!("{} {} &", command.as_str(), arguments.as_str()).as_str(), - "disown" - ].join("\n"); +static TEMPLATE: &'static &str = &r#" +#!/usr/bin/env bash +# This script will be overwritten at each start! - match File::create("./start_server_rusty.sh") { - Ok(mut file) => { - match file.write_all(source.as_bytes()) { - Ok(_) => println!("Successfully written script file."), - _ => println!("Failed to write script file.") - }; +{command} {arguments} & +disown - match create_execution("chmod").args(&["+x", "./start_server_rusty.sh"]).output() { - Ok(_) =>println!("Success changing permission"), - _ => println!("Unable to change permissions") - }; - } - _ => println!("Failed to write script file.") - }; +"#; + +fn parse_to_script(context: Context) -> String{ + let mut tt = TinyTemplate::new(); + tt.add_template( + "hello", &TEMPLATE).unwrap(); + tt.render("hello", &context).unwrap().replace(""", "\"") } -pub fn invoke(args: Option<&ArgMatches>) { - let paths = &[get_working_dir(), "valheim_server.x86_64".to_string()]; - let script_path = &paths.join("/"); - let script_file = Path::new(script_path); - if script_file.exists() { - let mut command = create_execution("bash"); - let mut command_arguments: Vec = Vec::new(); +fn create_start_server_script(command: String, arguments: String, dry_run: bool) { + let context = Context { + command, + arguments + }; + let source = parse_to_script(context); + if dry_run { + info!("This would have written a file to ./start_server_rusty.sh with content: \n {}", source); + } else { + match File::create("./start_server_rusty.sh") { + Ok(mut file) => { + match file.write_all(source.as_bytes()) { + Ok(_) => println!("Successfully written script file."), + _ => println!("Failed to write script file.") + }; - if let Some(port) = get_variable("PORT", args, "2456".to_string()) { - println!("Found Port Argument: {}", port); - command_arguments.push(format!("-port {}", port)); - } - if let Some(name) = get_variable("NAME", args, "Valheim Docker".to_string()) { - println!("Adding Name Argument: {}", name); - command_arguments.push(format!("-name \"{}\"", name)); - } - if let Some(world) = get_variable("WORLD", args, "Dedicated".to_string()) { - println!("Adding World Argument: {}", world); - command_arguments.push(format!("-world \"{}\"", world)); - } - if let Some(password) = get_variable("PASSWORD", args, "".to_string()) { - if password.len() > 0 { - println!("Adding Password Argument"); - command_arguments.push(format!("-password \"{}\"", password)); + match create_execution("chmod").args(&["+x", "./start_server_rusty.sh"]).output() { + Ok(_) => info!("Success changing permission"), + _ => error!("Unable to change permissions") + }; } - } - - create_start_server_script(script_path.to_string(), command_arguments.join(" ")); - - let updated_command = command - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .arg("-c") - .arg("./start_server_rusty.sh") - .env("LD_LIBRARY_PATH", "${PWD}/linux64:${LD_LIBRARY_PATH}"); + _ => error!("Failed to write script file.") + }; + } +} +fn parse_arg(args: &ArgMatches, name: &str, default: &str) -> String { + format!("-{} \"{}\"", name, get_variable(args, name,default.to_string())) +} - match updated_command.output() { - Ok(output) => print!("Exit with code {}", output.status), - _ => { - print!("An error has occurred!") +pub fn invoke(args: &ArgMatches) { + let mut command = create_execution("bash"); + let command_args: &str = &[ + parse_arg(args, "port", "2456"), + parse_arg(args, "name", "Valheim Docker"), + parse_arg(args, "world", "Dedicated"), + parse_arg(args, "password", "12345"), + ].join(" "); + let dry_run: bool = args.is_present("dry_run"); + let server_executable = &[get_working_dir(), "valheim_server.x86_64".to_string()].join("/"); + create_start_server_script(server_executable.to_string(), command_args.to_string(), dry_run); + if !dry_run { + if server_installed() { + let updated_command = command + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .arg("-c") + .arg("./start_server_rusty.sh") + .env("LD_LIBRARY_PATH", "${PWD}/linux64:${LD_LIBRARY_PATH}"); + match updated_command.output() { + Ok(output) => print!("Exit with code {}", output.status), + _ => { + error!("An error has occurred!") + } } + } else { + error!("Could not find server executable! Please install the server!") } - // updated_command.exec(); - // let result = execute_mut(updated_command); - // handle_exit_status(result, "Server Started Successfully!".to_string()); - - } else { - println!("Cannot start server! valheim_server.x86_64 not found in current directory!"); - process::exit(1) } } diff --git a/src/commands/stop.rs b/src/commands/stop.rs index 5ab4902d..252cf8c0 100644 --- a/src/commands/stop.rs +++ b/src/commands/stop.rs @@ -1,16 +1,28 @@ -use crate::utils::get_working_dir; +use crate::utils::{get_working_dir, server_installed}; use crate::executable::{create_execution, execute_mut, handle_exit_status}; use std::process::Stdio; +use log::{info, error}; +use clap::ArgMatches; -pub fn invoke() { +pub fn invoke(args: &ArgMatches) { let paths = &[get_working_dir(), "server_exit.drp".to_string()]; let script_path = &paths.join("/"); - let mut command = create_execution(format!("echo 1 > {}", script_path).as_str()); - let updated_command = command - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()); - - let result = execute_mut(updated_command); - handle_exit_status(result, "Server Stopped Successfully!".to_string()) - + let mut command = create_execution("echo"); + info!("Stopping server"); + let command_arguments = format!("> {}", script_path); + if args.is_present("dry_run") { + info!("This command would have run: "); + info!("echo {}", command_arguments) + } else { + if !server_installed() { + error!("Failed to find server executable!"); + return; + } + let updated_command = command + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .arg(command_arguments); + let result = execute_mut(updated_command); + handle_exit_status(result, "Server Stopped Successfully!".to_string()) + } } diff --git a/src/logger.rs b/src/logger.rs new file mode 100644 index 00000000..fb4b42dc --- /dev/null +++ b/src/logger.rs @@ -0,0 +1,21 @@ +use log::{Record, Level, Metadata}; + +pub struct OdinLogger; + +impl log::Log for OdinLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= Level::Debug + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + let prefix = format!("{:width$}", format!("[ODIN][{}]", record.level()), width=13); + // This creates text blocks of logs if they include a new line. + // I think it looks good <3 + let message = format!("{} - {}", prefix, record.args()).replace("\n", format!("\n{} - ", prefix).as_str()); + println!("{}", message); + } + } + + fn flush(&self) {} +} diff --git a/src/main.rs b/src/main.rs index 54147ebf..4bd89c99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,30 +2,44 @@ mod steamcmd; mod commands; mod executable; mod utils; +mod logger; use clap::{App, load_yaml}; +use log::{SetLoggerError, LevelFilter, debug}; +use crate::logger::OdinLogger; -const GAME_ID: i64 = 896660; +static LOGGER: OdinLogger = OdinLogger; +static GAME_ID: i64 = 896660; + +fn setup_logger(debug: bool) -> Result<(), SetLoggerError> { + let level = if debug { + LevelFilter::Debug + } else { + LevelFilter::Info + }; + let result = log::set_logger(&LOGGER) + .map(|_| log::set_max_level(level)); + debug!("Debugging set to {}", debug.to_string()); + result +} fn main() { // The YAML file is found relative to the current file, similar to how modules are found let yaml = load_yaml!("cli.yaml"); let matches = App::from(yaml).get_matches(); + setup_logger(matches.is_present("debug")).unwrap(); + if let Some(ref _match) = matches.subcommand_matches("install") { commands::install::invoke(GAME_ID); }; - if let Some(ref _match) = matches.subcommand_matches("start") { - if let Some(start_args) = matches.subcommand_matches("start") { - commands::start::invoke(Option::from(start_args)); - } else { - commands::start::invoke(None); - } + if let Some(ref start_matches) = matches.subcommand_matches("start") { + commands::start::invoke(start_matches); }; - if let Some(ref _match) = matches.subcommand_matches("stop") { - commands::stop::invoke(); + if let Some(ref stop_matches) = matches.subcommand_matches("stop") { + commands::stop::invoke(stop_matches); }; } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3db8c464..3a13fcfb 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,8 @@ use std::env; use clap::ArgMatches; use std::process::exit; +use log::{debug}; +use std::path::Path; pub fn get_working_dir() -> String { match env::current_dir() { @@ -12,20 +14,16 @@ pub fn get_working_dir() -> String { } } -pub fn get_variable(name: &str, args: Option<&ArgMatches>, default: String) -> Option { - let mut variable_value: Option = None; - match env::var(name) { - Ok(val) => variable_value = Option::from(val), - Err(_e) => { - if let Some(existing_args) = args { - match existing_args.value_of(name) { - Some(val) => { - variable_value = Option::from(val.to_string()); - } - None => variable_value = Option::from(default) - } - } - }, +pub fn get_variable(args: &ArgMatches, name: &str, default: String) -> String { + debug!("Checking env for {}", name); + if let Ok(env_val) = env::var(name) { + debug!("Env variable found {}={}", name, env_val); + return env_val; } - variable_value + args.value_of(name).unwrap_or(default.as_str()).to_string() +} + + +pub fn server_installed() -> bool { + Path::new( &[get_working_dir(), "valheim_server.x86_64".to_string()].join("/")).exists() } From 6111de0d0613e88a5a584d44688f3907c7d16c12 Mon Sep 17 00:00:00 2001 From: mbround18 <12646562+mbround18@users.noreply.github.com> Date: Thu, 4 Feb 2021 22:34:34 -0700 Subject: [PATCH 2/2] Changed server stop to use native functions --- docker-compose.yml | 3 ++- src/commands/start.rs | 4 ++-- src/commands/stop.rs | 21 +++++++++++---------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5c68d4f8..bfe32b2f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,4 +10,5 @@ services: - "2457:2457/udp" - "2458:2458/udp" volumes: - - ./tmp/valheim:/home/steam/valheim + - ./tmp/valheim/saves:/home/steam/.config/unity3d/IronGate/Valheim + - ./tmp/valheim/server:/home/steam/valheim diff --git a/src/commands/start.rs b/src/commands/start.rs index 97c91be0..876c69ec 100644 --- a/src/commands/start.rs +++ b/src/commands/start.rs @@ -16,9 +16,10 @@ struct Context { static TEMPLATE: &'static &str = &r#" #!/usr/bin/env bash +cd "$(dirname "$0")" # This script will be overwritten at each start! -{command} {arguments} & +{command} {arguments} 2>&1 | tee ./output.log & disown "#; @@ -45,7 +46,6 @@ fn create_start_server_script(command: String, arguments: String, dry_run: bool) Ok(_) => println!("Successfully written script file."), _ => println!("Failed to write script file.") }; - match create_execution("chmod").args(&["+x", "./start_server_rusty.sh"]).output() { Ok(_) => info!("Success changing permission"), _ => error!("Unable to change permissions") diff --git a/src/commands/stop.rs b/src/commands/stop.rs index 252cf8c0..eacdabb8 100644 --- a/src/commands/stop.rs +++ b/src/commands/stop.rs @@ -1,14 +1,15 @@ use crate::utils::{get_working_dir, server_installed}; -use crate::executable::{create_execution, execute_mut, handle_exit_status}; -use std::process::Stdio; use log::{info, error}; use clap::ArgMatches; +use std::fs::{File, remove_file}; +use std::io::Write; +use std::thread::sleep; +use std::time::Duration; pub fn invoke(args: &ArgMatches) { let paths = &[get_working_dir(), "server_exit.drp".to_string()]; let script_path = &paths.join("/"); - let mut command = create_execution("echo"); - info!("Stopping server"); + info!("Stopping server {}", get_working_dir()); let command_arguments = format!("> {}", script_path); if args.is_present("dry_run") { info!("This command would have run: "); @@ -18,11 +19,11 @@ pub fn invoke(args: &ArgMatches) { error!("Failed to find server executable!"); return; } - let updated_command = command - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .arg(command_arguments); - let result = execute_mut(updated_command); - handle_exit_status(result, "Server Stopped Successfully!".to_string()) + let mut file = File::create(script_path).unwrap(); + file.write_all(b"1").unwrap(); + info!("Stop file created, waiting for server to stop!"); + sleep(Duration::from_secs(5)); + remove_file(script_path).unwrap(); + info!("Server has been halted!"); } }