1use anyhow::{bail, Context, Result};
2use cloudpub_common::constants::DEFAULT_HEARTBEAT_TIMEOUT_SECS;
3use cloudpub_common::{DOMAIN, PORT};
4use serde::{Deserialize, Serialize};
5use std::fmt::{Debug, Display, Formatter};
6use std::str::FromStr;
7
8use crate::options::ConfigOption;
9pub use cloudpub_common::config::{MaskedString, TransportConfig};
10use lazy_static::lazy_static;
11use machineid_rs::{Encryption, HWIDComponent, IdBuilder};
12use std::collections::HashMap;
13use std::fs::{self, create_dir_all, File};
14use std::io::Write;
15use std::path::PathBuf;
16use tracing::debug;
17use url::Url;
18use uuid::Uuid;
19
20#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
21pub enum Platform {
22 #[default]
23 X64,
24 X32,
25}
26
27#[derive(Clone)]
28pub struct EnvConfig {
29 pub home_1c: PathBuf,
30 #[cfg(target_os = "windows")]
31 pub redist: String,
32 pub httpd: String,
33 pub httpd_dir: String,
34}
35
36impl Default for EnvConfig {
37 fn default() -> Self {
38 #[cfg(target_pointer_width = "64")]
39 return ENV_CONFIG.get(&Platform::X64).unwrap().clone();
40 #[cfg(target_pointer_width = "32")]
41 return ENV_CONFIG.get(&Platform::X32).unwrap().clone();
42 }
43}
44
45lazy_static! {
46 pub static ref ENV_CONFIG: HashMap<Platform, EnvConfig> = {
47 let mut m = HashMap::new();
48 #[cfg(target_os = "windows")]
49 m.insert(
50 Platform::X64,
51 EnvConfig {
52 home_1c: PathBuf::from("C:\\Program Files\\1cv8\\"),
53 redist: "vc_redist.x64.exe".to_string(),
54 httpd: "httpd-2.4.61-240703-win64-VS17.zip".to_string(),
55 httpd_dir: "httpd-x64".to_string(),
56 },
57 );
58 #[cfg(target_os = "windows")]
59 m.insert(
60 Platform::X32,
61 EnvConfig {
62 home_1c: PathBuf::from("C:\\Program Files (x86)\\1cv8"),
63 redist: "vc_redist.x86.exe".to_string(),
64 httpd: "httpd-2.4.61-240703-win32-vs17.zip".to_string(),
65 httpd_dir: "httpd-x32".to_string(),
66 },
67 );
68 #[cfg(target_os = "linux")]
69 m.insert(
70 Platform::X64,
71 EnvConfig {
72 home_1c: PathBuf::from("/opt/1C"),
73 httpd: "httpd-2.4.63-linux.zip".to_string(),
74 httpd_dir: "httpd-x64".to_string(),
75 },
76 );
77 #[cfg(target_os = "macos")]
78 m.insert(
79 Platform::X64,
80 EnvConfig {
81 home_1c: PathBuf::from("/Applications/1C"),
82 httpd: "httpd-2.4.62-macos.zip".to_string(),
83 httpd_dir: "httpd-x64".to_string(),
84 },
85 );
86 m
87 };
88}
89
90impl Display for Platform {
91 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
92 match self {
93 Platform::X64 => write!(f, "x64"),
94 Platform::X32 => write!(f, "x32"),
95 }
96 }
97}
98
99impl std::str::FromStr for Platform {
100 type Err = anyhow::Error;
101
102 fn from_str(s: &str) -> Result<Self> {
103 match s {
104 "x64" => Ok(Platform::X64),
105 "x32" => Ok(Platform::X32),
106 _ => bail!("Invalid platform: {}", s),
107 }
108 }
109}
110
111#[derive(Debug, PartialEq, Eq, Clone, Default)]
112pub struct ClientOpts {
113 pub gui: bool,
114 pub credentials: Option<(String, String)>,
115 pub transient: bool,
116 pub secondary: bool,
117 pub is_service: bool,
118}
119
120#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
121pub struct ClientConfig {
122 #[serde(skip)]
124 config_path: PathBuf,
125 #[serde(skip)]
126 pub readonly: bool,
127 pub agent_id: String,
128 pub hwid: Option<String>,
129 pub server: Url,
130 pub token: Option<MaskedString>,
131 pub heartbeat_timeout: u64,
132 pub one_c_home: Option<String>,
133 pub one_c_platform: Option<Platform>,
134 pub one_c_publish_dir: Option<String>,
135 pub minecraft_server: Option<String>,
136 pub minecraft_java_opts: Option<String>,
137 pub minimize_to_tray_on_close: Option<bool>,
138 pub minimize_to_tray_on_start: Option<bool>,
139 pub transport: TransportConfig,
140}
141
142impl ClientConfig {
143 pub fn get_config_path(&self) -> &PathBuf {
144 &self.config_path
145 }
146
147 pub fn from_file(path: &PathBuf, readonly: bool) -> Result<Self> {
148 if !path.exists() && !readonly {
149 let default_config = Self::default();
150 debug!("Creating default config at {:?}", default_config);
151 let s = toml::to_string_pretty(&default_config)
152 .context("Failed to serialize the default config")?;
153 fs::write(path, s).context("Failed to write the default config")?;
154 }
155
156 let s: String = fs::read_to_string(path)
157 .with_context(|| format!("Failed to read the config {:?}", path))?;
158 let mut cfg: Self = toml::from_str(&s).with_context(|| {
159 "Configuration is invalid. Please refer to the configuration specification."
160 })?;
161
162 cfg.config_path = path.clone();
163 cfg.readonly = readonly;
164 Ok(cfg)
165 }
166
167 pub fn get_config_dir(user_dir: bool) -> Result<PathBuf> {
168 let dir = if user_dir {
169 let mut dir = dirs::config_dir().context("Can't get config_dir")?;
170 dir.push("cloudpub");
171 dir
172 } else if cfg!(target_family = "unix") {
173 PathBuf::from("./etc/cloudpub")
174 } else {
175 PathBuf::from("C:\\Windows\\system32\\config\\systemprofile\\AppData\\Local\\cloudpub")
176 };
177 if !dir.exists() {
178 create_dir_all(&dir).context("Can't create config dir")?;
179 }
180 debug!("Config dir: {:?}", dir);
181 Ok(dir)
182 }
183
184 pub fn save(&self) -> Result<()> {
185 if self.readonly {
186 debug!("Skipping saving the config in readonly mode");
187 } else {
188 let s = toml::to_string_pretty(self).context("Failed to serialize the config")?;
189 let mut f = File::create(&self.config_path)?;
190 f.write_all(s.as_bytes())?;
191 f.sync_all()?;
192 }
193 Ok(())
194 }
195
196 pub fn load(cfg_name: &str, user_dir: bool, readonly: bool) -> Result<Self> {
197 let mut config_path = Self::get_config_dir(user_dir)?;
198 config_path.push(cfg_name);
199
200 Self::from_file(&config_path, readonly)
201 }
202
203 pub fn set(&mut self, key: &str, value: &str) -> Result<()> {
204 let option = ConfigOption::from_str(key)?;
205 self.set_option(option, value)
206 }
207
208 pub fn get(&self, key: &str) -> Result<String> {
209 let option = ConfigOption::from_str(key)?;
210 self.get_option(option)
211 }
212
213 pub fn get_all_options(&self) -> Vec<(String, String)> {
214 self.get_all_config_options()
215 }
216
217 pub fn validate(&self) -> Result<()> {
218 if self.token.is_none() {
219 bail!("{}", crate::t!("error-auth-missing"));
220 }
221 TransportConfig::validate(&self.transport, false)?;
222 Ok(())
223 }
224
225 pub fn get_hwid(&self) -> String {
226 self.hwid
227 .as_ref()
228 .unwrap_or(
229 &IdBuilder::new(Encryption::SHA256)
230 .add_component(HWIDComponent::OSName)
231 .add_component(HWIDComponent::SystemID)
232 .add_component(HWIDComponent::MachineName)
233 .add_component(HWIDComponent::CPUID)
234 .build("cloudpub")
235 .unwrap_or(self.agent_id.clone()),
236 )
237 .to_string()
238 }
239}
240
241impl Default for ClientConfig {
242 fn default() -> Self {
243 Self {
244 agent_id: Uuid::new_v4().to_string(),
245 hwid: None,
246 config_path: PathBuf::new(),
247 server: format!("https://{}:{}", DOMAIN, PORT).parse().unwrap(),
248 token: None,
249 heartbeat_timeout: DEFAULT_HEARTBEAT_TIMEOUT_SECS,
250 one_c_home: None,
251 one_c_platform: None,
252 one_c_publish_dir: None,
253 minecraft_server: None,
254 minecraft_java_opts: None,
255 minimize_to_tray_on_close: Some(true),
256 minimize_to_tray_on_start: Some(false),
257 transport: TransportConfig::default(),
258 readonly: false,
259 }
260 }
261}