veloren_server_cli/
settings.rs

1use serde::{Deserialize, Serialize};
2use std::{
3    fs,
4    net::{Ipv4Addr, SocketAddr},
5    path::{Path, PathBuf},
6};
7use tracing::{error, warn};
8
9#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
10#[expect(clippy::upper_case_acronyms)]
11pub enum ShutdownSignal {
12    SIGUSR1,
13    SIGUSR2,
14    SIGTERM,
15}
16
17impl ShutdownSignal {
18    #[cfg(any(target_os = "linux", target_os = "macos"))]
19    pub fn to_signal(self) -> core::ffi::c_int {
20        match self {
21            Self::SIGUSR1 => signal_hook::consts::SIGUSR1,
22            Self::SIGUSR2 => signal_hook::consts::SIGUSR2,
23            Self::SIGTERM => signal_hook::consts::SIGTERM,
24        }
25    }
26}
27
28#[derive(Clone, Debug, Serialize, Deserialize)]
29#[serde(default)]
30pub struct Settings {
31    pub update_shutdown_grace_period_secs: u32,
32    pub update_shutdown_message: String,
33    pub web_address: SocketAddr,
34    /// SECRET API HEADER used to access the chat api, if disabled the API is
35    /// unreachable
36    pub web_chat_secret: Option<String>,
37    /// public SECRET API HEADER used to access the /ui_api, if disabled the API
38    /// is reachable localhost only (by /ui)
39    pub ui_api_secret: Option<String>,
40    pub shutdown_signals: Vec<ShutdownSignal>,
41}
42
43impl Default for Settings {
44    fn default() -> Self {
45        Self {
46            update_shutdown_grace_period_secs: 120,
47            update_shutdown_message: "The server is restarting for an update".to_owned(),
48            web_address: SocketAddr::from((Ipv4Addr::LOCALHOST, 14005)),
49            web_chat_secret: None,
50            ui_api_secret: None,
51            shutdown_signals: if cfg!(any(target_os = "linux", target_os = "macos")) {
52                vec![ShutdownSignal::SIGUSR1]
53            } else {
54                Vec::new()
55            },
56        }
57    }
58}
59
60impl Settings {
61    const FILENAME: &str = "settings.ron";
62
63    pub fn load() -> Option<Self> {
64        let path = Self::get_settings_path();
65        let template_path = path.with_extension("template.ron");
66
67        let settings = if let Ok(file) = fs::File::open(&path) {
68            match ron::de::from_reader(file) {
69                Ok(s) => return Some(s),
70                Err(e) => {
71                    error!(
72                        ?e,
73                        "FATAL: Failed to parse setting file! Creating a template file for you to \
74                         migrate your current settings file: {}",
75                        template_path.display()
76                    );
77                    None
78                },
79            }
80        } else {
81            warn!(
82                "Settings file not found! Creating a template file: {} — If you wish to change \
83                 any settings, copy/move the template to {} and edit the fields as you wish.",
84                template_path.display(),
85                Self::FILENAME
86            );
87            Some(Self::default())
88        };
89
90        // This is reached if either:
91        // - The file can't be opened (presumably it doesn't exist)
92        // - Or there was an error parsing the file
93        if let Err(e) = Self::save_template(&template_path) {
94            error!(?e, "Failed to create template settings file!");
95        }
96
97        settings
98    }
99
100    fn save_template(path: &Path) -> std::io::Result<()> {
101        if let Some(dir) = path.parent() {
102            fs::create_dir_all(dir)?;
103        }
104
105        let ron = ron::ser::to_string_pretty(&Self::default(), ron::ser::PrettyConfig::default())
106            .unwrap();
107        fs::write(path, ron.as_bytes())
108    }
109
110    pub fn get_settings_path() -> PathBuf {
111        let mut path = data_dir();
112        path.push(Self::FILENAME);
113        path
114    }
115}
116
117pub fn data_dir() -> PathBuf {
118    let mut path = common_base::userdata_dir_workspace!();
119    path.push("server-cli");
120    path
121}