CH-J ToolsSerververwaltung über SSH
Menü
Veröffentlichte Quellen

CH-J Tools

Durchsuchen Sie Verzeichnisse und Dateien einer bestimmten Anwendungsausgabe.

Quellen als ZIP herunterladen
CH-J / Server Manager 872 Dateien
core-20261030T220507Z-61e4558d889f Hinzugefügt: · Erstellt:
bootstrap.rs Hinzugefügt: Datum nicht erfasst 15,7 KB · 347 Zeilen
Code Datei herunterladen
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 Server Manager").kind(MessageDialogKind::Error).show(move |_| handle.exit(1));
49pub fn start(app: &AppHandle) {
50 let resources = app.path().resource_dir().unwrap_or_default();
51 let paths = match AppPaths::resolve(app.path().config_dir().ok(), resources) {
52 Ok(paths) => paths,
53 Err(error) => return fatal(app, &error),
54 };
55 if migration::legacy_electron_running(&paths.data) {
56 return fatal(
57 app,
58 "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.",
59 );
60 }
61 match migration::acquire_runtime_lock(&paths.data) {
62 Ok(lock) => {
63 app.manage(RuntimeGuard(lock));
64 }
65 Err(error) => return fatal(app, &error.message),
66 }
67 let logger = Arc::new(Logger::new(&paths.data));
68 let build = BuildInfo::current(&app.package_info().version.to_string());
69 let backup = migration::prepare_data_directory(&paths.data, &build.version, &build.build_id);
70 match &backup {
71 Ok(report) => logger.info("Data directory prepared.", serde_json::to_value(report).unwrap_or(Value::Null)),
72 Err(error) => logger.error("Pre-update backup failed.", json!({ "code": error.code, "message": error.message })),
73 }
74 let assets = app.asset_resolver();
75 let i18n = Arc::new(I18n::load(|path| assets.get(path.to_string()).map(|asset| asset.bytes().to_vec())));
76 let config = Arc::new(ConfigStore::new(&paths.data, i18n.codes()));
77 config.load();
78 let prepared = Prepared { paths, logger, i18n, config, build };
79 if let Err(error) = backup {
80 // The data was not modified; continuing is the user's explicit decision.
81 let handle = app.clone();
82 let message = format!(
83 "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?",
84 error.code, error.code
85 );
86 app.dialog()
87 .message(message)
88 .title("CH-J Server Manager")
89 .kind(MessageDialogKind::Warning)
90 .buttons(MessageDialogButtons::OkCancelCustom("Continue / Pokračovat".into(), "Quit / Ukončit".into()))
91 .show(move |proceed| if proceed { license_then_core(&handle, prepared) } else { handle.exit(0) });
92 return;
93 }
94 license_then_core(app, prepared);
97fn license_then_core(app: &AppHandle, prepared: Prepared) {
98 let acceptance = match LicenseAcceptance::new(
99 &prepared.paths.data,
100 &prepared.paths.licenses().join("APPLICATION_LICENSE.txt"),
101 &prepared.paths.docs(),
102 &prepared.build.version,
103 &prepared.build.build_id,
104 ) {
105 Ok(acceptance) => Arc::new(acceptance),
106 Err(error) => return fatal(app, &error.message),
107 };
108 if acceptance.is_accepted() {
109 compose(app, prepared);
110 return;
111 }
112 let (tx, rx) = tokio::sync::oneshot::channel();
113 *app.state::<LicenseSlot>().0.lock() = Some(LicenseFlow { acceptance, language: prepared.config.language(), presented: false, shown: false, accepted: false, done: Some(tx) });
114 if let Err(error) = crate::windows::create_license_window(app, &prepared.i18n, &prepared.config.language()) {
115 return fatal(app, &error.to_string());
116 }
117 let handle = app.clone();
118 tauri::async_runtime::spawn(async move {
119 let accepted = rx.await.unwrap_or(false);
120 if accepted {
121 compose(&handle, prepared);
122 } else {
123 handle.exit(0);
124 }
125 });
128/// License window closed: resolve the flow (accepted or declined/closed).
129pub fn license_window_closed(app: &AppHandle) {
130 let flow = app.state::<LicenseSlot>().0.lock().take();
131 if let Some(mut flow) = flow {
132 if let Some(done) = flow.done.take() {
133 let _ = done.send(flow.accepted);
134 }
135 }
138fn compose(app: &AppHandle, prepared: Prepared) {
139 let Prepared { paths, logger, i18n, config, build } = prepared;
140 let vault = Arc::new(VaultStore::new(&paths.data));
141 let profiles = Arc::new(ProfileService::new(vault.clone()));
142 let main_emitter = {
143 let handle = app.clone();
144 Arc::new(move |event: &str, payload: Value| {
145 let _ = handle.emit_to(tauri::EventTarget::webview_window(crate::security::caller::MAIN_LABEL), event, payload);
146 })
147 };
148 let sessions = SessionManager::new(profiles.clone(), logger.clone(), main_emitter.clone());
149 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();
150 let https = match HttpsClient::new(&base_urls, &INSECURE_LEGACY_ORIGINS) {
151 Ok(client) => Arc::new(client),
152 Err(error) => return fatal(app, &error.message),
153 };
154 let registry = Arc::new(ModuleRegistry::new(&paths.data, Some(paths.bundled_modules()), logger.clone()));
155 let installer = ModuleInstaller::new(&paths.data, &build.version, crate::core::app::MODULE_API_VERSION);
156 let biometrics = {
157 let config_auth = config.clone();
158 let config_reason = config.clone();
159 let i18n_reason = i18n.clone();
160 Arc::new(BiometricService::new(
161 &paths.data,
162 &paths.resources,
163 vault.clone(),
164 Box::new(move || config_auth.get()["security"]["requireSystemAuthentication"] == true),
165 Box::new(move || i18n_reason.t(&config_reason.language(), "native.biometric.reason")),
166 ))
167 };
168 let diagnostics = {
169 let emitter = main_emitter.clone();
170 Arc::new(DiagnosticsService::new(&paths.data, paths.resources.clone(), Arc::new(move |event: Value| emitter("diagnostics:progress", event))))
171 };
172 let updates = {
173 let config_for = config.clone();
174 let https_for = https.clone();
175 let logger_for = logger.clone();
176 let root = paths.data.join("updates").join("downloads");
177 let verifier = Arc::new(crate::core::updates::openpgp::OpenPgpVerifier::new(paths.resources.join("signing").join("ch-j-signing-public.asc")));
178 let (platform, arch) = crate::core::diagnostics::http::node_platform_arch();
179 Arc::new(crate::core::updates::service::UpdateService::new(
180 &build.version,
181 &build.build_id,
182 &platform,
183 &arch,
184 config.clone(),
185 Box::new(move || {
186 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();
187 crate::core::updates::provider::ReleaseProvider::new(&urls, https_for.clone(), root.clone(), verifier.clone(), logger_for.clone())
188 }),
189 logger.clone(),
190 ))
191 };
192 let privileged = Arc::new(PrivilegedExecutor::new(sessions.clone()));
193 let files = Arc::new(crate::core::files::service::RemoteFileService::new(sessions.clone(), privileged.clone(), logger.clone()));
194 let transfers = {
195 let handle = app.clone();
196 crate::core::files::transfers::TransferManager::new(
197 sessions.clone(),
198 files.clone(),
199 Some(&paths.data),
200 logger.clone(),
201 // Transfer progress goes only to the module that owns the jobs; jobs keep running while it is closed.
202 Arc::new(move |owner: &str, update: Value| {
203 if let Some(core) = handle.state::<CoreSlot>().try_get() {
204 for context in core.windows.modules().into_iter().filter(|c| c.id == owner) {
205 core.emit_window(&context.label, "module:files:transfers:update", update.clone());
206 }
207 }
208 }),
209 )
210 };
211 {
212 let transfers = transfers.clone();
213 tauri::async_runtime::spawn(async move { transfers.start_session_listener() });
214 }
215 let windows_executor = Arc::new(WindowsExecutor { sessions: sessions.clone() });
216 let users = Arc::new(crate::core::users::service::UserAdmin {
217 sessions: sessions.clone(),
218 linux: crate::core::users::linux::LinuxUsers { sessions: sessions.clone(), executor: privileged.clone(), logger: logger.clone() },
219 windows: crate::core::users::windows::WindowsUsers { sessions: sessions.clone(), executor: windows_executor.clone(), logger: logger.clone() },
220 });
221 let virtual_memory = Arc::new(crate::core::system::virtual_memory::VirtualMemoryService::new(sessions.clone(), privileged.clone(), logger.clone()));
222 let disks = Arc::new(crate::core::disks::service::DiskService::new(sessions.clone(), privileged.clone(), logger.clone()));
223 let services = Services {
224 availability: Default::default(),
225 disks,
226 virtual_memory,
227 users,
228 files,
229 transfers,
230 updates,
231 https,
232 privileged,
233 windows: windows_executor,
234 sessions,
235 modules: Arc::new(ModuleService::new(registry, installer)),
236 biometrics,
237 diagnostics,
238 lock: LockController::default(),
239 keys: Default::default(),
240 hashing: {
241 let handle = app.clone();
242 Arc::new(crate::core::hashing::service::LocalHashService::new(
243 Arc::new(move |module_id: &str, job: Value| {
244 if let Some(core) = handle.state::<CoreSlot>().try_get() {
245 for context in core.windows.modules().into_iter().filter(|c| c.id == module_id) {
246 core.emit_window(&context.label, "module:hashing:progress", job.clone());
247 }
248 }
249 }),
250 Some(logger.clone()),
251 ))
252 },
253 };
254 let core = Arc::new(Core {
255 app: app.clone(),
256 paths,
257 build,
258 logger,
259 i18n,
260 config,
261 vault,
262 profiles,
263 profile_transfer: ProfileTransfer::default(),
264 windows: Arc::new(WindowRegistry::default()),
265 services,
266 });
267 match core.services.updates.cleanup_downloads() {
268 Ok(true) => core.logger.info("Core update download cache cleaned on startup.", json!({})),
269 Ok(false) => {}
270 Err(error) => core.logger.warn("Core update download cache cleanup failed.", json!({ "message": error.message })),
271 }
272 match core.services.modules.cleanup_downloads(&core) {
273 Ok(true) => core.logger.info("plugin package download cache cleaned on startup.", json!({})),
274 Ok(false) => {}
275 Err(error) => core.logger.warn("plugin package download cache cleanup failed.", json!({ "message": error.message })),
276 }
277 app.state::<CoreSlot>().set(core.clone());
278 match crate::windows::create_main_window(app, &core.i18n) {
279 Ok(window) => {
280 #[cfg(windows)]
281 if let Ok(hwnd) = window.hwnd() {
282 core.services.biometrics.set_window_handle(Some((hwnd.0 as usize).to_string()));
283 }
284 let _ = window;
285 }
286 Err(error) => return fatal(app, &error.to_string()),
287 }
288 let monitor = core.services.availability.clone();
289 let monitor_host = Arc::new(CoreMonitorHost(Arc::downgrade(&core)));
290 tauri::async_runtime::spawn(monitor.run(monitor_host));
291 // Inactivity auto-lock check every 5 s.
292 let checker = core.clone();
293 tauri::async_runtime::spawn(async move {
294 loop {
295 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
296 checker.services.lock.check(&checker).await;
297 }
298 });
299 core.logger.info(
300 "Core initialized.",
301 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" }),
302 );
305/// The availability monitor's view of the Core (`Weak`: the task must not keep the Core alive).
306struct CoreMonitorHost(std::sync::Weak<Core>);
308impl MonitorHost for CoreMonitorHost {
309 fn available(&self) -> bool {
310 self.0.upgrade().is_some_and(|core| core.vault.is_unlocked() && core.app.get_webview_window(crate::security::caller::MAIN_LABEL).is_some())
311 }
312 fn settings(&self) -> (bool, std::time::Duration) {
313 let config = self.0.upgrade().map(|core| core.config.get()).unwrap_or_default();
314 (config["monitoring"]["enabled"].as_bool() != Some(false), std::time::Duration::from_secs(config["monitoring"]["intervalSeconds"].as_u64().unwrap_or(DEFAULT_INTERVAL_SECS)))
315 }
316 fn targets(&self) -> Option<Vec<Target>> {
317 let profiles = self.0.upgrade()?.profiles.list().ok()?;
318 Some(profiles.iter().filter_map(Target::from_profile).collect())
319 }
320 fn publish(&self, event: Value) {
321 if let Some(core) = self.0.upgrade() {
322 core.emit_main("availability:changed", event);
323 }
324 }
325 fn probe_jump(&self, jump_id: &str, target: &Target) -> futures::future::BoxFuture<'static, Outcome> {
326 let (core, jump_id, host, port) = (self.0.upgrade(), jump_id.to_string(), target.host.clone(), target.port);
327 Box::pin(async move {
328 match core {
329 Some(core) => core.services.sessions.probe_through_jump(&jump_id, &host, port, JUMP_TIMEOUT).await,
330 None => Outcome::Unverified { reason: "noJumpSession" },
331 }
332 })
333 }
336/// Orderly shutdown (Electron `before-quit`).
337pub fn shutdown(core: &Arc<Core>) {
338 core.services.availability.stop();
339 core.services.transfers.shutdown();
340 core.services.biometrics.cancel();
341 core.services.diagnostics.stop_all();
342 let sessions = core.services.sessions.clone();
343 tauri::async_runtime::block_on(async move {
344 let _ = tokio::time::timeout(std::time::Duration::from_secs(3), sessions.disconnect_all("app-quit")).await;
345 });
346 core.vault.lock();
SHA-256513517a04d6b9df7ffde42e6c1ccf2dae3664fce02e0f52b9606888f5ba8998e

CH-J Proprietary Software License 1.14 · Die Quellen werden unter der CH-J Proprietary Software License 1.14 bereitgestellt. Ihre Verfügbarkeit ändert die Lizenzbedingungen nicht und gewährt keine zusätzlichen Rechte.

SHA-256 des Archivs77380d4805485ffa4027fbe7858b5836f197a4aa9d52358a43e2d12d018744df
Dateien