Vydané zdroje
Stáhnout zdroje ZIP
CH-J Tools
Procházejte adresáře a soubory konkrétního vydání aplikace.
CH-J / Server Manager
1704 souborů
0.1.2-beta
Přidáno: · Sestaveno:
1
//! CH-J Standby Helper (`ch-j-standby-helper`): a small separate process without WebView or Tauri. It is2
//! installed with the application (same bundle / installer) and only *calls* the application: every action3
//! starts `ch-j-tools --chj-quick <verify|sign> <file>`, which shows the small quick window. The helper4
//! never reads keys, the vault or passphrases and cannot sign anything by itself.5
//!6
//! Commands:7
//! run background process (started at login when enabled); local socket for the menu entries8
//! verify|sign <file> entry point of the system file-manager menu9
//! apply syncs login item and menu entries with `standby-helper.json`; prints the status as JSON10
//! status prints the status as JSON11
//! remove removes every system entry of the helper (used by uninstallers)12
//! stop ends a running background process13
//! keysync one KeySync pass (queued revocations/publications) when the app is closed; prints JSON14
//! monitor one server availability round; prints the status as JSON15
// No console window on Windows (the helper is started by Explorer and at login).16
#![cfg_attr(windows, windows_subsystem = "windows")]18
mod daemon;19
mod integration;20
mod keysync;21
mod lockfile;22
mod monitor;23
mod settings;25
use serde_json::json;26
use std::path::{Path, PathBuf};28
pub const VERSION: &str = env!("CARGO_PKG_VERSION");30
/// The application binary next to this helper (Windows install folder, Linux `/usr/bin`, development builds).31
/// In the macOS release bundle the helper is its own app `Contents/Library/LoginItems/CH-J-Standby-Helper.app`32
/// (so login items show its name), six levels below the outer `Contents` that holds `MacOS/ch-j-tools`.33
pub fn app_binary() -> PathBuf {34
let name = if cfg!(windows) { "ch-j-tools.exe" } else { "ch-j-tools" };35
match std::env::current_exe() {36
Ok(exe) => app_binary_for(&exe, name),37
Err(_) => PathBuf::from(name),38
}39
}41
fn app_binary_for(exe: &Path, name: &str) -> PathBuf {42
let sibling = exe.parent().map(|d| d.join(name)).unwrap_or_else(|| PathBuf::from(name));43
if sibling.is_file() {44
return sibling;45
}46
let parts: Vec<&Path> = exe.ancestors().take(7).collect();47
let named = |i: usize, want: &str| parts.get(i).and_then(|p| p.file_name()).is_some_and(|n| n == want);48
if named(3, "CH-J-Standby-Helper.app") && named(4, "LoginItems") && named(5, "Library") && named(6, "Contents") {49
return parts[6].join("MacOS").join(name);50
}51
sibling52
}54
/// Windows inherits every inheritable handle into a child, including this process's stdout pipe. The background55
/// helper started by `apply` would then keep the caller's pipe open for its whole life, and whoever reads the56
/// `apply` output to the end (the app's Core, scripts) would wait forever. Clear the inherit flag on our own57
/// standard handles before starting it (kernel32 is linked by std; no extra crate).58
#[cfg(windows)]59
fn windows_keep_std_handles_private() {60
use std::ffi::c_void;61
#[link(name = "kernel32")]62
extern "system" {63
fn GetStdHandle(which: u32) -> *mut c_void;64
fn SetHandleInformation(handle: *mut c_void, mask: u32, flags: u32) -> i32;65
}66
const HANDLE_FLAG_INHERIT: u32 = 1;67
// STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE (-10, -11, -12 as DWORD)68
for which in [-10i32 as u32, -11i32 as u32, -12i32 as u32] {69
// SAFETY: plain Win32 calls on this process's own standard handles; invalid/null handles just fail.70
unsafe {71
let handle = GetStdHandle(which);72
if !handle.is_null() && handle as isize != -1 {73
SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);74
}75
}76
}77
}79
pub fn helper_binary() -> PathBuf {80
std::env::current_exe().unwrap_or_else(|_| PathBuf::from("ch-j-standby-helper"))81
}83
/// Starts the application's quick window for one file. Detached: the helper does not wait for it.84
pub fn launch_quick(action: &str, file: &Path) -> std::io::Result<()> {85
let mut command = std::process::Command::new(app_binary());86
command.arg("--chj-quick").arg(action).arg(file).stdin(std::process::Stdio::null()).stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null());87
#[cfg(windows)]88
{89
use std::os::windows::process::CommandExt;90
command.creation_flags(0x0000_0008 /* DETACHED_PROCESS */);91
}92
let mut child = command.spawn()?;93
// Reap the child in the background process; a one-shot client exits right away anyway.94
std::thread::spawn(move || {95
let _ = child.wait();96
});97
Ok(())98
}100
/// Accepts only an existing regular file; a relative path (Nautilus scripts) is resolved against the cwd.101
pub fn checked_file(raw: &str) -> Option<PathBuf> {102
let path = std::fs::canonicalize(raw).ok()?;103
let path = strip_verbatim(path);104
std::fs::metadata(&path).ok().filter(|m| m.is_file()).map(|_| path)105
}107
/// `canonicalize` on Windows returns `\\?\C:\…`; the application expects a normal absolute path.108
fn strip_verbatim(path: PathBuf) -> PathBuf {109
#[cfg(windows)]110
{111
let text = path.to_string_lossy();112
if let Some(rest) = text.strip_prefix(r"\\?\") {113
if !rest.starts_with("UNC\\") {114
return PathBuf::from(rest);115
}116
}117
}118
path119
}121
fn status_json() -> serde_json::Value {122
let data = settings::data_dir();123
let settings = settings::load(&data);124
let app = app_binary();125
json!({126
"helperVersion": VERSION,127
"helperPath": helper_binary().to_string_lossy(),128
"appPath": app.to_string_lossy(),129
"appFound": app.is_file(),130
"settings": settings.to_json(),131
"installed": integration::installed(&settings::Context::new(&data)),132
"running": daemon::ping(&data),133
"platform": std::env::consts::OS,134
})135
}137
fn main() {138
let args: Vec<String> = std::env::args().skip(1).collect();139
let code = match args.first().map(String::as_str) {140
Some("run") => daemon::run(&settings::data_dir()),141
Some(action @ ("verify" | "sign")) => {142
let Some(file) = args.get(1).and_then(|raw| checked_file(raw)) else { std::process::exit(2) };143
let data = settings::data_dir();144
// Prefer the running background process; without it the helper starts the application itself.145
if daemon::request(&data, action, &file) || launch_quick(action, &file).is_ok() { 0 } else { 1 }146
}147
Some("apply") => {148
let data = settings::data_dir();149
let wanted = settings::load(&data);150
let ctx = settings::Context::new(&data);151
#[cfg(target_os = "macos")]152
let agent_before = integration::agent_text(&ctx);153
let result = integration::apply(&ctx, &wanted);154
// macOS: launchd runs the helper when it starts at login (listed as running in Login Items).155
#[cfg(target_os = "macos")]156
let by_launchd = integration::launchd_sync(&ctx, &wanted, agent_before != integration::agent_text(&ctx), daemon::ping(&data), || {157
// A helper started directly must be gone, or the launchd instance sees it running and exits.158
daemon::stop(&data);159
for _ in 0..40 {160
if !daemon::ping(&data) && !lockfile::held_by_other(&data) {161
break;162
}163
std::thread::sleep(std::time::Duration::from_millis(50));164
}165
});166
#[cfg(not(target_os = "macos"))]167
let by_launchd = false;168
if by_launchd {169
for _ in 0..40 {170
if daemon::ping(&data) {171
break;172
}173
std::thread::sleep(std::time::Duration::from_millis(50));174
}175
}176
// Otherwise start or stop the background process to match the settings now, not only at the next login.177
if !by_launchd && wanted.enabled && cfg!(any(unix, windows)) && !daemon::ping(&data) {178
let mut command = std::process::Command::new(helper_binary());179
command.arg("run").stdin(std::process::Stdio::null()).stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null());180
#[cfg(windows)]181
{182
use std::os::windows::process::CommandExt;183
command.creation_flags(0x0800_0000 /* CREATE_NO_WINDOW */ | 0x0000_0008 /* DETACHED_PROCESS */);184
windows_keep_std_handles_private();185
}186
let _ = command.spawn();187
for _ in 0..20 {188
if daemon::ping(&data) {189
break;190
}191
std::thread::sleep(std::time::Duration::from_millis(50));192
}193
} else if !wanted.enabled {194
daemon::stop(&data);195
// No status (names, addresses) may stay behind in the app data or the widget container.196
monitor::publish_off(&data);197
}198
let mut status = status_json();199
if let Err(error) = result {200
status["error"] = json!(error);201
}202
println!("{status}");203
if status.get("error").is_some() { 1 } else { 0 }204
}205
Some("status") => {206
println!("{}", status_json());207
0208
}209
Some("remove") => {210
let data = settings::data_dir();211
daemon::stop(&data);212
monitor::publish_off(&data);213
match integration::remove_all(&settings::Context::new(&data)) {214
Ok(()) => 0,215
Err(error) => {216
eprintln!("{error}");217
1218
}219
}220
}221
Some("keysync") => {222
println!("{}", keysync::pass(&settings::data_dir()));223
0224
}225
Some("monitor") => {226
println!("{}", monitor::round(&settings::data_dir(), &mut monitor::State::default()));227
0228
}229
Some("stop") => {230
daemon::stop(&settings::data_dir());231
0232
}233
Some("--version") => {234
println!("ch-j-standby-helper {VERSION}");235
0236
}237
_ => {238
eprintln!("usage: ch-j-standby-helper <run|verify FILE|sign FILE|apply|status|remove|stop|keysync|monitor|--version>");239
2240
}241
};242
std::process::exit(code);243
}245
#[cfg(test)]246
mod tests {247
use super::app_binary_for;248
use std::path::Path;250
#[test]251
fn app_is_found_from_the_nested_macos_helper_bundle_and_next_to_a_plain_helper() {252
let nested = Path::new("/Applications/CH-J Tools.app/Contents/Library/LoginItems/CH-J-Standby-Helper.app/Contents/MacOS/ch-j-standby-helper");253
assert_eq!(app_binary_for(nested, "ch-j-tools"), Path::new("/Applications/CH-J Tools.app/Contents/MacOS/ch-j-tools"));254
assert_eq!(app_binary_for(Path::new("/usr/bin/ch-j-standby-helper"), "ch-j-tools"), Path::new("/usr/bin/ch-j-tools"));255
let other = Path::new("/opt/Other.app/Contents/Library/LoginItems/Foreign.app/Contents/MacOS/ch-j-standby-helper");256
assert_eq!(app_binary_for(other, "ch-j-tools"), other.parent().unwrap().join("ch-j-tools"));257
}258
}SHA-256
2de464106d6c70d84c770410e48dbdf0201a5cadd6318ad8b454ac4ea085b60bCH-J Proprietary Software License 1.14 · Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.
SHA-256 archivu
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f