mirror of
https://github.com/mbround18/valheim-docker.git
synced 2021-10-22 21:53:54 +03:00
Added BepInEx Support (#148)
* Memory Check * Removed path on ci * Valheim BepInEx support * Separation of concern. * Added switch for installation * Yall dont need to run the dev compose ;P * Refactored to make debugging easier * Updated to use quotes and readme
This commit is contained in:
@@ -3,7 +3,7 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
# charset = utf-8
|
# charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
# max_line_length = 120
|
# max_line_length = 120
|
||||||
|
|||||||
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@@ -20,3 +20,5 @@ jobs:
|
|||||||
run: cargo build --verbose
|
run: cargo build --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
run: cargo test --verbose
|
||||||
|
- name: Lint
|
||||||
|
run: cargo fmt -- --check
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
target/
|
target/
|
||||||
tmp/
|
tmp/
|
||||||
*.env*
|
*.env*
|
||||||
|
docker-compose.*.yml
|
||||||
|
|||||||
1
.rustfmt.toml
Normal file
1
.rustfmt.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
tab_spaces = 2
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -234,7 +234,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "odin"
|
name = "odin"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cargo-husky",
|
"cargo-husky",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "odin"
|
name = "odin"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
authors = ["mbround18 <12646562+mbround18@users.noreply.github.com>"]
|
authors = ["mbround18 <12646562+mbround18@users.noreply.github.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ daemonize = "0.4"
|
|||||||
tar = "0.4"
|
tar = "0.4"
|
||||||
flate2 = "1.0"
|
flate2 = "1.0"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
|
|
||||||
|
|||||||
34
Dockerfile
34
Dockerfile
@@ -1,21 +1,22 @@
|
|||||||
# ------------------ #
|
# ------------------ #
|
||||||
# -- Odin Builder -- #
|
# -- Odin Builder -- #
|
||||||
# ------------------ #
|
# ------------------ #
|
||||||
FROM mbround18/valheim-odin:latest as RustBuilder
|
ARG ODIN_IMAGE_VERSION=latest
|
||||||
|
FROM mbround18/valheim-odin:${ODIN_IMAGE_VERSION} as RustBuilder
|
||||||
|
|
||||||
# --------------- #
|
# --------------- #
|
||||||
# -- Steam CMD -- #
|
# -- Steam CMD -- #
|
||||||
# --------------- #
|
# --------------- #
|
||||||
FROM cm2network/steamcmd:root
|
FROM cm2network/steamcmd:root
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
htop net-tools nano \
|
htop net-tools nano gcc g++ \
|
||||||
netcat curl wget \
|
netcat curl wget zip unzip \
|
||||||
cron sudo gosu dos2unix \
|
cron sudo gosu dos2unix \
|
||||||
libsdl2-2.0-0 jq \
|
libsdl2-2.0-0 jq libc6-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& gosu nobody true \
|
&& gosu nobody true \
|
||||||
&& dos2unix
|
&& dos2unix
|
||||||
|
|
||||||
# Container informaiton
|
# Container informaiton
|
||||||
@@ -49,19 +50,22 @@ ENV AUTO_BACKUP_DAYS_TO_LIVE "3"
|
|||||||
ENV AUTO_BACKUP_ON_UPDATE "0"
|
ENV AUTO_BACKUP_ON_UPDATE "0"
|
||||||
ENV AUTO_BACKUP_ON_SHUTDOWN "0"
|
ENV AUTO_BACKUP_ON_SHUTDOWN "0"
|
||||||
|
|
||||||
COPY --chmod=755 ./src/scripts/*.sh /home/steam/scripts/
|
COPY ./src/scripts/*.sh /home/steam/scripts/
|
||||||
COPY --chmod=755 ./src/scripts/entrypoint.sh /entrypoint.sh
|
COPY ./src/scripts/entrypoint.sh /entrypoint.sh
|
||||||
COPY --from=RustBuilder --chmod=755 /data/odin/target/release/odin /usr/local/bin/odin
|
COPY --from=RustBuilder /data/odin/target/release/odin /usr/local/bin/odin
|
||||||
COPY --chown=steam:steam ./src/scripts/steam_bashrc.sh /home/steam/.bashrc
|
COPY ./src/scripts/steam_bashrc.sh /home/steam/.bashrc
|
||||||
|
|
||||||
RUN usermod -u ${PUID} steam \
|
RUN usermod -u ${PUID} steam \
|
||||||
&& groupmod -g ${PGID} steam \
|
&& groupmod -g ${PGID} steam \
|
||||||
&& chsh -s /bin/bash steam \
|
&& chsh -s /bin/bash steam \
|
||||||
&& printf "${GITHUB_SHA}\n${GITHUB_REF}\n${GITHUB_REPOSITORY}\n" >/home/steam/.version
|
&& printf "${GITHUB_SHA}\n${GITHUB_REF}\n${GITHUB_REPOSITORY}\n" >/home/steam/.version \
|
||||||
|
&& chmod 755 -R /home/steam/scripts/ \
|
||||||
|
&& chmod 755 /entrypoint.sh \
|
||||||
|
&& chmod 755 /usr/local/bin/odin
|
||||||
|
|
||||||
|
|
||||||
HEALTHCHECK --interval=1m --timeout=3s \
|
HEALTHCHECK --interval=1m --timeout=3s \
|
||||||
CMD gosu steam pidof valheim_server.x86_64 || exit 1
|
CMD pidof valheim_server.x86_64 || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/bash","/entrypoint.sh"]
|
ENTRYPOINT ["/bin/bash","/entrypoint.sh"]
|
||||||
CMD ["/bin/bash", "/home/steam/scripts/start_valheim.sh"]
|
CMD ["/bin/bash", "/home/steam/scripts/start_valheim.sh"]
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ FROM rust:latest as RustBuilder
|
|||||||
WORKDIR /data/odin
|
WORKDIR /data/odin
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN cargo build --release
|
RUN cargo build --release && find . ! -name 'odin' -type f -exec rm -f {} +
|
||||||
|
|
||||||
|
|
||||||
ENTRYPOINT ["/data/odin/target/release/odin"]
|
ENTRYPOINT ["/data/odin/target/release/odin"]
|
||||||
CMD ["--version"]
|
CMD ["--version"]
|
||||||
|
|||||||
44
README.md
44
README.md
@@ -13,14 +13,18 @@
|
|||||||
<img src="https://img.shields.io/github/workflow/status/mbround18/valheim-docker/Rust?label=Docker&style=for-the-badge">
|
<img src="https://img.shields.io/github/workflow/status/mbround18/valheim-docker/Rust?label=Docker&style=for-the-badge">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
> [If you are looking for a guide on how to get started click here](https://github.com/mbround18/valheim-docker/discussions/28)
|
> [If you are looking for a guide on how to get started click here](https://github.com/mbround18/valheim-docker/discussions/28)
|
||||||
|
>
|
||||||
|
> Mod Support! It is supported to launch the server with BepInEx but!!!!! as a disclaimer! You take responsibility for debugging why your server won't start.
|
||||||
|
> Modding is not supported by the Valheim officially currently; Which means you WILL run into errors. This repo has been tested with running ValheimPlus as a test mod and does not have any issues.
|
||||||
|
> See [Getting started with mods]
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
|
> See further on down for advanced environment variables.
|
||||||
|
|
||||||
| Variable | Default | Required | Description |
|
| Variable | Default | Required | Description |
|
||||||
|--------------------------|------------------------|----------|-------------|
|
|--------------------------|------------------------|----------|-------------|
|
||||||
| TZ | `America/Los_Angeles` | FALSE | Sets what timezone your container is running on. This is used for timestamps and cron jobs. [Click Here for which timezones are valid.](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) |
|
| TZ | `America/Los_Angeles` | FALSE | Sets what timezone your container is running on. This is used for timestamps and cron jobs. [Click Here for which timezones are valid.](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) |
|
||||||
@@ -39,7 +43,6 @@
|
|||||||
| AUTO_BACKUP_DAYS_TO_LIVE | `3` | FALSE | This is the number of days you would like to keep backups for. While backups are compressed and generally small it is best to change this number as needed. |
|
| AUTO_BACKUP_DAYS_TO_LIVE | `3` | FALSE | This is the number of days you would like to keep backups for. While backups are compressed and generally small it is best to change this number as needed. |
|
||||||
| AUTO_BACKUP_ON_UPDATE | `0` | FALSE | Create a backup on right before updating and starting your server. |
|
| AUTO_BACKUP_ON_UPDATE | `0` | FALSE | Create a backup on right before updating and starting your server. |
|
||||||
| AUTO_BACKUP_ON_SHUTDOWN | `0` | FALSE | Create a backup on shutdown. |
|
| AUTO_BACKUP_ON_SHUTDOWN | `0` | FALSE | Create a backup on shutdown. |
|
||||||
| ODIN_CONFIG_FILE | `config.json` | FALSE | This file stores start parameters to restart the instance, change if you run multiple container instances on the same host |
|
|
||||||
|
|
||||||
### Docker Compose
|
### Docker Compose
|
||||||
|
|
||||||
@@ -100,6 +103,36 @@ services:
|
|||||||
- ./valheim/backups:/home/steam/backups
|
- ./valheim/backups:/home/steam/backups
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Advanced Environment Variables
|
||||||
|
|
||||||
|
> Editing or adding these can cause issues! They are intended to give you more access to the system.
|
||||||
|
|
||||||
|
#### Odin Specific
|
||||||
|
|
||||||
|
> These are set automatically by [Odin];
|
||||||
|
> you DO NOT need to set these and only mess with them if you Know what you are doing.
|
||||||
|
|
||||||
|
| Variable | Default | Required | Description |
|
||||||
|
|--------------------------|------------------------|----------|-------------|
|
||||||
|
| DEBUG_MOD | `0` | FALSE | Set to `1` if you want a noisy output and to see what Odin is doing.
|
||||||
|
| ODIN_CONFIG_FILE | `config.json` | FALSE | This file stores start parameters to restart the instance, change if you run multiple container instances on the same host |
|
||||||
|
| ODIN_WORKING_DIR | `$PWD` | FALSE | Sets the directory you wish to run `odin` commands in and can be used to set where valheim is managed from. |
|
||||||
|
|
||||||
|
#### BepInEx/Modded Variables
|
||||||
|
|
||||||
|
> These are set automatically by [Odin] for a basic BepInEx installation;
|
||||||
|
> you DO NOT need to set these and only mess with them if you Know what you are doing.
|
||||||
|
|
||||||
|
| Variable | Default | Required | Description |
|
||||||
|
|--------------------------|----------------------------------------------------------|----------|-------------|
|
||||||
|
| LD_PRELOAD | `libdoorstop_x64.so` | TRUE | Sets which library to preload on Valheim start. |
|
||||||
|
| LD_LIBRARY_PATH | `./linux64:/home/steam/valheim/doorstop_libs` | TRUE | Sets which library paths it should look in for preload libs. |
|
||||||
|
| DOORSTOP_ENABLE | `TRUE` | TRUE | Enables Doorstop or not. |
|
||||||
|
| DOORSTOP_LIB | `libdoorstop_x64.so` | TRUE | Which doorstop lib to load |
|
||||||
|
| DOORSTOP_LIBS | `/home/steam/valheim/doorstop_libs` | TRUE | Where to look for doorstop libs. |
|
||||||
|
| DOORSTOP_INVOKE_DLL_PATH | `/home/steam/valheim/BepInEx/core/BepInEx.Preloader.dll` | TRUE | BepInEx preload dll to load. |
|
||||||
|
| DYLD_LIBRARY_PATH | `"/home/steam/valheim/doorstop_libs"` | TRUE | Sets the library paths. NOTE: This variable is weird and MUST have quotes around it! |
|
||||||
|
| DYLD_INSERT_LIBRARIES | `/home/steam/valheim/doorstop_libs/libdoorstop_x64.so` | TRUE | Sets which library to load. |
|
||||||
|
|
||||||
### [Odin]
|
### [Odin]
|
||||||
|
|
||||||
@@ -108,7 +141,8 @@ This repo has a CLI tool called [Odin] in it! It is used for managing the server
|
|||||||
## Versions:
|
## Versions:
|
||||||
|
|
||||||
- latest (Stable):
|
- latest (Stable):
|
||||||
- [#100] Added backup feature to run based on cronjob.
|
- [#100] Added backup feature to run based on cronjob.
|
||||||
|
- [#148] Added Mod support
|
||||||
- 1.2.0 (Stable):
|
- 1.2.0 (Stable):
|
||||||
- Readme update to include the versions section and environment variables section.
|
- Readme update to include the versions section and environment variables section.
|
||||||
- [#18] Changed to `root` as the default user to allow updated steams User+Group IDs.
|
- [#18] Changed to `root` as the default user to allow updated steams User+Group IDs.
|
||||||
@@ -135,6 +169,7 @@ This repo has a CLI tool called [Odin] in it! It is used for managing the server
|
|||||||
- Has a bug in which it does not read passed in variables appropriately to Odin. Env variables are not impacted see [#3].
|
- Has a bug in which it does not read passed in variables appropriately to Odin. Env variables are not impacted see [#3].
|
||||||
|
|
||||||
[//]: <> (Github Issues below...........)
|
[//]: <> (Github Issues below...........)
|
||||||
|
[#148]: https://github.com/mbround18/valheim-docker/pull/148
|
||||||
[#100]: https://github.com/mbround18/valheim-docker/pull/100
|
[#100]: https://github.com/mbround18/valheim-docker/pull/100
|
||||||
[#89]: https://github.com/mbround18/valheim-docker/pull/89
|
[#89]: https://github.com/mbround18/valheim-docker/pull/89
|
||||||
[#77]: https://github.com/mbround18/valheim-docker/pull/77
|
[#77]: https://github.com/mbround18/valheim-docker/pull/77
|
||||||
@@ -151,6 +186,7 @@ This repo has a CLI tool called [Odin] in it! It is used for managing the server
|
|||||||
[//]: <> (Links below...................)
|
[//]: <> (Links below...................)
|
||||||
[Odin]: ./docs/odin.md
|
[Odin]: ./docs/odin.md
|
||||||
[Valheim]: https://www.valheimgame.com/
|
[Valheim]: https://www.valheimgame.com/
|
||||||
|
[Getting started with mods]: ./docs/getting_started_with_mods.md
|
||||||
[If you need help figuring out a cron schedule click here]: https://crontab.guru/#0_1_*_*_*
|
[If you need help figuring out a cron schedule click here]: https://crontab.guru/#0_1_*_*_*
|
||||||
|
|
||||||
[//]: <> (Image Base Url: https://github.com/mbround18/valheim-docker/blob/main/docs/assets/name.png?raw=true)
|
[//]: <> (Image Base Url: https://github.com/mbround18/valheim-docker/blob/main/docs/assets/name.png?raw=true)
|
||||||
|
|||||||
23
docs/getting_started_with_mods.md
Normal file
23
docs/getting_started_with_mods.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Getting started with Mods
|
||||||
|
|
||||||
|
> For this example we will be going over installing ValheimPlus. There is a lot of mysteries when it comes to modding but this should help you get started.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Download the mod file, for our example we want the `UnixServer.zip` from `https://github.com/nxPublic/ValheimPlus/releases`
|
||||||
|
2. Place the file in your server volume mount. `cp UnixServer.zip /home/youruser/valheim/server`
|
||||||
|
3. Unzip the archive `unzip UnixServer.zip -d .` hit A to replace all as needed.
|
||||||
|
4. Restart your server.
|
||||||
|
|
||||||
|
> Odin automatically detects if you are running with BepInEx and adds the environment variables appropriately.
|
||||||
|
>
|
||||||
|
> DISCLAIMER! Modding your server can cause a lot of errors.
|
||||||
|
> Please do NOT post an issue on the valheim-docker repo based on mod issues.
|
||||||
|
> By installing mods, you agree that you will do a root cause analysis to why your server is failing before you make a post.
|
||||||
|
> Modding is currently unsupported by the Valheim developers and limited support by the valheim-docker repo.
|
||||||
|
> If you have issues please contact the MOD developer FIRST based on the output logs.
|
||||||
|
|
||||||
|
## Valheim Updated Help!!!!
|
||||||
|
|
||||||
|
Mod development is slow, and the more mods you have the more complicated it will be to keep everything up to date.
|
||||||
|
It is a suggestion that you turn off the AUTO_UPDATE variable when you are using mods and refrain from updating your local client until all your mods have been updated.
|
||||||
@@ -59,7 +59,7 @@ subcommands:
|
|||||||
takes_value: true
|
takes_value: true
|
||||||
- install:
|
- install:
|
||||||
about: Installs Valheim with steamcmd
|
about: Installs Valheim with steamcmd
|
||||||
version: "2.0"
|
version: "2.1"
|
||||||
author: mbround18
|
author: mbround18
|
||||||
- start:
|
- start:
|
||||||
about: Starts Valheim
|
about: Starts Valheim
|
||||||
|
|||||||
@@ -6,24 +6,24 @@ use std::fs::File;
|
|||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
pub fn invoke(args: &ArgMatches) {
|
pub fn invoke(args: &ArgMatches) {
|
||||||
let input = args.value_of("INPUT_DIR").unwrap();
|
let input = args.value_of("INPUT_DIR").unwrap();
|
||||||
let output = args.value_of("OUTPUT_FILE").unwrap();
|
let output = args.value_of("OUTPUT_FILE").unwrap();
|
||||||
debug!("Creating archive of {}", input);
|
debug!("Creating archive of {}", input);
|
||||||
debug!("Output set to {}", output);
|
debug!("Output set to {}", output);
|
||||||
let tar_gz = match File::create(output) {
|
let tar_gz = match File::create(output) {
|
||||||
Ok(file) => file,
|
Ok(file) => file,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error!("Failed to create backup file at {}", output);
|
error!("Failed to create backup file at {}", output);
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let enc = GzEncoder::new(tar_gz, Compression::default());
|
let enc = GzEncoder::new(tar_gz, Compression::default());
|
||||||
let mut tar = tar::Builder::new(enc);
|
let mut tar = tar::Builder::new(enc);
|
||||||
match tar.append_dir_all("saves", input) {
|
match tar.append_dir_all("saves", input) {
|
||||||
Ok(_) => debug!("Successfully created backup zip at {}", output),
|
Ok(_) => debug!("Successfully created backup zip at {}", output),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error!("Failed to add {} to backup file", input);
|
error!("Failed to add {} to backup file", input);
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
use crate::files::config::{config_file, write_config};
|
use crate::files::config::{config_file, write_config};
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
use log::debug;
|
||||||
|
|
||||||
pub fn invoke(args: &ArgMatches) {
|
pub fn invoke(args: &ArgMatches) {
|
||||||
let config = config_file();
|
debug!("Pulling config file...");
|
||||||
write_config(config, args);
|
let config = config_file();
|
||||||
|
debug!("Writing config file...");
|
||||||
|
write_config(config, args);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
use crate::executable::execute_mut;
|
use crate::executable::execute_mut;
|
||||||
use crate::steamcmd::steamcmd_command;
|
use crate::steamcmd::steamcmd_command;
|
||||||
use crate::utils::get_working_dir;
|
use crate::utils::get_working_dir;
|
||||||
use log::info;
|
use log::{debug, info};
|
||||||
use std::process::{ExitStatus, Stdio};
|
use std::process::{ExitStatus, Stdio};
|
||||||
|
|
||||||
pub fn invoke(app_id: i64) -> std::io::Result<ExitStatus> {
|
pub fn invoke(app_id: i64) -> std::io::Result<ExitStatus> {
|
||||||
info!("Installing {} to {}", app_id, get_working_dir());
|
info!("Installing {} to {}", app_id, get_working_dir());
|
||||||
let login = "+login anonymous".to_string();
|
let login = "+login anonymous".to_string();
|
||||||
let force_install_dir = format!("+force_install_dir {}", get_working_dir());
|
debug!("Argument set: {}", login);
|
||||||
let app_update = format!("+app_update {}", app_id);
|
let force_install_dir = format!("+force_install_dir {}", get_working_dir());
|
||||||
let mut steamcmd = steamcmd_command();
|
debug!("Argument set: {}", force_install_dir);
|
||||||
let install_command = steamcmd
|
let app_update = format!("+app_update {}", app_id);
|
||||||
.args(&[login, force_install_dir, app_update])
|
debug!("Argument set: {}", app_update);
|
||||||
.arg("+quit")
|
let mut steamcmd = steamcmd_command();
|
||||||
.stdout(Stdio::inherit())
|
debug!("Setting up install command...");
|
||||||
.stderr(Stdio::inherit());
|
let install_command = steamcmd
|
||||||
execute_mut(install_command)
|
.args(&[login, force_install_dir, app_update])
|
||||||
|
.arg("+quit")
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.stderr(Stdio::inherit());
|
||||||
|
debug!("Launching up install command...");
|
||||||
|
execute_mut(install_command)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,65 +1,108 @@
|
|||||||
|
mod bepinex;
|
||||||
|
|
||||||
|
use crate::commands::start::bepinex::{build_environment, is_bepinex_installed};
|
||||||
use crate::executable::create_execution;
|
use crate::executable::create_execution;
|
||||||
use crate::files::config::{config_file, read_config};
|
use crate::files::config::{config_file, read_config};
|
||||||
use crate::utils::{create_file, get_working_dir};
|
use crate::files::{create_file, ValheimArguments};
|
||||||
|
use crate::messages::modding_disclaimer;
|
||||||
|
use crate::utils::{fetch_env, get_working_dir};
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use daemonize::Daemonize;
|
use daemonize::Daemonize;
|
||||||
use log::{error, info};
|
use log::{debug, error, info};
|
||||||
use std::process::exit;
|
use std::process::{exit, Child};
|
||||||
|
|
||||||
|
const LD_LIBRARY_PATH_VAR: &str = "LD_LIBRARY_PATH";
|
||||||
|
const LD_PRELOAD_VAR: &str = "LD_PRELOAD";
|
||||||
|
|
||||||
|
fn exit_action() {
|
||||||
|
if is_bepinex_installed() {
|
||||||
|
info!("Server has been started with BepInEx! Keep in mind this may cause errors!!");
|
||||||
|
modding_disclaimer()
|
||||||
|
}
|
||||||
|
info!("Server has been started and Daemonized. It should be online shortly!");
|
||||||
|
info!("Keep an eye out for 'Game server connected' in the log!");
|
||||||
|
info!("(this indicates its online without any errors.)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_server(config: &ValheimArguments) -> std::io::Result<Child> {
|
||||||
|
let mut command = create_execution(&config.command);
|
||||||
|
info!("--------------------------------------------------------------------------------------------------------------");
|
||||||
|
let ld_library_path_value = fetch_env(
|
||||||
|
LD_LIBRARY_PATH_VAR,
|
||||||
|
format!("{}/linux64", get_working_dir()).as_str(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
debug!("Setting up base command");
|
||||||
|
let base_command = command
|
||||||
|
.args(&[
|
||||||
|
"-nographics",
|
||||||
|
"-batchmode",
|
||||||
|
"-port",
|
||||||
|
&config.port.as_str(),
|
||||||
|
"-name",
|
||||||
|
&config.name.as_str(),
|
||||||
|
"-world",
|
||||||
|
&config.world.as_str(),
|
||||||
|
"-password",
|
||||||
|
&config.password.as_str(),
|
||||||
|
"-public",
|
||||||
|
&config.public.as_str(),
|
||||||
|
])
|
||||||
|
.env("SteamAppId", fetch_env("APPID", "892970", false))
|
||||||
|
.current_dir(get_working_dir());
|
||||||
|
info!("Executable: {}", &config.command);
|
||||||
|
info!("Launching Command...");
|
||||||
|
|
||||||
|
if is_bepinex_installed() {
|
||||||
|
info!("BepInEx detected! Switching to run with BepInEx...");
|
||||||
|
let bepinex_env = build_environment();
|
||||||
|
bepinex::invoke(base_command, &bepinex_env)
|
||||||
|
} else {
|
||||||
|
info!("Everything looks good! Running normally!");
|
||||||
|
base_command
|
||||||
|
.env(LD_LIBRARY_PATH_VAR, ld_library_path_value)
|
||||||
|
.spawn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn invoke(args: &ArgMatches) {
|
pub fn invoke(args: &ArgMatches) {
|
||||||
info!("Setting up start scripts...");
|
info!("Setting up start scripts...");
|
||||||
|
debug!("Loading config file...");
|
||||||
|
let config = config_file();
|
||||||
|
let config_content: ValheimArguments = read_config(config);
|
||||||
|
debug!("Checking password compliance...");
|
||||||
|
if config_content.password.len() < 5 {
|
||||||
|
error!("The supplied password is too short! It much be 5 characters or greater!");
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
let dry_run: bool = args.is_present("dry_run");
|
||||||
|
debug!("Dry run condition: {}", dry_run);
|
||||||
|
info!("Looking for burial mounds...");
|
||||||
|
if !dry_run {
|
||||||
|
let stdout = create_file(format!("{}/logs/valheim_server.log", get_working_dir()).as_str());
|
||||||
|
let stderr = create_file(format!("{}/logs/valheim_server.err", get_working_dir()).as_str());
|
||||||
|
let daemonize = Daemonize::new()
|
||||||
|
.working_directory(get_working_dir())
|
||||||
|
.user("steam")
|
||||||
|
.group("steam")
|
||||||
|
.stdout(stdout)
|
||||||
|
.stderr(stderr)
|
||||||
|
.exit_action(exit_action)
|
||||||
|
.privileged_action(move || spawn_server(&config_content));
|
||||||
|
|
||||||
let config = config_file();
|
match daemonize.start() {
|
||||||
let config_content = read_config(config);
|
Ok(_) => info!("Success, daemonized"),
|
||||||
if config_content.password.len() < 5 {
|
Err(e) => error!("Error, {}", e),
|
||||||
error!("The supplied password is too short! It much be 5 characters or greater!");
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
let dry_run: bool = args.is_present("dry_run");
|
|
||||||
info!("Looking for burial mounds...");
|
|
||||||
if !dry_run {
|
|
||||||
let stdout = create_file(format!("{}/logs/valheim_server.log", get_working_dir()).as_str());
|
|
||||||
let stderr = create_file(format!("{}/logs/valheim_server.err", get_working_dir()).as_str());
|
|
||||||
let daemonize = Daemonize::new()
|
|
||||||
.working_directory(get_working_dir())
|
|
||||||
.user("steam")
|
|
||||||
.group("steam")
|
|
||||||
.stdout(stdout)
|
|
||||||
.stderr(stderr)
|
|
||||||
.exit_action(|| {
|
|
||||||
info!("Server has been started and Daemonized. It should be online shortly!")
|
|
||||||
})
|
|
||||||
.privileged_action(move || {
|
|
||||||
create_execution(&config_content.command.as_str())
|
|
||||||
.args(&[
|
|
||||||
"-nographics",
|
|
||||||
"-port",
|
|
||||||
&config_content.port.as_str(),
|
|
||||||
"-name",
|
|
||||||
&config_content.name.as_str(),
|
|
||||||
"-world",
|
|
||||||
&config_content.world.as_str(),
|
|
||||||
"-password",
|
|
||||||
&config_content.password.as_str(),
|
|
||||||
"-public",
|
|
||||||
&config_content.public.as_str(),
|
|
||||||
])
|
|
||||||
.spawn()
|
|
||||||
});
|
|
||||||
|
|
||||||
match daemonize.start() {
|
|
||||||
Ok(_) => info!("Success, daemonized"),
|
|
||||||
Err(e) => error!("Error, {}", e),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info!(
|
|
||||||
"This command would have launched\n{} -port {} -name {} -world {} -password {} -public {}",
|
|
||||||
&config_content.command,
|
|
||||||
&config_content.port,
|
|
||||||
&config_content.name,
|
|
||||||
&config_content.world,
|
|
||||||
&config_content.password,
|
|
||||||
&config_content.public,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
"This command would have launched\n{} -nographics -batchmode -port {} -name {} -world {} -password {} -public {}",
|
||||||
|
&config_content.command,
|
||||||
|
&config_content.port,
|
||||||
|
&config_content.name,
|
||||||
|
&config_content.world,
|
||||||
|
&config_content.password,
|
||||||
|
&config_content.public,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
123
src/commands/start/bepinex.rs
Normal file
123
src/commands/start/bepinex.rs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
use crate::commands::start::{LD_LIBRARY_PATH_VAR, LD_PRELOAD_VAR};
|
||||||
|
use crate::utils::{fetch_env, get_working_dir};
|
||||||
|
use log::{debug, info};
|
||||||
|
use std::ops::Add;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{Child, Command};
|
||||||
|
|
||||||
|
const DYLD_LIBRARY_PATH_VAR: &str = "DYLD_LIBRARY_PATH";
|
||||||
|
const DYLD_INSERT_LIBRARIES_VAR: &str = "DYLD_INSERT_LIBRARIES";
|
||||||
|
const DOORSTOP_ENABLE_VAR: &str = "DOORSTOP_ENABLE";
|
||||||
|
const DOORSTOP_LIB_VAR: &str = "DOORSTOP_LIB";
|
||||||
|
const DOORSTOP_LIBS_VAR: &str = "DOORSTOP_LIBS";
|
||||||
|
const DOORSTOP_INVOKE_DLL_PATH_VAR: &str = "DOORSTOP_INVOKE_DLL_PATH";
|
||||||
|
|
||||||
|
fn doorstop_lib() -> String {
|
||||||
|
fetch_env(DOORSTOP_LIB_VAR, "libdoorstop_x64.so", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doorstop_libs() -> String {
|
||||||
|
fetch_env(
|
||||||
|
DOORSTOP_LIBS_VAR,
|
||||||
|
format!("{}/doorstop_libs", get_working_dir()).as_str(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doorstop_insert_lib() -> String {
|
||||||
|
let default = format!("{}/{}", doorstop_libs(), doorstop_lib().replace(":", ""));
|
||||||
|
fetch_env(DYLD_INSERT_LIBRARIES_VAR, default.as_str(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doorstop_invoke_dll() -> String {
|
||||||
|
fetch_env(
|
||||||
|
DOORSTOP_INVOKE_DLL_PATH_VAR,
|
||||||
|
format!("{}/BepInEx/core/BepInEx.Preloader.dll", get_working_dir()).as_str(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_bepinex_installed() -> bool {
|
||||||
|
let doorstep_insert_lib_exists = Path::new(doorstop_insert_lib().as_str()).exists();
|
||||||
|
let doorstep_libs_dir_exists = Path::new(doorstop_libs().as_str()).exists();
|
||||||
|
debug!("doorstep insert lib exists: {}", doorstep_insert_lib_exists);
|
||||||
|
debug!(
|
||||||
|
"doorstep libs directory exists: {}",
|
||||||
|
doorstep_libs_dir_exists
|
||||||
|
);
|
||||||
|
doorstep_insert_lib_exists && doorstep_libs_dir_exists
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BepInExEnvironment {
|
||||||
|
ld_preload: String,
|
||||||
|
ld_library_path: String,
|
||||||
|
doorstop_enable: String,
|
||||||
|
doorstop_invoke_dll: String,
|
||||||
|
dyld_library_path: String,
|
||||||
|
dyld_insert_libraries: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_environment() -> BepInExEnvironment {
|
||||||
|
let ld_preload = fetch_env(LD_PRELOAD_VAR, "", false).add(doorstop_lib().as_str());
|
||||||
|
let ld_library_path = fetch_env(
|
||||||
|
LD_LIBRARY_PATH_VAR,
|
||||||
|
format!("./linux64:{}", doorstop_libs()).as_str(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let doorstop_invoke_dll_value = doorstop_invoke_dll();
|
||||||
|
let dyld_library_path = fetch_env(DYLD_LIBRARY_PATH_VAR, doorstop_libs().as_str(), false);
|
||||||
|
let dyld_insert_libraries = fetch_env(
|
||||||
|
DYLD_INSERT_LIBRARIES_VAR,
|
||||||
|
doorstop_insert_lib().as_str(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
info!("Loading BepInEx Environment...");
|
||||||
|
let environment = BepInExEnvironment {
|
||||||
|
ld_preload,
|
||||||
|
ld_library_path,
|
||||||
|
doorstop_enable: true.to_string().to_uppercase(),
|
||||||
|
doorstop_invoke_dll: doorstop_invoke_dll_value,
|
||||||
|
dyld_library_path,
|
||||||
|
dyld_insert_libraries,
|
||||||
|
};
|
||||||
|
debug!("LD_PRELOAD: {}", &environment.ld_preload);
|
||||||
|
debug!("LD_LIBRARY_PATH: {}", &environment.ld_library_path);
|
||||||
|
debug!("DOORSTOP_ENABLE: {}", &environment.doorstop_enable);
|
||||||
|
debug!(
|
||||||
|
"DOORSTOP_INVOKE_DLL_PATH: {}",
|
||||||
|
&environment.doorstop_invoke_dll
|
||||||
|
);
|
||||||
|
debug!("DYLD_LIBRARY_PATH: {}", &environment.dyld_library_path);
|
||||||
|
debug!(
|
||||||
|
"DYLD_INSERT_LIBRARIES: {}",
|
||||||
|
&environment.dyld_insert_libraries
|
||||||
|
);
|
||||||
|
environment
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invoke(command: &mut Command, environment: &BepInExEnvironment) -> std::io::Result<Child> {
|
||||||
|
info!("BepInEx found! Setting up Environment...");
|
||||||
|
command
|
||||||
|
// DOORSTOP_ENABLE must not have quotes around it.
|
||||||
|
.env(DOORSTOP_ENABLE_VAR, &environment.doorstop_enable)
|
||||||
|
// DOORSTOP_INVOKE_DLL_PATH must not have quotes around it.
|
||||||
|
.env(
|
||||||
|
DOORSTOP_INVOKE_DLL_PATH_VAR,
|
||||||
|
&environment.doorstop_invoke_dll,
|
||||||
|
)
|
||||||
|
// LD_LIBRARY_PATH must not have quotes around it.
|
||||||
|
.env(LD_LIBRARY_PATH_VAR, &environment.ld_library_path)
|
||||||
|
// LD_PRELOAD must not have quotes around it.
|
||||||
|
.env(LD_PRELOAD_VAR, &environment.ld_preload)
|
||||||
|
// DYLD_LIBRARY_PATH is weird af and MUST have quotes around it.
|
||||||
|
.env(
|
||||||
|
DYLD_LIBRARY_PATH_VAR,
|
||||||
|
format!("\"{}\"", &environment.dyld_library_path),
|
||||||
|
)
|
||||||
|
// DYLD_INSERT_LIBRARIES must not have quotes around it.
|
||||||
|
.env(
|
||||||
|
DYLD_INSERT_LIBRARIES_VAR,
|
||||||
|
&environment.dyld_insert_libraries,
|
||||||
|
)
|
||||||
|
.spawn()
|
||||||
|
}
|
||||||
@@ -7,54 +7,54 @@ use sysinfo::{ProcessExt, Signal, System, SystemExt};
|
|||||||
use std::{thread, time::Duration};
|
use std::{thread, time::Duration};
|
||||||
|
|
||||||
fn send_shutdown() {
|
fn send_shutdown() {
|
||||||
info!("Scanning for Valheim process");
|
info!("Scanning for Valheim process");
|
||||||
let mut system = System::new();
|
let mut system = System::new();
|
||||||
system.refresh_all();
|
system.refresh_all();
|
||||||
let processes = system.get_process_by_name(VALHEIM_EXECUTABLE_NAME);
|
let processes = system.get_process_by_name(VALHEIM_EXECUTABLE_NAME);
|
||||||
if processes.is_empty() {
|
if processes.is_empty() {
|
||||||
info!("Process NOT found!")
|
info!("Process NOT found!")
|
||||||
} else {
|
} else {
|
||||||
for found_process in processes {
|
for found_process in processes {
|
||||||
info!(
|
info!(
|
||||||
"Found Process with pid {}! Sending Interrupt!",
|
"Found Process with pid {}! Sending Interrupt!",
|
||||||
found_process.pid()
|
found_process.pid()
|
||||||
);
|
);
|
||||||
if found_process.kill(Signal::Interrupt) {
|
if found_process.kill(Signal::Interrupt) {
|
||||||
info!("Process signal interrupt sent successfully!")
|
info!("Process signal interrupt sent successfully!")
|
||||||
} else {
|
} else {
|
||||||
error!("Failed to send signal interrupt!")
|
error!("Failed to send signal interrupt!")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_for_server_exit() {
|
fn wait_for_server_exit() {
|
||||||
info!("Waiting for server to completely shutdown...");
|
info!("Waiting for server to completely shutdown...");
|
||||||
let mut system = System::new();
|
let mut system = System::new();
|
||||||
loop {
|
loop {
|
||||||
system.refresh_all();
|
system.refresh_all();
|
||||||
let processes = system.get_process_by_name(VALHEIM_EXECUTABLE_NAME);
|
let processes = system.get_process_by_name(VALHEIM_EXECUTABLE_NAME);
|
||||||
if processes.is_empty() {
|
if processes.is_empty() {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// Delay to keep down CPU usage
|
// Delay to keep down CPU usage
|
||||||
thread::sleep(Duration::from_secs(1));
|
thread::sleep(Duration::from_secs(1));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
info!("Server has been shutdown successfully!")
|
}
|
||||||
|
info!("Server has been shutdown successfully!")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invoke(args: &ArgMatches) {
|
pub fn invoke(args: &ArgMatches) {
|
||||||
info!("Stopping server {}", get_working_dir());
|
info!("Stopping server {}", get_working_dir());
|
||||||
if args.is_present("dry_run") {
|
if args.is_present("dry_run") {
|
||||||
info!("This command would have run: ");
|
info!("This command would have run: ");
|
||||||
info!("kill -2 {}", VALHEIM_EXECUTABLE_NAME)
|
info!("kill -2 {}", VALHEIM_EXECUTABLE_NAME)
|
||||||
} else {
|
} else {
|
||||||
if !server_installed() {
|
if !server_installed() {
|
||||||
error!("Failed to find server executable!");
|
error!("Failed to find server executable!");
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
send_shutdown();
|
|
||||||
wait_for_server_exit();
|
|
||||||
}
|
}
|
||||||
|
send_shutdown();
|
||||||
|
wait_for_server_exit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,56 +3,56 @@ use std::path::Path;
|
|||||||
use std::process::{exit, Command, ExitStatus};
|
use std::process::{exit, Command, ExitStatus};
|
||||||
|
|
||||||
pub fn find_command(executable: &str) -> Option<Command> {
|
pub fn find_command(executable: &str) -> Option<Command> {
|
||||||
let script_file = Path::new(executable);
|
let script_file = Path::new(executable);
|
||||||
if script_file.exists() {
|
if script_file.exists() {
|
||||||
info!("Executing: {} .....", executable.to_string());
|
info!("Executing: {} .....", executable.to_string());
|
||||||
Option::from(Command::new(executable.to_string()))
|
Option::from(Command::new(executable.to_string()))
|
||||||
} else {
|
} else {
|
||||||
match which::which(executable) {
|
match which::which(executable) {
|
||||||
Ok(executable_path) => Option::from(Command::new(executable_path)),
|
Ok(executable_path) => Option::from(Command::new(executable_path)),
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
error!("Failed to find {} in path", executable);
|
error!("Failed to find {} in path", executable);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_execution(executable: &str) -> Command {
|
pub fn create_execution(executable: &str) -> Command {
|
||||||
match find_command(executable) {
|
match find_command(executable) {
|
||||||
Some(command) => command,
|
Some(command) => command,
|
||||||
None => {
|
None => {
|
||||||
error!("Unable to launch command {}", executable);
|
error!("Unable to launch command {}", executable);
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_mut(command: &mut Command) -> std::io::Result<ExitStatus> {
|
pub fn execute_mut(command: &mut Command) -> std::io::Result<ExitStatus> {
|
||||||
match command.spawn() {
|
match command.spawn() {
|
||||||
Ok(mut subprocess) => subprocess.wait(),
|
Ok(mut subprocess) => subprocess.wait(),
|
||||||
_ => {
|
_ => {
|
||||||
error!("Failed to run process!");
|
error!("Failed to run process!");
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_exit_status(result: std::io::Result<ExitStatus>, success_message: String) {
|
pub fn handle_exit_status(result: std::io::Result<ExitStatus>, success_message: String) {
|
||||||
match result {
|
match result {
|
||||||
Ok(exit_status) => {
|
Ok(exit_status) => {
|
||||||
if exit_status.success() {
|
if exit_status.success() {
|
||||||
info!("{}", success_message);
|
info!("{}", success_message);
|
||||||
} else {
|
} else {
|
||||||
match exit_status.code() {
|
match exit_status.code() {
|
||||||
Some(code) => info!("Exited with status code: {}", code),
|
Some(code) => info!("Exited with status code: {}", code),
|
||||||
None => info!("Process terminated by signal"),
|
None => info!("Process terminated by signal"),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error!("An error has occurred and the command returned no exit code!");
|
|
||||||
exit(1)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
error!("An error has occurred and the command returned no exit code!");
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::files::ValheimArguments;
|
|||||||
use crate::files::{FileManager, ManagedFile};
|
use crate::files::{FileManager, ManagedFile};
|
||||||
use crate::utils::{get_variable, get_working_dir, VALHEIM_EXECUTABLE_NAME};
|
use crate::utils::{get_variable, get_working_dir, VALHEIM_EXECUTABLE_NAME};
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use log::error;
|
use log::{debug, error};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -11,66 +11,70 @@ use std::process::exit;
|
|||||||
const ODIN_CONFIG_FILE_VAR: &str = "ODIN_CONFIG_FILE";
|
const ODIN_CONFIG_FILE_VAR: &str = "ODIN_CONFIG_FILE";
|
||||||
|
|
||||||
pub fn config_file() -> ManagedFile {
|
pub fn config_file() -> ManagedFile {
|
||||||
let name = env::var(ODIN_CONFIG_FILE_VAR).unwrap_or_else(|_| "config.json".to_string());
|
let name = env::var(ODIN_CONFIG_FILE_VAR).unwrap_or_else(|_| "config.json".to_string());
|
||||||
ManagedFile { name }
|
debug!("Config file set to: {}", name);
|
||||||
|
ManagedFile { name }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_config(config: ManagedFile) -> ValheimArguments {
|
pub fn read_config(config: ManagedFile) -> ValheimArguments {
|
||||||
let content = config.read();
|
let content = config.read();
|
||||||
if content.is_empty() {
|
if content.is_empty() {
|
||||||
panic!("Please initialize odin with `odin configure`. See `odin configure --help`")
|
panic!("Please initialize odin with `odin configure`. See `odin configure --help`")
|
||||||
}
|
}
|
||||||
serde_json::from_str(content.as_str()).unwrap()
|
serde_json::from_str(content.as_str()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_config(config: ManagedFile, args: &ArgMatches) -> bool {
|
pub fn write_config(config: ManagedFile, args: &ArgMatches) -> bool {
|
||||||
let server_executable: &str =
|
let server_executable: &str = &[get_working_dir(), VALHEIM_EXECUTABLE_NAME.to_string()].join("/");
|
||||||
&[get_working_dir(), VALHEIM_EXECUTABLE_NAME.to_string()].join("/");
|
let command = match fs::canonicalize(PathBuf::from(get_variable(
|
||||||
|
args,
|
||||||
|
"server_executable",
|
||||||
|
server_executable.to_string(),
|
||||||
|
))) {
|
||||||
|
std::result::Result::Ok(command_path) => command_path.to_str().unwrap().to_string(),
|
||||||
|
std::result::Result::Err(_) => {
|
||||||
|
error!("Failed to find server executable! Please run `odin install`");
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let command = match fs::canonicalize(PathBuf::from(get_variable(
|
let content = &ValheimArguments {
|
||||||
args,
|
port: get_variable(args, "port", "2456".to_string()),
|
||||||
"server_executable",
|
name: get_variable(args, "name", "Valheim powered by Odin".to_string()),
|
||||||
server_executable.to_string(),
|
world: get_variable(args, "world", "Dedicated".to_string()),
|
||||||
))) {
|
public: get_variable(args, "public", "1".to_string()),
|
||||||
std::result::Result::Ok(command_path) => command_path.to_str().unwrap().to_string(),
|
password: get_variable(args, "password", "12345".to_string()),
|
||||||
std::result::Result::Err(_) => {
|
command,
|
||||||
error!("Failed to find server executable! Please run `odin install`");
|
};
|
||||||
exit(1)
|
let content_to_write = serde_json::to_string(content).unwrap();
|
||||||
}
|
debug!(
|
||||||
};
|
"Writing config content: \n{}",
|
||||||
|
serde_json::to_string(content).unwrap()
|
||||||
let content = &ValheimArguments {
|
);
|
||||||
port: get_variable(args, "port", "2456".to_string()),
|
config.write(content_to_write)
|
||||||
name: get_variable(args, "name", "Valheim powered by Odin".to_string()),
|
|
||||||
world: get_variable(args, "world", "Dedicated".to_string()),
|
|
||||||
public: get_variable(args, "public", "1".to_string()),
|
|
||||||
password: get_variable(args, "password", "12345".to_string()),
|
|
||||||
command,
|
|
||||||
};
|
|
||||||
config.write(serde_json::to_string(content).unwrap())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic(
|
||||||
expected = "Please initialize odin with `odin configure`. See `odin configure --help`"
|
expected = "Please initialize odin with `odin configure`. See `odin configure --help`"
|
||||||
)]
|
)]
|
||||||
fn can_read_config_panic() {
|
fn can_read_config_panic() {
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let n1: u8 = rng.gen();
|
let n1: u8 = rng.gen();
|
||||||
env::set_var(
|
env::set_var(
|
||||||
ODIN_CONFIG_FILE_VAR,
|
ODIN_CONFIG_FILE_VAR,
|
||||||
format!(
|
format!(
|
||||||
"{}/config.{}.json",
|
"{}/config.{}.json",
|
||||||
current_dir().unwrap().to_str().unwrap(),
|
current_dir().unwrap().to_str().unwrap(),
|
||||||
n1
|
n1
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
read_config(config_file());
|
read_config(config_file());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
134
src/files/mod.rs
134
src/files/mod.rs
@@ -1,4 +1,5 @@
|
|||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
use crate::executable::create_execution;
|
use crate::executable::create_execution;
|
||||||
use crate::utils::get_working_dir;
|
use crate::utils::get_working_dir;
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
@@ -7,84 +8,91 @@ use std::fs;
|
|||||||
use std::fs::{remove_file, File};
|
use std::fs::{remove_file, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct ValheimArguments {
|
pub struct ValheimArguments {
|
||||||
pub(crate) port: String,
|
pub(crate) port: String,
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
pub(crate) world: String,
|
pub(crate) world: String,
|
||||||
pub(crate) public: String,
|
pub(crate) public: String,
|
||||||
pub(crate) password: String,
|
pub(crate) password: String,
|
||||||
pub(crate) command: String,
|
pub(crate) command: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_file(path: &str) -> File {
|
||||||
|
let output_path = Path::new(path);
|
||||||
|
match File::create(output_path) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(_) => {
|
||||||
|
error!("Failed to create {}", path);
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FileManager {
|
pub trait FileManager {
|
||||||
fn path(&self) -> String;
|
fn path(&self) -> String;
|
||||||
fn exists(&self) -> bool {
|
fn exists(&self) -> bool {
|
||||||
Path::new(self.path().as_str()).exists()
|
Path::new(self.path().as_str()).exists()
|
||||||
|
}
|
||||||
|
fn remove(&self) -> bool {
|
||||||
|
match remove_file(self.path()) {
|
||||||
|
Ok(_) => {
|
||||||
|
info!("Successfully deleted {}", self.path());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
error!("Did not find or could not delete {}", self.path());
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn remove(&self) -> bool {
|
}
|
||||||
match remove_file(self.path()) {
|
fn read(&self) -> String {
|
||||||
Ok(_) => {
|
if self.exists() {
|
||||||
info!("Successfully deleted {}", self.path());
|
fs::read_to_string(self.path()).unwrap()
|
||||||
true
|
} else {
|
||||||
}
|
"".to_string()
|
||||||
Err(_) => {
|
|
||||||
error!("Did not find or could not delete {}", self.path());
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn read(&self) -> String {
|
}
|
||||||
if self.exists() {
|
fn write(&self, content: String) -> bool {
|
||||||
fs::read_to_string(self.path()).unwrap()
|
let mut file = create_file(self.path().as_str());
|
||||||
} else {
|
match file.write_all(content.as_bytes()) {
|
||||||
"".to_string()
|
Ok(_) => {
|
||||||
}
|
info!("Successfully written {}", self.path());
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Failed to write {}", self.path());
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn write(&self, content: String) -> bool {
|
}
|
||||||
match File::create(self.path()) {
|
fn set_executable(&self) -> bool {
|
||||||
Ok(mut file) => match file.write_all(content.as_bytes()) {
|
if let Ok(_output) = create_execution("chmod")
|
||||||
Ok(_) => {
|
.args(&["+x", self.path().as_str()])
|
||||||
info!("Successfully written {}", self.path());
|
.output()
|
||||||
true
|
{
|
||||||
}
|
info!("Successfully set {} to executable", self.path());
|
||||||
_ => {
|
true
|
||||||
error!("Failed to write {}", self.path());
|
} else {
|
||||||
false
|
error!("Unable to set {} to executable", self.path());
|
||||||
}
|
false
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
error!("Failed to write {}", self.path());
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn set_executable(&self) -> bool {
|
|
||||||
if let Ok(_output) = create_execution("chmod")
|
|
||||||
.args(&["+x", self.path().as_str()])
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
info!("Successfully set {} to executable", self.path());
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
error!("Unable to set {} to executable", self.path());
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ManagedFile {
|
pub struct ManagedFile {
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileManager for ManagedFile {
|
impl FileManager for ManagedFile {
|
||||||
fn path(&self) -> String {
|
fn path(&self) -> String {
|
||||||
let supplied_path = Path::new(self.name.as_str());
|
let supplied_path = Path::new(self.name.as_str());
|
||||||
if supplied_path.exists() {
|
if supplied_path.exists() {
|
||||||
supplied_path.to_str().unwrap().to_string()
|
supplied_path.to_str().unwrap().to_string()
|
||||||
} else {
|
} else {
|
||||||
format!("{}/{}", get_working_dir(), self.name)
|
format!("{}/{}", get_working_dir(), self.name)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,24 @@ use log::{Level, Metadata, Record};
|
|||||||
pub struct OdinLogger;
|
pub struct OdinLogger;
|
||||||
|
|
||||||
impl log::Log for OdinLogger {
|
impl log::Log for OdinLogger {
|
||||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||||
metadata.level() <= Level::Debug
|
metadata.level() <= Level::Debug
|
||||||
}
|
}
|
||||||
|
|
||||||
fn log(&self, record: &Record) {
|
fn log(&self, record: &Record) {
|
||||||
if self.enabled(record.metadata()) {
|
if self.enabled(record.metadata()) {
|
||||||
let prefix = format!(
|
let prefix = format!(
|
||||||
"{:width$}",
|
"{:width$}",
|
||||||
format!("[ODIN][{}]", record.level()),
|
format!("[ODIN][{}]", record.level()),
|
||||||
width = 13
|
width = 13
|
||||||
);
|
);
|
||||||
// This creates text blocks of logs if they include a new line.
|
// This creates text blocks of logs if they include a new line.
|
||||||
// I think it looks good <3
|
// I think it looks good <3
|
||||||
let message = format!("{} - {}", prefix, record.args())
|
let message = format!("{} - {}", prefix, record.args())
|
||||||
.replace("\n", format!("\n{} - ", prefix).as_str());
|
.replace("\n", format!("\n{} - ", prefix).as_str());
|
||||||
println!("{}", message);
|
println!("{}", message);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn flush(&self) {}
|
fn flush(&self) {}
|
||||||
}
|
}
|
||||||
|
|||||||
82
src/main.rs
82
src/main.rs
@@ -1,50 +1,64 @@
|
|||||||
|
use clap::{load_yaml, App};
|
||||||
|
use log::{debug, info, LevelFilter, SetLoggerError};
|
||||||
|
|
||||||
|
use crate::executable::handle_exit_status;
|
||||||
|
use crate::logger::OdinLogger;
|
||||||
|
use crate::utils::fetch_env;
|
||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
mod executable;
|
mod executable;
|
||||||
mod files;
|
mod files;
|
||||||
mod logger;
|
mod logger;
|
||||||
|
mod messages;
|
||||||
mod steamcmd;
|
mod steamcmd;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
use crate::executable::handle_exit_status;
|
|
||||||
use crate::logger::OdinLogger;
|
|
||||||
use clap::{load_yaml, App};
|
|
||||||
use log::{debug, LevelFilter, SetLoggerError};
|
|
||||||
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
static LOGGER: OdinLogger = OdinLogger;
|
static LOGGER: OdinLogger = OdinLogger;
|
||||||
static GAME_ID: i64 = 896660;
|
static GAME_ID: i64 = 896660;
|
||||||
|
|
||||||
fn setup_logger(debug: bool) -> Result<(), SetLoggerError> {
|
fn setup_logger(debug: bool) -> Result<(), SetLoggerError> {
|
||||||
let level = if debug {
|
let level = if debug {
|
||||||
LevelFilter::Debug
|
LevelFilter::Debug
|
||||||
} else {
|
} else {
|
||||||
LevelFilter::Info
|
LevelFilter::Info
|
||||||
};
|
};
|
||||||
let result = log::set_logger(&LOGGER).map(|_| log::set_max_level(level));
|
let result = log::set_logger(&LOGGER).map(|_| log::set_max_level(level));
|
||||||
debug!("Debugging set to {}", debug.to_string());
|
debug!("Debugging set to {}", debug.to_string());
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// The YAML file is found relative to the current file, similar to how modules are found
|
// The YAML file is found relative to the current file, similar to how modules are found
|
||||||
let yaml = load_yaml!("cli.yaml");
|
let yaml = load_yaml!("cli.yaml");
|
||||||
let app = App::from(yaml).version(VERSION);
|
let app = App::from(yaml).version(VERSION);
|
||||||
let matches = app.get_matches();
|
let matches = app.get_matches();
|
||||||
setup_logger(matches.is_present("debug")).unwrap();
|
let debug_mode = matches.is_present("debug") || fetch_env("DEBUG_MODE", "0", false).eq("1");
|
||||||
if let Some(ref configure_matches) = matches.subcommand_matches("configure") {
|
setup_logger(debug_mode).unwrap();
|
||||||
commands::configure::invoke(configure_matches);
|
|
||||||
};
|
if !debug_mode {
|
||||||
if let Some(ref _match) = matches.subcommand_matches("install") {
|
info!("Run with DEBUG_MODE as 1 if you think there is an issue with Odin");
|
||||||
let result = commands::install::invoke(GAME_ID);
|
}
|
||||||
handle_exit_status(result, "Successfully installed Valheim!".to_string())
|
debug!("Debug mode enabled!");
|
||||||
};
|
if let Some(ref configure_matches) = matches.subcommand_matches("configure") {
|
||||||
if let Some(ref start_matches) = matches.subcommand_matches("start") {
|
debug!("Launching configure command...");
|
||||||
commands::start::invoke(start_matches);
|
commands::configure::invoke(configure_matches);
|
||||||
};
|
};
|
||||||
if let Some(ref stop_matches) = matches.subcommand_matches("stop") {
|
if matches.subcommand_matches("install").is_some() {
|
||||||
commands::stop::invoke(stop_matches);
|
debug!("Launching install command...");
|
||||||
};
|
let result = commands::install::invoke(GAME_ID);
|
||||||
if let Some(ref backup_matches) = matches.subcommand_matches("backup") {
|
handle_exit_status(result, "Successfully installed Valheim!".to_string())
|
||||||
commands::backup::invoke(backup_matches);
|
};
|
||||||
};
|
if let Some(ref start_matches) = matches.subcommand_matches("start") {
|
||||||
|
debug!("Launching start command...");
|
||||||
|
commands::start::invoke(start_matches);
|
||||||
|
};
|
||||||
|
if let Some(ref stop_matches) = matches.subcommand_matches("stop") {
|
||||||
|
debug!("Launching stop command...");
|
||||||
|
commands::stop::invoke(stop_matches);
|
||||||
|
};
|
||||||
|
if let Some(ref backup_matches) = matches.subcommand_matches("backup") {
|
||||||
|
debug!("Launching backup command...");
|
||||||
|
commands::backup::invoke(backup_matches);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/messages/mod.rs
Normal file
13
src/messages/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use log::info;
|
||||||
|
|
||||||
|
pub fn modding_disclaimer() {
|
||||||
|
info!("##########################################################################################################################");
|
||||||
|
info!("DISCLAIMER! Modding your server can cause a lot of errors.");
|
||||||
|
info!("Please do NOT post issue on the valheim-docker repo based on mod issues.");
|
||||||
|
info!("By installing mods, you agree that you will do a root cause analysis to why your server is failing before you make a post.");
|
||||||
|
info!("Modding is currently unsupported by the Valheim developers and limited support by the valheim-docker repo.");
|
||||||
|
info!("If you have issues please contact the MOD developer FIRST based on the output logs.");
|
||||||
|
info!("----------------------------------------------------------------");
|
||||||
|
info!("Additional Note: BepInEx does not support SIGINT shutdown, which means you will have to manually save your world before shutting down.");
|
||||||
|
info!("##########################################################################################################################");
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ log "Starting auto backup process..."
|
|||||||
odin backup /home/steam/.config/unity3d/IronGate/Valheim "/home/steam/backups/${file_name}" || exit 1
|
odin backup /home/steam/.config/unity3d/IronGate/Valheim "/home/steam/backups/${file_name}" || exit 1
|
||||||
|
|
||||||
if [ "${AUTO_BACKUP_REMOVE_OLD:=0}" -eq 1 ]; then
|
if [ "${AUTO_BACKUP_REMOVE_OLD:=0}" -eq 1 ]; then
|
||||||
find /home/steam/backups/*.tar.gz -mtime +${AUTO_BACKUP_DAYS_TO_LIVE:-5} -exec rm {} \;
|
find /home/steam/backups -mtime +${AUTO_BACKUP_DAYS_TO_LIVE:-5} -exec rm {} \;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "Backup process complete! Created ${file_name}"
|
log "Backup process complete! Created ${file_name}"
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ setup_cron() {
|
|||||||
CRON_SCHEDULE=$3
|
CRON_SCHEDULE=$3
|
||||||
CRON_ENV="$4"
|
CRON_ENV="$4"
|
||||||
LOG_LOCATION="/home/steam/valheim/logs/$CRON_NAME.out"
|
LOG_LOCATION="/home/steam/valheim/logs/$CRON_NAME.out"
|
||||||
rm $LOG_LOCATION
|
rm $LOG_LOCATION > /dev/null
|
||||||
printf "%s %s /usr/sbin/gosu steam /bin/bash %s >> %s 2>&1" \
|
printf "%s %s /usr/sbin/gosu steam /bin/bash %s >> %s 2>&1" \
|
||||||
"${CRON_SCHEDULE}" \
|
"${CRON_SCHEDULE}" \
|
||||||
"${CRON_ENV:-""}" \
|
"${CRON_ENV:-""}" \
|
||||||
|
|||||||
@@ -31,44 +31,45 @@ cleanup() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
initialize "Installing Valheim via Odin..."
|
initialize "Installing Valheim via $(odin --version)..."
|
||||||
|
|
||||||
log "Variables loaded....."
|
log "Variables loaded....."
|
||||||
log "
|
log "Port: ${PORT}"
|
||||||
Port: ${PORT}
|
log "Name: ${NAME}"
|
||||||
Name: ${NAME}
|
log "World: ${WORLD}"
|
||||||
World: ${WORLD}
|
log "Public: ${PUBLIC}"
|
||||||
Public: ${PUBLIC}
|
log "Password: (REDACTED)"
|
||||||
Password: (REDACTED)
|
|
||||||
"
|
|
||||||
|
|
||||||
export SteamAppId=${APPID:-892970}
|
export SteamAppId=${APPID:-892970}
|
||||||
|
|
||||||
# Setting up server
|
# Setting up server
|
||||||
log "Running Install..."
|
log "Running Install..."
|
||||||
odin install || exit 1
|
if [ ! -f "./valheim_server.x86_64" ] || [ "${FORCE_INSTALL:-0}" -eq 1 ]; then
|
||||||
|
odin install || exit 1
|
||||||
|
else
|
||||||
|
log "Skipping install process, looks like valheim_server is already installed :)"
|
||||||
|
fi
|
||||||
|
cp /home/steam/steamcmd/linux64/steamclient.so /home/steam/valheim/linux64/
|
||||||
|
|
||||||
|
|
||||||
|
# Setting up server
|
||||||
log "Initializing Variables...."
|
log "Initializing Variables...."
|
||||||
odin configure || exit 1
|
odin configure || exit 1
|
||||||
|
|
||||||
|
|
||||||
|
# Setting up script traps
|
||||||
trap 'cleanup' INT TERM
|
trap 'cleanup' INT TERM
|
||||||
|
|
||||||
log "Herding Cats..."
|
# Starting server
|
||||||
log "Starting server..."
|
log "Starting server..."
|
||||||
|
|
||||||
odin start || exit 1
|
odin start || exit 1
|
||||||
|
|
||||||
initialize "
|
sleep 2
|
||||||
Valheim Server Started...
|
|
||||||
|
|
||||||
Keep an eye out for 'Game server connected' in the log!
|
|
||||||
(this indicates its online without any errors.)
|
|
||||||
" >> /home/steam/valheim/logs/output.log
|
|
||||||
|
|
||||||
|
|
||||||
|
# Initializing all logs
|
||||||
|
log "Herding Graydwarfs..."
|
||||||
log_names=("valheim_server.log" "valheim_server.err" "output.log" "auto-update.out" "auto-backup.out")
|
log_names=("valheim_server.log" "valheim_server.err" "output.log" "auto-update.out" "auto-backup.out")
|
||||||
log_files=("${log_names[@]/#/\/home\/steam\/valheim\/logs/}")
|
log_files=("${log_names[@]/#/\/home\/steam\/valheim\/logs/}")
|
||||||
touch "${log_files[@]}"
|
touch "${log_files[@]}" # Destroy logs on start up, this can be changed later to roll logs or archive them.
|
||||||
tail -F ${log_files[*]} &
|
tail -F ${log_files[*]} &
|
||||||
export TAIL_PID=$!
|
export TAIL_PID=$!
|
||||||
|
# Waiting for logs.
|
||||||
wait $TAIL_PID
|
wait $TAIL_PID
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ use std::process::{exit, Command};
|
|||||||
|
|
||||||
const STEAMCMD_EXE: &str = "/home/steam/steamcmd/steamcmd.sh";
|
const STEAMCMD_EXE: &str = "/home/steam/steamcmd/steamcmd.sh";
|
||||||
pub fn steamcmd_command() -> Command {
|
pub fn steamcmd_command() -> Command {
|
||||||
match find_command("steamcmd") {
|
match find_command("steamcmd") {
|
||||||
|
Some(steamcmd) => {
|
||||||
|
info!("steamcmd found in path");
|
||||||
|
steamcmd
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Checking for script under steam user.");
|
||||||
|
match find_command(STEAMCMD_EXE) {
|
||||||
Some(steamcmd) => {
|
Some(steamcmd) => {
|
||||||
info!("steamcmd found in path");
|
info!("Using steamcmd script at {}", STEAMCMD_EXE);
|
||||||
steamcmd
|
steamcmd
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
error!("Checking for script under steam user.");
|
error!("\nSteamCMD Executable Not Found! \nPlease install steamcmd... \nhttps://developer.valvesoftware.com/wiki/SteamCMD\n");
|
||||||
match find_command(STEAMCMD_EXE) {
|
exit(1);
|
||||||
Some(steamcmd) => {
|
|
||||||
info!("Using steamcmd script at {}", STEAMCMD_EXE);
|
|
||||||
steamcmd
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
error!("\nSteamCMD Executable Not Found! \nPlease install steamcmd... \nhttps://developer.valvesoftware.com/wiki/SteamCMD\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
106
src/utils/mod.rs
106
src/utils/mod.rs
@@ -1,45 +1,99 @@
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use log::error;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::File;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
|
const ODIN_WORKING_DIR: &str = "ODIN_WORKING_DIR";
|
||||||
pub const VALHEIM_EXECUTABLE_NAME: &str = "valheim_server.x86_64";
|
pub const VALHEIM_EXECUTABLE_NAME: &str = "valheim_server.x86_64";
|
||||||
|
|
||||||
pub fn get_working_dir() -> String {
|
pub fn get_working_dir() -> String {
|
||||||
env::current_dir().unwrap().to_str().unwrap().to_string()
|
fetch_env(
|
||||||
|
ODIN_WORKING_DIR,
|
||||||
|
env::current_dir().unwrap().to_str().unwrap(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_variable(args: &ArgMatches, name: &str, default: String) -> String {
|
pub fn get_variable(args: &ArgMatches, name: &str, default: String) -> String {
|
||||||
debug!("Checking env for {}", name);
|
debug!("Checking env for {}", name);
|
||||||
if let Ok(env_val) = env::var(name.to_uppercase()) {
|
if let Ok(env_val) = env::var(name.to_uppercase()) {
|
||||||
if !env_val.is_empty() {
|
if !env_val.is_empty() {
|
||||||
debug!("Env variable found {}={}", name, env_val);
|
debug!("Env variable found {}={}", name, env_val);
|
||||||
return env_val;
|
return env_val;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Ok(env_val) = env::var(format!("SERVER_{}", name).to_uppercase()) {
|
}
|
||||||
debug!("Env variable found {}={}", name, env_val);
|
if let Ok(env_val) = env::var(format!("SERVER_{}", name).to_uppercase()) {
|
||||||
return env_val;
|
debug!("Env variable found {}={}", name, env_val);
|
||||||
}
|
return env_val;
|
||||||
args.value_of(name)
|
}
|
||||||
.unwrap_or_else(|| default.as_str())
|
args
|
||||||
.to_string()
|
.value_of(name)
|
||||||
|
.unwrap_or_else(|| default.as_str())
|
||||||
|
.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server_installed() -> bool {
|
pub fn server_installed() -> bool {
|
||||||
Path::new(&[get_working_dir(), VALHEIM_EXECUTABLE_NAME.to_string()].join("/")).exists()
|
Path::new(&[get_working_dir(), VALHEIM_EXECUTABLE_NAME.to_string()].join("/")).exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_file(path: &str) -> File {
|
pub(crate) fn fetch_env(name: &str, default: &str, is_multiple: bool) -> String {
|
||||||
let output_path = Path::new(path);
|
let mut formatted_value = match env::var(name) {
|
||||||
match File::create(output_path) {
|
Ok(val) => val.replace("\"", ""),
|
||||||
Ok(file) => file,
|
Err(_) => {
|
||||||
Err(_) => {
|
debug!("Using default env var '{}': '{}'", name, default);
|
||||||
error!("Failed to create {}", path);
|
default.to_string()
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
if is_multiple && !formatted_value.is_empty() {
|
||||||
|
formatted_value = format!("{}:", formatted_value)
|
||||||
|
}
|
||||||
|
debug!("Found env var '{}': '{}'", name, formatted_value);
|
||||||
|
formatted_value
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod fetch_env_tests {
|
||||||
|
use crate::utils::fetch_env;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_multiple_false() {
|
||||||
|
let expected_key = "is_multiple_false";
|
||||||
|
let expected_value = "123";
|
||||||
|
env::set_var(expected_key, expected_value);
|
||||||
|
let observed_value = fetch_env(expected_key, "", false);
|
||||||
|
assert_eq!(expected_value, observed_value);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn is_multiple_true() {
|
||||||
|
let expected_key = "is_multiple_true";
|
||||||
|
let expected_value = "456";
|
||||||
|
env::set_var(expected_key, expected_value);
|
||||||
|
let observed_value = fetch_env(expected_key, "", false);
|
||||||
|
assert_eq!(expected_value, observed_value);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn has_default() {
|
||||||
|
let expected_key = "has_default";
|
||||||
|
let expected_value = "789";
|
||||||
|
env::remove_var(expected_key);
|
||||||
|
let observed_value = fetch_env(expected_key, expected_value, false);
|
||||||
|
assert_eq!(expected_value, observed_value);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn is_empty() {
|
||||||
|
let expected_key = "is_empty";
|
||||||
|
let expected_value = "";
|
||||||
|
env::remove_var(expected_key);
|
||||||
|
let observed_value = fetch_env(expected_key, expected_value, false);
|
||||||
|
assert_eq!(expected_value, observed_value);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn is_empty_multiple() {
|
||||||
|
let expected_key = "is_empty_multiple";
|
||||||
|
let expected_value = "";
|
||||||
|
env::remove_var(expected_key);
|
||||||
|
let observed_value = fetch_env(expected_key, expected_value, true);
|
||||||
|
assert_eq!(expected_value, observed_value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user