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