CH-J Toolsδιαχείριση διακομιστή πάνω από SSH
Μενού
Δημοσίευση πηγής

CH-J Tools

Περιηγηθείτε σε καταλόγους και αρχεία για μια συγκεκριμένη έκδοση εφαρμογών.

Κατεβάστε την πηγή ZIP
CH-J / Server Manager 1704 αρχεία
0.1.2-beta Προστέθηκε: · Χτισμένο:
bootstrap.rs Προστέθηκε: Ημερομηνία που δεν έχει καταγραφεί 20,1 KB · 422 γραμμές
Κωδικός Λήψη αρχείου
1//! Application start (replaces Electron `bootstrap()`): data-directory guard and backup,
2//! mandatory license dialog, then Core composition and the main window.
4use crate::commands::license::{LicenseFlow, LicenseSlot};
5use crate::core::app::{BuildInfo, Core, CoreSlot};
6use crate::core::availability::{MonitorHost, Outcome, Target, DEFAULT_INTERVAL_SECS, JUMP_TIMEOUT};
7use crate::core::biometrics::BiometricService;
8use crate::core::config::ConfigStore;
9use crate::core::diagnostics::service::DiagnosticsService;
10use crate::core::i18n::I18n;
11use crate::core::legal::LicenseAcceptance;
12use crate::core::lock::LockController;
13use crate::core::logging::Logger;
14use crate::core::migration;
15use crate::core::modules::installer::ModuleInstaller;
16use crate::core::modules::registry::ModuleRegistry;
17use crate::core::modules::service::ModuleService;
18use crate::core::net::https::{HttpsClient, INSECURE_LEGACY_ORIGINS};
19use crate::core::paths::AppPaths;
20use crate::core::profile_transfer::ProfileTransfer;
21use crate::core::profiles::ProfileService;
22use crate::core::services::Services;
23use crate::core::ssh::privileged::PrivilegedExecutor;
24use crate::core::ssh::session::SessionManager;
25use crate::core::ssh::windows_exec::WindowsExecutor;
26use crate::core::vault::VaultStore;
27use crate::security::caller::WindowRegistry;
28use serde_json::{json, Value};
29use std::sync::Arc;
30use tauri::{AppHandle, Emitter, Manager};
31use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind};
33/// Held for the whole process lifetime.
34pub struct RuntimeGuard(#[allow(dead_code)] pub migration::RuntimeLock);
36pub struct Prepared {
37 pub paths: AppPaths,
38 pub logger: Arc<Logger>,
39 pub i18n: Arc<I18n>,
40 pub config: Arc<ConfigStore>,
41 pub build: BuildInfo,
44fn fatal(app: &AppHandle, message: &str) {
45 let handle = app.clone();
46 app.dialog().message(message).title("CH-J Tools").kind(MessageDialogKind::Error).show(move |_| handle.exit(1));
49pub fn start(app: &AppHandle) {
50 let resources = app.path().resource_dir().unwrap_or_default();
51 // Renaming the old "CH-J Server Manager" data folder can fail on Windows while its helper still runs.
52 let stop_helper = |legacy: &std::path::Path| {
53 let _ = crate::core::standby::stop(legacy);
54 };
55 let paths = match AppPaths::resolve(app.path().config_dir().ok(), resources, stop_helper) {
56 Ok(paths) => paths,
57 Err(error) => return fatal(app, &error),
58 };
59 if migration::legacy_electron_running(&paths.data) {
60 return fatal(
61 app,
62 "CH-J Server Manager 0.1.0 is still running and uses the same data. Quit it first, then start the application again.\n\nCH-J Server Manager 0.1.0 stále běží nad stejnými daty. Nejprve ho ukončete a aplikaci spusťte znovu.",
63 );
64 }
65 match migration::acquire_runtime_lock(&paths.data) {
66 Ok(lock) => {
67 app.manage(RuntimeGuard(lock));
68 }
69 Err(_) if paths.relocation.as_ref().is_some_and(|r| r.outcome == "kept-legacy-in-use") => {
70 return fatal(
71 app,
72 "CH-J Server Manager (the previous name of this application) is still running and uses your data. Quit it, then start CH-J Tools again. Your data is moved to CH-J Tools at that start.\n\nCH-J Server Manager (předchozí název této aplikace) stále běží a používá vaše data. Ukončete ho a spusťte CH-J Tools znovu; data se při tom přesunou do CH-J Tools.",
73 );
74 }
75 Err(error) => return fatal(app, &error.message),
76 }
77 let logger = Arc::new(Logger::new(&paths.data));
78 if let Some(relocation) = &paths.relocation {
79 logger.info("Data folder of the renamed application checked.", serde_json::to_value(relocation).unwrap_or(Value::Null));
80 }
81 let build = BuildInfo::current(&app.package_info().version.to_string());
82 let backup = migration::prepare_data_directory(&paths.data, &build.version, &build.build_id);
83 let _ = std::fs::remove_dir_all(paths.legacy_module_webview_data());
84 match &backup {
85 Ok(report) => logger.info("Data directory prepared.", serde_json::to_value(report).unwrap_or(Value::Null)),
86 Err(error) => logger.error("Pre-update backup failed.", json!({ "code": error.code, "message": error.message })),
87 }
88 let assets = app.asset_resolver();
89 let i18n = Arc::new(I18n::load(|path| assets.get(path.to_string()).map(|asset| asset.bytes().to_vec())));
90 let config = Arc::new(ConfigStore::new(&paths.data, i18n.codes()));
91 config.load();
92 let prepared = Prepared { paths, logger, i18n, config, build };
93 if let Err(error) = backup {
94 // The data was not modified; continuing is the user's explicit decision.
95 let handle = app.clone();
96 let message = format!(
97 "The automatic backup before this update failed ({}). Your data was not changed.\n\nContinue without a new backup?\n\nAutomatická záloha před aktualizací selhala ({}). Data nebyla změněna. Pokračovat bez nové zálohy?",
98 error.code, error.code
99 );
100 app.dialog()
101 .message(message)
102 .title("CH-J Tools")
103 .kind(MessageDialogKind::Warning)
104 .buttons(MessageDialogButtons::OkCancelCustom("Continue / Pokračovat".into(), "Quit / Ukončit".into()))
105 .show(move |proceed| if proceed { license_then_core(&handle, prepared) } else { handle.exit(0) });
106 return;
107 }
108 license_then_core(app, prepared);
111fn license_then_core(app: &AppHandle, prepared: Prepared) {
112 let acceptance = match LicenseAcceptance::new(
113 &prepared.paths.data,
114 &prepared.paths.licenses().join("APPLICATION_LICENSE.txt"),
115 &prepared.paths.docs(),
116 &prepared.build.version,
117 &prepared.build.build_id,
118 ) {
119 Ok(acceptance) => Arc::new(acceptance),
120 Err(error) => return fatal(app, &error.message),
121 };
122 if acceptance.is_accepted() {
123 compose(app, prepared);
124 return;
125 }
126 let (tx, rx) = tokio::sync::oneshot::channel();
127 *app.state::<LicenseSlot>().0.lock() = Some(LicenseFlow { acceptance, language: prepared.config.language(), presented: false, shown: false, accepted: false, done: Some(tx) });
128 if let Err(error) = crate::windows::create_license_window(app, &prepared.i18n, &prepared.config.language()) {
129 return fatal(app, &error.to_string());
130 }
131 let handle = app.clone();
132 tauri::async_runtime::spawn(async move {
133 let accepted = rx.await.unwrap_or(false);
134 if accepted {
135 compose(&handle, prepared);
136 if let Some(window) = handle.get_webview_window(crate::security::caller::LICENSE_LABEL) {
137 let _ = window.close();
138 }
139 } else {
140 handle.exit(0);
141 }
142 });
145/// License window closed: resolve the flow (accepted or declined/closed).
146pub fn license_window_closed(app: &AppHandle) {
147 let flow = app.state::<LicenseSlot>().0.lock().take();
148 if let Some(mut flow) = flow {
149 if let Some(done) = flow.done.take() {
150 let _ = done.send(flow.accepted);
151 }
152 }
155fn compose(app: &AppHandle, prepared: Prepared) {
156 let Prepared { paths, logger, i18n, config, build } = prepared;
157 let vault = Arc::new(VaultStore::new(&paths.data));
158 // An update can move the helper (macOS 0.1.2: Contents/MacOS → Contents/Library/LoginItems/CH-J-Standby-Helper.app),
159 // so the installed app rewrites the helper's system entries once per start. Development builds never touch them.
160 #[cfg(not(debug_assertions))]
161 let _ = crate::core::standby::enable_on_first_start(&paths.data);
162 #[cfg(not(debug_assertions))]
163 if crate::core::standby::load(&paths.data)["enabled"] == true {
164 let data = paths.data.clone();
165 std::thread::spawn(move || {
166 let _ = crate::core::standby::apply(&data);
167 });
168 }
169 let profiles = Arc::new(ProfileService::new(vault.clone()));
170 let main_emitter = {
171 let handle = app.clone();
172 Arc::new(move |event: &str, payload: Value| {
173 let _ = handle.emit_to(tauri::EventTarget::webview_window(crate::security::caller::MAIN_LABEL), event, payload);
174 })
175 };
176 let sessions = SessionManager::new(profiles.clone(), logger.clone(), main_emitter.clone());
177 let base_urls: Vec<String> = config.get()["updates"]["baseUrls"].as_array().map(|a| a.iter().filter_map(|v| v.as_str().map(str::to_string)).collect()).unwrap_or_default();
178 let https = match HttpsClient::new(&base_urls, &INSECURE_LEGACY_ORIGINS) {
179 Ok(client) => Arc::new(client),
180 Err(error) => return fatal(app, &error.message),
181 };
182 let registry = Arc::new(ModuleRegistry::new(&paths.data, Some(paths.bundled_modules()), logger.clone()));
183 let installer = ModuleInstaller::new(&paths.data, &build.version, crate::core::app::MODULE_API_VERSION);
184 let biometrics = {
185 let config_auth = config.clone();
186 let config_reason = config.clone();
187 let i18n_reason = i18n.clone();
188 Arc::new(BiometricService::new(
189 &paths.data,
190 &paths.resources,
191 vault.clone(),
192 Box::new(move || config_auth.get()["security"]["requireSystemAuthentication"] == true),
193 Box::new(move || i18n_reason.t(&config_reason.language(), "native.biometric.reason")),
194 ))
195 };
196 let diagnostics = {
197 let emitter = main_emitter.clone();
198 Arc::new(DiagnosticsService::new(&paths.data, paths.resources.clone(), Arc::new(move |event: Value| emitter("diagnostics:progress", event))))
199 };
200 let updates = {
201 let config_for = config.clone();
202 let https_for = https.clone();
203 let logger_for = logger.clone();
204 let root = paths.data.join("updates").join("downloads");
205 let verifier = Arc::new(crate::core::updates::openpgp::OpenPgpVerifier::new(paths.resources.join("signing").join("ch-j-signing-public.asc")));
206 let (platform, arch) = crate::core::diagnostics::http::node_platform_arch();
207 Arc::new(crate::core::updates::service::UpdateService::new(
208 &build.version,
209 &build.build_id,
210 &platform,
211 &arch,
212 config.clone(),
213 Box::new(move || {
214 let urls: Vec<String> = config_for.get()["updates"]["baseUrls"].as_array().map(|a| a.iter().filter_map(|v| v.as_str().map(str::to_string)).collect()).unwrap_or_default();
215 crate::core::updates::provider::ReleaseProvider::new(&urls, https_for.clone(), root.clone(), verifier.clone(), logger_for.clone())
216 }),
217 logger.clone(),
218 ))
219 };
220 let privileged = Arc::new(PrivilegedExecutor::new(sessions.clone()));
221 let files = Arc::new(crate::core::files::service::RemoteFileService::new(sessions.clone(), privileged.clone(), logger.clone()));
222 let transfers = {
223 let handle = app.clone();
224 crate::core::files::transfers::TransferManager::new(
225 sessions.clone(),
226 files.clone(),
227 Some(&paths.data),
228 logger.clone(),
229 // Transfer progress goes only to the module that owns the jobs; jobs keep running while it is closed.
230 Arc::new(move |owner: &str, update: Value| {
231 if let Some(core) = handle.state::<CoreSlot>().try_get() {
232 for context in core.windows.modules().into_iter().filter(|c| c.id == owner) {
233 core.emit_window(&context.label, "module:files:transfers:update", update.clone());
234 }
235 }
236 }),
237 )
238 };
239 {
240 let transfers = transfers.clone();
241 tauri::async_runtime::spawn(async move { transfers.start_session_listener() });
242 }
243 let windows_executor = Arc::new(WindowsExecutor { sessions: sessions.clone() });
244 let users = Arc::new(crate::core::users::service::UserAdmin {
245 sessions: sessions.clone(),
246 linux: crate::core::users::linux::LinuxUsers { sessions: sessions.clone(), executor: privileged.clone(), logger: logger.clone() },
247 windows: crate::core::users::windows::WindowsUsers { sessions: sessions.clone(), executor: windows_executor.clone(), logger: logger.clone() },
248 });
249 let virtual_memory = Arc::new(crate::core::system::virtual_memory::VirtualMemoryService::new(sessions.clone(), privileged.clone(), logger.clone()));
250 let disks = Arc::new(crate::core::disks::service::DiskService::new(sessions.clone(), privileged.clone(), logger.clone()));
251 let rsync = crate::core::rsync::service::RsyncService::new(sessions.clone(), privileged.clone(), logger.clone(), &paths.data, std::env::current_exe().unwrap_or_default());
252 {
253 let rsync = rsync.clone();
254 tauri::async_runtime::spawn(async move { rsync.start_scheduler() });
255 }
256 let network = crate::core::network::service::NetworkService::new(sessions.clone(), privileged.clone(), logger.clone());
257 let services = Services {
258 network,
259 rsync,
260 availability: Default::default(),
261 keysync: crate::core::keysync::KeySync::new(&paths.data),
262 suggest: Arc::new(crate::core::suggest::SuggestService::new(sessions.clone(), vault.clone())),
263 disks,
264 virtual_memory,
265 users,
266 files,
267 transfers,
268 updates,
269 https,
270 privileged,
271 windows: windows_executor,
272 sessions,
273 modules: Arc::new(ModuleService::new(registry, installer)),
274 biometrics,
275 diagnostics,
276 lock: LockController::default(),
277 keys: Default::default(),
278 hashing: {
279 let handle = app.clone();
280 Arc::new(crate::core::hashing::service::LocalHashService::new(
281 Arc::new(move |module_id: &str, job: Value| {
282 if let Some(core) = handle.state::<CoreSlot>().try_get() {
283 for context in core.windows.modules().into_iter().filter(|c| c.id == module_id) {
284 core.emit_window(&context.label, "module:hashing:progress", job.clone());
285 }
286 }
287 }),
288 Some(logger.clone()),
289 ))
290 },
291 };
292 let core = Arc::new(Core {
293 app: app.clone(),
294 paths,
295 build,
296 logger,
297 i18n,
298 config,
299 vault,
300 profiles,
301 profile_transfer: ProfileTransfer::default(),
302 windows: Arc::new(WindowRegistry::default()),
303 services,
304 });
305 match core.services.updates.cleanup_downloads() {
306 Ok(true) => core.logger.info("Core update download cache cleaned on startup.", json!({})),
307 Ok(false) => {}
308 Err(error) => core.logger.warn("Core update download cache cleanup failed.", json!({ "message": error.message })),
309 }
310 match core.services.modules.cleanup_downloads(&core) {
311 Ok(true) => core.logger.info("plugin package download cache cleaned on startup.", json!({})),
312 Ok(false) => {}
313 Err(error) => core.logger.warn("plugin package download cache cleanup failed.", json!({ "message": error.message })),
314 }
315 app.state::<CoreSlot>().set(core.clone());
316 let quick = app.state::<crate::core::quick::QuickSlot>();
317 let quick_only = quick.get().is_some() && *quick.only_quick.lock();
318 let quick_pending = quick.get().is_some();
319 if quick_pending && !quick_only {
320 // Forwarded during start-up of a normal launch: show both.
321 let _ = crate::windows::open_quick_window(app);
322 }
323 let window_started = std::time::Instant::now();
324 let opened = if quick_only { crate::windows::open_quick_window(app) } else { crate::windows::create_main_window(app, &core.i18n, &core.config.language()) };
325 core.logger.info("Start-up window created.", json!({ "quick": quick_only, "ms": window_started.elapsed().as_millis() as u64 }));
326 match opened {
327 Ok(window) => {
328 #[cfg(windows)]
329 if let Ok(hwnd) = window.hwnd() {
330 core.services.biometrics.set_window_handle(Some((hwnd.0 as usize).to_string()));
331 }
332 let _ = window;
333 }
334 Err(error) => return fatal(app, &error.to_string()),
335 }
336 let monitor = core.services.availability.clone();
337 let monitor_host = Arc::new(CoreMonitorHost(Arc::downgrade(&core)));
338 tauri::async_runtime::spawn(monitor.run(monitor_host));
339 // KeySyncManager: queue and verified server state for keys.ch-j.de, every 90 s and right after a new job.
340 let keysync = core.services.keysync.clone();
341 let sync_core = Arc::downgrade(&core);
342 tauri::async_runtime::spawn(async move {
343 loop {
344 let Some(core) = sync_core.upgrade() else { break };
345 if let Ok(transport) = crate::core::keysync::Transport::https() {
346 if keysync.run_once(&transport).await {
347 keysync.apply_to_vault(&core.vault);
348 core.emit_main("keychain:changed", json!({}));
349 }
350 }
351 drop(core);
352 tokio::select! { _ = tokio::time::sleep(crate::core::keysync::INTERVAL) => {}, _ = keysync.notify.notified() => {} }
353 }
354 });
355 // Helper availability monitor: keep `standby-servers.json` current while the vault is unlocked (written only on
356 // change; off removes it). See `core::standby::sync_servers`.
357 let standby_sync = core.clone();
358 tauri::async_runtime::spawn(async move {
359 loop {
360 tokio::time::sleep(std::time::Duration::from_secs(30)).await;
361 let profiles = if standby_sync.vault.is_unlocked() { standby_sync.profiles.list().ok() } else { None };
362 let data = standby_sync.paths.data.clone();
363 let _ = tokio::task::spawn_blocking(move || crate::core::standby::sync_servers(&data, profiles.as_deref())).await;
364 }
365 });
366 // Inactivity auto-lock check every 5 s.
367 let checker = core.clone();
368 tauri::async_runtime::spawn(async move {
369 loop {
370 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
371 checker.services.lock.check(&checker).await;
372 }
373 });
374 core.logger.info(
375 "Core initialized.",
376 json!({ "version": core.build.version, "buildId": core.build.build_id, "platform": crate::windows::platform_name(), "arch": std::env::consts::ARCH, "storageRoot": core.paths.data.to_string_lossy(), "runtime": "tauri" }),
377 );
380/// The availability monitor's view of the Core (`Weak`: the task must not keep the Core alive).
381struct CoreMonitorHost(std::sync::Weak<Core>);
383impl MonitorHost for CoreMonitorHost {
384 fn available(&self) -> bool {
385 self.0.upgrade().is_some_and(|core| core.vault.is_unlocked() && core.app.get_window(crate::security::caller::MAIN_LABEL).is_some())
386 }
387 fn settings(&self) -> (bool, std::time::Duration) {
388 let config = self.0.upgrade().map(|core| core.config.get()).unwrap_or_default();
389 (config["monitoring"]["enabled"].as_bool() != Some(false), std::time::Duration::from_secs(config["monitoring"]["intervalSeconds"].as_u64().unwrap_or(DEFAULT_INTERVAL_SECS)))
390 }
391 fn targets(&self) -> Option<Vec<Target>> {
392 let profiles = self.0.upgrade()?.profiles.list().ok()?;
393 Some(profiles.iter().filter_map(Target::from_profile).collect())
394 }
395 fn publish(&self, event: Value) {
396 if let Some(core) = self.0.upgrade() {
397 core.emit_main("availability:changed", event);
398 }
399 }
400 fn probe_jump(&self, jump_id: &str, target: &Target) -> futures::future::BoxFuture<'static, Outcome> {
401 let (core, jump_id, host, port) = (self.0.upgrade(), jump_id.to_string(), target.host.clone(), target.port);
402 Box::pin(async move {
403 match core {
404 Some(core) => core.services.sessions.probe_through_jump(&jump_id, &host, port, JUMP_TIMEOUT).await,
405 None => Outcome::Unverified { reason: "noJumpSession" },
406 }
407 })
408 }
411/// Orderly shutdown (Electron `before-quit`).
412pub fn shutdown(core: &Arc<Core>) {
413 core.services.availability.stop();
414 core.services.transfers.shutdown();
415 core.services.biometrics.cancel();
416 core.services.diagnostics.stop_all();
417 let sessions = core.services.sessions.clone();
418 tauri::async_runtime::block_on(async move {
419 let _ = tokio::time::timeout(std::time::Duration::from_secs(3), sessions.disconnect_all("app-quit")).await;
420 });
421 core.vault.lock();
SHA-256cafa7226ad2da5e11e564ba0815c964344c3eea336ffb8a7b43b8b0dce9a2646

CH-J Proprietary Software License 1.14 · Η πηγή παρέχεται στο CH-J Proprietary Software License 1.14. Η διαθεσιμότητά του δεν αλλάζει τους όρους της άδειας χρήσης ούτε χορηγεί πρόσθετα δικαιώματα.

Archive SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Αρχεία