CH-J Toolsbainisteoireacht freastalaí SSH
Roghchlár
Foinse foilsithe

CH-J Tools

Brabhsáil comhadlanna agus comhaid chun feidhmchlár áirithe a scaoileadh.

Íosluchtaigh foinse ZIP
CH-J / Server Manager 1704 Comhaid
0.1.2-beta Suimigh: · Tógtha:
build.rs Suimigh: Dáta gan taifeadadh 19.0 KB · 250 líne
Cód Íosluchtaigh comhad
1//! Build script: Tauri ACL for every Core command and build identity.
2//!
3//! Each command gets its own `allow-<command>` permission (app manifest). Capabilities
4//! grant them per window class, so a module window cannot even reach a Core command
5//! through Tauri; the Rust Core additionally checks the calling window and the module
6//! manifest permissions at runtime (defence in depth).
8const SHARED: &[&str] = &["window_control", "window_is_maximized", "window_activity"];
9/// Registered only with the `smoke` cargo feature (test builds); absent from release handlers.
10const SMOKE: &[&str] = &["smoke_report", "smoke_snapshot"];
12const 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];
30const 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];
56const 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.
58const QUICK: &[&str] = &["quick_state", "quick_unlock", "quick_verify", "quick_verify_key", "quick_verify_keyserver", "quick_sign", "quick_open_main"];
60fn permission(command: &str) -> String {
61 format!("\"allow-{}\"", command.replace('_', "-"))
64fn 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 )
78fn 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 }
84fn create_if_missing(path: &std::path::Path, content: &str) {
85 if !path.exists() {
86 std::fs::write(path, content).expect("write resource placeholder");
87 }
90/// UTC timestamp without external crates (YYYY-MM-DDTHH:MM:SS.000Z).
91fn 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")
108fn 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 id
118 }
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 not
134 // 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}");
146fn 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");
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.
166fn 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");
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.
213fn 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");
SHA-256e54c07b5d5c4c0822dded238c8213e7cccc82696c98fad5ebf70b7e202fb98fc

CH-J Proprietary Software License 1.14 · Tá Foinse ar fáil faoin CH-J Proprietary Software License 1.14. Ní chuireann a infhaighteacht a athrú ar na téarmaí ceadúnais nó cearta breise a dheonú.

Cartlann SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
Comhaid