sharing/
lib.rs

1//! # sharing
2//!
3//! Shared primitives consumed by every binary and package in the workspace.
4//!
5//! The crate is intentionally a grab-bag of small utilities that would create
6//! a dependency cycle if they lived in any one binary:
7//!
8//! * [`paths`]        — canonical locations (install dir, bin dir, log dir, `.env` path).
9//! * [`utils`]        — [`utils::ConfigEnv`], env-file round-trip, install helpers.
10//! * [`service`]      — IPC [`service::Action`] codec used by the tray → service channel.
11//! * [`natives::api`] — OS-specific primitives (single-instance lock, logged user, service name).
12//! * [`crypto`], [`fs`], [`string`], [`download_file`] — small, focused helpers.
13//!
14//! This crate also exports constants that are baked into binaries and consumed
15//! by installer-side code and scripts, so they must stay the single source of
16//! truth. The most important one is [`PNA_ALLOWED_ORIGINS`] (Chrome/Edge
17//! allow-list for the local WebSocket).
18//!
19//! ## Windows specifics
20//!
21//! The installer and the elevator rely on [`create_registry_entries`] and
22//! [`remove_registry_entries`] to register the app under
23//! `HKLM\SOFTWARE\...` for auto-start, the `Run` key, and uninstall metadata.
24//! Writing to `HKLM` requires admin rights, so these must be invoked from a
25//! code path that has already passed through `pispas-elevator.exe`.
26
27pub mod env;
28pub mod proc;
29pub mod natives;
30pub mod crypto;
31pub mod string;
32pub mod fs;
33pub mod paths;
34pub mod service;
35pub mod types;
36pub mod utils;
37pub mod download_file;
38
39#[cfg(target_os = "windows")]
40use winreg::enums::*;
41#[cfg(target_os = "windows")]
42use winreg::RegKey;
43#[cfg(target_os = "windows")]
44use std::path::Path;
45use std::process::Stdio;
46use std::thread::sleep;
47use semver::Version;
48
49use std::io::Write;
50use std::net::TcpStream;
51
52pub type PisPasResult<T> = Result<T, anyhow::Error>;
53pub type CancelToken = std::sync::Arc<std::sync::atomic::AtomicBool>;
54
55pub use natives::api::{Lock, SingleInstance};
56
57pub const VERSION: &str = env!("CARGO_PKG_VERSION");
58pub fn get_version() -> Version {
59    Version::parse(VERSION).unwrap_or_else(|e| {
60        tracing::warn!("Error parsing version: {}", e);
61        Version::new(0, 0, 0)
62    })
63}
64#[allow(unused_variables)]
65pub(crate) const KEY_FILE_NAME: &str = ".secret";
66#[cfg(target_os = "windows")]
67pub const CHANNEL_NAME_OLD: &str = r"\\.\pipe\pispas-channel2";
68#[cfg(not(target_os = "windows"))]
69pub const CHANNEL_NAME_OLD: &str = r"/tmp/pispas-channel2";
70pub const CHANNEL_NAME: &str = r"127.0.0.1:7878";
71pub const CHANNEL_NAME_CFG: &str = r"127.0.0.1:7879";
72
73pub const DATA_VALUES_DELIMITER: &str = "!";
74pub const DATA_DELIMITER: &str = "|";
75
76const ROOT_FOLDER: &str = ".config";
77pub const BASE_FOLDER: &str = "pispas";
78pub const CONFIG_FILE_NAME: &str = "printsvc.json";
79pub const ENV_FILE_NAME: &str = ".env";
80pub const SUMATRA_FILE: &str = "SumatraPDF-3.5.2-64.exe";
81#[cfg(target_os = "windows")]
82pub const WKHTMLTOPDF_FILE: &str = "wkhtmltopdf.exe";
83#[cfg(not(target_os = "windows"))]
84pub const WKHTMLTOPDF_FILE: &str = "wkhtmltopdf";
85pub const PDF_INFO_FILE: &str = "pdfinfo.exe";
86pub const PYTHON_FOLDER: &str = "python";
87pub const POS_DRIVER_FILE: &str = "PosDriver.exe";
88pub const MYPOS_SERVER_FILE: &str = "server_mypos.exe";
89pub const PRINTER_TEST_FILE: &str = "Printer_Test.exe";
90pub const BEEPER_PDF_FILE: &str = "Configuracion_Alarma_Beeper.pdf";
91
92pub const APP_DEV_HOST: &str = "app_dev.unpispas.es";
93pub const APP_PRO_HOST: &str = "unpispas.es";
94pub const ICON_PISPAS: &str = "pispas.ico";
95pub const CONF_PATH: &str = "conf";
96pub const LOGS_PATH: &str = "logs";
97
98pub const LIBRARIES_PATH: &str = "lib";
99pub const BINARIES_PATH: &str = "bin";
100pub const TRAY_ICON_FOLDER: &str = "pispas-tray-app";
101pub const NAME_LEGAL: &str = "© 2024 Gekkotech S.L.";
102
103
104pub const SERVICE_NAME: &str = "pispas-service";
105#[cfg(target_os = "windows")]
106pub const SERVICE_NAME_EXE: &str = "pispas-service.exe";
107#[cfg(not(target_os = "windows"))]
108pub const SERVICE_NAME_EXE: &str = "pispas-service";
109#[cfg(target_os = "windows")]
110pub const TRAY_ICON_NAME: &str = "pispas-tray-app.exe";
111#[cfg(not(target_os = "windows"))]
112pub const TRAY_ICON_NAME: &str = "pispas-tray-app";
113#[cfg(target_os = "windows")]
114pub const NAME_SHORTCUT_DESKTOP: &str = "pispas-tray-icon.exe";
115
116#[cfg(target_os = "windows")]
117pub const PISPAS_CONFIGURATOR: &str = "pispas-configurator-html.exe";
118#[cfg(not(target_os = "windows"))]
119pub const PISPAS_CONFIGURATOR: &str = "pispas-configurator-html";
120#[cfg(target_os = "windows")]
121pub const PRINT_SERVICE: &str = "pispas-modules.exe";
122#[cfg(not(target_os = "windows"))]
123pub const PRINT_SERVICE: &str = "pispas-modules";
124
125#[cfg(target_os = "windows")]
126pub const ORDER_KITCHEN: &str = "pispas-order-kitchen.exe";
127#[cfg(not(target_os = "windows"))]
128pub const ORDER_KITCHEN: &str = "pispas-order-kitchen";
129#[cfg(target_os = "windows")]
130pub const PISPAS_ELEVATOR: &str = "pispas-elevator.exe";
131#[cfg(not(target_os = "windows"))]
132pub const PISPAS_ELEVATOR: &str = "pispas-elevator";
133
134pub const SERVICE_PYTHON_NAME: &str = "client.py";
135pub const OLD_SERVICE_PYTHON_NAME: &str = "local.py";
136pub const PRINTSVC_PYTHON_NAME: &str = "printsvc.py";
137pub const PRINTSVC_PYTHON_NAME_NEW: &str = "print.py";
138
139/// Filename of the on-disk persistence blob used by the `persistence` module
140/// in `pispas-modules`. Lives under the install `bin/` directory.
141pub const PERSISTANCE_FILE_NAME: &str = "file.bin";
142
143/// Origins allowed to make Local Network Access requests to the local
144/// WebSocket service.
145///
146/// This is the **single source of truth** for the allow-list. Two consumers
147/// derive from it:
148///
149/// * The origin-validation callback in the local WebSocket handshake
150///   (see `pispas_modules::utils::is_origin_allowed`). Matching is literal
151///   plus wildcard subdomain (`*.example.com`) and wildcard port (`:*`).
152/// * The Chrome/Edge enterprise policy installer
153///   `resources/win/disable-chrome-pna.ps1`, which is called by
154///   `packages/installer/src/resources.rs::run_chrome_pna_script` with each
155///   origin passed as its own CLI argument so PowerShell binds them into a
156///   real `[string[]]`.
157///
158/// Changing this list requires no code change anywhere else — a rebuild and
159/// re-install is sufficient. Avoid broadening it without a good reason.
160pub const PNA_ALLOWED_ORIGINS: &[&str] = &[
161    "https://*.unpispas.es",
162    "https://*.unpispas.es:*",
163    "https://*.mywire.org",
164    "https://*.mywire.org:*",
165];
166
167#[cfg(target_os = "windows")]
168pub const PISPAS_INSTALLER: &str = "pispas-installer.exe";
169
170#[cfg(not(target_os = "windows"))]
171pub const PISPAS_INSTALLER: &str = "pispas-installer";
172
173
174#[cfg(target_os = "windows")]
175const PROGRAM_FILES_ENV_VAR: &'static str = "ProgramFiles";
176
177#[cfg(target_os = "windows")]
178pub const PROGRAM_FILES_KEY: &'static str = r"SOFTWARE\Microsoft\Windows\CurrentVersion";
179#[cfg(target_os = "windows")]
180pub const USER_PROFILE_KEY: &'static str = "USERPROFILE";
181#[cfg(target_os = "windows")]
182pub const TMP_KEY: &'static str = "TMP";
183#[cfg(target_os = "windows")]
184pub const ENVIRONMENT: &'static str = r"Environment";
185#[cfg(target_os = "windows")]
186pub const VOLATILE_ENVIRONMENT: &'static str = r"Volatile Environment";
187#[cfg(target_os = "windows")]
188const RUN: &'static str = "Run";
189#[cfg(target_os = "windows")]
190const PROGRAM_FILES_SUBKEY: &'static str = r"ProgramFilesDir";
191#[cfg(target_os = "windows")]
192const PATH_PROGRAMS_KEY: &'static str = r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
193#[cfg(target_os = "windows")]
194const PATH_PROGRAMS_UNINSTLAL_KEY: &'static str = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
195
196#[cfg(target_os = "windows")]
197pub fn get_info_parent() -> Option<proc::UserInfo> {
198    proc::get_gran_father_info().ok()
199}
200
201pub fn get_persistance_path() -> String {
202    let path = paths::bin_dir().join(PERSISTANCE_FILE_NAME);
203    if !path.exists() {
204        std::fs::create_dir_all(&path).expect("Failed to create persistance path");
205    }
206    path.to_str().unwrap().to_string()
207}
208pub fn get_path_mypos_server() -> String {
209    #[cfg(target_os = "windows")]
210    let path = paths::win_dir().join(MYPOS_SERVER_FILE);
211    #[cfg(not(target_os = "windows"))]
212    let path = paths::bin_dir().join(MYPOS_SERVER_FILE);
213    path.to_str().unwrap().to_string()
214}
215pub fn get_path_env() -> String {
216    let path_os_values = std::env::var(env::PATH_ENV).unwrap_or_default();
217    let env = format!(
218        "{}{}{}{}{}",
219        paths::lib_dir().to_str().unwrap(),
220        env::PATH_SEPARATOR,
221        paths::bin_dir().to_str().unwrap(),
222        env::PATH_SEPARATOR,
223        path_os_values
224    );
225    tracing::info!("env path: {}", env);
226    env
227}
228
229#[cfg(target_os = "windows")]
230pub fn get_python_path_install() -> String {
231    // Leer PYTHON_PATH desde el registro
232    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
233    match hklm.open_subkey("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"){
234        Ok(environment) => {
235            let python_path: String = environment.get_value("PYTHON_PATH").unwrap_or_default();
236            tracing::info!("PYTHON_PATH: {}", python_path);
237            python_path
238        }
239        Err(e) => {
240            tracing::warn!("Error reading PYTHON_PATH: {}", e);
241            "python.exe".to_string()
242        }
243    }
244}
245pub fn add_envs(command: &mut std::process::Command) {
246    command.current_dir(paths::PROGRAM_HOME_DIR.clone())
247        .env(env::PATH_ENV, get_path_env())
248        .env(env::LD_LIBRARY_PATH_ENV, paths::lib_dir().to_str().unwrap())
249        .stdout(Stdio::piped())
250        .stderr(Stdio::piped());
251}
252
253pub fn is_windows_7() -> bool {
254    #[cfg(target_os = "windows")]
255    {
256        let output = std::process::Command::new("cmd")
257            .arg("/c")
258            .arg("ver")
259            .output();
260
261        match output {
262            Ok(output) => {
263                let version_output = String::from_utf8_lossy(&output.stdout);
264                tracing::info!("Running on windows version: {}", version_output);
265                return version_output.contains("6.1.");
266            }
267            Err(e) => {
268                tracing::warn!("Running on windows with error: {}", e);
269                return false;
270            }
271        }
272    }
273
274    #[cfg(not(target_os = "windows"))]
275    {
276        tracing::info!("running on other windows version");
277        false
278    }
279}
280
281#[cfg(target_os = "windows")]
282pub fn remove_registry_entries() -> Result<(), Box<dyn std::error::Error>> {
283    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
284    match hklm.open_subkey(PATH_PROGRAMS_KEY)
285    {
286        Ok(subkey) => {
287            let _ = subkey.delete_subkey(Path::new(TRAY_ICON_NAME));
288        }
289        Err(e) => {
290            tracing::warn!("Error removing registry entries: {}", e);
291        }
292    }
293    match hklm.open_subkey(PATH_PROGRAMS_UNINSTLAL_KEY) {
294        Ok(subkey) => {
295            let _ = subkey.delete_subkey(Path::new(TRAY_ICON_NAME));
296        }
297        Err(e) => {
298            tracing::warn!("Error removing registry entries: {}", e);
299        }
300    }
301
302    match hklm.create_subkey(format!("{}\\{}", PROGRAM_FILES_KEY, RUN)) {
303        Ok((key, disp)) => {
304            match disp {
305                REG_CREATED_NEW_KEY => tracing::info!("A new key has been created {:?}", key),
306                REG_OPENED_EXISTING_KEY => tracing::info!("An existing key has been opened {:?}", key),
307            }
308            let _ = key.delete_value(BASE_FOLDER);
309        }
310        Err(e) => {
311            tracing::warn!("Error creating registry entries: {}", e);
312        }
313    }
314
315    Ok(())
316}
317
318#[cfg(target_os = "windows")]
319pub fn create_registry_entries() -> Result<(), Box<dyn std::error::Error>> {
320    let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
321    match hklm.open_subkey(PATH_PROGRAMS_KEY)
322    {
323        Ok(subkey) => {
324            let (key, disp) = subkey.create_subkey(Path::new(TRAY_ICON_NAME))?;
325            match disp {
326                REG_CREATED_NEW_KEY => tracing::info!("A new key has been created {:?}", key),
327                REG_OPENED_EXISTING_KEY => tracing::info!("An existing key has been opened {:?}", key),
328            }
329            let _ = key.set_value("", &paths::tray_icon_path().display().to_string());
330            let _ = key.set_value("Path", &paths::bin_dir().display().to_string());
331        }
332        Err(e) => {
333            tracing::warn!("Error creating registry entries: {}", e);
334        }
335    }
336
337
338    match hklm.open_subkey(PATH_PROGRAMS_UNINSTLAL_KEY)
339    {
340        Ok(subkey) => {
341            let (key, disp) = subkey.create_subkey(Path::new(crate::TRAY_ICON_NAME))?;
342            match disp {
343                REG_CREATED_NEW_KEY => {
344                    tracing::info!("A new key has been created {:?}", key);
345                    let _ = key.set_value("DisplayIcon", &paths::tray_icon_path().display().to_string());
346                    let _ = key.set_value("DisplayName", &"pispas Service");
347                    let _ = key.set_value("DisplayVersion", &VERSION);
348                    let _ = key.set_value("InstallLocation", &paths::bin_dir().display().to_string());
349                    let _ = key.set_value("Publisher", &"GEKKOTECH S.L");
350                    let uninstal_string = format!("{} -sd", paths::elevator_path().display().to_string());
351                    let _ = key.set_value("UninstallString", &uninstal_string);
352                }
353                REG_OPENED_EXISTING_KEY => {
354                    tracing::info!("An existing key has been opened {:?}", key);
355                    let _ = key.set_value("DisplayVersion", &VERSION);
356                }
357            }
358            // let _ = key.set_value("", &tray_icon_path().display().to_string());
359        }
360        Err(e) => {
361            tracing::warn!("Error creating registry entries: {}", e);
362        }
363    }
364
365    match hklm.create_subkey(format!("{}\\{}", PROGRAM_FILES_KEY, RUN)) {
366        Ok((key, disp)) => {
367            match disp {
368                REG_CREATED_NEW_KEY => tracing::info!("A new key has been created {:?}", key),
369                REG_OPENED_EXISTING_KEY => tracing::info!("An existing key has been opened {:?}", key),
370            }
371            let _ = key.set_value(BASE_FOLDER, &paths::tray_icon_path().display().to_string());
372        }
373        Err(e) => {
374            tracing::warn!("Error creating registry entries: {}", e);
375        }
376    }
377
378    // Chrome/Edge PNA policies are handled by disable-chrome-pna.ps1,
379    // invoked from the installer's extract_resources() step.
380
381    Ok(())
382}
383
384pub fn stop_pispas_modules() -> crate::PisPasResult<()> {
385    match TcpStream::connect(crate::CHANNEL_NAME) {
386        Ok(mut stream) => {
387            match stream.write_all(crate::service::Action::Stop.to_string().as_bytes()) {
388                Ok(_) => {
389                    tracing::info!("Message sent: {}", crate::service::Action::Stop.to_string());
390                    return Ok(());
391                }
392                Err(e) => {
393                    tracing::error!("Failed to write to socket in dedicated thread {}", e);
394                    return Err(anyhow::anyhow!("Failed to write to socket in dedicated thread {}", e));
395                }
396            }
397        }
398        Err(e) => {
399            tracing::error!("Error connecting to pispas-channel: {}", e);
400        }
401    }
402    sleep(std::time::Duration::from_secs(1));
403    Ok(())
404}
405
406pub fn launch_tray_icon() -> Result<(), Box<dyn std::error::Error>> {
407
408
409    let tray_string = crate::paths::tray_icon_path().display().to_string();
410
411
412    #[cfg(target_os = "windows")]
413    if let Err(err) = crate::natives::api::run_in_all_sessions(&tray_string, "", false, false) {
414        tracing::error!("Error launching tray icon: {} for all users", err);
415    }
416    #[cfg(not(target_os = "windows"))]
417    {
418        if let Err(err) = crate::natives::api::run_in_all_sessions(&tray_string, "", None, None) {
419            tracing::error!("Error launching tray icon: {} for all users", err);
420        }
421    }
422
423    sleep(std::time::Duration::from_secs(1));
424
425    Ok(())
426}