From 9bbac01dbc6ac42932c73867fd0916bb936e1763 Mon Sep 17 00:00:00 2001 From: Michael <12646562+mbround18@users.noreply.github.com> Date: Tue, 10 Oct 2023 20:04:31 -0700 Subject: [PATCH] Add additional arguments for Valheim Server (#742) * Initial pass for #741 * Fixed variables with parser * upgrade deps * Fix test data Signed-off-by: MBRound18 <12646562+mbround18@users.noreply.github.com> * formatted * Fix test data Signed-off-by: MBRound18 <12646562+mbround18@users.noreply.github.com> * formatted * rustfmt --------- Signed-off-by: MBRound18 <12646562+mbround18@users.noreply.github.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .dockerignore | 1 + .idea/inspectionProfiles/Project_Default.xml | 6 + Cargo.lock | 210 ++++++++++++++++--- Makefile | 36 ++++ README.md | 5 + src/odin/Cargo.toml | 11 +- src/odin/cli.rs | 26 ++- src/odin/commands/configure.rs | 59 +++++- src/odin/files/config.rs | 55 ++++- src/odin/logger.rs | 21 +- src/odin/main.rs | 26 ++- src/odin/mods/bepinex.rs | 12 +- src/odin/server/startup.rs | 119 +++++++---- src/odin/utils/mod.rs | 2 + src/odin/utils/parse_truthy.rs | 22 ++ src/scripts/entrypoint.sh | 3 + src/scripts/start_valheim.sh | 16 ++ test.sh | 11 + 18 files changed, 541 insertions(+), 100 deletions(-) create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 Makefile create mode 100644 src/odin/utils/parse_truthy.rs create mode 100644 test.sh diff --git a/.dockerignore b/.dockerignore index 330b0e5e..bdc368d2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,3 +10,4 @@ package.json yarn.lock .yarn .env* +.vscode diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..89a103a9 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index d9337383..b5782d03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f58811cfac344940f1a400b6e6231ce35171f614f26439e80f8c1465c5cc0c" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" dependencies = [ "anstyle", "anstyle-parse", @@ -71,9 +71,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "2.1.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f54d10c6dfa51283a066ceab3ec1ab78d13fae00aa49243a45e4571fb79dfd" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys", @@ -171,9 +171,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -234,6 +234,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets", ] @@ -250,9 +251,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.5" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3" +checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956" dependencies = [ "clap_builder", "clap_derive", @@ -260,9 +261,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.5" +version = "4.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab" +checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45" dependencies = [ "anstream", "anstyle", @@ -300,6 +301,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -361,6 +372,41 @@ dependencies = [ "libc", ] +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -368,7 +414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "lock_api", "once_cell", "parking_lot_core", @@ -385,6 +431,9 @@ name = "deranged" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +dependencies = [ + "serde", +] [[package]] name = "digest" @@ -418,11 +467,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "add4f07d43996f76ef320709726a556a9d4f965d9410d8d0271132d2f8293480" dependencies = [ "errno-dragonfly", "libc", @@ -616,7 +671,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util", @@ -645,9 +700,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" [[package]] name = "headers" @@ -685,6 +740,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" @@ -809,6 +870,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -827,6 +894,18 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown 0.14.1", + "serde", ] [[package]] @@ -882,15 +961,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "3852614a3bd9ca9804678ba6be5e3b8ce76dfc902cae004e3e0c44051b6e88db" [[package]] name = "lock_api" @@ -916,9 +995,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" @@ -985,9 +1064,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -1033,6 +1112,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "serde_with", "serial_test", "sysinfo", "tar", @@ -1189,9 +1269,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c" dependencies = [ "unicode-ident", ] @@ -1246,9 +1326,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ "base64", "bytes", @@ -1272,6 +1352,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-rustls", "tower-service", @@ -1306,9 +1387,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.14" +version = "0.38.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +checksum = "f25469e9ae0f3d0047ca8b93fc56843f38e6774f0914a107ff8b41be8be8e0b7" dependencies = [ "bitflags 2.4.0", "errno", @@ -1419,6 +1500,35 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca3b16a3d82c4088f343b7480a93550b3eabe1a358569c2dfe38bbcead07237" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.0.2", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e6be15c453eb305019bfa438b1593c731f36a289a7853f7707ee29e870b3b3c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serial_test" version = "2.0.0" @@ -1536,9 +1646,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.37" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -1559,6 +1669,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tar" version = "0.4.40" @@ -1597,8 +1728,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ "deranged", + "itoa", "serde", "time-core", + "time-macros", ] [[package]] @@ -1607,6 +1740,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..56445ff1 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +# Following this tutorial: https://markentier.tech/posts/2022/01/speedy-rust-builds-under-wsl2/ +# This makes developing on windows significantly easier for rust projects!! + +SOURCE_DIR = $(PWD) +# `notdir` returns the part after the last `/` +# so if the source was "/some/nested/project", only "project" remains +BUILD_DIR = ~/tmp/$(notdir $(SOURCE_DIR)) + +wsl.build: wsl.sync + cd $(BUILD_DIR) && cargo build + rsync -av $(BUILD_DIR)/target/debug/ $(SOURCE_DIR)/target/debug/ \ + --exclude .git \ + --exclude target \ + --exclude .fingerprint \ + --exclude build \ + --exclude incremental \ + --exclude deps + +wsl.run: wsl.sync + cd $(BUILD_DIR) && cargo run + +wsl.test: wsl.sync + cd $(BUILD_DIR) && cargo test + +wsl.sync: + mkdir -p $(BUILD_DIR) + rsync -av $(SOURCE_DIR)/ $(BUILD_DIR)/ --exclude .git --exclude target --exclude tmp + +wsl.clean: + rm -rf $(BUILD_DIR)/target + +wsl.clean-all: + rm -rf $(BUILD_DIR) + +wsl.clippy: wsl.sync + cd $(BUILD_DIR) && cargo clippy diff --git a/README.md b/README.md index 2f9a0e7d..04fa5fa3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ + [![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors-) + ## Table of Contents @@ -128,6 +130,9 @@ If you purely want to run this on a Linux based system, without docker, take a l | PASSWORD | `` | TRUE | Set this to something unique! | | ENABLE_CROSSPLAY | `0` | FALSE | Enable crossplay support as of `Valheim Version >0.211.8` | | TYPE | `Vanilla` | FALSE | This can be set to `ValheimPlus`, `BepInEx`, `BepInExFull` or `Vanilla` | +| PRESET | `` | FALSE | Normal, Casual, Easy, Hard, Hardcore, Immersive, Hammer | +| MODIFIERS | `` | FALSE | Comma-separated array of modifiers. EX: `combat=easy,raids=muchmore` | +| SET_KEY | `` | FALSE | Can be one of the following: nobuildcost, playerevents, passivemobs, nomap | | MODS | `` | FALSE | This is an array of mods separated by comma and a new line. [Click Here for Examples](./docs/tutorials/getting_started_with_mods.md) Supported files are `zip`, `dll`, and `cfg`. | | WEBHOOK_URL | `` | FALSE | Supply this to get information regarding your server's status in a webhook or Discord notification! [Click here to learn how to get a webhook url for Discord](https://help.dashe.io/en/articles/2521940-how-to-create-a-discord-webhook-url) | | WEBHOOK_INCLUDE_PUBLIC_IP | `0` | FALSE | Optionally include your server's public IP in webhook notications, useful if not using a static IP address. NOTE: If your server is behind a NAT using PAT with more than one external IP address (very unlikely on a home network), this could be inaccurate if your NAT doesn't maintain your server to a single external IP. | diff --git a/src/odin/Cargo.toml b/src/odin/Cargo.toml index 074ac605..0d205c99 100644 --- a/src/odin/Cargo.toml +++ b/src/odin/Cargo.toml @@ -30,7 +30,7 @@ path = "lib.rs" handlebars = "4" dotenv = "0.15" log = "0.4" -clap = { version = "4.3", features = [ "derive", "env" ] } +clap = { version = "4.4", features = [ "derive", "env" ] } which = "4.4" serde = { version = "1.0", features = ["derive"], default_features = false } sysinfo = { version = "0.29", default_features = false } @@ -42,10 +42,11 @@ inflections = "1.1.1" md5 = "0.7" reqwest = { version = "0.11", default_features = false, features = ["blocking", "json", "rustls-tls"] } chrono = "0.4" -zip = { version = "0.6.3" } -fs_extra = "1.2.0" -glob = "0.3.0" -a2s = "0.5.2" +zip = { version = "0.6" } +fs_extra = "1.3" +glob = "0.3" +a2s = "0.5" +serde_with = "3.3.0" [dev-dependencies] once_cell = "1" diff --git a/src/odin/cli.rs b/src/odin/cli.rs index e1e2b263..354a609d 100644 --- a/src/odin/cli.rs +++ b/src/odin/cli.rs @@ -1,19 +1,22 @@ use clap::{Parser, Subcommand}; +use crate::utils::parse_truthy::parse_truthy; + #[derive(Parser)] #[command(author, version)] #[command(propagate_version = true)] pub struct Cli { /// Allows you to run as root - #[arg(long)] + #[arg(long, env = "I_ACCEPT_TO_RUN_THINGS_UNSAFELY", value_parser = parse_truthy)] pub run_as_root: bool, /// Make everything noisy but very helpful to identify issues. - #[arg(long)] + /// This will enable debugging, you can use the env variable DEBUG_MODE to set this as well. + #[arg(long, env = "DEBUG_MODE", value_parser = parse_truthy)] pub debug: bool, /// Will spit out the commands as if it were to run them but not really. - #[arg(short = 'r', long)] + #[arg(short = 'r', long, env = "DRY_RUN", value_parser = parse_truthy)] pub dry_run: bool, #[command(subcommand)] @@ -52,6 +55,23 @@ pub enum Commands { /// Sets the public state of the server, (Can be set with ENV variable PUBLIC) #[arg(short = 'o', long, env = "PUBLIC")] public: String, + + /// Sets flag modifiers for launching the server, (Can be set with ENV variable MODIFIERS) + /// This should be comma separated with equal variables, e.g. "raids=none,combat=hard" + #[arg(long, env = "MODIFIERS")] + modifiers: Option, + + /// Sets flag preset for launching the server, (Can be set with ENV variable PRESET) + #[arg(long, env = "PRESET")] + preset: Option, + + /// Sets flag set_key for launching the server, (Can be set with ENV variable SET_KEY) + #[arg(long, env = "SET_KEY")] + set_key: Option, + + /// Sets the save interval in seconds + #[arg(long, env = "SAVE_INTERVAL")] + save_interval: Option, }, /// Installs Valheim with steamcmd diff --git a/src/odin/commands/configure.rs b/src/odin/commands/configure.rs index 274de022..0d7d94f7 100644 --- a/src/odin/commands/configure.rs +++ b/src/odin/commands/configure.rs @@ -1,18 +1,65 @@ +use log::debug; +use serde::{Deserialize, Serialize}; + use crate::files::config::{config_file, write_config}; use crate::files::discord::{discord_file, write_discord}; -use log::debug; +/// See: https://user-images.githubusercontent.com/34519392/273088066-b9c94664-9eef-419d-999a-8b8798462dee.PNG +/// for a list of modifiers +#[derive(Deserialize, Serialize, Debug, Clone)] +pub struct Modifiers { + /// The name of the modifier + pub name: String, + + /// The value of the modifier + pub value: String, +} + +impl From for Modifiers { + /// Creates a new modifier from a string + fn from(value: String) -> Self { + let mut split = value.split('='); + let name = split.next().unwrap().to_string(); + let value = split.next().unwrap().to_string(); + Modifiers { name, value } + } +} pub struct Configuration { + /// Sets the name of the server, (Can be set with ENV variable NAME) pub name: String, + + /// Sets the servers executable path. pub server_executable: String, + + /// Sets the port of the server, (Can be set with ENV variable PORT) pub port: u16, + + /// Sets the world of the server, (Can be set with ENV variable WORLD) pub world: String, + + /// Sets the password of the server, (Can be set with ENV variable PASSWORD) pub password: String, + + /// Sets the public state of the server, (Can be set with ENV variable PUBLIC) pub public: bool, + + /// Sets flag preset for launching the server, (Can be set with ENV variable PRESET) + pub preset: Option, + + /// Sets flag modifiers for launching the server, (Can be set with ENV variable MODIFIERS) + pub modifiers: Option>, + + /// Sets flag set_key for launching the server, (Can be set with ENV variable SET_KEY) + pub set_key: Option, + + /// Sets the save interval in seconds + pub save_interval: Option, } impl Configuration { + /// Creates a new configuration + #[allow(clippy::too_many_arguments)] pub fn new( name: String, server_executable: String, @@ -20,6 +67,10 @@ impl Configuration { world: String, password: String, public: bool, + preset: Option, + modifiers: Option>, + set_key: Option, + save_interval: Option, ) -> Self { Configuration { name, @@ -28,8 +79,14 @@ impl Configuration { world, password, public, + preset, + modifiers, + set_key, + save_interval, } } + + /// Invokes the configuration by writing the config file pub fn invoke(self) { debug!("Pulling config file..."); let config = config_file(); diff --git a/src/odin/files/config.rs b/src/odin/files/config.rs index e05848f9..62791c5d 100644 --- a/src/odin/files/config.rs +++ b/src/odin/files/config.rs @@ -1,25 +1,54 @@ -use crate::commands::configure::Configuration; -use crate::files::{FileManager, ManagedFile}; -use crate::traits::AsOneOrZero; -use crate::utils::environment::fetch_var; +use std::{fs, path::PathBuf, process::exit}; use log::{debug, error}; use serde::{Deserialize, Serialize}; -use std::{fs, path::PathBuf, process::exit}; + +use crate::commands::configure::{Configuration, Modifiers}; +use crate::files::{FileManager, ManagedFile}; +use crate::traits::AsOneOrZero; +use crate::utils::environment::fetch_var; const ODIN_CONFIG_FILE_VAR: &str = "ODIN_CONFIG_FILE"; -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Debug, Clone)] pub struct ValheimArguments { + /// The port of the server, (Can be set with ENV variable PORT) pub(crate) port: String, + + /// The name of the server, (Can be set with ENV variable NAME) pub(crate) name: String, + + /// The world of the server, (Can be set with ENV variable WORLD) pub(crate) world: String, + + /// The public state of the server, (Can be set with ENV variable PUBLIC) pub(crate) public: String, + + /// The password of the server, (Can be set with ENV variable PASSWORD) pub(crate) password: String, + + /// The command to launch the server pub(crate) command: String, + + /// The preset for launching the server, (Can be set with ENV variable PRESET) + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) preset: Option, + + /// The modifiers for launching the server, (Can be set with ENV variable MODIFIERS) + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) modifiers: Option>, + + /// The set_key for launching the server, (Can be set with ENV variable SET_KEY) + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) set_key: Option, + + /// Sets the save interval in seconds + #[serde(skip_serializing_if = "Option::is_none")] + pub save_interval: Option, } impl From for ValheimArguments { + /// Creates a new ValheimArguments from a Configuration fn from(value: Configuration) -> Self { let command = match fs::canonicalize(PathBuf::from(value.server_executable)) { Ok(command_path) => command_path.to_str().unwrap().to_string(), @@ -36,10 +65,15 @@ impl From for ValheimArguments { public: value.public.as_string(), password: value.password, command, + preset: value.preset, + modifiers: value.modifiers, + set_key: value.set_key, + save_interval: value.save_interval, } } } +/// Loads the configuration from the config file pub fn load_config() -> ValheimArguments { let file = config_file(); let config = read_config(file); @@ -52,12 +86,14 @@ pub fn load_config() -> ValheimArguments { config } +/// Creates a new config file pub fn config_file() -> ManagedFile { let name = fetch_var(ODIN_CONFIG_FILE_VAR, "config.json"); debug!("Config file set to: {}", name); ManagedFile { name } } +/// Reads the config file pub fn read_config(config: ManagedFile) -> ValheimArguments { let content = config.read(); if content.is_empty() { @@ -66,6 +102,7 @@ pub fn read_config(config: ManagedFile) -> ValheimArguments { serde_json::from_str(content.as_str()).unwrap() } +/// Writes the config file pub fn write_config(config: ManagedFile, args: Configuration) -> bool { let content = ValheimArguments::from(args); @@ -79,11 +116,13 @@ pub fn write_config(config: ManagedFile, args: Configuration) -> bool { #[cfg(test)] mod tests { - use super::*; - use rand::Rng; use std::env; use std::env::current_dir; + use rand::Rng; + + use super::*; + #[test] #[should_panic( expected = "Please initialize odin with `odin configure`. See `odin configure --help`" diff --git a/src/odin/logger.rs b/src/odin/logger.rs index 5132e1ba..eaa905dc 100644 --- a/src/odin/logger.rs +++ b/src/odin/logger.rs @@ -1,5 +1,9 @@ +use std::env; + use log::{debug, Level, LevelFilter, Metadata, Record, SetLoggerError}; +use crate::utils::parse_truthy::parse_truthy; + pub struct OdinLogger; impl log::Log for OdinLogger { @@ -34,6 +38,19 @@ pub fn initialize_logger(debug: bool) -> Result<(), SetLoggerError> { LevelFilter::Info }; let result = log::set_logger(&LOGGER).map(|_| log::set_max_level(level)); - debug!("Debugging set to {}", debug.to_string()); - result + match result { + Err(err) => { + println!("Error setting logger: {:?}", err); + Err(err) + } + Ok(_) => { + debug!("Logger initialized"); + Ok(()) + } + } +} + +pub fn debug_mode() -> bool { + let debug_mode = env::var("DEBUG_MODE").unwrap_or(String::new()); + parse_truthy(&debug_mode).unwrap_or(false) } diff --git a/src/odin/main.rs b/src/odin/main.rs index d2045aa6..8be3587e 100644 --- a/src/odin/main.rs +++ b/src/odin/main.rs @@ -2,7 +2,11 @@ use clap::Parser; use dotenv::dotenv; use log::debug; +use commands::configure::Configuration; + +use crate::commands::configure::Modifiers; use crate::executable::handle_exit_status; +use crate::logger::debug_mode; use crate::messages::about; mod cli; @@ -26,7 +30,7 @@ fn main() { use cli::{Cli, Commands}; let cli = Cli::parse(); - logger::initialize_logger(cli.debug).unwrap(); + logger::initialize_logger(cli.debug || debug_mode()).unwrap(); if cli.debug { debug!("Debug mode enabled!"); @@ -40,13 +44,28 @@ fn main() { server_executable, world, port, - } => commands::configure::Configuration::new( + modifiers, + preset, + set_key, + save_interval, + } => Configuration::new( name, server_executable, port, world, password, { public.eq("1") }.to_owned(), + preset, + { + modifiers.map(|modifiers| { + modifiers + .split(',') + .map(|modifier| Modifiers::from(modifier.to_string())) + .collect() + }) + }, + set_key, + save_interval, ) .invoke(), Commands::Install {} => handle_exit_status( @@ -81,9 +100,10 @@ fn main() { mod tests { // use super::*; - use crate::cli::Cli; use clap::CommandFactory; + use crate::cli::Cli; + #[test] fn asserts() { Cli::command().debug_assert(); diff --git a/src/odin/mods/bepinex.rs b/src/odin/mods/bepinex.rs index 2ed3542a..fcd174ec 100644 --- a/src/odin/mods/bepinex.rs +++ b/src/odin/mods/bepinex.rs @@ -1,10 +1,12 @@ +use std::ops::Add; +use std::process::{Child, Command}; + +use log::{debug, info}; +use serde::{Deserialize, Serialize}; + use crate::constants; use crate::utils::common_paths::{bepinex_directory, bepinex_plugin_directory, game_directory}; use crate::utils::{environment, path_exists}; -use log::{debug, info}; -use serde::{Deserialize, Serialize}; -use std::ops::Add; -use std::process::{Child, Command}; const DYLD_LIBRARY_PATH_VAR: &str = "DYLD_LIBRARY_PATH"; const DYLD_INSERT_LIBRARIES_VAR: &str = "DYLD_INSERT_LIBRARIES"; @@ -103,7 +105,7 @@ impl BepInExEnvironment { if output { debug!("Yay! looks like we found all the required files for BepInEx to run! <3") } else { - debug!("Uhh ohh!!! Looks like you are missing something.") + debug!("We didn't find a modded instance! Launching a normal instance!") } output } diff --git a/src/odin/server/startup.rs b/src/odin/server/startup.rs index 2ff7faaa..1dc27ad6 100644 --- a/src/odin/server/startup.rs +++ b/src/odin/server/startup.rs @@ -1,8 +1,9 @@ +use std::process::exit; +use std::{io, process::Child}; + use daemonize::{Daemonize, Error}; use log::{debug, error, info}; -use std::{io, process::Child}; - use crate::mods::bepinex::BepInExEnvironment; use crate::notifications::enums::event_status::EventStatus; use crate::notifications::enums::notification_event::NotificationEvent; @@ -15,13 +16,14 @@ use crate::{ messages, utils::environment, }; -use std::process::exit; type CommandResult = io::Result; pub fn start_daemonized(config: ValheimArguments) -> Result { + debug!("Starting server daemonized..."); let stdout = create_file(format!("{}/logs/valheim_server.log", game_directory()).as_str()); let stderr = create_file(format!("{}/logs/valheim_server.err", game_directory()).as_str()); + let command = start(config); Daemonize::new() .working_directory(game_directory()) .user("steam") @@ -31,81 +33,120 @@ pub fn start_daemonized(config: ValheimArguments) -> Result CommandResult { +pub fn start(config: ValheimArguments) -> CommandResult { let mut command = create_execution(&config.command); - info!(target: "server_startup","--------------------------------------------------------------------------------------------------------------"); + + debug!("--------------------------------------------------------------------------------------------------------------"); + let ld_library_path_value = environment::fetch_multiple_var( constants::LD_LIBRARY_PATH_VAR, format!("{}/linux64", game_directory()).as_str(), ); - debug!(target: "server_startup","Setting up base command"); + info!("Setting up base command"); + info!("Launching With Args: \n{:#?}", &config); + // Sets the base command for the server let mut base_command = command - // Extra launch arguments - .arg(fetch_var( - "SERVER_EXTRA_LAUNCH_ARGS", - "-nographics -batchmode", - )) - // Required vars - .args([ - "-port", - config.port.as_str(), - "-name", - config.name.as_str(), - "-world", - config.world.as_str(), - "-public", - config.public.as_str(), - ]) - .env("SteamAppId", environment::fetch_var("APPID", "892970")) + .env("SteamAppId", fetch_var("APPID", "892970")) .current_dir(game_directory()); + // Sets the name of the server, (Can be set with ENV variable NAME) + let name = format!("-name {}", fetch_var("NAME", config.name.as_str())); + base_command.arg(name); + + // Sets the port of the server, (Can be set with ENV variable PORT) + let port = format!("-port {}", fetch_var("PORT", config.port.as_str())); + base_command.arg(port); + + // Sets the world of the server, (Can be set with ENV variable WORLD) + let world = format!("-world {}", fetch_var("WORLD", config.world.as_str())); + base_command.arg(world); + + // Determines if the server is public or not + let public = format!("-public {}", fetch_var("PUBLIC", config.public.as_str())); + base_command.arg(public); + + // Sets the save interval in seconds + if let Some(save_interval) = &config.save_interval { + base_command.arg(format!("-saveinterval {}", save_interval)); + }; + + // Add set_key to the command + if let Some(set_key) = &config.set_key { + base_command.arg(format!("-setkey {}", set_key)); + }; + + // Add preset to the command + if let Some(preset) = &config.preset { + base_command.arg(format!("-preset {}", preset)); + }; + + // Add modifiers to the command + if let Some(modifiers) = &config.modifiers { + base_command.args( + modifiers + .iter() + .map(|modifier| format!("-modifier {} {}", modifier.name, modifier.value)), + ); + }; + + // Extra args for the server + let extra_args = format!( + "-nographics -batchmode {}", + fetch_var("SERVER_EXTRA_LAUNCH_ARGS", "") + ) + .trim() + .to_string(); + base_command.arg(extra_args); + let is_public = config.public.eq("1"); let is_vanilla = fetch_var("TYPE", "vanilla").eq_ignore_ascii_case("vanilla"); let no_password = config.password.is_empty(); // If no password env variable if !is_public && !is_vanilla && no_password { - debug!(target: "server_startup","No password found, skipping password flag.") + info!("No password found, skipping password flag.") } else if no_password && (is_public || is_vanilla) { error!("Cannot run you server with no password! PUBLIC must be 0 and cannot be a Vanilla type server."); exit(1) } else { - debug!(target: "server_startup","Password found, adding password flag."); - base_command = base_command.args(["-password", config.password.as_str()]); + info!("Password found, adding password flag."); + base_command = base_command.arg(format!("-password {}", config.password)); } if fetch_var("ENABLE_CROSSPLAY", "0").eq("1") { info!("Launching with Crossplay! <3"); base_command = base_command.arg("-crossplay") } else { - debug!("No Crossplay Enabled!") + info!("No Crossplay Enabled!") } // Tack on save dir at the end. - base_command = base_command.args(["-savedir", &saves_directory()]); + base_command = base_command.arg(format!("-savedir {}", &saves_directory())); + + debug!("Base Command: {:#?}", base_command); - info!(target: "server_startup","Executable: {}", &config.command); - info!(target: "server_startup","Launching Command..."); + debug!("Executable: {}", &config.command); + info!("Launching Command..."); let bepinex_env = BepInExEnvironment::new(); if bepinex_env.is_installed() { - info!(target: "server_startup","BepInEx detected! Switching to run with BepInEx..."); - debug!(target: "server_startup","BepInEx Environment: \n{:#?}", bepinex_env); + info!("BepInEx detected! Switching to run with BepInEx..."); + info!("BepInEx Environment: \n{:#?}", bepinex_env); bepinex_env.launch(base_command) } else { - info!(target: "server_startup","Everything looks good! Running normally!"); + info!("Everything looks good! Running normally!"); base_command .env(constants::LD_LIBRARY_PATH_VAR, ld_library_path_value) .spawn() diff --git a/src/odin/utils/mod.rs b/src/odin/utils/mod.rs index 6bbf54ea..a96f79e1 100644 --- a/src/odin/utils/mod.rs +++ b/src/odin/utils/mod.rs @@ -1,6 +1,8 @@ pub mod common_paths; pub mod environment; pub mod fetch_public_ip_address; +pub mod parse_truthy; + pub use fetch_public_ip_address::fetch_public_address; use log::debug; diff --git a/src/odin/utils/parse_truthy.rs b/src/odin/utils/parse_truthy.rs new file mode 100644 index 00000000..7299c642 --- /dev/null +++ b/src/odin/utils/parse_truthy.rs @@ -0,0 +1,22 @@ +use std::fmt::Error; + +pub fn parse_truthy(value: &str) -> Result { + Ok(match value.to_lowercase().as_str() { + "true" => true, + "false" => false, + "1" => true, + "0" => false, + _ => false, + }) +} + +// test the parse_truthy function +#[test] +fn test_parse_truthy() { + assert_eq!(parse_truthy("true"), Ok(true)); + assert_eq!(parse_truthy("false"), Ok(false)); + assert_eq!(parse_truthy("1"), Ok(true)); + assert_eq!(parse_truthy("0"), Ok(false)); + assert_eq!(parse_truthy(""), Ok(false)); + assert_eq!(parse_truthy("qwdqwdqwd"), Ok(false)); +} diff --git a/src/scripts/entrypoint.sh b/src/scripts/entrypoint.sh index f4547db5..f81d700f 100644 --- a/src/scripts/entrypoint.sh +++ b/src/scripts/entrypoint.sh @@ -68,6 +68,9 @@ setup_cron_env() { ENABLE_CROSSPLAY=${ENABLE_CROSSPLAY:-"0"} UPDATE_ON_STARTUP=${UPDATE_ON_STARTUP} SERVER_EXTRA_LAUNCH_ARGS=${SERVER_EXTRA_LAUNCH_ARGS} + PRESET=${PRESET} + MODIFIERS=$(echo "${MODIFIERS}" | xargs echo -n | tr ' ' ',' | sed 's/,,/,/g') + SET_KEY=${SET_KEY} WEBHOOK_URL=${WEBHOOK_URL:-""} WEBHOOK_STATUS_SUCCESSFUL=${WEBHOOK_STATUS_SUCCESSFUL:-"1"} diff --git a/src/scripts/start_valheim.sh b/src/scripts/start_valheim.sh index bb4fdba1..d183a7da 100644 --- a/src/scripts/start_valheim.sh +++ b/src/scripts/start_valheim.sh @@ -90,6 +90,22 @@ log "World: ${WORLD}" log "Public: ${PUBLIC}" log "With Crossplay: ${ENABLE_CROSSPLAY}" log "Password: (REDACTED)" +log "Preset: ${PRESET}" +log "Modifiers: ${MODIFIERS}" +log "Set Key: ${SET_KEY}" +log "Auto Update: ${AUTO_UPDATE}" +log "Auto Backup: ${AUTO_BACKUP}" +log "Auto Backup On Update: ${AUTO_BACKUP_ON_UPDATE}" +log "Auto Backup On Shutdown: ${AUTO_BACKUP_ON_SHUTDOWN}" +log "Auto Backup Pause With No Players: ${AUTO_BACKUP_PAUSE_WITH_NO_PLAYERS}" +log "Auto Backup Pause With Players: ${AUTO_BACKUP_PAUSE_WITH_PLAYERS}" +log "Auto Backup Remove Old: ${AUTO_BACKUP_REMOVE_OLD}" +log "Auto Backup Days To Live: ${AUTO_BACKUP_DAYS_TO_LIVE}" +log "Auto Backup Nice Level: ${AUTO_BACKUP_NICE_LEVEL}" +log "Update On Startup: ${UPDATE_ON_STARTUP}" +log "Mods: ${MODS}" +line + export SteamAppId=${APPID:-892970} diff --git a/test.sh b/test.sh new file mode 100644 index 00000000..07ef268b --- /dev/null +++ b/test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + + + +export MODIFIERS=" +raids=muchmore +combat=hard +deathpenalty=casual +" + +echo "${MODIFIERS}" | xargs echo -n | tr ' ' ',' | sed 's/,,/,/g' \ No newline at end of file