use crate::client::client::{
DependencyDef, HealthCommon, HealthDef, LifecycleDef, LoggingDef, RestartPolicy, ServiceClass,
ServiceConfig, ServiceDef, SocketAddr, State, Status, XinetConfig, XinetStatusFull,
};
use crate::client::handle::ZinitHandle;
use rhai::{Dynamic, Engine, EvalAltResult, Map, Position};
use std::collections::HashMap;
use std::sync::Mutex;
use std::time::Duration;
lazy_static::lazy_static! {
static ref GLOBAL_HANDLE: Mutex<Option<ZinitHandle>> = Mutex::new(None);
static ref OUTPUT_BUFFER: Mutex<Vec<String>> = Mutex::new(Vec::new());
static ref LOG_LEVEL: Mutex<u32> = Mutex::new(1); }
pub fn set_global_handle(handle: ZinitHandle) {
if let Ok(mut h) = GLOBAL_HANDLE.lock() {
*h = Some(handle);
}
}
pub fn get_global_handle() -> Option<ZinitHandle> {
GLOBAL_HANDLE.lock().ok().and_then(|h| h.clone())
}
#[allow(dead_code)]
fn get_log_level() -> u32 {
LOG_LEVEL.lock().ok().map(|l| *l).unwrap_or(1)
}
fn set_log_level(level: u32) {
if let Ok(mut l) = LOG_LEVEL.lock() {
*l = level.min(3);
}
}
#[allow(dead_code)]
fn log_at_level(message: &str, min_level: u32) {
let current_level = get_log_level();
if current_level >= min_level {
println!("{}", message);
}
}
fn append_output(s: &str) {
if let Ok(mut buf) = OUTPUT_BUFFER.lock() {
buf.push(s.to_string());
}
println!("{}", s);
}
pub fn normalize_service_name(name: &str) -> String {
name.replace('-', "_").to_lowercase()
}
fn make_error<T>(msg: impl Into<String>) -> Result<T, Box<EvalAltResult>> {
Err(Box::new(EvalAltResult::ErrorRuntime(
Dynamic::from(msg.into()),
Position::NONE,
)))
}
#[derive(Debug, Clone)]
pub struct ZinitFactory {
socket_path: Option<String>,
log_level: u32,
}
impl ZinitFactory {
pub fn new() -> Self {
Self {
socket_path: None,
log_level: 1,
}
}
pub fn socket(mut self, path: &str) -> Self {
self.socket_path = Some(path.to_string());
self
}
pub fn log_level(mut self, level: i64) -> Self {
self.log_level = (level as u32).min(3);
self
}
pub fn connect(&self) -> Result<ZinitHandle, Box<EvalAltResult>> {
set_log_level(self.log_level);
let handle = if let Some(ref path) = self.socket_path {
ZinitHandle::with_socket(path)
} else {
ZinitHandle::new()
};
let handle = handle.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
Dynamic::from(e.to_string()),
Position::NONE,
))
})?;
set_global_handle(handle.clone());
if self.log_level >= 1 {
println!("✓ Connected to zinit server");
}
Ok(handle)
}
}
impl Default for ZinitFactory {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ServiceBuilder {
name: String,
exec: String,
dir: Option<String>,
oneshot: bool,
env: HashMap<String, String>,
status: Status,
class: ServiceClass,
critical: bool,
ports: Vec<u16>,
after: Vec<String>,
requires: Vec<String>,
wants: Vec<String>,
conflicts: Vec<String>,
restart: RestartPolicy,
restart_delay_ms: u64,
restart_delay_max_ms: u64,
max_restarts: u32,
stability_period_ms: u64,
start_timeout_ms: u64,
stop_timeout_ms: u64,
stop_signal: String,
health_type: Option<String>, health_target: String,
health_expect_status: u16, health_interval_ms: u64,
health_timeout_ms: u64,
health_retries: u32,
health_start_period_ms: u64,
log_buffer_lines: usize,
log_file: Option<String>,
log_forward: Option<String>,
reset: bool,
kill_others: bool, process_filters: Vec<String>, }
impl Default for ServiceBuilder {
fn default() -> Self {
Self::new()
}
}
impl ServiceBuilder {
pub fn new() -> Self {
Self {
name: String::new(),
exec: String::new(),
dir: None,
oneshot: false,
env: HashMap::new(),
status: Status::Start,
class: ServiceClass::User,
critical: false,
ports: Vec::new(),
after: Vec::new(),
requires: Vec::new(),
wants: Vec::new(),
conflicts: Vec::new(),
restart: RestartPolicy::OnFailure,
restart_delay_ms: 1000,
restart_delay_max_ms: 300000,
max_restarts: 10,
stability_period_ms: 30000,
start_timeout_ms: 30000,
stop_timeout_ms: 10000,
stop_signal: "SIGTERM".to_string(),
health_type: None,
health_target: String::new(),
health_expect_status: 200,
health_interval_ms: 10000,
health_timeout_ms: 5000,
health_retries: 3,
health_start_period_ms: 0,
log_buffer_lines: 1000,
log_file: None,
log_forward: None,
reset: false,
kill_others: false,
process_filters: Vec::new(),
}
}
pub fn should_reset(&self) -> bool {
self.reset
}
pub fn get_name(&self) -> &str {
&self.name
}
pub fn name(mut self, name: &str) -> Self {
self.name = name.to_string();
self
}
pub fn exec(mut self, exec: &str) -> Self {
self.exec = exec.to_string();
self
}
pub fn dir(mut self, dir: &str) -> Self {
self.dir = Some(dir.to_string());
self
}
pub fn oneshot(mut self, oneshot: bool) -> Self {
self.oneshot = oneshot;
self
}
pub fn env(mut self, key: &str, value: &str) -> Self {
self.env.insert(key.to_string(), value.to_string());
self
}
pub fn status(mut self, status: &str) -> Self {
self.status = match status.to_lowercase().as_str() {
"stop" => Status::Stop,
"ignore" => Status::Ignore,
_ => Status::Start,
};
self
}
pub fn class(mut self, class: &str) -> Self {
self.class = match class.to_lowercase().as_str() {
"system" => ServiceClass::System,
_ => ServiceClass::User,
};
self
}
pub fn critical(mut self, critical: bool) -> Self {
self.critical = critical;
self
}
pub fn after(mut self, dep: &str) -> Self {
self.after.push(dep.to_string());
self
}
pub fn requires(mut self, dep: &str) -> Self {
self.requires.push(dep.to_string());
self
}
pub fn wants(mut self, dep: &str) -> Self {
self.wants.push(dep.to_string());
self
}
pub fn conflicts(mut self, dep: &str) -> Self {
self.conflicts.push(dep.to_string());
self
}
pub fn restart(mut self, policy: &str) -> Self {
self.restart = match policy.to_lowercase().as_str() {
"always" => RestartPolicy::Always,
"never" => RestartPolicy::Never,
_ => RestartPolicy::OnFailure,
};
self
}
pub fn restart_delay_ms(mut self, ms: i64) -> Self {
self.restart_delay_ms = ms.max(0) as u64;
self
}
pub fn restart_delay_max_ms(mut self, ms: i64) -> Self {
self.restart_delay_max_ms = ms.max(0) as u64;
self
}
pub fn max_restarts(mut self, count: i64) -> Self {
self.max_restarts = count.max(0) as u32;
self
}
pub fn stability_period_ms(mut self, ms: i64) -> Self {
self.stability_period_ms = ms.max(0) as u64;
self
}
pub fn start_timeout_ms(mut self, ms: i64) -> Self {
self.start_timeout_ms = ms.max(0) as u64;
self
}
pub fn stop_timeout_ms(mut self, ms: i64) -> Self {
self.stop_timeout_ms = ms.max(0) as u64;
self
}
pub fn stop_signal(mut self, signal: &str) -> Self {
self.stop_signal = signal.to_uppercase();
self
}
pub fn health_tcp(mut self, target: &str) -> Self {
self.health_type = Some("tcp".to_string());
self.health_target = target.to_string();
self
}
pub fn health_http(mut self, target: &str) -> Self {
self.health_type = Some("http".to_string());
self.health_target = target.to_string();
self
}
pub fn health_exec(mut self, target: &str) -> Self {
self.health_type = Some("exec".to_string());
self.health_target = target.to_string();
self
}
pub fn health_expect_status(mut self, status: i64) -> Self {
self.health_expect_status = status.max(100).min(599) as u16;
self
}
pub fn health_interval_ms(mut self, ms: i64) -> Self {
self.health_interval_ms = ms.max(1000) as u64;
self
}
pub fn health_timeout_ms(mut self, ms: i64) -> Self {
self.health_timeout_ms = ms.max(0) as u64;
self
}
pub fn health_retries(mut self, count: i64) -> Self {
self.health_retries = count.max(1) as u32;
self
}
pub fn health_start_period_ms(mut self, ms: i64) -> Self {
self.health_start_period_ms = ms.max(0) as u64;
self
}
pub fn log_buffer_lines(mut self, lines: i64) -> Self {
self.log_buffer_lines = lines.max(0) as usize;
self
}
pub fn log_file(mut self, path: &str) -> Self {
self.log_file = Some(path.to_string());
self
}
pub fn log_forward(mut self, addr: &str) -> Self {
self.log_forward = Some(addr.to_string());
self
}
pub fn port(mut self, port: i64) -> Self {
if port > 0 && port <= 65535 {
self.ports.push(port as u16);
}
self
}
pub fn reset(mut self) -> Self {
self.reset = true;
self
}
pub fn kill_others(mut self) -> Self {
self.kill_others = true;
self
}
pub fn process_filter(mut self, filter: &str) -> Self {
self.process_filters.push(filter.to_string());
self
}
pub fn to_config(&self) -> ServiceConfig {
let health = self.health_type.as_ref().map(|t| {
let common = HealthCommon {
interval_ms: self.health_interval_ms,
timeout_ms: self.health_timeout_ms,
retries: self.health_retries,
start_period_ms: self.health_start_period_ms,
};
match t.as_str() {
"tcp" => HealthDef::Tcp {
target: self.health_target.clone(),
common,
},
"http" => HealthDef::Http {
target: self.health_target.clone(),
expect_status: self.health_expect_status,
common,
},
"exec" => HealthDef::Exec {
target: self.health_target.clone(),
common,
},
_ => HealthDef::Tcp {
target: self.health_target.clone(),
common,
},
}
});
ServiceConfig {
service: ServiceDef {
name: self.name.clone(),
exec: self.exec.clone(),
dir: self.dir.clone(),
oneshot: self.oneshot,
env: self.env.clone(),
status: self.status,
class: self.class,
critical: self.critical,
ports: self.ports.clone(),
kill_others: self.kill_others,
process_filters: self.process_filters.clone(),
},
dependencies: DependencyDef {
after: self.after.clone(),
requires: self.requires.clone(),
wants: self.wants.clone(),
conflicts: self.conflicts.clone(),
},
lifecycle: LifecycleDef {
restart: self.restart,
restart_delay_ms: self.restart_delay_ms,
restart_delay_max_ms: self.restart_delay_max_ms,
max_restarts: self.max_restarts,
stability_period_ms: self.stability_period_ms,
start_timeout_ms: self.start_timeout_ms,
stop_timeout_ms: self.stop_timeout_ms,
stop_signal: self.stop_signal.clone(),
},
health,
logging: LoggingDef {
buffer_lines: self.log_buffer_lines,
file: self.log_file.clone(),
forward: self.log_forward.clone(),
},
}
}
pub fn to_dynamic(&self) -> Dynamic {
let mut map = Map::new();
map.insert("name".into(), self.name.clone().into());
map.insert("exec".into(), self.exec.clone().into());
map.insert("oneshot".into(), self.oneshot.into());
map.insert(
"restart".into(),
match self.restart {
RestartPolicy::Always => "always",
RestartPolicy::OnFailure => "on-failure",
RestartPolicy::Never => "never",
}
.into(),
);
if let Some(ref dir) = self.dir {
map.insert("dir".into(), dir.clone().into());
}
if !self.after.is_empty() {
let arr: Vec<Dynamic> = self.after.iter().map(|s| s.clone().into()).collect();
map.insert("after".into(), arr.into());
}
if !self.requires.is_empty() {
let arr: Vec<Dynamic> = self.requires.iter().map(|s| s.clone().into()).collect();
map.insert("requires".into(), arr.into());
}
if let Some(ref ht) = self.health_type {
map.insert("health_type".into(), ht.clone().into());
map.insert("health_target".into(), self.health_target.clone().into());
}
Dynamic::from_map(map)
}
pub fn to_toml(&self) -> String {
match self.to_config().to_toml() {
Ok(s) => s,
Err(_) => String::from("# Error generating TOML"),
}
}
pub fn to_markdown(&self) -> String {
let mut output = String::new();
output.push_str(&format!(
"## Service: {}\n\n",
if self.name.is_empty() {
"(unnamed)"
} else {
&self.name
}
));
output.push_str("### [service]\n");
output.push_str("| Property | Value |\n");
output.push_str("|----------|-------|\n");
output.push_str(&format!("| exec | `{}` |\n", self.exec));
output.push_str(&format!("| oneshot | {} |\n", self.oneshot));
output.push_str(&format!("| status | {:?} |\n", self.status));
output.push_str(&format!("| class | {:?} |\n", self.class));
if let Some(ref dir) = self.dir {
output.push_str(&format!("| dir | {} |\n", dir));
}
if self.critical {
output.push_str("| critical | true |\n");
}
if !self.after.is_empty()
|| !self.requires.is_empty()
|| !self.wants.is_empty()
|| !self.conflicts.is_empty()
{
output.push_str("\n### [dependencies]\n");
output.push_str("| Type | Services |\n");
output.push_str("|------|----------|\n");
if !self.after.is_empty() {
output.push_str(&format!("| after | {} |\n", self.after.join(", ")));
}
if !self.requires.is_empty() {
output.push_str(&format!("| requires | {} |\n", self.requires.join(", ")));
}
if !self.wants.is_empty() {
output.push_str(&format!("| wants | {} |\n", self.wants.join(", ")));
}
if !self.conflicts.is_empty() {
output.push_str(&format!("| conflicts | {} |\n", self.conflicts.join(", ")));
}
}
output.push_str("\n### [lifecycle]\n");
output.push_str("| Property | Value |\n");
output.push_str("|----------|-------|\n");
output.push_str(&format!("| restart | {:?} |\n", self.restart));
output.push_str(&format!(
"| restart_delay_ms | {} |\n",
self.restart_delay_ms
));
output.push_str(&format!(
"| start_timeout_ms | {} |\n",
self.start_timeout_ms
));
output.push_str(&format!("| stop_timeout_ms | {} |\n", self.stop_timeout_ms));
output.push_str(&format!("| stop_signal | {} |\n", self.stop_signal));
if let Some(ref ht) = self.health_type {
output.push_str("\n### [health]\n");
output.push_str("| Property | Value |\n");
output.push_str("|----------|-------|\n");
output.push_str(&format!("| type | {} |\n", ht));
output.push_str(&format!("| target | {} |\n", self.health_target));
if ht == "http" {
output.push_str(&format!(
"| expect_status | {} |\n",
self.health_expect_status
));
}
output.push_str(&format!("| interval_ms | {} |\n", self.health_interval_ms));
output.push_str(&format!("| timeout_ms | {} |\n", self.health_timeout_ms));
output.push_str(&format!("| retries | {} |\n", self.health_retries));
}
output
}
}
impl std::fmt::Display for ServiceBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_toml())
}
}
#[derive(Debug, Clone)]
pub struct XinetBuilder {
name: String,
listen_addrs: Vec<SocketAddr>,
backend: Option<SocketAddr>,
service: String,
connect_timeout: u64,
idle_timeout: u64,
single_connection: bool,
reset: bool,
}
impl Default for XinetBuilder {
fn default() -> Self {
Self::new()
}
}
impl XinetBuilder {
pub fn new() -> Self {
Self {
name: String::new(),
listen_addrs: Vec::new(),
backend: None,
service: String::new(),
connect_timeout: 30,
idle_timeout: 0,
single_connection: false,
reset: false,
}
}
pub fn should_reset(&self) -> bool {
self.reset
}
pub fn get_name(&self) -> &str {
&self.name
}
pub fn name(mut self, name: &str) -> Self {
self.name = name.to_string();
self
}
pub fn listen_tcp(mut self, addr: &str) -> Self {
self.listen_addrs = vec![SocketAddr::tcp(addr)];
self
}
pub fn listen_unix(mut self, path: &str) -> Self {
self.listen_addrs = vec![SocketAddr::unix(path)];
self
}
pub fn add_listen_tcp(mut self, addr: &str) -> Self {
self.listen_addrs.push(SocketAddr::tcp(addr));
self
}
pub fn add_listen_unix(mut self, path: &str) -> Self {
self.listen_addrs.push(SocketAddr::unix(path));
self
}
pub fn backend_tcp(mut self, addr: &str) -> Self {
self.backend = Some(SocketAddr::tcp(addr));
self
}
pub fn backend_unix(mut self, path: &str) -> Self {
self.backend = Some(SocketAddr::unix(path));
self
}
pub fn service(mut self, service: &str) -> Self {
self.service = service.to_string();
self
}
pub fn reset(mut self) -> Self {
self.reset = true;
self
}
pub fn connect_timeout(mut self, seconds: i64) -> Self {
self.connect_timeout = seconds.max(1) as u64;
self
}
pub fn idle_timeout(mut self, seconds: i64) -> Self {
self.idle_timeout = seconds.max(0) as u64;
self
}
pub fn single_connection(mut self, single: bool) -> Self {
self.single_connection = single;
self
}
pub fn listen_addrs_string(&self) -> String {
self.listen_addrs
.iter()
.map(|a| a.to_string())
.collect::<Vec<_>>()
.join(", ")
}
pub fn to_config(&self) -> Option<XinetConfig> {
let backend = self.backend.clone()?;
Some(XinetConfig {
name: self.name.clone(),
listen: self.listen_addrs.clone(),
backend,
service: self.service.clone(),
connect_timeout: self.connect_timeout,
idle_timeout: self.idle_timeout,
single_connection: self.single_connection,
})
}
pub fn to_toml(&self) -> String {
let mut output = String::new();
output.push_str(&format!(
"# Xinet Proxy: {}\n",
if self.name.is_empty() {
"(unnamed)"
} else {
&self.name
}
));
output.push_str(&format!("name = \"{}\"\n", self.name));
if self.listen_addrs.len() == 1 {
output.push_str(&format!("listen = \"{}\"\n", self.listen_addrs[0]));
} else {
let addrs: Vec<String> = self
.listen_addrs
.iter()
.map(|a| format!("\"{}\"", a))
.collect();
output.push_str(&format!("listen = [{}]\n", addrs.join(", ")));
}
if let Some(ref backend) = self.backend {
output.push_str(&format!("backend = \"{}\"\n", backend));
}
if !self.service.is_empty() {
output.push_str(&format!("service = \"{}\"\n", self.service));
}
output.push_str(&format!("connect_timeout = {}\n", self.connect_timeout));
if self.idle_timeout > 0 {
output.push_str(&format!("idle_timeout = {}\n", self.idle_timeout));
}
if self.single_connection {
output.push_str("single_connection = true\n");
}
output
}
pub fn to_markdown(&self) -> String {
let mut output = String::new();
output.push_str(&format!(
"## Xinet Proxy: {}\n\n",
if self.name.is_empty() {
"(unnamed)"
} else {
&self.name
}
));
output.push_str("| Property | Value |\n");
output.push_str("|----------|-------|\n");
output.push_str(&format!("| listen | {} |\n", self.listen_addrs_string()));
if let Some(ref backend) = self.backend {
output.push_str(&format!("| backend | {} |\n", backend));
}
if !self.service.is_empty() {
output.push_str(&format!("| service | {} |\n", self.service));
}
output.push_str(&format!(
"| connect_timeout | {}s |\n",
self.connect_timeout
));
if self.idle_timeout > 0 {
output.push_str(&format!("| idle_timeout | {}s |\n", self.idle_timeout));
}
if self.single_connection {
output.push_str("| single_connection | true |\n");
}
output
}
}
impl std::fmt::Display for XinetBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_toml())
}
}
pub fn register_zinit_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
engine
.register_type_with_name::<ZinitFactory>("ZinitFactory")
.register_fn("zinit", ZinitFactory::new)
.register_fn("socket", |f: ZinitFactory, path: &str| f.socket(path))
.register_fn("log_level", |f: ZinitFactory, level: i64| {
f.log_level(level)
})
.register_fn("connect", |f: ZinitFactory| f.connect());
engine
.register_type_with_name::<ServiceBuilder>("ServiceBuilder")
.register_fn("zinit_service_new", ServiceBuilder::new)
.register_fn("name", service_name)
.register_fn("exec", service_exec)
.register_fn("dir", service_dir)
.register_fn("oneshot", service_oneshot)
.register_fn("env", service_env)
.register_fn("status", service_status)
.register_fn("class", service_class)
.register_fn("critical", service_critical)
.register_fn("after", service_after)
.register_fn("requires", service_requires)
.register_fn("wants", service_wants)
.register_fn("conflicts", service_conflicts)
.register_fn("restart", service_restart)
.register_fn("restart_delay_ms", service_restart_delay_ms)
.register_fn("restart_delay_max_ms", service_restart_delay_max_ms)
.register_fn("max_restarts", service_max_restarts)
.register_fn("stability_period_ms", service_stability_period_ms)
.register_fn("start_timeout_ms", service_start_timeout_ms)
.register_fn("stop_timeout_ms", service_stop_timeout_ms)
.register_fn("stop_signal", service_stop_signal)
.register_fn("health_tcp", service_health_tcp)
.register_fn("health_http", service_health_http)
.register_fn("health_exec", service_health_exec)
.register_fn("health_expect_status", service_health_expect_status)
.register_fn("health_interval_ms", service_health_interval_ms)
.register_fn("health_timeout_ms", service_health_timeout_ms)
.register_fn("health_retries", service_health_retries)
.register_fn("health_start_period_ms", service_health_start_period_ms)
.register_fn("port", service_port)
.register_fn("log_buffer_lines", service_log_buffer_lines)
.register_fn("log_file", service_log_file)
.register_fn("log_forward", service_log_forward)
.register_fn("reset", service_reset)
.register_fn("kill_others", service_kill_others)
.register_fn("to_map", service_to_map)
.register_fn("to_string", service_to_string)
.register_fn("to_toml", service_to_toml)
.register_fn("to_markdown", service_to_markdown)
.register_fn("register", service_register)
.register_fn("start", service_start)
.register_fn("wait", service_wait);
engine
.register_type_with_name::<XinetBuilder>("XinetBuilder")
.register_fn("xinet_proxy_new", XinetBuilder::new)
.register_fn("name", xinet_name)
.register_fn("listen_tcp", xinet_listen_tcp)
.register_fn("listen_unix", xinet_listen_unix)
.register_fn("add_listen_tcp", xinet_add_listen_tcp)
.register_fn("add_listen_unix", xinet_add_listen_unix)
.register_fn("backend_tcp", xinet_backend_tcp)
.register_fn("backend_unix", xinet_backend_unix)
.register_fn("service", xinet_service)
.register_fn("connect_timeout", xinet_connect_timeout)
.register_fn("idle_timeout", xinet_idle_timeout)
.register_fn("single_connection", xinet_single_connection)
.register_fn("reset", xinet_reset)
.register_fn("to_string", xinet_to_string)
.register_fn("to_toml", xinet_to_toml)
.register_fn("to_markdown", xinet_to_markdown)
.register_fn("register", xinet_register);
if let Ok(handle) = ZinitHandle::new() {
register_zinit_client_functions(engine, handle);
}
Ok(())
}
pub fn register_zinit_client_functions(engine: &mut Engine, handle: ZinitHandle) {
set_global_handle(handle.clone());
let h = handle.clone();
engine.register_fn(
"zinit_ping",
move || -> Result<Dynamic, Box<EvalAltResult>> {
match h.ping() {
Ok(resp) => {
let mut map = Map::new();
map.insert("version".into(), resp.version.into());
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("zinit_ping failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_shutdown",
move || -> Result<(), Box<EvalAltResult>> {
match h.shutdown() {
Ok(_) => {
append_output("Server shutting down");
Ok(())
}
Err(e) => make_error(format!("zinit_shutdown failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_list",
move || -> Result<Dynamic, Box<EvalAltResult>> {
match h.list() {
Ok(services) => Ok(Dynamic::from_array(
services.into_iter().map(Dynamic::from).collect(),
)),
Err(e) => make_error(format!("zinit_list failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_list_md",
move || -> Result<(), Box<EvalAltResult>> {
match h.list() {
Ok(services) => {
if services.is_empty() {
append_output("No services registered.");
} else {
let mut output = String::new();
output.push_str("| Service | State | PID |\n");
output.push_str("|---------|-------|-----|\n");
for name in services {
match h.status(&name) {
Ok(status) => {
output.push_str(&format!(
"| {} | {} | {} |\n",
status.name, status.state, status.pid
));
}
Err(_) => {
output.push_str(&format!("| {} | unknown | - |\n", name));
}
}
}
append_output(&output);
}
Ok(())
}
Err(e) => make_error(format!("zinit_list_md failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_status",
move |name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.status(&name) {
Ok(status) => {
let mut map = Map::new();
map.insert("name".into(), status.name.into());
map.insert("pid".into(), (status.pid as i64).into());
map.insert("state".into(), status.state.to_string().into());
if let Some(exit_code) = status.exit_code {
map.insert("exit_code".into(), (exit_code as i64).into());
}
if let Some(error) = status.error {
map.insert("error".into(), error.into());
}
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("zinit_status '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_status_md",
move |name: &str| -> Result<(), Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.status(&name) {
Ok(status) => {
let mut output = String::new();
output.push_str("| Property | Value |\n");
output.push_str("|----------|-------|\n");
output.push_str(&format!("| Name | {} |\n", status.name));
output.push_str(&format!("| State | {} |\n", status.state));
output.push_str(&format!("| PID | {} |\n", status.pid));
if let Some(exit_code) = status.exit_code {
output.push_str(&format!("| Exit Code | {} |\n", exit_code));
}
if let Some(ref error) = status.error {
output.push_str(&format!("| Error | {} |\n", error));
}
append_output(&output);
Ok(())
}
Err(e) => make_error(format!("zinit_status_md '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_start",
move |name: &str| -> Result<(), Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.start(&name) {
Ok(_) => {
append_output(&format!("Started: {}", name));
Ok(())
}
Err(e) => make_error(format!("zinit_start '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_stop",
move |name: &str| -> Result<(), Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.stop(&name) {
Ok(_) => {
append_output(&format!("Stopped: {}", name));
Ok(())
}
Err(e) => make_error(format!("zinit_stop '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_restart",
move |name: &str| -> Result<(), Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.restart(&name) {
Ok(_) => {
append_output(&format!("Restarted: {}", name));
Ok(())
}
Err(e) => make_error(format!("zinit_restart '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_kill",
move |name: &str, signal: &str| -> Result<(), Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.kill(&name, Some(signal)) {
Ok(_) => {
append_output(&format!("Sent {} to: {}", signal, name));
Ok(())
}
Err(e) => make_error(format!("zinit_kill '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_why",
move |name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.why(&name) {
Ok(why) => {
let mut map = Map::new();
map.insert("name".into(), why.name.into());
map.insert("blocked".into(), why.blocked.into());
let waiting: Vec<Dynamic> =
why.waiting_on.into_iter().map(Dynamic::from).collect();
map.insert("waiting_on".into(), waiting.into());
let conflicts: Vec<Dynamic> =
why.conflicts_with.into_iter().map(Dynamic::from).collect();
map.insert("conflicts_with".into(), conflicts.into());
map.insert("ascii".into(), why.ascii.into());
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("zinit_why '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_tree",
move || -> Result<String, Box<EvalAltResult>> {
match h.tree() {
Ok(tree) => Ok(tree),
Err(e) => make_error(format!("zinit_tree failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_tree_print",
move || -> Result<(), Box<EvalAltResult>> {
match h.tree() {
Ok(tree) => {
append_output(&tree);
Ok(())
}
Err(e) => make_error(format!("zinit_tree_print failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_delete",
move |name: &str| -> Result<(), Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.service_delete(&name) {
Ok(_) => {
append_output(&format!("Deleted: {}", name));
Ok(())
}
Err(e) => make_error(format!("zinit_delete '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_stats",
move |name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.stats(&name) {
Ok(stats) => {
let mut map = Map::new();
map.insert("pid".into(), (stats.pid as i64).into());
map.insert("memory_bytes".into(), (stats.memory_bytes as i64).into());
map.insert("cpu_percent".into(), (stats.cpu_percent as f64).into());
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("zinit_stats '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_stats_md",
move |name: &str| -> Result<(), Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.stats(&name) {
Ok(stats) => {
let mut output = String::new();
output.push_str("| Metric | Value |\n");
output.push_str("|--------|-------|\n");
output.push_str(&format!("| PID | {} |\n", stats.pid));
output.push_str(&format!("| Memory | {} bytes |\n", stats.memory_bytes));
output.push_str(&format!("| CPU | {:.2}% |\n", stats.cpu_percent));
append_output(&output);
Ok(())
}
Err(e) => make_error(format!("zinit_stats_md '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn("zinit_is_running", move |name: &str| -> bool {
let name = normalize_service_name(name);
h.is_running(&name).unwrap_or(false)
});
let h = handle.clone();
engine.register_fn("zinit_wait_for", move |name: &str, secs: i64| -> bool {
let name = normalize_service_name(name);
let deadline = std::time::Instant::now() + Duration::from_secs(secs.max(1) as u64);
loop {
if std::time::Instant::now() >= deadline {
return false;
}
if let Ok(status) = h.status(&name) {
if status.state == State::Running {
return true;
}
if status.state == State::Failed || status.state == State::Exited {
return false;
}
}
std::thread::sleep(Duration::from_millis(100));
}
});
let h = handle.clone();
engine.register_fn(
"zinit_service_set",
move |builder: &mut ServiceBuilder| -> Result<Dynamic, Box<EvalAltResult>> {
let config = builder.to_config();
match h.service_set(config) {
Ok(result) => {
let mut map = Map::new();
map.insert("name".into(), result.name.into());
if let Some(path) = result.path {
map.insert("path".into(), path.into());
}
if !result.warnings.is_empty() {
let warnings: Vec<Dynamic> =
result.warnings.into_iter().map(Dynamic::from).collect();
map.insert("warnings".into(), warnings.into());
}
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("zinit_service_set failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_service_get",
move |name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.service_get(&name) {
Ok(config) => {
let mut map = Map::new();
map.insert("name".into(), config.service.name.into());
map.insert("exec".into(), config.service.exec.into());
map.insert("oneshot".into(), config.service.oneshot.into());
if let Some(ref dir) = config.service.dir {
map.insert("dir".into(), dir.clone().into());
}
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("zinit_service_get '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_logs",
move |name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.logs(Some(&name), None) {
Ok(logs) => Ok(Dynamic::from_array(
logs.into_iter().map(Dynamic::from).collect(),
)),
Err(e) => make_error(format!("zinit_logs failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_logs_n",
move |name: &str, lines: i64| -> Result<Dynamic, Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.logs(Some(&name), Some(lines.max(1) as usize)) {
Ok(logs) => Ok(Dynamic::from_array(
logs.into_iter().map(Dynamic::from).collect(),
)),
Err(e) => make_error(format!("zinit_logs_n failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_logs_print",
move |name: &str| -> Result<(), Box<EvalAltResult>> {
let name = normalize_service_name(name);
match h.logs(Some(&name), None) {
Ok(logs) => {
for line in logs {
append_output(&line);
}
Ok(())
}
Err(e) => make_error(format!("zinit_logs_print failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"zinit_logs_all",
move || -> Result<Dynamic, Box<EvalAltResult>> {
match h.logs(None, None) {
Ok(logs) => Ok(Dynamic::from_array(
logs.into_iter().map(Dynamic::from).collect(),
)),
Err(e) => make_error(format!("zinit_logs_all failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"xinet_list",
move || -> Result<Dynamic, Box<EvalAltResult>> {
match h.xinet_list() {
Ok(proxies) => Ok(Dynamic::from_array(
proxies.into_iter().map(Dynamic::from).collect(),
)),
Err(e) => make_error(format!("xinet_list failed: {}", e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"xinet_delete",
move |name: &str| -> Result<(), Box<EvalAltResult>> {
match h.xinet_delete(name) {
Ok(_) => {
append_output(&format!("Deleted xinet proxy '{}'", name));
Ok(())
}
Err(e) => make_error(format!("xinet_delete '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"xinet_status",
move |name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
match h.xinet_status(name) {
Ok(status) => {
let mut map = Map::new();
map.insert("name".into(), status.name.into());
map.insert("running".into(), status.running.into());
map.insert(
"active_connections".into(),
(status.active_connections as i64).into(),
);
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("xinet_status '{}' failed: {}", name, e)),
}
},
);
let h = handle.clone();
engine.register_fn(
"xinet_status_all",
move || -> Result<Dynamic, Box<EvalAltResult>> {
match h.xinet_status_all() {
Ok(statuses) => {
let arr: Vec<Dynamic> = statuses
.into_iter()
.map(|status: XinetStatusFull| {
let mut map = Map::new();
map.insert("name".into(), status.name.into());
map.insert("listen".into(), status.listen.into());
map.insert("backend".into(), status.backend.into());
map.insert("service".into(), status.service.into());
map.insert("running".into(), status.running.into());
map.insert(
"total_connections".into(),
(status.total_connections as i64).into(),
);
map.insert(
"active_connections".into(),
(status.active_connections as i64).into(),
);
map.insert(
"bytes_to_backend".into(),
(status.bytes_to_backend as i64).into(),
);
map.insert(
"bytes_from_backend".into(),
(status.bytes_from_backend as i64).into(),
);
Dynamic::from_map(map)
})
.collect();
Ok(Dynamic::from_array(arr))
}
Err(e) => make_error(format!("xinet_status_all failed: {}", e)),
}
},
);
}
fn service_name(builder: &mut ServiceBuilder, name: &str) -> ServiceBuilder {
builder.clone().name(name)
}
fn service_exec(builder: &mut ServiceBuilder, exec: &str) -> ServiceBuilder {
builder.clone().exec(exec)
}
fn service_dir(builder: &mut ServiceBuilder, dir: &str) -> ServiceBuilder {
builder.clone().dir(dir)
}
fn service_oneshot(builder: &mut ServiceBuilder, oneshot: bool) -> ServiceBuilder {
builder.clone().oneshot(oneshot)
}
fn service_env(builder: &mut ServiceBuilder, key: &str, value: &str) -> ServiceBuilder {
builder.clone().env(key, value)
}
fn service_status(builder: &mut ServiceBuilder, status: &str) -> ServiceBuilder {
builder.clone().status(status)
}
fn service_class(builder: &mut ServiceBuilder, class: &str) -> ServiceBuilder {
builder.clone().class(class)
}
fn service_critical(builder: &mut ServiceBuilder, critical: bool) -> ServiceBuilder {
builder.clone().critical(critical)
}
fn service_after(builder: &mut ServiceBuilder, dep: &str) -> ServiceBuilder {
builder.clone().after(dep)
}
fn service_requires(builder: &mut ServiceBuilder, dep: &str) -> ServiceBuilder {
builder.clone().requires(dep)
}
fn service_wants(builder: &mut ServiceBuilder, dep: &str) -> ServiceBuilder {
builder.clone().wants(dep)
}
fn service_conflicts(builder: &mut ServiceBuilder, dep: &str) -> ServiceBuilder {
builder.clone().conflicts(dep)
}
fn service_restart(builder: &mut ServiceBuilder, policy: &str) -> ServiceBuilder {
builder.clone().restart(policy)
}
fn service_restart_delay_ms(builder: &mut ServiceBuilder, ms: i64) -> ServiceBuilder {
builder.clone().restart_delay_ms(ms)
}
fn service_restart_delay_max_ms(builder: &mut ServiceBuilder, ms: i64) -> ServiceBuilder {
builder.clone().restart_delay_max_ms(ms)
}
fn service_max_restarts(builder: &mut ServiceBuilder, count: i64) -> ServiceBuilder {
builder.clone().max_restarts(count)
}
fn service_stability_period_ms(builder: &mut ServiceBuilder, ms: i64) -> ServiceBuilder {
builder.clone().stability_period_ms(ms)
}
fn service_start_timeout_ms(builder: &mut ServiceBuilder, ms: i64) -> ServiceBuilder {
builder.clone().start_timeout_ms(ms)
}
fn service_stop_timeout_ms(builder: &mut ServiceBuilder, ms: i64) -> ServiceBuilder {
builder.clone().stop_timeout_ms(ms)
}
fn service_stop_signal(builder: &mut ServiceBuilder, signal: &str) -> ServiceBuilder {
builder.clone().stop_signal(signal)
}
fn service_health_tcp(builder: &mut ServiceBuilder, target: &str) -> ServiceBuilder {
builder.clone().health_tcp(target)
}
fn service_health_http(builder: &mut ServiceBuilder, target: &str) -> ServiceBuilder {
builder.clone().health_http(target)
}
fn service_health_exec(builder: &mut ServiceBuilder, target: &str) -> ServiceBuilder {
builder.clone().health_exec(target)
}
fn service_health_expect_status(builder: &mut ServiceBuilder, status: i64) -> ServiceBuilder {
builder.clone().health_expect_status(status)
}
fn service_health_interval_ms(builder: &mut ServiceBuilder, ms: i64) -> ServiceBuilder {
builder.clone().health_interval_ms(ms)
}
fn service_health_timeout_ms(builder: &mut ServiceBuilder, ms: i64) -> ServiceBuilder {
builder.clone().health_timeout_ms(ms)
}
fn service_health_retries(builder: &mut ServiceBuilder, count: i64) -> ServiceBuilder {
builder.clone().health_retries(count)
}
fn service_health_start_period_ms(builder: &mut ServiceBuilder, ms: i64) -> ServiceBuilder {
builder.clone().health_start_period_ms(ms)
}
fn service_port(builder: &mut ServiceBuilder, port: i64) -> ServiceBuilder {
builder.clone().port(port)
}
fn service_log_buffer_lines(builder: &mut ServiceBuilder, lines: i64) -> ServiceBuilder {
builder.clone().log_buffer_lines(lines)
}
fn service_log_file(builder: &mut ServiceBuilder, path: &str) -> ServiceBuilder {
builder.clone().log_file(path)
}
fn service_log_forward(builder: &mut ServiceBuilder, addr: &str) -> ServiceBuilder {
builder.clone().log_forward(addr)
}
fn service_reset(builder: &mut ServiceBuilder) -> ServiceBuilder {
builder.clone().reset()
}
fn service_kill_others(builder: &mut ServiceBuilder) -> ServiceBuilder {
builder.clone().kill_others()
}
fn service_to_map(builder: &mut ServiceBuilder) -> Dynamic {
builder.to_dynamic()
}
fn service_to_string(builder: &mut ServiceBuilder) -> String {
builder.to_toml()
}
fn service_to_toml(builder: &mut ServiceBuilder) -> String {
builder.to_toml()
}
fn service_to_markdown(builder: &mut ServiceBuilder) -> String {
builder.to_markdown()
}
fn service_wait(builder: &mut ServiceBuilder, secs: i64) -> Result<(), Box<EvalAltResult>> {
let handle = match get_global_handle() {
Some(h) => h,
None => return make_error("No zinit connection available"),
};
let name = builder.get_name();
if name.is_empty() {
return make_error("wait: service name not set");
}
let name = normalize_service_name(name);
if secs <= 0 {
return Ok(());
}
let deadline = std::time::Instant::now() + Duration::from_secs(secs as u64);
let mut consecutive_errors = 0;
loop {
if std::time::Instant::now() >= deadline {
let final_state = handle
.status(&name)
.map(|s| s.state.to_string())
.unwrap_or_else(|_| "unknown".to_string());
return make_error(format!(
"service '{}' did not start within {} seconds (state: {})",
name, secs, final_state
));
}
match handle.status(&name) {
Ok(status) => {
consecutive_errors = 0;
if status.state == State::Running {
return Ok(());
}
if status.state == State::Failed {
let err = status.error.unwrap_or_else(|| "unknown".to_string());
return make_error(format!("service '{}' failed: {}", name, err));
}
}
Err(e) => {
consecutive_errors += 1;
if consecutive_errors >= 10 {
return make_error(format!(
"service '{}' status check failed repeatedly: {}",
name, e
));
}
}
}
std::thread::sleep(Duration::from_millis(100));
}
}
fn service_register(builder: &mut ServiceBuilder) -> Result<ServiceBuilder, Box<EvalAltResult>> {
let handle = match get_global_handle() {
Some(h) => h,
None => return make_error("No zinit connection available"),
};
let name = builder.get_name();
if name.is_empty() {
return make_error("service creation: .name() is required");
}
if builder.exec.is_empty() {
return make_error("service creation: .exec() is required");
}
let name = normalize_service_name(name);
if builder.should_reset() {
let _ = handle.service_delete(&name);
}
let config = builder.to_config();
match handle.service_set(config) {
Ok(result) => {
let path_info = result.path.map(|p| format!(" ({})", p)).unwrap_or_default();
println!("Registered: {}{}", result.name, path_info);
Ok(builder.clone())
}
Err(e) => make_error(format!("service '{}' registration failed: {}", name, e)),
}
}
fn service_start(builder: &mut ServiceBuilder) -> Result<String, Box<EvalAltResult>> {
let handle = match get_global_handle() {
Some(h) => h,
None => return make_error("No zinit connection available"),
};
let name = builder.get_name();
if name.is_empty() {
return make_error("service start: .name() is required");
}
if builder.exec.is_empty() {
return make_error("service start: .exec() is required");
}
let name = normalize_service_name(name);
if builder.should_reset() {
let _ = handle.service_delete(&name);
}
let config = builder.to_config();
if let Err(e) = handle.service_set(config) {
return make_error(format!("failed to register service '{}': {}", name, e));
}
match handle.start(&name) {
Ok(_) => {
let msg = format!("Registered and started service: {}", name);
println!("{}", msg);
Ok(msg)
}
Err(e) => make_error(format!("failed to start service '{}': {}", name, e)),
}
}
fn xinet_name(builder: &mut XinetBuilder, name: &str) -> XinetBuilder {
builder.clone().name(name)
}
fn xinet_listen_tcp(builder: &mut XinetBuilder, addr: &str) -> XinetBuilder {
builder.clone().listen_tcp(addr)
}
fn xinet_listen_unix(builder: &mut XinetBuilder, path: &str) -> XinetBuilder {
builder.clone().listen_unix(path)
}
fn xinet_add_listen_tcp(builder: &mut XinetBuilder, addr: &str) -> XinetBuilder {
builder.clone().add_listen_tcp(addr)
}
fn xinet_add_listen_unix(builder: &mut XinetBuilder, path: &str) -> XinetBuilder {
builder.clone().add_listen_unix(path)
}
fn xinet_backend_tcp(builder: &mut XinetBuilder, addr: &str) -> XinetBuilder {
builder.clone().backend_tcp(addr)
}
fn xinet_backend_unix(builder: &mut XinetBuilder, path: &str) -> XinetBuilder {
builder.clone().backend_unix(path)
}
fn xinet_service(builder: &mut XinetBuilder, service: &str) -> XinetBuilder {
builder.clone().service(service)
}
fn xinet_connect_timeout(builder: &mut XinetBuilder, seconds: i64) -> XinetBuilder {
builder.clone().connect_timeout(seconds)
}
fn xinet_idle_timeout(builder: &mut XinetBuilder, seconds: i64) -> XinetBuilder {
builder.clone().idle_timeout(seconds)
}
fn xinet_single_connection(builder: &mut XinetBuilder, single: bool) -> XinetBuilder {
builder.clone().single_connection(single)
}
fn xinet_reset(builder: &mut XinetBuilder) -> XinetBuilder {
builder.clone().reset()
}
fn xinet_to_string(builder: &mut XinetBuilder) -> String {
builder.to_toml()
}
fn xinet_to_toml(builder: &mut XinetBuilder) -> String {
builder.to_toml()
}
fn xinet_to_markdown(builder: &mut XinetBuilder) -> String {
builder.to_markdown()
}
fn xinet_register(builder: &mut XinetBuilder) -> Result<(), Box<EvalAltResult>> {
let handle = match get_global_handle() {
Some(h) => h,
None => return make_error("No zinit connection available"),
};
let name = builder.get_name();
if name.is_empty() {
return make_error("xinet proxy: .name() is required");
}
if builder.listen_addrs.is_empty() {
return make_error("xinet proxy: listen address is required");
}
if builder.backend.is_none() {
return make_error("xinet proxy: backend address is required");
}
if builder.should_reset() {
let _ = handle.xinet_delete(name);
}
let config = builder.to_config().unwrap();
match handle.xinet_set(config) {
Ok(_) => {
let idle_info = if builder.idle_timeout > 0 {
format!(" (idle timeout: {}s)", builder.idle_timeout)
} else {
String::new()
};
let listen_str = builder.listen_addrs_string();
let backend_str = builder
.backend
.as_ref()
.map(|b| b.to_string())
.unwrap_or_default();
println!(
"Registered xinet proxy '{}': {} -> {}{}",
name, listen_str, backend_str, idle_info
);
Ok(())
}
Err(e) => {
let err_str = e.to_string();
if err_str.contains("already") {
return make_error(format!(
"xinet proxy '{}' already exists. Use .reset() to delete it first",
name
));
}
make_error(format!("xinet proxy '{}' registration failed: {}", name, e))
}
}
}
fn create_rhai_engine() -> Engine {
let mut engine = Engine::new();
let _ = register_zinit_module(&mut engine);
register_handle_methods(&mut engine);
engine
}
fn register_handle_methods(engine: &mut Engine) {
engine.register_type_with_name::<ZinitHandle>("ZinitHandle");
engine.register_fn(
"list",
|h: &mut ZinitHandle| -> Result<rhai::Array, Box<EvalAltResult>> {
match h.list() {
Ok(services) => {
let arr: rhai::Array = services.into_iter().map(|s| Dynamic::from(s)).collect();
Ok(arr)
}
Err(e) => make_error(format!("list failed: {}", e)),
}
},
);
engine.register_fn(
"status",
|h: &mut ZinitHandle, name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
match h.status(name) {
Ok(status) => {
let mut map = Map::new();
map.insert("name".into(), status.name.into());
map.insert("state".into(), status.state.to_string().into());
map.insert("pid".into(), (status.pid as i64).into());
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("status failed: {}", e)),
}
},
);
engine.register_fn("service_new", |_h: &mut ZinitHandle| ServiceBuilder::new());
engine.register_fn(
"start",
|h: &mut ZinitHandle, name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
match h.start(name) {
Ok(_) => {
let mut map = Map::new();
map.insert("success".into(), true.into());
map.insert(
"message".into(),
format!("Started service: {}", name).into(),
);
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("start failed: {}", e)),
}
},
);
engine.register_fn(
"stop",
|h: &mut ZinitHandle, name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
match h.stop(name) {
Ok(_) => {
let mut map = Map::new();
map.insert("success".into(), true.into());
map.insert(
"message".into(),
format!("Stopped service: {}", name).into(),
);
Ok(Dynamic::from_map(map))
}
Err(e) => make_error(format!("stop failed: {}", e)),
}
},
);
}
pub fn execute_script(script: &str) -> Result<(), Box<dyn std::error::Error>> {
let engine = create_rhai_engine();
let mut scope = rhai::Scope::new();
scope.push("zinit", ZinitFactory::new());
let script_content = if script.starts_with("#!") {
script.lines().skip(1).collect::<Vec<_>>().join("\n")
} else {
script.to_string()
};
let _result: () = engine.eval_with_scope(&mut scope, &script_content)?;
Ok(())
}