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
872 souborů
core-20261030T220507Z-61e4558d889f
Přidáno: · Sestaveno:
1
//! Application start (replaces Electron `bootstrap()`): data-directory guard and backup,2
//! mandatory license dialog, then Core composition and the main window.4
use crate::commands::license::{LicenseFlow, LicenseSlot};5
use crate::core::app::{BuildInfo, Core, CoreSlot};6
use crate::core::availability::{MonitorHost, Outcome, Target, DEFAULT_INTERVAL_SECS, JUMP_TIMEOUT};7
use crate::core::biometrics::BiometricService;8
use crate::core::config::ConfigStore;9
use crate::core::diagnostics::service::DiagnosticsService;10
use crate::core::i18n::I18n;11
use crate::core::legal::LicenseAcceptance;12
use crate::core::lock::LockController;13
use crate::core::logging::Logger;14
use crate::core::migration;15
use crate::core::modules::installer::ModuleInstaller;16
use crate::core::modules::registry::ModuleRegistry;17
use crate::core::modules::service::ModuleService;18
use crate::core::net::https::{HttpsClient, INSECURE_LEGACY_ORIGINS};19
use crate::core::paths::AppPaths;20
use crate::core::profile_transfer::ProfileTransfer;21
use crate::core::profiles::ProfileService;22
use crate::core::services::Services;23
use crate::core::ssh::privileged::PrivilegedExecutor;24
use crate::core::ssh::session::SessionManager;25
use crate::core::ssh::windows_exec::WindowsExecutor;26
use crate::core::vault::VaultStore;27
use crate::security::caller::WindowRegistry;28
use serde_json::{json, Value};29
use std::sync::Arc;30
use tauri::{AppHandle, Emitter, Manager};31
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind};33
/// Held for the whole process lifetime.34
pub struct RuntimeGuard(#[allow(dead_code)] pub migration::RuntimeLock);36
pub 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,42
}44
fn 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));47
}49
pub 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.code85
);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);95
}97
fn 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
});126
}128
/// License window closed: resolve the flow (accepted or declined/closed).129
pub 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
}136
}138
fn 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
);303
}305
/// The availability monitor's view of the Core (`Weak`: the task must not keep the Core alive).306
struct CoreMonitorHost(std::sync::Weak<Core>);308
impl 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
}334
}336
/// Orderly shutdown (Electron `before-quit`).337
pub 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();347
}SHA-256
513517a04d6b9df7ffde42e6c1ccf2dae3664fce02e0f52b9606888f5ba8998eCH-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
77380d4805485ffa4027fbe7858b5836f197a4aa9d52358a43e2d12d018744df