Fuente publicada
Descargar fuente ZIP
CH-J Tools
Busque directorios y archivos para obtener una versión de aplicación específica.
CH-J / Server Manager
1704 archivos
0.1.2-beta
Añadido: · Construido:
1
//! Matrix API: the only source of remote commands. Core services name an operation; the matrix, compiled2
//! into the binary from `Matrix json`, decides whether it exists on the server's OS and which fixed script3
//! and leading arguments run. Nothing here accepts commands or scripts from the UI or from files at run time.5
use crate::core::error::{CoreError, CoreResult};6
use crate::core::ssh::session::shell_quote;7
use once_cell::sync::Lazy;8
use serde::Deserialize;9
use serde_json::Value;10
use std::collections::HashMap;12
#[derive(Deserialize)]13
struct Runtime {14
script: Option<String>,15
#[serde(default)]16
argv: Vec<String>,17
key: Option<String>,18
}20
#[derive(Deserialize)]21
struct Catalog {22
operations: HashMap<String, HashMap<String, Runtime>>,23
available: HashMap<String, Vec<String>>,24
scripts: HashMap<String, String>,25
}27
static CATALOG: Lazy<Catalog> = Lazy::new(|| serde_json::from_str(include_str!(concat!(env!("OUT_DIR"), "/matrix-runtime.json"))).expect("matrix runtime"));29
/// Scripts are generic for a family, so an unrecognised POSIX system behaves like its nearest relative.30
const FALLBACK_OS: &str = "debian";32
#[derive(Debug)]33
pub struct Binding {34
pub script: &'static str,35
pub argv: &'static [String],36
}38
/// The matrix OS of a detected platform.39
pub fn os_of(platform: &Value) -> &str {40
if platform["os"] == "windows" {41
return "windows";42
}43
platform["matrixOs"].as_str().unwrap_or(FALLBACK_OS)44
}46
fn unsupported(operation: &str, os: &str) -> CoreError {47
CoreError::new("FEATURE_PLATFORM_UNSUPPORTED", format!("This operation is not available on this server's operating system ({os}).")).with("operation", Value::from(operation))48
}50
pub fn binding(operation: &str, os: &str) -> CoreResult<Binding> {51
let runtime = CATALOG.operations.get(operation).and_then(|systems| systems.get(os)).filter(|runtime| runtime.script.is_some()).ok_or_else(|| unsupported(operation, os))?;52
Ok(Binding { script: script(runtime.script.as_deref().expect("filtered")), argv: &runtime.argv })53
}55
/// Fails on a system where the matrix does not offer the operation, also for operations that run no script (SFTP).56
pub fn require(operation: &str, os: &str) -> CoreResult<()> {57
if CATALOG.available.get(operation).is_some_and(|systems| systems.iter().any(|system| system == os)) {58
Ok(())59
} else {60
Err(unsupported(operation, os))61
}62
}64
/// A script by file name. Names come from Core constants and the build rejects a missing file.65
pub fn script(name: &str) -> &'static str {66
CATALOG.scripts.get(name).map(String::as_str).unwrap_or_else(|| panic!("matrix script {name}"))67
}69
/// The operation behind a dynamic action (`users:manage action=...`) of one script.70
pub fn operation_by_key(script_name: &str, key: &str, os: &str) -> Option<&'static str> {71
CATALOG.operations.iter().find_map(|(id, systems)| {72
let runtime = systems.get(os)?;73
(runtime.key.as_deref() == Some(key) && runtime.script.as_deref() == Some(script_name)).then_some(id.as_str())74
})75
}77
/// A script as a command for a server whose login shell may not be POSIX.78
pub fn shell(script_text: &str) -> String {79
format!("sh -c {}", shell_quote(script_text))80
}82
/// The command line of a script. Arguments are quoted and never become part of the script text.83
pub fn command(script_text: &str, args: &[String]) -> String {84
if args.is_empty() {85
return script_text.to_string();86
}87
format!("sh -c {} chj {}", shell_quote(script_text), args.iter().map(|arg| shell_quote(arg)).collect::<Vec<_>>().join(" "))88
}90
#[cfg(test)]91
mod tests {92
use super::*;93
use serde_json::json;95
const OSES: [&str; 7] = ["ubuntu", "debian", "arch", "rocky", "alpine", "windows", "openwrt"];97
#[test]98
fn every_binding_names_a_compiled_script() {99
let mut bound = 0;100
for (id, systems) in &CATALOG.operations {101
for os in systems.keys() {102
assert!(OSES.contains(&os.as_str()), "{id}/{os}");103
if systems[os].script.is_some() {104
assert!(!binding(id, os).unwrap().script.is_empty(), "{id}/{os}");105
bound += 1;106
}107
}108
}109
assert!(bound > 400, "{bound}");110
}112
#[test]113
fn operations_named_by_core_exist() {114
for id in ["users.inventory", "users.detail", "users.create", "users.keys.add", "users.keys.remove", "users.ssh-policy.apply", "disk.inventory.read", "disk.health.read", "disk.job.start", "disk.job.read", "disk.jobs.list", "system.swap.configuration.read", "system.os.read", "nginx.inspect", "nginx.config.read", "nginx.config.dump", "nginx.config.test", "nginx.config.save", "nginx.reload", "editor.read", "files.checksum.read", "terminal.suggestions.read", "privilege.probe", "ssh.platform.detect"] {115
assert!(binding(id, "ubuntu").is_ok(), "{id}");116
}117
for id in ["users.privileges", "users.inventory", "users.detail", "users.create", "users.delete", "users.keys.add", "users.keys.remove", "users.ssh-policy.apply", "system.pagefile.read", "system.pagefile.set", "system.pagefile.automatic", "system.pagefile.remove", "system.os.read", "hash.remote.calculate", "ssh.platform.detect"] {118
assert!(binding(id, "windows").is_ok(), "{id}");119
}120
for action in ["create", "activate", "deactivate", "priority", "persist", "resize", "delete"] {121
assert_eq!(binding(&format!("system.swap.{action}"), "rocky").unwrap().argv, ["manage", action]);122
}123
}125
#[test]126
fn dynamic_actions_reach_one_operation() {127
for key in ["lock", "unlock", "expire", "unexpire", "password", "shell", "comment", "home", "primary", "group-add", "group-remove", "sudo-grant", "sudo-revoke", "delete"] {128
assert!(operation_by_key("remoteUsers.sh", key, "alpine").is_some(), "{key}");129
}130
for key in ["enable", "disable", "password", "fullName", "description", "group-add", "group-remove", "admin-grant", "admin-revoke"] {131
assert!(operation_by_key("windows/remoteUsers.ps1", key, "windows").is_some(), "{key}");132
}133
assert_eq!(binding("users.groups.add", "debian").unwrap().argv, ["account", "group-add"]);134
assert_eq!(binding("users.groups.add", "windows").unwrap().argv, ["account", "group-add"]);135
}137
#[test]138
fn openwrt_has_only_the_terminal() {139
assert_eq!(binding("users.inventory", "openwrt").unwrap_err().code, "FEATURE_PLATFORM_UNSUPPORTED");140
assert_eq!(binding("system.os.read", "openwrt").unwrap_err().code, "FEATURE_PLATFORM_UNSUPPORTED");141
assert_eq!(require("files.channel.open", "openwrt").unwrap_err().code, "FEATURE_PLATFORM_UNSUPPORTED");142
assert!(binding("terminal.suggestions.read", "openwrt").is_ok());143
assert!(require("files.channel.open", "ubuntu").is_ok());144
}146
#[test]147
fn platform_selects_the_matrix() {148
assert_eq!(os_of(&json!({ "os": "windows" })), "windows");149
assert_eq!(os_of(&json!({ "os": "linux", "matrixOs": "rocky" })), "rocky");150
assert_eq!(os_of(&json!({ "os": "posix" })), "debian");151
}153
#[test]154
fn arguments_stay_outside_the_script() {155
assert_eq!(command("exit 0\n", &[]), "exit 0\n");156
assert_eq!(command("echo \"$1\"", &["a b; rm".into()]), "sh -c 'echo \"$1\"' chj 'a b; rm'");157
}158
}SHA-256
79404cdfdd4754c09c113280dcb4a2575d1d0c812e587654671cd7e7a3af6738CH-J Proprietary Software License 1.14 · La fuente se proporciona en el CH-J Proprietary Software License 1.14. Su disponibilidad no cambia los términos de la licencia ni otorga derechos adicionales.
Archivo SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f