//! Build script: Tauri ACL for every Core command and build identity. //! //! Each command gets its own `allow-` permission (app manifest). Capabilities //! grant them per window class, so a module window cannot even reach a Core command //! through Tauri; the Rust Core additionally checks the calling window and the module //! manifest permissions at runtime (defence in depth). const SHARED: &[&str] = &["window_control", "window_is_maximized", "window_activity"]; /// Registered only with the `smoke` cargo feature (test builds); absent from release handlers. const SMOKE: &[&str] = &["smoke_report", "smoke_snapshot"]; const CORE: &[&str] = &[ "core_get_info", "core_legal_open", "core_open_external", "core_config_get", "core_config_update", "core_i18n_languages", "core_vault_status", "core_vault_create", "core_vault_unlock", "core_vault_lock", "core_vault_reset", "core_biometrics_status", "core_biometrics_enable", "core_biometrics_disable", "core_biometrics_unlock", "core_biometrics_auto_unlock", "core_biometrics_authenticate", "core_profiles_list", "core_profiles_save", "core_profiles_delete", "core_profiles_select_private_key", "core_profiles_export", "core_profiles_preview_import", "core_profiles_commit_import", "core_profiles_cancel_import", "core_availability_list", "core_availability_refresh", "core_ssh_list", "core_ssh_connect", "core_ssh_trust_host_key", "core_ssh_disconnect", "core_ssh_resize", "core_ssh_input", "core_diagnostics_resolve", "core_diagnostics_start", "core_diagnostics_cancel", "core_diagnostics_get", "core_diagnostics_history", "core_diagnostics_remove_history", "core_diagnostics_export", "core_diagnostics_copy", "core_diagnostics_import", "core_updates_get_state", "core_updates_check", "core_updates_select", "core_updates_download", "core_updates_install", "core_updates_reveal", "core_modules_list", "core_modules_get_state", "core_modules_check_catalog", "core_modules_install", "core_modules_uninstall", "core_modules_open", ]; const MODULE: &[&str] = &[ "module_get_info", "module_ui_get_language", "module_close", "module_window_minimize", "module_sessions_list", "module_system_metrics", "module_system_virtual_memory", "module_system_manage_virtual_memory", "module_disks_inspect", "module_disks_smart", "module_disks_plan", "module_disks_auto_expand", "module_disks_cancel_plan", "module_disks_apply", "module_disks_jobs", "module_disks_job_status", "module_keys_generate", "module_keys_save", "module_keys_password", "module_keys_copy_password", "module_logs_read", "module_users_list", "module_users_manage", "module_users_privileges", "module_users_inventory", "module_users_detail", "module_users_keys", "module_users_ssh_policy", "module_nginx_inspect", "module_nginx_read_config", "module_nginx_dump_config", "module_nginx_test_config", "module_nginx_save_config", "module_nginx_reload", "module_files_list", "module_files_privileges", "module_files_read_text", "module_files_write_text", "module_files_save_text", "module_files_list_recovery", "module_files_read_recovery", "module_files_cleanup_recovery", "module_files_close_text", "module_files_create_file", "module_files_mkdir", "module_files_rename", "module_files_remove", "module_files_remove_many", "module_files_upload", "module_files_download", "module_files_download_many", "module_files_download_archive", "module_transfers_list", "module_transfers_plan_upload", "module_transfers_plan_download", "module_transfers_start", "module_transfers_pause", "module_transfers_resume", "module_transfers_retry", "module_transfers_cancel", "module_transfers_remove", "module_transfers_settings", "module_transfers_plan_copy", "module_local_places", "module_local_list", "module_local_mkdir", "module_local_create_file", "module_local_rename", "module_local_remove", "module_hashing_algorithms", "module_hashing_select_files", "module_hashing_select_directory", "module_hashing_select_manifest", "module_hashing_select_manifest_destination", "module_hashing_start", "module_hashing_verify", "module_hashing_compare", "module_hashing_generate_manifest", "module_hashing_verify_manifest", "module_hashing_export_results", "module_hashing_copy_result", "module_hashing_status", "module_hashing_cancel", ]; const LICENSE: &[&str] = &["license_read", "license_show", "license_accept", "license_decline"]; fn permission(command: &str) -> String { format!("\"allow-{}\"", command.replace('_', "-")) } fn capability(identifier: &str, description: &str, windows: &[&str], commands: &[&[&str]]) -> String { let mut permissions: Vec = vec!["\"core:event:allow-listen\"".into(), "\"core:event:allow-unlisten\"".into()]; for group in commands { permissions.extend(group.iter().map(|c| permission(c))); } format!( "{{\n \"identifier\": \"{identifier}\",\n \"description\": \"{description} (generated by build.rs, do not edit)\",\n \"local\": true,\n \"windows\": [{}],\n \"permissions\": [\n {}\n ]\n}}\n", windows.iter().map(|w| format!("\"{w}\"")).collect::>().join(", "), permissions.join(",\n ") ) } fn write_if_changed(path: &std::path::Path, content: &str) { if std::fs::read_to_string(path).ok().as_deref() != Some(content) { std::fs::write(path, content).expect("write capability"); } } /// UTC timestamp without external crates (YYYY-MM-DDTHH:MM:SS.000Z). fn utc_now() -> String { let secs = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0) as i64; let days = secs.div_euclid(86_400); let rem = secs.rem_euclid(86_400); let (h, m, s) = (rem / 3600, (rem % 3600) / 60, rem % 60); let z = days + 719_468; let era = z.div_euclid(146_097); let doe = z - era * 146_097; let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); let mp = (5 * doy + 2) / 153; let d = doy - (153 * mp + 2) / 5 + 1; let mo = if mp < 10 { mp + 3 } else { mp - 9 }; let y = yoe + era * 400 + if mo <= 2 { 1 } else { 0 }; format!("{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}.000Z") } fn stamp_build_identity() { println!("cargo:rerun-if-env-changed=CHJ_BUILD_ID"); let built_at = utc_now(); let build_id = match std::env::var("CHJ_BUILD_ID") { _ if std::env::var("PROFILE").as_deref() != Ok("release") => "core-development-build".to_string(), Ok(id) => { if id.len() < 8 || id.len() > 128 || !id.chars().all(|c| c.is_ascii_alphanumeric() || "._-".contains(c)) { panic!("CHJ_BUILD_ID must be 8-128 characters of [A-Za-z0-9._-]"); } id } Err(_) if std::env::var("PROFILE").as_deref() == Ok("release") => { let nanos = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("system clock").as_nanos(); format!("release-{nanos}-{}", std::process::id()) } Err(_) => "core-development-build".to_string(), }; { let resources = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("resources"); if std::env::var("PROFILE").as_deref() != Ok("release") { // tauri-build requires the bundled directory; only release preparation fills it with the helper. for platform in ["darwin-arm64", "linux-x64", "win32-x64"] { std::fs::create_dir_all(resources.join("biometrics").join(platform)).expect("biometrics resource directory"); } write_if_changed(&resources.join("release-inventory.json"), "{\"buildId\":\"core-development-build\",\"files\":{}}\n"); } let stamp = format!("{{\n \"version\": \"{}\",\n \"buildId\": \"{build_id}\"\n}}\n", env!("CARGO_PKG_VERSION")); write_if_changed(&resources.join("build-info.json"), &stamp); } println!("cargo:rustc-env=CHJ_BUILD_ID={build_id}"); println!("cargo:rustc-env=CHJ_BUILT_AT={built_at}"); } fn main() { let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("capabilities"); std::fs::create_dir_all(&dir).expect("capabilities dir"); write_if_changed(&dir.join("main.json"), &capability("main-window", "Main window: Core API", &["main"], &[CORE, SHARED, SMOKE])); write_if_changed(&dir.join("modules.json"), &capability("module-windows", "Module windows: public Module API only", &["module-*"], &[MODULE, SHARED, SMOKE])); write_if_changed(&dir.join("license.json"), &capability("license-window", "License window: license API only", &["license"], &[LICENSE, SHARED, SMOKE])); stamp_build_identity(); for bridge in ["core-bridge.js", "module-bridge.js", "license-bridge.js"] { println!("cargo:rerun-if-changed=bridges/{bridge}"); } let all: &'static [&'static str] = Box::leak(SHARED.iter().chain(SMOKE).chain(CORE).chain(MODULE).chain(LICENSE).copied().collect::>().into_boxed_slice()); tauri_build::try_build(tauri_build::Attributes::new().app_manifest(tauri_build::AppManifest::new().commands(all))) .expect("failed to run tauri-build"); }