CH-J ToolsPalvelimen hallinta yli SSH
Valikko
Julkaistu lähde

CH-J Tools

Selaile hakemistoja ja tiedostoja tietyn sovelluksen julkaisua varten.

Lataa lähde ZIP
CH-J / Server Manager 1704 tiedostot
0.1.2-beta Lisätty: · Rakennettu:
main.rs Lisätty: Kirjaamaton päivä 11,2 KB · 258 rivit
Koodi Lataa tiedosto
1//! CH-J Standby Helper (`ch-j-standby-helper`): a small separate process without WebView or Tauri. It is
2//! installed with the application (same bundle / installer) and only *calls* the application: every action
3//! starts `ch-j-tools --chj-quick <verify|sign> <file>`, which shows the small quick window. The helper
4//! 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 entries
8//! verify|sign <file> entry point of the system file-manager menu
9//! apply syncs login item and menu entries with `standby-helper.json`; prints the status as JSON
10//! status prints the status as JSON
11//! remove removes every system entry of the helper (used by uninstallers)
12//! stop ends a running background process
13//! keysync one KeySync pass (queued revocations/publications) when the app is closed; prints JSON
14//! monitor one server availability round; prints the status as JSON
15// No console window on Windows (the helper is started by Explorer and at login).
16#![cfg_attr(windows, windows_subsystem = "windows")]
18mod daemon;
19mod integration;
20mod keysync;
21mod lockfile;
22mod monitor;
23mod settings;
25use serde_json::json;
26use std::path::{Path, PathBuf};
28pub 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`.
33pub 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 }
41fn 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 sibling
54/// Windows inherits every inheritable handle into a child, including this process's stdout pipe. The background
55/// helper started by `apply` would then keep the caller's pipe open for its whole life, and whoever reads the
56/// `apply` output to the end (the app's Core, scripts) would wait forever. Clear the inherit flag on our own
57/// standard handles before starting it (kernel32 is linked by std; no extra crate).
58#[cfg(windows)]
59fn 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 }
79pub fn helper_binary() -> PathBuf {
80 std::env::current_exe().unwrap_or_else(|_| PathBuf::from("ch-j-standby-helper"))
83/// Starts the application's quick window for one file. Detached: the helper does not wait for it.
84pub 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(())
100/// Accepts only an existing regular file; a relative path (Nautilus scripts) is resolved against the cwd.
101pub 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)
107/// `canonicalize` on Windows returns `\\?\C:\…`; the application expects a normal absolute path.
108fn 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 path
121fn 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 })
137fn 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 0
208 }
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 1
218 }
219 }
220 }
221 Some("keysync") => {
222 println!("{}", keysync::pass(&settings::data_dir()));
223 0
224 }
225 Some("monitor") => {
226 println!("{}", monitor::round(&settings::data_dir(), &mut monitor::State::default()));
227 0
228 }
229 Some("stop") => {
230 daemon::stop(&settings::data_dir());
231 0
232 }
233 Some("--version") => {
234 println!("ch-j-standby-helper {VERSION}");
235 0
236 }
237 _ => {
238 eprintln!("usage: ch-j-standby-helper <run|verify FILE|sign FILE|apply|status|remove|stop|keysync|monitor|--version>");
239 2
240 }
241 };
242 std::process::exit(code);
245#[cfg(test)]
246mod 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 }
SHA-2562de464106d6c70d84c770410e48dbdf0201a5cadd6318ad8b454ac4ea085b60b

CH-J Proprietary Software License 1.14 · Lähde ilmoitetaan CH-J Proprietary Software License 1.14-koodilla. Sen saatavuus ei muuta lisenssiehtoja eikä myönnä lisäoikeuksia.

Arkisto SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Tiedostot