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