Sumber yang diterbitkan
Muat turun ZIP sumber
CH-J Tools
Layari direktori dan fail untuk keluaran aplikasi tertentu.
CH-J / Server Manager
1704 fail
0.1.2-beta
Ditambah: · Dibina:
1
//! Build script: Tauri ACL for every Core command and build identity.2
//!3
//! Each command gets its own `allow-<command>` permission (app manifest). Capabilities4
//! grant them per window class, so a module window cannot even reach a Core command5
//! through Tauri; the Rust Core additionally checks the calling window and the module6
//! manifest permissions at runtime (defence in depth).8
const SHARED: &[&str] = &["window_control", "window_is_maximized", "window_activity"];9
/// Registered only with the `smoke` cargo feature (test builds); absent from release handlers.10
const SMOKE: &[&str] = &["smoke_report", "smoke_snapshot"];12
const CORE: &[&str] = &[13
"core_get_info", "core_legal_open", "core_open_external", "core_config_get", "core_config_update", "core_i18n_languages", "core_i18n_catalogs",14
"core_keychain", "core_standby_get", "core_standby_set", "core_vault_status", "core_vault_create", "core_vault_unlock", "core_vault_lock", "core_vault_reset",15
"core_biometrics_status", "core_biometrics_enable", "core_biometrics_disable", "core_biometrics_unlock",16
"core_biometrics_auto_unlock", "core_biometrics_authenticate",17
"core_profiles_list", "core_profiles_save", "core_profiles_delete", "core_profiles_select_private_key",18
"core_profiles_export", "core_profiles_preview_import", "core_profiles_commit_import", "core_profiles_cancel_import",19
"core_availability_list", "core_availability_refresh",20
"core_terminal_suggest", "core_terminal_dictionary_get", "core_terminal_dictionary_save", "core_ssh_list", "core_ssh_connect", "core_ssh_trust_host_key", "core_ssh_disconnect", "core_ssh_resize", "core_ssh_input",21
"core_diagnostics_resolve", "core_diagnostics_start", "core_diagnostics_cancel", "core_diagnostics_get",22
"core_diagnostics_history", "core_diagnostics_remove_history", "core_diagnostics_export", "core_diagnostics_copy",23
"core_diagnostics_import",24
"core_updates_get_state", "core_updates_check", "core_updates_select", "core_updates_download", "core_updates_install",25
"core_updates_reveal",26
"core_modules_list", "core_modules_get_state", "core_modules_check_catalog", "core_modules_install",27
"core_modules_uninstall", "core_modules_open", "core_modules_set_area", "core_modules_detach", "core_modules_collapse", "core_modules_close",28
];30
const MODULE: &[&str] = &[31
"module_get_info", "module_ui_get_language", "module_close", "module_window_minimize", "module_host_state", "module_host_detach", "module_host_attach", "module_sessions_list",32
"module_system_metrics", "module_system_virtual_memory", "module_system_manage_virtual_memory",33
"module_disks_inspect", "module_disks_smart", "module_disks_plan", "module_disks_auto_expand", "module_disks_cancel_plan",34
"module_disks_apply", "module_disks_jobs", "module_disks_job_status",35
"module_keys_generate", "module_keys_state", "module_keys_edit", "module_keys_save_artifact", "module_keys_save_bundle", "module_keys_store_keychain", "module_keychain_public_ssh", "module_keys_save", "module_keys_password", "module_keys_copy_password", "module_logs_read",36
"module_users_list", "module_users_manage", "module_users_privileges", "module_users_inventory", "module_users_detail",37
"module_users_keys", "module_users_ssh_policy", "module_users_ssh_password_off_all",38
"module_apache_inspect", "module_apache_inventory", "module_apache_read_log", "module_apache_probe_backends", "module_apache_read_file", "module_apache_backups", "module_apache_read_backup", "module_apache_plan_site", "module_apache_plan_edit", "module_apache_plan_fix", "module_apache_save_file", "module_apache_restore_backup", "module_apache_create_site", "module_apache_set_site_enabled", "module_apache_adopt_site", "module_apache_drop_broken_link", "module_apache_set_module", "module_apache_reload", "module_network_inspect", "module_network_pending", "module_network_diagnose", "module_network_plan_interface", "module_network_plan_firewall", "module_network_apply", "module_network_verify", "module_network_confirm", "module_network_rollback", "module_network_extend", "module_network_transaction_log", "module_rsync_ssh_profiles", "module_rsync_local_probe", "module_rsync_remote_probe", "module_rsync_inspect", "module_rsync_jobs", "module_rsync_job", "module_rsync_describe", "module_rsync_versions", "module_rsync_profiles", "module_rsync_history", "module_rsync_schedule_list", "module_rsync_discover", "module_rsync_schedule_log", "module_rsync_take_handoff", "module_rsync_preview", "module_rsync_run", "module_rsync_cancel", "module_rsync_install", "module_rsync_connect", "module_rsync_trust_host", "module_rsync_disconnect", "module_rsync_save_profile", "module_rsync_delete_profile", "module_rsync_clear_history", "module_rsync_prune_versions", "module_rsync_schedule_install", "module_rsync_schedule_start", "module_rsync_schedule_remove", "module_rsync_open_manager", "module_apache_install_certbot", "module_apache_issue_certificate", "module_nginx_inspect", "module_nginx_read_config", "module_nginx_dump_config", "module_nginx_test_config",39
"module_nginx_save_config", "module_nginx_reload",40
"module_nginx_sites", "module_nginx_plan_site", "module_nginx_create_site", "module_nginx_set_site_enabled", "module_nginx_adopt_site", "module_nginx_drop_broken_link", "module_nginx_plan_route", "module_nginx_apply_route", "module_nginx_diagnose_domain", "module_nginx_plan_edit", "module_nginx_backups", "module_nginx_read_backup", "module_nginx_restore_backup", "module_nginx_certificates", "module_nginx_install_certbot", "module_nginx_issue_certificate", "module_nginx_read_log", "module_nginx_probe_backends",41
"module_files_list", "module_files_privileges", "module_files_read_text", "module_files_write_text", "module_files_save_text",42
"module_files_list_recovery", "module_files_read_recovery", "module_files_cleanup_recovery", "module_files_close_text",43
"module_files_create_file", "module_files_mkdir", "module_files_rename", "module_files_remove", "module_files_remove_many",44
"module_files_upload", "module_files_download", "module_files_download_many", "module_files_download_archive",45
"module_transfers_list", "module_transfers_plan_upload", "module_transfers_plan_download", "module_transfers_start",46
"module_transfers_pause", "module_transfers_resume", "module_transfers_retry", "module_transfers_cancel",47
"module_transfers_remove", "module_transfers_settings", "module_transfers_plan_copy",48
"module_local_places", "module_local_list", "module_local_mkdir", "module_local_create_file", "module_local_rename", "module_local_remove",49
"module_hashing_algorithms", "module_hashing_select_files", "module_hashing_select_directory", "module_hashing_select_manifest",50
"module_hashing_select_manifest_destination", "module_hashing_start", "module_hashing_verify", "module_hashing_compare",51
"module_hashing_generate_manifest", "module_hashing_verify_manifest", "module_hashing_export_results",52
"module_hashing_copy_result", "module_hashing_status", "module_hashing_cancel",53
"module_hashing_select_pgp_file", "module_hashing_pgp_verify", "module_hashing_pgp_keys", "module_hashing_pgp_remove_key",54
];56
const LICENSE: &[&str] = &["license_read", "license_show", "license_accept", "license_decline"];57
/// Quick-action window (system file-manager menu): verify/sign the one file of the current request.58
const QUICK: &[&str] = &["quick_state", "quick_unlock", "quick_verify", "quick_verify_key", "quick_verify_keyserver", "quick_sign", "quick_open_main"];60
fn permission(command: &str) -> String {61
format!("\"allow-{}\"", command.replace('_', "-"))62
}64
fn capability(identifier: &str, description: &str, windows: &[&str], commands: &[&[&str]]) -> String {65
// WebView2 reaches a custom protocol over http; every module has its own origin there.66
let remote = if identifier == "module-windows" { "\n \"remote\": { \"urls\": [\"http://chj-module.*.localhost/*\"] }," } else { "" };67
let mut permissions: Vec<String> = vec!["\"core:event:allow-listen\"".into(), "\"core:event:allow-unlisten\"".into()];68
for group in commands {69
permissions.extend(group.iter().map(|c| permission(c)));70
}71
format!(72
"{{\n \"identifier\": \"{identifier}\",\n \"description\": \"{description} (generated by build.rs, do not edit)\",\n \"local\": true,{remote}\n \"webviews\": [{}],\n \"permissions\": [\n {}\n ]\n}}\n",73
windows.iter().map(|w| format!("\"{w}\"")).collect::<Vec<_>>().join(", "),74
permissions.join(",\n ")75
)76
}78
fn write_if_changed(path: &std::path::Path, content: &str) {79
if std::fs::read_to_string(path).ok().as_deref() != Some(content) {80
std::fs::write(path, content).expect("write capability");81
}82
}84
fn create_if_missing(path: &std::path::Path, content: &str) {85
if !path.exists() {86
std::fs::write(path, content).expect("write resource placeholder");87
}88
}90
/// UTC timestamp without external crates (YYYY-MM-DDTHH:MM:SS.000Z).91
fn utc_now() -> String {92
let secs = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0) as i64;93
let days = secs.div_euclid(86_400);94
let rem = secs.rem_euclid(86_400);95
let (h, m, s) = (rem / 3600, (rem % 3600) / 60, rem % 60);96
let z = days + 719_468;97
let era = z.div_euclid(146_097);98
let doe = z - era * 146_097;99
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;100
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);101
let mp = (5 * doy + 2) / 153;102
let d = doy - (153 * mp + 2) / 5 + 1;103
let mo = if mp < 10 { mp + 3 } else { mp - 9 };104
let y = yoe + era * 400 + if mo <= 2 { 1 } else { 0 };105
format!("{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}.000Z")106
}108
fn stamp_build_identity() {109
println!("cargo:rerun-if-env-changed=CHJ_BUILD_ID");110
let built_at = utc_now();111
let build_id = match std::env::var("CHJ_BUILD_ID") {112
_ if std::env::var("PROFILE").as_deref() != Ok("release") => "core-development-build".to_string(),113
Ok(id) => {114
if id.len() < 8 || id.len() > 128 || !id.chars().all(|c| c.is_ascii_alphanumeric() || "._-".contains(c)) {115
panic!("CHJ_BUILD_ID must be 8-128 characters of [A-Za-z0-9._-]");116
}117
id118
}119
Err(_) if std::env::var("PROFILE").as_deref() == Ok("release") => {120
let nanos = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("system clock").as_nanos();121
format!("release-{nanos}-{}", std::process::id())122
}123
Err(_) => "core-development-build".to_string(),124
};125
{126
let resources = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")).join("resources");127
let stamp = format!("{{\n \"version\": \"{}\",\n \"buildId\": \"{build_id}\"\n}}\n", env!("CARGO_PKG_VERSION"));128
if std::env::var("PROFILE").as_deref() != Ok("release") {129
// tauri-build requires the bundled directory; only release preparation fills it with the helper.130
for platform in ["darwin-arm64", "linux-x64", "win32-x64"] {131
std::fs::create_dir_all(resources.join("biometrics").join(platform)).expect("biometrics resource directory");132
}133
// Development builds only create missing files: a dev build running next to a release build must not134
// overwrite the prepared release inventory/stamp (it did on 2026-11-06 and the bundle failed verify).135
// The resources are never read at runtime; the dev binary carries its own CHJ_BUILD_ID.136
create_if_missing(&resources.join("release-inventory.json"), "{\"buildId\":\"core-development-build\",\"files\":{}}\n");137
create_if_missing(&resources.join("build-info.json"), &stamp);138
} else {139
write_if_changed(&resources.join("build-info.json"), &stamp);140
}141
}142
println!("cargo:rustc-env=CHJ_BUILD_ID={build_id}");143
println!("cargo:rustc-env=CHJ_BUILT_AT={built_at}");144
}146
fn main() {147
build_suggestions();148
build_matrix_runtime();149
let dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")).join("capabilities");150
std::fs::create_dir_all(&dir).expect("capabilities dir");151
write_if_changed(&dir.join("main.json"), &capability("main-window", "Main window: Core API", &["main"], &[CORE, SHARED, SMOKE]));152
write_if_changed(&dir.join("modules.json"), &capability("module-windows", "Module windows: public Module API only", &["module-*"], &[MODULE, SHARED, SMOKE]));153
write_if_changed(&dir.join("license.json"), &capability("license-window", "License window: license API only", &["license"], &[LICENSE, SHARED, SMOKE]));154
write_if_changed(&dir.join("quick.json"), &capability("quick-window", "Quick-action window: quick API only", &["quick"], &[QUICK, SHARED, SMOKE]));155
stamp_build_identity();156
for bridge in ["core-bridge.js", "module-bridge.js", "license-bridge.js", "quick-bridge.js"] {157
println!("cargo:rerun-if-changed=bridges/{bridge}");158
}159
let all: &'static [&'static str] = Box::leak(SHARED.iter().chain(SMOKE).chain(CORE).chain(MODULE).chain(LICENSE).chain(QUICK).copied().collect::<Vec<_>>().into_boxed_slice());160
tauri_build::try_build(tauri_build::Attributes::new().app_manifest(tauri_build::AppManifest::new().commands(all)))161
.expect("failed to run tauri-build");162
}164
/// Compiles the Matrix API into the binary: the runtime binding of every operation and the fixed scripts.165
/// A binding to a missing script stops the build.166
fn build_matrix_runtime() {167
use serde_json::{json, Map, Value};168
let manifest = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));169
let matrix = manifest.parent().expect("project root").join("Matrix json");170
let operations = matrix.join("operations");171
println!("cargo:rerun-if-changed={}", operations.display());172
let scripts_root = matrix.join("scripts");173
let mut bound = Map::new();174
let mut available = Map::new();175
let mut scripts = Map::new();176
for entry in std::fs::read_dir(&operations).expect("matrix operations") {177
let path = entry.expect("matrix entry").path();178
if path.extension().and_then(|e| e.to_str()) != Some("json") {179
continue;180
}181
let op: Value = serde_json::from_slice(&std::fs::read(&path).expect("matrix file")).unwrap_or_else(|error| panic!("{}: {error}", path.display()));182
let id = op["id"].as_str().expect("operation id").to_string();183
let mut systems = Map::new();184
let mut live = Vec::new();185
for (os, implementation) in op["implementations"].as_object().expect("implementations") {186
if op["origin"] == "application" && matches!(implementation["status"].as_str(), Some("IMPLEMENTED" | "PARTIAL")) {187
live.push(json!(os));188
}189
let runtime = &implementation["runtime"];190
if runtime["script"].is_null() {191
continue;192
}193
let uses = runtime["uses"].as_array().cloned().unwrap_or_default();194
for name in uses.iter().chain(std::iter::once(&runtime["script"])).filter_map(Value::as_str) {195
let file = scripts_root.join(name);196
println!("cargo:rerun-if-changed={}", file.display());197
let text = std::fs::read_to_string(&file).unwrap_or_else(|error| panic!("{}: {error}", file.display()));198
scripts.insert(name.to_string(), json!(text));199
}200
systems.insert(os.clone(), json!({ "script": runtime["script"], "argv": runtime["argv"], "key": runtime["key"] }));201
}202
if !systems.is_empty() {203
bound.insert(id.clone(), Value::Object(systems));204
}205
available.insert(id, Value::Array(live));206
}207
let out = std::path::Path::new(&std::env::var("OUT_DIR").expect("OUT_DIR")).join("matrix-runtime.json");208
std::fs::write(out, json!({ "operations": bound, "available": available, "scripts": scripts }).to_string()).expect("matrix runtime");209
}211
/// Compiles the terminal autocomplete catalog from `Matrix json`, the only matrix input the app needs.212
/// Offers only knowledge that is safe as text: never an operation with a secret input.213
fn build_suggestions() {214
use serde_json::{json, Value};215
const OSES: [&str; 7] = ["ubuntu", "debian", "arch", "rocky", "alpine", "windows", "openwrt"];216
let manifest = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"));217
let matrix = manifest.parent().expect("project root").join("Matrix json");218
let operations = matrix.join("operations");219
println!("cargo:rerun-if-changed={}", operations.display());220
let mut files: Vec<_> = std::fs::read_dir(&operations).unwrap_or_else(|error| panic!("{}: {error}", operations.display())).map(|entry| entry.expect("matrix entry").path()).filter(|path| path.extension().is_some_and(|ext| ext == "json")).collect();221
files.sort();222
let records: Vec<Value> = files.iter().map(|path| serde_json::from_slice(&std::fs::read(path).expect("matrix file")).unwrap_or_else(|error| panic!("{}: {error}", path.display()))).collect();223
let inventory = matrix.join("inventories/windows.inventory.json");224
println!("cargo:rerun-if-changed={}", inventory.display());225
let windows: Value = serde_json::from_slice(&std::fs::read(&inventory).expect("windows inventory")).expect("windows inventory json");226
let mut catalog = serde_json::Map::new();227
for os in OSES {228
let mut items = Vec::new();229
for op in &records {230
let implementation = &op["implementations"][os];231
let status = implementation["status"].as_str().unwrap_or("");232
let commands = implementation["commands"].as_array().cloned().unwrap_or_default();233
let secret = op["inputs"].as_array().is_some_and(|inputs| inputs.iter().any(|input| input["secret"] == true));234
if !matches!(status, "DISCOVERED" | "PROPOSED") || commands.is_empty() || secret {235
continue;236
}237
let tested = implementation["knowledge"]["tested"].as_array().is_some_and(|tests| tests.iter().any(|test| test["result"] == "passed"));238
let level = if status == "PROPOSED" { "proposed" } else if tested { "tested" } else { "discovered" };239
items.push(json!({ "id": op["id"], "title": op["title"], "risk": op["risk"], "privileged": implementation["requires_privilege"] == true, "level": level, "commands": commands, "keywords": op["keywords"] }));240
}241
let mut names: Vec<String> = if os == "windows" { windows["commands"].as_object().expect("commands").keys().cloned().collect() } else { Vec::new() };242
names.sort();243
catalog.insert(os.to_string(), json!({ "items": items, "commands": names }));244
}245
let syntax_file = matrix.join("syntax/commands.json");246
println!("cargo:rerun-if-changed={}", syntax_file.display());247
let syntax: Value = serde_json::from_slice(&std::fs::read(&syntax_file).expect("command syntax")).expect("command syntax json");248
let out = std::path::Path::new(&std::env::var("OUT_DIR").expect("OUT_DIR")).join("terminal-suggestions.json");249
std::fs::write(out, json!({ "os": catalog, "examples": syntax["families"] }).to_string()).expect("catalog");250
}SHA-256
e54c07b5d5c4c0822dded238c8213e7cccc82696c98fad5ebf70b7e202fb98fcCH-J Proprietary Software License 1.14 · Kod sumber disediakan di bawah CH-J Proprietary Software License 1.14. Ketersediaannya tidak mengubah terma lesen atau memberikan hak tambahan.
SHA-256 arkib
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f