mirror of
https://github.com/mbround18/valheim-docker.git
synced 2021-10-22 21:53:54 +03:00
Update check without shutdown (#177)
* Add in update subcommand * Setup skeleton for updating * Reorganize server functions and constants * Cleanup logic * Add more help info for cli * Add in parsing buildids * Test cleanup * Remove appinfo to force latest info to be pulled * Change update script to use `odin update` * Implement review comments * Fix incorrect command exit status check * Add in unit tests for `UpdateInfo` * Small spelling and grammar fixes * Update `AUTO_UPDATE` description * Another grammatical fix * Address review comments * Update outdated message * Fix check exit statuses * Supress `pidof` output * Use more appropriate variable name Co-authored-by: Michael <12646562+mbround18@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
78e6b3610a
commit
3e2c851a7f
5
Cargo.lock
generated
5
Cargo.lock
generated
@@ -609,6 +609,7 @@ dependencies = [
|
||||
"flate2",
|
||||
"inflections",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"reqwest",
|
||||
"serde",
|
||||
@@ -621,9 +622,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.5.2"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
||||
checksum = "10acf907b94fc1b1a152d08ef97e7759650268cf986bf127f387e602b02c7e5a"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
|
||||
@@ -21,6 +21,7 @@ reqwest = { version = "0.11.1", features = ["blocking", "json"] }
|
||||
chrono = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
once_cell = "1.7"
|
||||
rand = "0.8.3"
|
||||
serial_test = "0.5.1"
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
| WORLD | `Dedicated` | TRUE | This is used to generate the name of your world. |
|
||||
| PUBLIC | `1` | FALSE | Sets whether or not your server is public on the server list. |
|
||||
| PASSWORD | `12345` | TRUE | Set this to something unique! |
|
||||
| AUTO_UPDATE | `0` | FALSE | Set to `1` if you want your container to auto update! This means at 1 am it will update, stop, and then restart your server. |
|
||||
| AUTO_UPDATE | `0` | FALSE | Set to `1` if you want your container to auto update! This means at the times indicated by `AUTO_UPDATE_SCHEDULE` it will check for server updates. If there is an update then the server will be shut down, updated, and brought back online if the server was running before. |
|
||||
| AUTO_UPDATE_SCHEDULE | `0 1 * * *` | FALSE | This works in conjunction with `AUTO_UPDATE` and sets the schedule to which it will run an auto update. [If you need help figuring out a cron schedule click here]
|
||||
| AUTO_BACKUP | `0` | FALSE | Set to `1` to enable auto backups. Backups are stored under `/home/steam/backups` which means you will have to add a volume mount for this directory. |
|
||||
| AUTO_BACKUP_SCHEDULE | `*/15 * * * *` | FALSE | Change to set how frequently you would like the server to backup. [If you need help figuring out a cron schedule click here].
|
||||
|
||||
22
src/cli.yaml
22
src/cli.yaml
@@ -82,6 +82,28 @@ subcommands:
|
||||
about: Sets the output file to use
|
||||
required: true
|
||||
index: 2
|
||||
- update:
|
||||
about: >
|
||||
Attempts to update an existing Valheim server installation. By
|
||||
default this involves checking for an update, if an update is
|
||||
available, the server will be shut down, updated, and brought back online
|
||||
if it was running before. If no update is available then there should
|
||||
be no effect from calling this.
|
||||
args:
|
||||
- check:
|
||||
long: check
|
||||
short: c
|
||||
about: >
|
||||
Check for a server update, exiting with 0 if one is available
|
||||
and 10 if the server is up to date.
|
||||
conflicts_with:
|
||||
- force
|
||||
- force:
|
||||
long: force
|
||||
short: f
|
||||
about: Force an update attempt, even if no update is detected.
|
||||
conflicts_with:
|
||||
- check
|
||||
- notify:
|
||||
about: Sends a notification to the provided webhook.
|
||||
version: "1.1"
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
use crate::executable::execute_mut;
|
||||
use crate::steamcmd::steamcmd_command;
|
||||
use crate::utils::get_working_dir;
|
||||
use log::{debug, info};
|
||||
use std::process::{ExitStatus, Stdio};
|
||||
use crate::server;
|
||||
use std::process::ExitStatus;
|
||||
|
||||
pub fn invoke(app_id: i64) -> std::io::Result<ExitStatus> {
|
||||
info!("Installing {} to {}", app_id, get_working_dir());
|
||||
let login = "+login anonymous".to_string();
|
||||
debug!("Argument set: {}", login);
|
||||
let force_install_dir = format!("+force_install_dir {}", get_working_dir());
|
||||
debug!("Argument set: {}", force_install_dir);
|
||||
let app_update = format!("+app_update {}", app_id);
|
||||
debug!("Argument set: {}", app_update);
|
||||
let mut steamcmd = steamcmd_command();
|
||||
debug!("Setting up install command...");
|
||||
let install_command = steamcmd
|
||||
.args(&[login, force_install_dir, app_update])
|
||||
.arg("+quit")
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
debug!("Launching up install command...");
|
||||
execute_mut(install_command)
|
||||
server::install(app_id)
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ pub mod install;
|
||||
pub mod notify;
|
||||
pub mod start;
|
||||
pub mod stop;
|
||||
pub mod update;
|
||||
|
||||
@@ -1,107 +1,35 @@
|
||||
mod bepinex;
|
||||
|
||||
use crate::commands::start::bepinex::{build_environment, is_bepinex_installed};
|
||||
use crate::executable::create_execution;
|
||||
use crate::files::config::{config_file, read_config};
|
||||
use crate::files::{create_file, ValheimArguments};
|
||||
use crate::messages::modding_disclaimer;
|
||||
use crate::utils::{environment, get_working_dir};
|
||||
use crate::files::config::load_config;
|
||||
use crate::server;
|
||||
use clap::ArgMatches;
|
||||
use daemonize::Daemonize;
|
||||
use log::{debug, error, info};
|
||||
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 = environment::fetch_multiple_var(
|
||||
LD_LIBRARY_PATH_VAR,
|
||||
format!("{}/linux64", get_working_dir()).as_str(),
|
||||
);
|
||||
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", environment::fetch_var("APPID", "892970"))
|
||||
.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()
|
||||
}
|
||||
}
|
||||
use std::process::exit;
|
||||
|
||||
pub fn invoke(args: &ArgMatches) {
|
||||
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 config = load_config();
|
||||
|
||||
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));
|
||||
|
||||
match daemonize.start() {
|
||||
match server::start_daemonized(config) {
|
||||
Ok(_) => info!("Success, daemonized"),
|
||||
Err(e) => error!("Error, {}", e),
|
||||
Err(e) => {
|
||||
error!("Error: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
} 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,
|
||||
&config.command,
|
||||
&config.port,
|
||||
&config.name,
|
||||
&config.world,
|
||||
&config.password,
|
||||
&config.public,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,20 @@
|
||||
use crate::utils::{get_working_dir, server_installed, VALHEIM_EXECUTABLE_NAME};
|
||||
|
||||
use clap::ArgMatches;
|
||||
use log::{error, info};
|
||||
use sysinfo::{ProcessExt, Signal, System, SystemExt};
|
||||
|
||||
use std::{thread, time::Duration};
|
||||
use std::process::exit;
|
||||
|
||||
fn send_shutdown() {
|
||||
info!("Scanning for Valheim process");
|
||||
let mut system = System::new();
|
||||
system.refresh_all();
|
||||
let processes = system.get_process_by_name(VALHEIM_EXECUTABLE_NAME);
|
||||
if processes.is_empty() {
|
||||
info!("Process NOT found!")
|
||||
} else {
|
||||
for found_process in processes {
|
||||
info!(
|
||||
"Found Process with pid {}! Sending Interrupt!",
|
||||
found_process.pid()
|
||||
);
|
||||
if found_process.kill(Signal::Interrupt) {
|
||||
info!("Process signal interrupt sent successfully!")
|
||||
} else {
|
||||
error!("Failed to send signal interrupt!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_server_exit() {
|
||||
info!("Waiting for server to completely shutdown...");
|
||||
let mut system = System::new();
|
||||
loop {
|
||||
system.refresh_all();
|
||||
let processes = system.get_process_by_name(VALHEIM_EXECUTABLE_NAME);
|
||||
if processes.is_empty() {
|
||||
break;
|
||||
} else {
|
||||
// Delay to keep down CPU usage
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
info!("Server has been shutdown successfully!")
|
||||
}
|
||||
use crate::{constants, server, utils::get_working_dir};
|
||||
|
||||
pub fn invoke(args: &ArgMatches) {
|
||||
info!("Stopping server {}", get_working_dir());
|
||||
if args.is_present("dry_run") {
|
||||
info!("This command would have run: ");
|
||||
info!("kill -2 {}", VALHEIM_EXECUTABLE_NAME)
|
||||
info!("kill -2 {}", constants::VALHEIM_EXECUTABLE_NAME)
|
||||
} else {
|
||||
if !server_installed() {
|
||||
if !server::is_installed() {
|
||||
error!("Failed to find server executable!");
|
||||
return;
|
||||
exit(1);
|
||||
}
|
||||
send_shutdown();
|
||||
wait_for_server_exit();
|
||||
server::blocking_shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
159
src/commands/update.rs
Normal file
159
src/commands/update.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
use clap::ArgMatches;
|
||||
use log::{debug, error, info};
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
use crate::server;
|
||||
|
||||
const EXIT_NO_UPDATE_AVAILABLE: i32 = 10;
|
||||
const EXIT_UPDATE_AVAILABLE: i32 = 0;
|
||||
|
||||
enum UpdateAction {
|
||||
Check,
|
||||
Force,
|
||||
Regular,
|
||||
}
|
||||
|
||||
impl UpdateAction {
|
||||
fn new(check: bool, force: bool) -> Self {
|
||||
match (check, force) {
|
||||
(true, true) => panic!("`check` and `force` are mutually exlusive!"),
|
||||
(true, false) => Self::Check,
|
||||
(false, true) => Self::Force,
|
||||
(false, false) => Self::Regular,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RunAction {
|
||||
Real,
|
||||
Dry,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum UpdateState {
|
||||
Pending,
|
||||
UpToDate,
|
||||
}
|
||||
|
||||
impl UpdateState {
|
||||
fn new() -> Self {
|
||||
if server::update_is_available() {
|
||||
Self::Pending
|
||||
} else {
|
||||
Self::UpToDate
|
||||
}
|
||||
}
|
||||
|
||||
fn as_exit_code(&self) -> i32 {
|
||||
match self {
|
||||
Self::UpToDate => EXIT_NO_UPDATE_AVAILABLE,
|
||||
Self::Pending => EXIT_UPDATE_AVAILABLE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ServerState {
|
||||
Running,
|
||||
Stopped,
|
||||
}
|
||||
|
||||
impl ServerState {
|
||||
fn new() -> Self {
|
||||
if server::is_running() {
|
||||
Self::Running
|
||||
} else {
|
||||
Self::Stopped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invoke(args: &ArgMatches) {
|
||||
info!("Checking for updates");
|
||||
|
||||
if !server::is_installed() {
|
||||
error!(
|
||||
"Failed to find server executable. Can't update! If the server isn't installed yet then you \
|
||||
likely need to run `odin install`."
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let run_action = if args.is_present("dry_run") {
|
||||
RunAction::Dry
|
||||
} else {
|
||||
RunAction::Real
|
||||
};
|
||||
let check = args.is_present("check");
|
||||
let force = args.is_present("force");
|
||||
|
||||
let server_state = ServerState::new();
|
||||
let update_state = UpdateState::new();
|
||||
match update_state {
|
||||
UpdateState::Pending => info!("A server update is available!"),
|
||||
UpdateState::UpToDate => info!("No server updates found"),
|
||||
}
|
||||
|
||||
match UpdateAction::new(check, force) {
|
||||
UpdateAction::Check => update_check(run_action, update_state),
|
||||
UpdateAction::Force => update_force(run_action, server_state),
|
||||
UpdateAction::Regular => update_regular(run_action, server_state, update_state),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_check(run_action: RunAction, update_state: UpdateState) {
|
||||
match (run_action, update_state) {
|
||||
(RunAction::Dry, UpdateState::Pending) => {
|
||||
info!(
|
||||
"Dry run: An update is available. This would exit with {} to indicate this.",
|
||||
update_state.as_exit_code()
|
||||
)
|
||||
}
|
||||
(RunAction::Dry, UpdateState::UpToDate) => {
|
||||
info!(
|
||||
"Dry run: No update is available. This would exit with {} to indicate this.",
|
||||
update_state.as_exit_code()
|
||||
)
|
||||
}
|
||||
(_, update_state) => exit(update_state.as_exit_code()),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_force(run_action: RunAction, server_state: ServerState) {
|
||||
match (run_action, server_state) {
|
||||
(RunAction::Dry, ServerState::Running) => {
|
||||
info!("Dry run: Server would be shutdown, updated, and brought back online")
|
||||
}
|
||||
(RunAction::Dry, ServerState::Stopped) => {
|
||||
info!("Dry run: The server is offline and would be updated")
|
||||
}
|
||||
_ => {
|
||||
debug!("Force updating!");
|
||||
server::update_server();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_regular(run_action: RunAction, server_state: ServerState, update_state: UpdateState) {
|
||||
match (run_action, server_state, update_state) {
|
||||
(RunAction::Dry, ServerState::Running, UpdateState::Pending) => {
|
||||
info!(
|
||||
"Dry run: An update is available and the server is ONLINE. The server would be shutdown \
|
||||
updated, and brought back online."
|
||||
)
|
||||
}
|
||||
(RunAction::Dry, ServerState::Stopped, UpdateState::Pending) => {
|
||||
info!(
|
||||
"Dry run: An update is available and the server is OFFLINE. The server would be updated."
|
||||
)
|
||||
}
|
||||
(RunAction::Dry, _, UpdateState::UpToDate) => {
|
||||
info!("Dry run: No update is available. Nothing to do.")
|
||||
}
|
||||
(_, _, UpdateState::Pending) => {
|
||||
debug!("Updating the installation!");
|
||||
server::update_server()
|
||||
}
|
||||
_ => debug!("No update available, nothing to do!"),
|
||||
}
|
||||
}
|
||||
9
src/constants.rs
Normal file
9
src/constants.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub const GAME_ID: i64 = 896660;
|
||||
|
||||
pub const VALHEIM_EXECUTABLE_NAME: &str = "valheim_server.x86_64";
|
||||
|
||||
pub const LD_LIBRARY_PATH_VAR: &str = "LD_LIBRARY_PATH";
|
||||
pub const LD_PRELOAD_VAR: &str = "LD_PRELOAD";
|
||||
pub const ODIN_WORKING_DIR: &str = "ODIN_WORKING_DIR";
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::constants;
|
||||
use crate::files::ValheimArguments;
|
||||
use crate::files::{FileManager, ManagedFile};
|
||||
use crate::utils::environment::fetch_var;
|
||||
use crate::utils::{get_variable, get_working_dir, VALHEIM_EXECUTABLE_NAME};
|
||||
use crate::utils::{get_variable, get_working_dir};
|
||||
use clap::ArgMatches;
|
||||
use log::{debug, error};
|
||||
use std::fs;
|
||||
@@ -10,6 +11,19 @@ use std::process::exit;
|
||||
|
||||
const ODIN_CONFIG_FILE_VAR: &str = "ODIN_CONFIG_FILE";
|
||||
|
||||
pub fn load_config() -> ValheimArguments {
|
||||
let file = config_file();
|
||||
let config = read_config(file);
|
||||
|
||||
debug!("Checking password compliance...");
|
||||
if config.password.len() < 5 {
|
||||
error!("The supplied password is too short! It must be 5 characters or greater!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
pub fn config_file() -> ManagedFile {
|
||||
let name = fetch_var(ODIN_CONFIG_FILE_VAR, "config.json");
|
||||
debug!("Config file set to: {}", name);
|
||||
@@ -25,7 +39,11 @@ pub fn read_config(config: ManagedFile) -> ValheimArguments {
|
||||
}
|
||||
|
||||
pub fn write_config(config: ManagedFile, args: &ArgMatches) -> bool {
|
||||
let server_executable: &str = &[get_working_dir(), VALHEIM_EXECUTABLE_NAME.to_string()].join("/");
|
||||
let server_executable: &str = &[
|
||||
get_working_dir(),
|
||||
constants::VALHEIM_EXECUTABLE_NAME.to_string(),
|
||||
]
|
||||
.join("/");
|
||||
let command = match fs::canonicalize(PathBuf::from(get_variable(
|
||||
args,
|
||||
"server_executable",
|
||||
|
||||
14
src/main.rs
14
src/main.rs
@@ -5,21 +5,22 @@ use crate::executable::handle_exit_status;
|
||||
use crate::logger::OdinLogger;
|
||||
use crate::utils::environment;
|
||||
mod commands;
|
||||
mod constants;
|
||||
mod errors;
|
||||
mod executable;
|
||||
mod files;
|
||||
mod logger;
|
||||
mod messages;
|
||||
mod mods;
|
||||
mod notifications;
|
||||
mod server;
|
||||
mod steamcmd;
|
||||
mod utils;
|
||||
|
||||
use crate::notifications::enums::event_status::EventStatus;
|
||||
use crate::notifications::enums::notification_event::NotificationEvent;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
static LOGGER: OdinLogger = OdinLogger;
|
||||
static GAME_ID: i64 = 896660;
|
||||
|
||||
fn setup_logger(debug: bool) -> Result<(), SetLoggerError> {
|
||||
let level = if debug {
|
||||
@@ -35,7 +36,7 @@ fn setup_logger(debug: bool) -> Result<(), SetLoggerError> {
|
||||
fn main() {
|
||||
// The YAML file is found relative to the current file, similar to how modules are found
|
||||
let yaml = load_yaml!("cli.yaml");
|
||||
let app = App::from(yaml).version(VERSION);
|
||||
let app = App::from(yaml).version(constants::VERSION);
|
||||
let matches = app.get_matches();
|
||||
let debug_mode = matches.is_present("debug") || environment::fetch_var("DEBUG_MODE", "0").eq("1");
|
||||
setup_logger(debug_mode).unwrap();
|
||||
@@ -49,7 +50,7 @@ fn main() {
|
||||
};
|
||||
if let Some(ref _match) = matches.subcommand_matches("install") {
|
||||
debug!("Launching install command...");
|
||||
let result = commands::install::invoke(GAME_ID);
|
||||
let result = commands::install::invoke(constants::GAME_ID);
|
||||
handle_exit_status(result, "Successfully installed Valheim!".to_string())
|
||||
};
|
||||
if let Some(ref start_matches) = matches.subcommand_matches("start") {
|
||||
@@ -72,4 +73,9 @@ fn main() {
|
||||
debug!("Launching notify command...");
|
||||
commands::notify::invoke(notify_matches);
|
||||
};
|
||||
|
||||
if let Some(ref update_matches) = matches.subcommand_matches("update") {
|
||||
debug!("Launching update command...");
|
||||
commands::update::invoke(update_matches);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::commands::start::{LD_LIBRARY_PATH_VAR, LD_PRELOAD_VAR};
|
||||
use crate::constants;
|
||||
use crate::utils::{environment, get_working_dir, path_exists};
|
||||
use log::{debug, info};
|
||||
use std::ops::Add;
|
||||
@@ -46,9 +46,10 @@ pub struct BepInExEnvironment {
|
||||
}
|
||||
|
||||
pub fn build_environment() -> BepInExEnvironment {
|
||||
let ld_preload = environment::fetch_var(LD_PRELOAD_VAR, "").add(doorstop_lib().as_str());
|
||||
let ld_preload =
|
||||
environment::fetch_var(constants::LD_PRELOAD_VAR, "").add(doorstop_lib().as_str());
|
||||
let ld_library_path = environment::fetch_var(
|
||||
LD_LIBRARY_PATH_VAR,
|
||||
constants::LD_LIBRARY_PATH_VAR,
|
||||
format!("./linux64:{}", doorstop_libs()).as_str(),
|
||||
);
|
||||
let doorstop_invoke_dll_value = doorstop_invoke_dll();
|
||||
@@ -122,9 +123,9 @@ pub fn invoke(command: &mut Command, environment: &BepInExEnvironment) -> std::i
|
||||
&environment.doorstop_corlib_override_path,
|
||||
)
|
||||
// LD_LIBRARY_PATH must not have quotes around it.
|
||||
.env(LD_LIBRARY_PATH_VAR, &environment.ld_library_path)
|
||||
.env(constants::LD_LIBRARY_PATH_VAR, &environment.ld_library_path)
|
||||
// LD_PRELOAD must not have quotes around it.
|
||||
.env(LD_PRELOAD_VAR, &environment.ld_preload)
|
||||
.env(constants::LD_PRELOAD_VAR, &environment.ld_preload)
|
||||
// DYLD_LIBRARY_PATH is weird af and MUST have quotes around it.
|
||||
.env(
|
||||
DYLD_LIBRARY_PATH_VAR,
|
||||
1
src/mods/mod.rs
Normal file
1
src/mods/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod bepinex;
|
||||
@@ -22,25 +22,38 @@ Password: (REDACTED)
|
||||
"
|
||||
line
|
||||
|
||||
|
||||
cd /home/steam/valheim || exit 1
|
||||
log "Stopping server..."
|
||||
odin stop || exit 1
|
||||
|
||||
if [ "${AUTO_BACKUP_ON_UPDATE:=0}" -eq 1 ]; then
|
||||
/bin/bash /home/steam/scripts/auto_backup.sh "pre-update-backup"
|
||||
if odin update --check; then
|
||||
log "An update is available. Starting the update process..."
|
||||
|
||||
# Store if the server is currently running
|
||||
! pidof valheim_server.x86_64 > /dev/null
|
||||
SERVER_RUNNING=$?
|
||||
|
||||
# Stop the server if it's running
|
||||
if [ "${SERVER_RUNNING}" -eq 1 ]; then
|
||||
odin stop || exit 1
|
||||
fi
|
||||
|
||||
if [ "${AUTO_BACKUP_ON_UPDATE:=0}" -eq 1 ]; then
|
||||
/bin/bash /home/steam/scripts/auto_backup.sh "pre-update-backup"
|
||||
fi
|
||||
|
||||
odin update || exit 1
|
||||
|
||||
# Start the server if it was running before
|
||||
if [ "${SERVER_RUNNING}" -eq 1 ]; then
|
||||
odin start || exit 1
|
||||
line
|
||||
log "
|
||||
Finished updating and everything looks happy <3
|
||||
|
||||
Check your output.log for 'Game server connected'
|
||||
"
|
||||
fi
|
||||
else
|
||||
log "No update available"
|
||||
fi
|
||||
|
||||
log "Installing Updates..."
|
||||
odin install || exit 1
|
||||
log "Starting server..."
|
||||
odin start || exit 1
|
||||
line
|
||||
log "
|
||||
Everything looks happy <3
|
||||
|
||||
Check your output.log for 'Game server connected'
|
||||
"
|
||||
line
|
||||
|
||||
|
||||
|
||||
34
src/server/install.rs
Normal file
34
src/server/install.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use log::{debug, info};
|
||||
|
||||
use std::{
|
||||
io,
|
||||
path::Path,
|
||||
process::{ExitStatus, Stdio},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
constants, executable::execute_mut, steamcmd::steamcmd_command, utils::get_working_dir,
|
||||
};
|
||||
|
||||
pub fn is_installed() -> bool {
|
||||
Path::new(&get_working_dir())
|
||||
.join(constants::VALHEIM_EXECUTABLE_NAME)
|
||||
.exists()
|
||||
}
|
||||
|
||||
pub fn install(app_id: i64) -> io::Result<ExitStatus> {
|
||||
info!("Installing {} to {}", app_id, get_working_dir());
|
||||
|
||||
let login = "+login anonymous".to_string();
|
||||
let force_install_dir = format!("+force_install_dir {}", get_working_dir());
|
||||
let app_update = format!("+app_update {}", app_id);
|
||||
let mut steamcmd = steamcmd_command();
|
||||
let install_command = steamcmd
|
||||
.args(&[login, force_install_dir, app_update])
|
||||
.arg("+quit")
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit());
|
||||
debug!("Launching install command: {:#?}", install_command);
|
||||
|
||||
execute_mut(install_command)
|
||||
}
|
||||
8
src/server/mod.rs
Normal file
8
src/server/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
mod install;
|
||||
mod shutdown;
|
||||
mod startup;
|
||||
mod update;
|
||||
mod utils;
|
||||
|
||||
// Rexport all public functions
|
||||
pub use crate::server::{install::*, shutdown::*, startup::*, update::*, utils::*};
|
||||
49
src/server/shutdown.rs
Normal file
49
src/server/shutdown.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use log::{error, info};
|
||||
use sysinfo::{ProcessExt, Signal, System, SystemExt};
|
||||
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
use crate::constants;
|
||||
|
||||
pub fn blocking_shutdown() {
|
||||
send_shutdown_signal();
|
||||
wait_for_exit();
|
||||
}
|
||||
|
||||
pub fn send_shutdown_signal() {
|
||||
info!("Scanning for Valheim process");
|
||||
let mut system = System::new();
|
||||
system.refresh_all();
|
||||
let processes = system.get_process_by_name(constants::VALHEIM_EXECUTABLE_NAME);
|
||||
if processes.is_empty() {
|
||||
info!("Process NOT found!")
|
||||
} else {
|
||||
for found_process in processes {
|
||||
info!(
|
||||
"Found Process with pid {}! Sending Interrupt!",
|
||||
found_process.pid()
|
||||
);
|
||||
if found_process.kill(Signal::Interrupt) {
|
||||
info!("Process signal interrupt sent successfully!")
|
||||
} else {
|
||||
error!("Failed to send signal interrupt!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_for_exit() {
|
||||
info!("Waiting for server to completely shutdown...");
|
||||
let mut system = System::new();
|
||||
loop {
|
||||
system.refresh_all();
|
||||
let processes = system.get_process_by_name(constants::VALHEIM_EXECUTABLE_NAME);
|
||||
if processes.is_empty() {
|
||||
break;
|
||||
} else {
|
||||
// Delay to keep down CPU usage
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
info!("Server has been shutdown successfully!")
|
||||
}
|
||||
77
src/server/startup.rs
Normal file
77
src/server/startup.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use daemonize::{Daemonize, DaemonizeError};
|
||||
use log::{debug, info};
|
||||
|
||||
use std::{io, process::Child};
|
||||
|
||||
use crate::{
|
||||
constants,
|
||||
executable::create_execution,
|
||||
files::{create_file, ValheimArguments},
|
||||
messages,
|
||||
mods::bepinex,
|
||||
utils::{environment, get_working_dir},
|
||||
};
|
||||
|
||||
type CommandResult = io::Result<Child>;
|
||||
|
||||
pub fn start_daemonized(config: ValheimArguments) -> Result<CommandResult, DaemonizeError> {
|
||||
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());
|
||||
Daemonize::new()
|
||||
.working_directory(get_working_dir())
|
||||
.user("steam")
|
||||
.group("steam")
|
||||
.stdout(stdout)
|
||||
.stderr(stderr)
|
||||
.exit_action(|| {
|
||||
if bepinex::is_bepinex_installed() {
|
||||
info!("Server has been started with BepInEx! Keep in mind this may cause errors!!");
|
||||
messages::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.)")
|
||||
})
|
||||
.privileged_action(move || start(&config))
|
||||
.start()
|
||||
}
|
||||
|
||||
pub fn start(config: &ValheimArguments) -> CommandResult {
|
||||
let mut command = create_execution(&config.command);
|
||||
info!("--------------------------------------------------------------------------------------------------------------");
|
||||
let ld_library_path_value = environment::fetch_multiple_var(
|
||||
constants::LD_LIBRARY_PATH_VAR,
|
||||
format!("{}/linux64", get_working_dir()).as_str(),
|
||||
);
|
||||
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", environment::fetch_var("APPID", "892970"))
|
||||
.current_dir(get_working_dir());
|
||||
info!("Executable: {}", &config.command);
|
||||
info!("Launching Command...");
|
||||
|
||||
if bepinex::is_bepinex_installed() {
|
||||
info!("BepInEx detected! Switching to run with BepInEx...");
|
||||
let bepinex_env = bepinex::build_environment();
|
||||
bepinex::invoke(base_command, &bepinex_env)
|
||||
} else {
|
||||
info!("Everything looks good! Running normally!");
|
||||
base_command
|
||||
.env(constants::LD_LIBRARY_PATH_VAR, ld_library_path_value)
|
||||
.spawn()
|
||||
}
|
||||
}
|
||||
250
src/server/update.rs
Normal file
250
src/server/update.rs
Normal file
@@ -0,0 +1,250 @@
|
||||
use log::{debug, error, info};
|
||||
|
||||
use std::{fs, io::ErrorKind, path::Path, process::exit};
|
||||
|
||||
use crate::{
|
||||
constants, files::config::load_config, server, steamcmd::steamcmd_command, utils::get_working_dir,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct UpdateInfo {
|
||||
current_build_id: String,
|
||||
latest_build_id: String,
|
||||
}
|
||||
|
||||
impl UpdateInfo {
|
||||
pub fn new() -> Self {
|
||||
let current_build_id = get_current_build_id();
|
||||
let latest_build_id = get_latest_build_id();
|
||||
|
||||
Self::internal_new(current_build_id, latest_build_id)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new_testing(manifest_contents: &str, app_info_output: &str) -> Self {
|
||||
let current_build_id = extract_build_id_from_manifest(manifest_contents).to_string();
|
||||
let latest_build_id = extract_build_id_from_app_info(app_info_output).to_string();
|
||||
|
||||
Self::internal_new(current_build_id, latest_build_id)
|
||||
}
|
||||
|
||||
fn internal_new(current_build_id: String, latest_build_id: String) -> Self {
|
||||
Self {
|
||||
current_build_id,
|
||||
latest_build_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_available(&self) -> bool {
|
||||
self.current_build_id != self.latest_build_id
|
||||
}
|
||||
|
||||
// pub fn current_build_id(&self) -> &str {
|
||||
// &self.current_build_id
|
||||
// }
|
||||
|
||||
// pub fn latest_build_id(&self) -> &str {
|
||||
// &self.latest_build_id
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn update_is_available() -> bool {
|
||||
let info = UpdateInfo::new();
|
||||
debug!("{:#?}", info);
|
||||
|
||||
info.update_available()
|
||||
}
|
||||
|
||||
pub fn update_server() {
|
||||
// Shutdown the server if it's running
|
||||
let server_was_running = server::is_running();
|
||||
if server_was_running {
|
||||
server::blocking_shutdown();
|
||||
}
|
||||
|
||||
// Update the installation
|
||||
if let Err(e) = server::install(constants::GAME_ID) {
|
||||
error!("Failed to install server: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Bring the server up if it was running before
|
||||
if server_was_running {
|
||||
let config = load_config();
|
||||
match server::start_daemonized(config) {
|
||||
Ok(_) => info!("Server daemon started"),
|
||||
Err(e) => {
|
||||
error!("Error daemonizing: {}", e);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_build_id() -> String {
|
||||
let manifest_path = Path::new(&get_working_dir())
|
||||
.join("steamapps")
|
||||
.join(&format!("appmanifest_{}.acf", constants::GAME_ID));
|
||||
let manifest_data = fs::read_to_string(&manifest_path).unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Failed to read manifest file at '{}'",
|
||||
manifest_path.display()
|
||||
)
|
||||
});
|
||||
extract_build_id_from_manifest(&manifest_data).to_string()
|
||||
}
|
||||
|
||||
fn get_latest_build_id() -> String {
|
||||
// Remove the cached file to force an updated response. This is done because `steamcmd` seems to
|
||||
// refuse to update information before querying the app_info even with `+app_info_update 1` or
|
||||
// `+@bCSForceNoCache 1`
|
||||
let appinfo_file = Path::new("/home/steam/Steam/appcache/appinfo.vdf");
|
||||
fs::remove_file(&appinfo_file).unwrap_or_else(|e| match e.kind() {
|
||||
// AOK if it doesn't exist
|
||||
ErrorKind::NotFound => {}
|
||||
err_kind => {
|
||||
error!(
|
||||
"Failed to remove appinfo file at '{}'! Error: {:?}",
|
||||
appinfo_file.display(),
|
||||
err_kind
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Now pull the latest app info
|
||||
let args = &[
|
||||
"+@ShutdownOnFailedCommand 1",
|
||||
"+login anonymous",
|
||||
&format!("+app_info_print {}", constants::GAME_ID),
|
||||
"+quit",
|
||||
];
|
||||
let mut steamcmd = steamcmd_command();
|
||||
let app_info_output = steamcmd
|
||||
.args(args)
|
||||
.output()
|
||||
.expect("Failed to run steamcmd");
|
||||
assert!(app_info_output.status.success());
|
||||
|
||||
let stdout = String::from_utf8(app_info_output.stdout).expect("steamcmd returned invalid UTF-8");
|
||||
extract_build_id_from_app_info(&stdout).to_string()
|
||||
}
|
||||
|
||||
fn extract_build_id_from_manifest(manifest: &str) -> &str {
|
||||
for line in manifest.lines() {
|
||||
if line.trim().starts_with("\"buildid\"") {
|
||||
return split_vdf_key_val(line).1;
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Unexpected manifest format:\n{}", manifest);
|
||||
}
|
||||
|
||||
fn extract_build_id_from_app_info(app_info: &str) -> &str {
|
||||
let mut lines = app_info.lines();
|
||||
while let Some(line) = lines.next() {
|
||||
if line.trim() == "\"public\"" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
lines.next().map(|line| line.trim()),
|
||||
Some("{"),
|
||||
"Invalid app info"
|
||||
);
|
||||
let build_id_line = lines
|
||||
.next()
|
||||
.unwrap_or_else(|| panic!("Invalid app info format:\n{}", app_info))
|
||||
.trim();
|
||||
assert!(build_id_line.starts_with("\"buildid\""), "Invalid app info");
|
||||
|
||||
split_vdf_key_val(build_id_line).1
|
||||
}
|
||||
|
||||
// Note: This is super brittle and will fail if there is whitespace within the key or value _or_ if
|
||||
// there are escaped " at the end of the key or value
|
||||
fn split_vdf_key_val(vdf_pair: &str) -> (&str, &str) {
|
||||
let mut pieces = vdf_pair.trim().split_whitespace();
|
||||
let key = pieces.next().expect("Missing vdf key").trim_matches('"');
|
||||
let val = pieces.next().expect("Missing vdf val").trim_matches('"');
|
||||
|
||||
(key, val)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
static TEST_ASSET_DIR: Lazy<PathBuf> = Lazy::new(|| {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
.join("assets")
|
||||
});
|
||||
|
||||
const CURRENT_MANIFEST_FILENAME: &str = "example_current_app_manifest.txt";
|
||||
const CURRENT_APP_INFO_FILENAME: &str = "example_current_steamcmd_app_info.txt";
|
||||
const UPDATED_APP_INFO_FILENAME: &str = "example_updated_steamcmd_app_info.txt";
|
||||
|
||||
const CURRENT_BUILD_ID: &str = "6246034";
|
||||
const UPDATED_BUILD_ID: &str = "6315977";
|
||||
|
||||
fn read_sample_file(filename: &str) -> String {
|
||||
let filepath = TEST_ASSET_DIR.join(filename);
|
||||
fs::read_to_string(&filepath)
|
||||
.unwrap_or_else(|_| panic!("Sample file missing: '{}'", filepath.display()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracting_build_id_from_manifest() {
|
||||
let manifest_data = read_sample_file(CURRENT_MANIFEST_FILENAME);
|
||||
assert_eq!(
|
||||
extract_build_id_from_manifest(&manifest_data),
|
||||
CURRENT_BUILD_ID
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracting_build_id_from_app_info() {
|
||||
let app_info_output = read_sample_file(CURRENT_APP_INFO_FILENAME);
|
||||
assert_eq!(
|
||||
extract_build_id_from_app_info(&app_info_output),
|
||||
CURRENT_BUILD_ID
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_info() {
|
||||
let current_manifest_data = read_sample_file(CURRENT_MANIFEST_FILENAME);
|
||||
let current_app_info_output = read_sample_file(CURRENT_APP_INFO_FILENAME);
|
||||
let updated_app_info_output = read_sample_file(UPDATED_APP_INFO_FILENAME);
|
||||
|
||||
// Verify updated info looks right
|
||||
let updated_update_info =
|
||||
UpdateInfo::new_testing(¤t_manifest_data, ¤t_app_info_output);
|
||||
assert_eq!(
|
||||
updated_update_info,
|
||||
UpdateInfo {
|
||||
current_build_id: CURRENT_BUILD_ID.to_string(),
|
||||
latest_build_id: CURRENT_BUILD_ID.to_string()
|
||||
}
|
||||
);
|
||||
assert!(!updated_update_info.update_available());
|
||||
|
||||
// Verify that info indicating an update looks right
|
||||
let pending_update_info =
|
||||
UpdateInfo::new_testing(¤t_manifest_data, &updated_app_info_output);
|
||||
assert_eq!(
|
||||
pending_update_info,
|
||||
UpdateInfo {
|
||||
current_build_id: CURRENT_BUILD_ID.to_string(),
|
||||
latest_build_id: UPDATED_BUILD_ID.to_string()
|
||||
}
|
||||
);
|
||||
assert!(pending_update_info.update_available());
|
||||
}
|
||||
}
|
||||
11
src/server/utils.rs
Normal file
11
src/server/utils.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use sysinfo::{System, SystemExt};
|
||||
|
||||
use crate::constants;
|
||||
|
||||
pub fn is_running() -> bool {
|
||||
let mut system = System::new();
|
||||
system.refresh_processes();
|
||||
let valheim_processes = system.get_process_by_name(constants::VALHEIM_EXECUTABLE_NAME);
|
||||
|
||||
!valheim_processes.is_empty()
|
||||
}
|
||||
@@ -5,12 +5,11 @@ use log::debug;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
|
||||
const ODIN_WORKING_DIR: &str = "ODIN_WORKING_DIR";
|
||||
pub const VALHEIM_EXECUTABLE_NAME: &str = "valheim_server.x86_64";
|
||||
use crate::constants;
|
||||
|
||||
pub fn get_working_dir() -> String {
|
||||
environment::fetch_var(
|
||||
ODIN_WORKING_DIR,
|
||||
constants::ODIN_WORKING_DIR,
|
||||
env::current_dir().unwrap().to_str().unwrap(),
|
||||
)
|
||||
}
|
||||
@@ -33,10 +32,6 @@ pub fn get_variable(args: &ArgMatches, name: &str, default: String) -> String {
|
||||
.to_string()
|
||||
}
|
||||
|
||||
pub fn server_installed() -> bool {
|
||||
Path::new(&[get_working_dir(), VALHEIM_EXECUTABLE_NAME.to_string()].join("/")).exists()
|
||||
}
|
||||
|
||||
pub(crate) fn path_exists(path: &str) -> bool {
|
||||
let state = Path::new(path).exists();
|
||||
debug!(
|
||||
|
||||
36
tests/assets/example_current_app_manifest.txt
Normal file
36
tests/assets/example_current_app_manifest.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
"AppState"
|
||||
{
|
||||
"appid" "896660"
|
||||
"Universe" "1"
|
||||
"name" "Valheim Dedicated Server"
|
||||
"StateFlags" "4"
|
||||
"installdir" "Valheim dedicated server"
|
||||
"LastUpdated" "1613644649"
|
||||
"UpdateResult" "0"
|
||||
"SizeOnDisk" "1045271601"
|
||||
"buildid" "6246034"
|
||||
"LastOwner" "76561201190449363"
|
||||
"BytesToDownload" "564763584"
|
||||
"BytesDownloaded" "564763584"
|
||||
"BytesToStage" "1045271601"
|
||||
"BytesStaged" "1045271601"
|
||||
"AutoUpdateBehavior" "0"
|
||||
"AllowOtherDownloadsWhileRunning" "0"
|
||||
"ScheduledAutoUpdate" "0"
|
||||
"InstalledDepots"
|
||||
{
|
||||
"1006"
|
||||
{
|
||||
"manifest" "6688153055340488873"
|
||||
"size" "59862244"
|
||||
}
|
||||
"896661"
|
||||
{
|
||||
"manifest" "521795651741005384"
|
||||
"size" "985409357"
|
||||
}
|
||||
}
|
||||
"UserConfig"
|
||||
{
|
||||
}
|
||||
}
|
||||
167
tests/assets/example_current_steamcmd_app_info.txt
Normal file
167
tests/assets/example_current_steamcmd_app_info.txt
Normal file
@@ -0,0 +1,167 @@
|
||||
WARNING: setlocale('en_US.UTF-8') failed, using locale: 'C'. International characters may not work.
|
||||
Redirecting stderr to '/home/steam/Steam/logs/stderr.txt'
|
||||
/tmp/dumps is not owned by us - delete and recreate
|
||||
Unable to delete /tmp/dumps. Continuing anyway.
|
||||
[ 0%] Checking for available updates...
|
||||
[----] Verifying installation...
|
||||
Steam Console Client (c) Valve Corporation
|
||||
-- type 'quit' to exit --
|
||||
Loading Steam API...OK.
|
||||
|
||||
Connecting anonymously to Steam Public...Logged in OK
|
||||
Waiting for user info...OK
|
||||
AppID : 896660, change number : 10778299/4294967295, last change : Fri Feb 19 18:42:01 2021
|
||||
"896660"
|
||||
{
|
||||
"common"
|
||||
{
|
||||
"name" "Valheim Dedicated Server"
|
||||
"type" "Tool"
|
||||
"parent" "892970"
|
||||
"oslist" "windows,linux"
|
||||
"osarch" ""
|
||||
"icon" "1aab0586723c8578c7990ced7d443568649d0df2"
|
||||
"logo" "233d73a1c963515ee4a9b59507bc093d85a4e2dc"
|
||||
"logo_small" "233d73a1c963515ee4a9b59507bc093d85a4e2dc_thumb"
|
||||
"clienticon" "c55a6b50b170ac6ed56cf90521273c30dccb5f12"
|
||||
"clienttga" "35e067b9efc8d03a9f1cdfb087fac4b970a48daf"
|
||||
"ReleaseState" "released"
|
||||
"associations"
|
||||
{
|
||||
}
|
||||
"gameid" "896660"
|
||||
}
|
||||
"config"
|
||||
{
|
||||
"installdir" "Valheim dedicated server"
|
||||
"launch"
|
||||
{
|
||||
"0"
|
||||
{
|
||||
"executable" "start_server_xterm.sh"
|
||||
"type" "server"
|
||||
"config"
|
||||
{
|
||||
"oslist" "linux"
|
||||
}
|
||||
}
|
||||
"1"
|
||||
{
|
||||
"executable" "start_headless_server.bat"
|
||||
"type" "server"
|
||||
"config"
|
||||
{
|
||||
"oslist" "windows"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"depots"
|
||||
{
|
||||
"1004"
|
||||
{
|
||||
"name" "Steamworks SDK Redist (WIN32)"
|
||||
"config"
|
||||
{
|
||||
"oslist" "windows"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "6473168357831043306"
|
||||
}
|
||||
"maxsize" "39546856"
|
||||
"depotfromapp" "1007"
|
||||
}
|
||||
"1005"
|
||||
{
|
||||
"name" "Steamworks SDK Redist (OSX32)"
|
||||
"config"
|
||||
{
|
||||
"oslist" "macos"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "2135359612286175146"
|
||||
}
|
||||
"depotfromapp" "1007"
|
||||
}
|
||||
"1006"
|
||||
{
|
||||
"name" "Steamworks SDK Redist (LINUX32)"
|
||||
"config"
|
||||
{
|
||||
"oslist" "linux"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "6688153055340488873"
|
||||
}
|
||||
"maxsize" "59862244"
|
||||
"depotfromapp" "1007"
|
||||
}
|
||||
"896661"
|
||||
{
|
||||
"name" "Valheim dedicated server Linux"
|
||||
"config"
|
||||
{
|
||||
"oslist" "linux"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "521795651741005384"
|
||||
}
|
||||
"maxsize" "985409357"
|
||||
"encryptedmanifests"
|
||||
{
|
||||
"experimental"
|
||||
{
|
||||
"encrypted_gid_2" "BEDF872D73873D16C025EF87E27C2BDB"
|
||||
"encrypted_size_2" "2559486959C6E5DCEA5C71ED32BA9080"
|
||||
}
|
||||
}
|
||||
}
|
||||
"896662"
|
||||
{
|
||||
"name" "Valheim dedicated server Windows"
|
||||
"config"
|
||||
{
|
||||
"oslist" "windows"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "5449924312569304795"
|
||||
}
|
||||
"maxsize" "963189471"
|
||||
"encryptedmanifests"
|
||||
{
|
||||
"experimental"
|
||||
{
|
||||
"encrypted_gid_2" "9FD2B7B42FACB1D1FC439DD83ED2BED9"
|
||||
"encrypted_size_2" "B2D602E667364DEDCB7C3D6EE9AA7374"
|
||||
}
|
||||
}
|
||||
}
|
||||
"branches"
|
||||
{
|
||||
"public"
|
||||
{
|
||||
"buildid" "6246034"
|
||||
"timeupdated" "1613558776"
|
||||
}
|
||||
"experimental"
|
||||
{
|
||||
"buildid" "6263839"
|
||||
"description" "Experimental version of Valheim"
|
||||
"pwdrequired" "1"
|
||||
"timeupdated" "1613728251"
|
||||
}
|
||||
"unstable"
|
||||
{
|
||||
"buildid" "6246034"
|
||||
"description" "Unstable test version of valheim"
|
||||
"pwdrequired" "1"
|
||||
"timeupdated" "1613469743"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
166
tests/assets/example_updated_steamcmd_app_info.txt
Normal file
166
tests/assets/example_updated_steamcmd_app_info.txt
Normal file
@@ -0,0 +1,166 @@
|
||||
WARNING: setlocale('en_US.UTF-8') failed, using locale: 'C'. International characters may not work.
|
||||
Redirecting stderr to '/home/steam/Steam/logs/stderr.txt'
|
||||
[ 0%] Checking for available updates...
|
||||
[----] Verifying installation...
|
||||
Steam Console Client (c) Valve Corporation
|
||||
-- type 'quit' to exit --
|
||||
Loading Steam API...OK.
|
||||
"@ShutdownOnFailedCommand" = "1"
|
||||
|
||||
Connecting anonymously to Steam Public...Logged in OK
|
||||
Waiting for user info...OK
|
||||
AppID : 896660, change number : 10865381/10865381, last change : Wed Mar 3 00:41:58 2021
|
||||
"896660"
|
||||
{
|
||||
"common"
|
||||
{
|
||||
"name" "Valheim Dedicated Server"
|
||||
"type" "Tool"
|
||||
"parent" "892970"
|
||||
"oslist" "windows,linux"
|
||||
"osarch" ""
|
||||
"icon" "1aab0586723c8578c7990ced7d443568649d0df2"
|
||||
"logo" "233d73a1c963515ee4a9b59507bc093d85a4e2dc"
|
||||
"logo_small" "233d73a1c963515ee4a9b59507bc093d85a4e2dc_thumb"
|
||||
"clienticon" "c55a6b50b170ac6ed56cf90521273c30dccb5f12"
|
||||
"clienttga" "35e067b9efc8d03a9f1cdfb087fac4b970a48daf"
|
||||
"ReleaseState" "released"
|
||||
"associations"
|
||||
{
|
||||
}
|
||||
"gameid" "896660"
|
||||
}
|
||||
"config"
|
||||
{
|
||||
"installdir" "Valheim dedicated server"
|
||||
"launch"
|
||||
{
|
||||
"0"
|
||||
{
|
||||
"executable" "start_server_xterm.sh"
|
||||
"type" "server"
|
||||
"config"
|
||||
{
|
||||
"oslist" "linux"
|
||||
}
|
||||
}
|
||||
"1"
|
||||
{
|
||||
"executable" "start_headless_server.bat"
|
||||
"type" "server"
|
||||
"config"
|
||||
{
|
||||
"oslist" "windows"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"depots"
|
||||
{
|
||||
"1004"
|
||||
{
|
||||
"name" "Steamworks SDK Redist (WIN32)"
|
||||
"config"
|
||||
{
|
||||
"oslist" "windows"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "6473168357831043306"
|
||||
}
|
||||
"maxsize" "39546856"
|
||||
"depotfromapp" "1007"
|
||||
}
|
||||
"1005"
|
||||
{
|
||||
"name" "Steamworks SDK Redist (OSX32)"
|
||||
"config"
|
||||
{
|
||||
"oslist" "macos"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "2135359612286175146"
|
||||
}
|
||||
"depotfromapp" "1007"
|
||||
}
|
||||
"1006"
|
||||
{
|
||||
"name" "Steamworks SDK Redist (LINUX32)"
|
||||
"config"
|
||||
{
|
||||
"oslist" "linux"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "6688153055340488873"
|
||||
}
|
||||
"maxsize" "59862244"
|
||||
"depotfromapp" "1007"
|
||||
}
|
||||
"896661"
|
||||
{
|
||||
"name" "Valheim dedicated server Linux"
|
||||
"config"
|
||||
{
|
||||
"oslist" "linux"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "6588021550109601388"
|
||||
}
|
||||
"maxsize" "991737299"
|
||||
"encryptedmanifests"
|
||||
{
|
||||
"experimental"
|
||||
{
|
||||
"encrypted_gid_2" "91B9C2C233637DEDCA25AE056AAD25FA"
|
||||
"encrypted_size_2" "652E81496E51312FC0F04B49B67718E8"
|
||||
}
|
||||
}
|
||||
}
|
||||
"896662"
|
||||
{
|
||||
"name" "Valheim dedicated server Windows"
|
||||
"config"
|
||||
{
|
||||
"oslist" "windows"
|
||||
}
|
||||
"manifests"
|
||||
{
|
||||
"public" "415644664754619686"
|
||||
}
|
||||
"maxsize" "984116773"
|
||||
"encryptedmanifests"
|
||||
{
|
||||
"experimental"
|
||||
{
|
||||
"encrypted_gid_2" "E7E59A011C08BFE6CA0FD3A704DA1C8D"
|
||||
"encrypted_size_2" "1578D11B163520DC211016502F4A37F2"
|
||||
}
|
||||
}
|
||||
}
|
||||
"branches"
|
||||
{
|
||||
"public"
|
||||
{
|
||||
"buildid" "6315977"
|
||||
"timeupdated" "1614679211"
|
||||
}
|
||||
"experimental"
|
||||
{
|
||||
"buildid" "6306893"
|
||||
"description" "Experimental version of Valheim"
|
||||
"pwdrequired" "1"
|
||||
"timeupdated" "1614510566"
|
||||
}
|
||||
"unstable"
|
||||
{
|
||||
"buildid" "6315977"
|
||||
"description" "Unstable test version of valheim"
|
||||
"pwdrequired" "1"
|
||||
"timeupdated" "1614676060"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user