1use crate::client::handle::ZinitHandle;
7use rhai::{AST, Dynamic, Engine, EvalAltResult, Map, Position, Scope};
8use std::path::Path;
9use std::sync::Mutex;
10use std::time::Duration;
11
12use super::service_builder::{ServiceBuilder, XinitBuilder, rhai_fns, xinit_rhai_fns};
13
14use herolib_clients::rhai::register_redisclient_module;
16use herolib_core::rhai::register_text_module;
17use herolib_os::rhai::{
18 register_git_module, register_net_module, register_os_module, register_process_module,
19};
20
21lazy_static::lazy_static! {
23 static ref OUTPUT_BUFFER: Mutex<Vec<String>> = Mutex::new(Vec::new());
24 static ref GLOBAL_HANDLE: Mutex<Option<ZinitHandle>> = Mutex::new(None);
25}
26
27fn set_global_handle(handle: ZinitHandle) {
29 if let Ok(mut h) = GLOBAL_HANDLE.lock() {
30 *h = Some(handle);
31 }
32}
33
34pub fn get_global_handle() -> Option<ZinitHandle> {
36 GLOBAL_HANDLE.lock().ok().and_then(|h| h.clone())
37}
38
39fn append_output(s: &str) {
41 if let Ok(mut buf) = OUTPUT_BUFFER.lock() {
42 buf.push(s.to_string());
43 }
44 println!("{}", s);
46}
47
48pub fn clear_output() {
50 if let Ok(mut buf) = OUTPUT_BUFFER.lock() {
51 buf.clear();
52 }
53}
54
55pub fn get_output() -> Vec<String> {
57 OUTPUT_BUFFER.lock().map(|b| b.clone()).unwrap_or_default()
58}
59
60pub fn normalize_service_name(name: &str) -> String {
62 name.replace('-', "_").to_lowercase()
63}
64
65fn make_error<T>(msg: impl Into<String>) -> Result<T, Box<EvalAltResult>> {
67 Err(Box::new(EvalAltResult::ErrorRuntime(
68 Dynamic::from(msg.into()),
69 Position::NONE,
70 )))
71}
72
73fn check_tcp_port(addr: &str) -> bool {
75 if addr.is_empty() {
76 return true;
77 }
78 use std::net::{TcpStream, ToSocketAddrs};
79 match addr.to_socket_addrs() {
80 Ok(mut addrs) => {
81 if let Some(socket_addr) = addrs.next() {
82 TcpStream::connect_timeout(&socket_addr, Duration::from_secs(2)).is_ok()
83 } else {
84 false
85 }
86 }
87 Err(_) => false,
88 }
89}
90
91fn check_http(url: &str) -> bool {
93 if url.is_empty() {
94 return true;
95 }
96 use std::io::{Read, Write};
97 use std::net::{TcpStream, ToSocketAddrs};
98
99 let url = url.trim();
100 let without_proto = url
101 .strip_prefix("http://")
102 .or_else(|| url.strip_prefix("https://"))
103 .unwrap_or(url);
104
105 let (host_port, path) = match without_proto.find('/') {
106 Some(i) => (&without_proto[..i], &without_proto[i..]),
107 None => (without_proto, "/"),
108 };
109
110 let host_port_with_default = if host_port.contains(':') {
111 host_port.to_string()
112 } else {
113 format!("{}:80", host_port)
114 };
115
116 let socket_addr = match host_port_with_default.to_socket_addrs() {
117 Ok(mut addrs) => match addrs.next() {
118 Some(a) => a,
119 None => return false,
120 },
121 Err(_) => return false,
122 };
123
124 match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(2)) {
125 Ok(mut stream) => {
126 stream.set_read_timeout(Some(Duration::from_secs(2))).ok();
127 stream.set_write_timeout(Some(Duration::from_secs(2))).ok();
128
129 let request = format!(
130 "GET {} HTTP/1.0\r\nHost: {}\r\nConnection: close\r\n\r\n",
131 path, host_port
132 );
133
134 if stream.write_all(request.as_bytes()).is_err() {
135 return false;
136 }
137
138 let mut response = [0u8; 32];
139 if stream.read(&mut response).is_err() {
140 return false;
141 }
142
143 let response_str = String::from_utf8_lossy(&response);
144 response_str.contains("HTTP/1.0 2") || response_str.contains("HTTP/1.1 2")
145 }
146 Err(_) => false,
147 }
148}
149
150fn get_child_pids(parent_pid: i32) -> Vec<i32> {
152 use std::process::Command;
153
154 let output = Command::new("pgrep")
155 .args(["-P", &parent_pid.to_string()])
156 .output();
157
158 match output {
159 Ok(out) if out.status.success() => std::str::from_utf8(&out.stdout)
160 .unwrap_or("")
161 .lines()
162 .filter_map(|s| s.trim().parse().ok())
163 .collect(),
164 _ => Vec::new(),
165 }
166}
167
168fn collect_descendants_depth_first(pid: i32, result: &mut Vec<i32>) {
170 let children = get_child_pids(pid);
171 for child in children {
172 collect_descendants_depth_first(child, result);
173 result.push(child);
174 }
175}
176
177fn kill_process_tree(pid: i32) {
179 use std::process::Command;
180
181 let mut descendants = Vec::new();
183 collect_descendants_depth_first(pid, &mut descendants);
184
185 for child_pid in &descendants {
187 let _ = Command::new("kill").arg(child_pid.to_string()).output();
188 }
189 let _ = Command::new("kill").arg(pid.to_string()).output();
190
191 std::thread::sleep(Duration::from_millis(100));
193
194 for child_pid in &descendants {
196 let check = Command::new("kill")
197 .args(["-0", &child_pid.to_string()])
198 .output();
199 if check.map(|o| o.status.success()).unwrap_or(false) {
200 let _ = Command::new("kill")
201 .args(["-9", &child_pid.to_string()])
202 .output();
203 }
204 }
205 let check = Command::new("kill").args(["-0", &pid.to_string()]).output();
206 if check.map(|o| o.status.success()).unwrap_or(false) {
207 let _ = Command::new("kill").args(["-9", &pid.to_string()]).output();
208 }
209}
210
211fn kill_process_on_port(port: u16) -> usize {
213 use std::process::Command;
214
215 let output = Command::new("lsof")
216 .args(["-ti", &format!(":{}", port)])
217 .output();
218
219 match output {
220 Ok(out) if out.status.success() => {
221 let pids: Vec<i32> = std::str::from_utf8(&out.stdout)
222 .unwrap_or("")
223 .lines()
224 .filter_map(|s| s.trim().parse().ok())
225 .collect();
226
227 let mut killed = 0;
228 for pid in &pids {
229 kill_process_tree(*pid);
230 killed += 1;
231 }
232 killed
233 }
234 _ => 0,
235 }
236}
237
238pub struct ZinitEngine {
240 engine: Engine,
241}
242
243impl ZinitEngine {
244 pub fn new() -> Self {
246 let mut engine = Engine::new();
247 Self::register_types(&mut engine);
248 Self::register_utility_functions(&mut engine);
249 Self { engine }
250 }
251
252 pub fn with_handle(handle: ZinitHandle) -> Self {
254 set_global_handle(handle.clone());
256
257 let mut engine = Engine::new();
258 Self::register_types(&mut engine);
259 Self::register_utility_functions(&mut engine);
260 Self::register_zinit_functions(&mut engine, handle);
261 Self { engine }
262 }
263
264 fn register_types(engine: &mut Engine) {
265 engine
266 .register_type_with_name::<ServiceBuilder>("ServiceBuilder")
267 .register_fn("zinit_service_new", rhai_fns::new_service)
268 .register_fn("exec", rhai_fns::service_exec)
269 .register_fn("status", rhai_fns::service_status)
270 .register_fn("period", rhai_fns::service_period)
271 .register_fn("retry", rhai_fns::service_retry)
272 .register_fn("test_cmd", rhai_fns::service_test_cmd)
273 .register_fn("oneshot", rhai_fns::service_oneshot)
274 .register_fn("shutdown_timeout", rhai_fns::service_shutdown_timeout)
275 .register_fn("after", rhai_fns::service_after)
276 .register_fn("signal_stop", rhai_fns::service_signal_stop)
277 .register_fn("log", rhai_fns::service_log)
278 .register_fn("env", rhai_fns::service_env)
279 .register_fn("dir", rhai_fns::service_dir)
280 .register_fn("to_map", rhai_fns::service_to_map)
281 .register_fn("to_string", rhai_fns::service_to_string)
282 .register_fn("to_toml", rhai_fns::service_to_toml)
283 .register_fn("to_markdown", rhai_fns::service_to_markdown)
284 .register_fn("name", rhai_fns::service_name)
285 .register_fn("test_tcp", rhai_fns::service_test_tcp)
286 .register_fn("test_http", rhai_fns::service_test_http)
287 .register_fn("tcp_kill", rhai_fns::service_tcp_kill)
288 .register_fn("reset", rhai_fns::service_reset)
289 .register_fn("wait", rhai_fns::service_wait)
290 .register_fn("register", rhai_fns::service_register);
291
292 engine
294 .register_type_with_name::<XinitBuilder>("XinitBuilder")
295 .register_fn("xinit_proxy_new", xinit_rhai_fns::new_xinit)
296 .register_fn("name", xinit_rhai_fns::xinit_name)
297 .register_fn("listen", xinit_rhai_fns::xinit_listen)
298 .register_fn("add_listen", xinit_rhai_fns::xinit_add_listen)
299 .register_fn("backend", xinit_rhai_fns::xinit_backend)
300 .register_fn("service", xinit_rhai_fns::xinit_service)
301 .register_fn("reset", xinit_rhai_fns::xinit_reset)
302 .register_fn("idle_timeout", xinit_rhai_fns::xinit_idle_timeout)
303 .register_fn("connect_timeout", xinit_rhai_fns::xinit_connect_timeout)
304 .register_fn("to_string", xinit_rhai_fns::xinit_to_string)
305 .register_fn("to_toml", xinit_rhai_fns::xinit_to_toml)
306 .register_fn("to_markdown", xinit_rhai_fns::xinit_to_markdown)
307 .register_fn("register", xinit_rhai_fns::xinit_register);
308 }
309
310 fn register_utility_functions(engine: &mut Engine) {
311 engine.on_print(|s| {
313 append_output(s);
314 });
315 engine.on_debug(|s, source, pos| {
316 let location = if let Some(src) = source {
317 format!("[{}:{}] ", src, pos)
318 } else if !pos.is_none() {
319 format!("[{}] ", pos)
320 } else {
321 String::new()
322 };
323 append_output(&format!("[DEBUG] {}{}", location, s));
324 });
325
326 engine.register_fn("sleep_ms", |ms: i64| {
327 std::thread::sleep(Duration::from_millis(ms as u64));
328 });
329
330 engine.register_fn("sleep", |secs: i64| {
331 std::thread::sleep(Duration::from_secs(secs as u64));
332 });
333
334 engine.register_fn("get_env", |key: &str| -> Dynamic {
335 match std::env::var(key) {
336 Ok(val) => Dynamic::from(val),
337 Err(_) => Dynamic::UNIT,
338 }
339 });
340
341 engine.register_fn("set_env", |key: &str, value: &str| {
342 unsafe { std::env::set_var(key, value) };
344 });
345
346 engine.register_fn("check_tcp", check_tcp_port);
347 engine.register_fn("check_http", check_http);
348
349 engine.register_fn("kill_port", |port: i64| -> i64 {
350 kill_process_on_port(port as u16) as i64
351 });
352
353 let _ = register_os_module(engine);
355 let _ = register_process_module(engine);
356 let _ = register_git_module(engine);
357 let _ = register_net_module(engine);
358
359 let _ = register_text_module(engine);
361
362 let _ = register_redisclient_module(engine);
364 }
365
366 fn register_zinit_functions(engine: &mut Engine, handle: ZinitHandle) {
367 let h = handle.clone();
369 engine.register_fn(
370 "zinit_ping",
371 move || -> Result<Dynamic, Box<EvalAltResult>> {
372 match h.ping() {
373 Ok(resp) => {
374 let mut map = Map::new();
375 map.insert("message".into(), resp.message.into());
376 map.insert("version".into(), resp.version.into());
377 Ok(Dynamic::from_map(map))
378 }
379 Err(e) => make_error(format!("zinit_ping failed: {}", e)),
380 }
381 },
382 );
383
384 let h = handle.clone();
386 engine.register_fn(
387 "zinit_list",
388 move || -> Result<Dynamic, Box<EvalAltResult>> {
389 match h.list() {
390 Ok(services) => Ok(Dynamic::from_array(
391 services.into_iter().map(Dynamic::from).collect(),
392 )),
393 Err(e) => make_error(format!("zinit_list failed: {}", e)),
394 }
395 },
396 );
397
398 let h = handle.clone();
400 engine.register_fn(
401 "zinit_list_md",
402 move || -> Result<(), Box<EvalAltResult>> {
403 match h.list() {
404 Ok(services) => {
405 if services.is_empty() {
406 append_output("No services registered.");
407 } else {
408 let mut output = String::new();
409 output.push_str("| Service | State | PID |\n");
410 output.push_str("|---------|-------|-----|\n");
411 for name in services {
412 match h.status(&name) {
413 Ok(status) => {
414 output.push_str(&format!(
415 "| {} | {} | {} |\n",
416 status.name, status.state, status.pid
417 ));
418 }
419 Err(_) => {
420 output.push_str(&format!("| {} | unknown | - |\n", name));
421 }
422 }
423 }
424 append_output(&output);
425 }
426 Ok(())
427 }
428 Err(e) => make_error(format!("zinit_list_md failed: {}", e)),
429 }
430 },
431 );
432
433 let h = handle.clone();
435 engine.register_fn(
436 "zinit_status",
437 move |name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
438 let name = normalize_service_name(name);
439 match h.status(&name) {
440 Ok(status) => {
441 let mut map = Map::new();
442 map.insert("name".into(), status.name.into());
443 map.insert("pid".into(), (status.pid as i64).into());
444 map.insert("state".into(), status.state.into());
445 map.insert("target".into(), status.target.into());
446 Ok(Dynamic::from_map(map))
447 }
448 Err(e) => make_error(format!("zinit_status '{}' failed: {}", name, e)),
449 }
450 },
451 );
452
453 let h = handle.clone();
455 engine.register_fn(
456 "zinit_status_md",
457 move |name: &str| -> Result<(), Box<EvalAltResult>> {
458 let name = normalize_service_name(name);
459 match h.status(&name) {
460 Ok(status) => {
461 let mut output = String::new();
462 output.push_str("| Property | Value |\n");
463 output.push_str("|----------|-------|\n");
464 output.push_str(&format!("| Name | {} |\n", status.name));
465 output.push_str(&format!("| PID | {} |\n", status.pid));
466 output.push_str(&format!("| State | {} |\n", status.state));
467 output.push_str(&format!("| Target | {} |\n", status.target));
468 append_output(&output);
469 Ok(())
470 }
471 Err(e) => make_error(format!("zinit_status_md '{}' failed: {}", name, e)),
472 }
473 },
474 );
475
476 let h = handle.clone();
478 engine.register_fn(
479 "zinit_start",
480 move |name: &str| -> Result<(), Box<EvalAltResult>> {
481 let name = normalize_service_name(name);
482 match h.start(&name) {
483 Ok(_) => {
484 append_output(&format!("Started: {}", name));
485 Ok(())
486 }
487 Err(e) => make_error(format!("zinit_start '{}' failed: {}", name, e)),
488 }
489 },
490 );
491
492 let h = handle.clone();
494 engine.register_fn(
495 "zinit_stop",
496 move |name: &str| -> Result<(), Box<EvalAltResult>> {
497 let name = normalize_service_name(name);
498 match h.stop(&name) {
499 Ok(_) => {
500 append_output(&format!("Stopped: {}", name));
501 Ok(())
502 }
503 Err(e) => make_error(format!("zinit_stop '{}' failed: {}", name, e)),
504 }
505 },
506 );
507
508 let h = handle.clone();
510 engine.register_fn(
511 "zinit_restart",
512 move |name: &str| -> Result<(), Box<EvalAltResult>> {
513 let name = normalize_service_name(name);
514 match h.restart(&name) {
515 Ok(_) => {
516 append_output(&format!("Restarted: {}", name));
517 Ok(())
518 }
519 Err(e) => make_error(format!("zinit_restart '{}' failed: {}", name, e)),
520 }
521 },
522 );
523
524 let h = handle.clone();
526 engine.register_fn(
527 "zinit_delete",
528 move |name: &str| -> Result<(), Box<EvalAltResult>> {
529 let name = normalize_service_name(name);
530 match h.delete(&name) {
531 Ok(_) => {
532 append_output(&format!("Deleted: {}", name));
533 Ok(())
534 }
535 Err(e) => make_error(format!("zinit_delete '{}' failed: {}", name, e)),
536 }
537 },
538 );
539
540 let h = handle.clone();
542 engine.register_fn(
543 "zinit_kill",
544 move |name: &str, signal: &str| -> Result<(), Box<EvalAltResult>> {
545 let name = normalize_service_name(name);
546 match h.kill(&name, signal) {
547 Ok(_) => {
548 append_output(&format!("Sent {} to: {}", signal, name));
549 Ok(())
550 }
551 Err(e) => make_error(format!("zinit_kill '{}' failed: {}", name, e)),
552 }
553 },
554 );
555
556 let h = handle.clone();
558 engine.register_fn(
559 "zinit_stats",
560 move |name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
561 let name = normalize_service_name(name);
562 match h.stats(&name) {
563 Ok(stats) => {
564 let mut map = Map::new();
565 map.insert("pid".into(), (stats.pid as i64).into());
566 map.insert("memory_usage".into(), (stats.memory_usage as i64).into());
567 map.insert("cpu_usage".into(), (stats.cpu_usage as f64).into());
568 Ok(Dynamic::from_map(map))
569 }
570 Err(e) => make_error(format!("zinit_stats '{}' failed: {}", name, e)),
571 }
572 },
573 );
574
575 let h = handle.clone();
577 engine.register_fn(
578 "zinit_stats_md",
579 move |name: &str| -> Result<(), Box<EvalAltResult>> {
580 let name = normalize_service_name(name);
581 match h.stats(&name) {
582 Ok(stats) => {
583 let mut output = String::new();
584 output.push_str("| Metric | Value |\n");
585 output.push_str("|--------|-------|\n");
586 output.push_str(&format!("| PID | {} |\n", stats.pid));
587 output.push_str(&format!("| Memory | {} bytes |\n", stats.memory_usage));
588 output.push_str(&format!("| CPU | {:.2}% |\n", stats.cpu_usage));
589 append_output(&output);
590 Ok(())
591 }
592 Err(e) => make_error(format!("zinit_stats_md '{}' failed: {}", name, e)),
593 }
594 },
595 );
596
597 let h = handle.clone();
599 engine.register_fn("zinit_is_running", move |name: &str| -> bool {
600 let name = normalize_service_name(name);
601 h.is_running(&name).unwrap_or(false)
602 });
603
604 let h = handle.clone();
606 engine.register_fn("zinit_wait_for", move |name: &str, secs: i64| -> bool {
607 let name = normalize_service_name(name);
608 let deadline = std::time::Instant::now() + Duration::from_secs(secs.max(1) as u64);
609 loop {
610 if std::time::Instant::now() >= deadline {
611 return false;
612 }
613 if let Ok(status) = h.status(&name) {
614 if status.state == "Running" || status.state == "Success" {
615 return true;
616 }
617 if status.state.contains("Error") || status.state.contains("Failed") {
618 return false;
619 }
620 }
621 std::thread::sleep(Duration::from_millis(100));
622 }
623 });
624
625 let h = handle.clone();
627 engine.register_fn(
628 "zinit_logs",
629 move || -> Result<Dynamic, Box<EvalAltResult>> {
630 match h.logs() {
631 Ok(logs) => Ok(Dynamic::from_array(
632 logs.into_iter().map(Dynamic::from).collect(),
633 )),
634 Err(e) => make_error(format!("zinit_logs failed: {}", e)),
635 }
636 },
637 );
638
639 let h = handle.clone();
641 engine.register_fn(
642 "zinit_logs_filter",
643 move |service: &str| -> Result<Dynamic, Box<EvalAltResult>> {
644 let service = normalize_service_name(service);
645 match h.logs_filter(&service) {
646 Ok(logs) => Ok(Dynamic::from_array(
647 logs.into_iter().map(Dynamic::from).collect(),
648 )),
649 Err(e) => make_error(format!("zinit_logs_filter failed: {}", e)),
650 }
651 },
652 );
653
654 let h = handle.clone();
656 engine.register_fn(
657 "zinit_logs_tail",
658 move |n: i64| -> Result<Dynamic, Box<EvalAltResult>> {
659 match h.logs() {
660 Ok(logs) => {
661 let n = n.max(0) as usize;
662 let start = if logs.len() > n { logs.len() - n } else { 0 };
663 Ok(Dynamic::from_array(
664 logs[start..].iter().cloned().map(Dynamic::from).collect(),
665 ))
666 }
667 Err(e) => make_error(format!("zinit_logs_tail failed: {}", e)),
668 }
669 },
670 );
671
672 let h = handle.clone();
674 engine.register_fn(
675 "zinit_logs_print",
676 move || -> Result<(), Box<EvalAltResult>> {
677 match h.logs() {
678 Ok(logs) => {
679 for line in logs {
680 append_output(&line);
681 }
682 Ok(())
683 }
684 Err(e) => make_error(format!("zinit_logs_print failed: {}", e)),
685 }
686 },
687 );
688
689 let h = handle.clone();
691 engine.register_fn(
692 "zinit_logs_print_filter",
693 move |service: &str| -> Result<(), Box<EvalAltResult>> {
694 let service = normalize_service_name(service);
695 match h.logs_filter(&service) {
696 Ok(logs) => {
697 for line in logs {
698 append_output(&line);
699 }
700 Ok(())
701 }
702 Err(e) => make_error(format!("zinit_logs_print_filter failed: {}", e)),
703 }
704 },
705 );
706
707 let h = handle.clone();
709 engine.register_fn(
710 "zinit_logs_follow",
711 move |secs: i64| -> Result<(), Box<EvalAltResult>> {
712 let deadline = std::time::Instant::now() + Duration::from_secs(secs.max(1) as u64);
713 let mut last_count = 0usize;
714 while std::time::Instant::now() < deadline {
715 if let Ok(logs) = h.logs()
716 && logs.len() > last_count
717 {
718 for line in &logs[last_count..] {
719 append_output(line);
720 }
721 last_count = logs.len();
722 }
723 std::thread::sleep(Duration::from_millis(100));
724 }
725 Ok(())
726 },
727 );
728
729 let h = handle.clone();
731 engine.register_fn(
732 "zinit_logs_follow_filter",
733 move |service: &str, secs: i64| -> Result<(), Box<EvalAltResult>> {
734 let service = normalize_service_name(service);
735 let deadline = std::time::Instant::now() + Duration::from_secs(secs.max(1) as u64);
736 let mut last_count = 0usize;
737 while std::time::Instant::now() < deadline {
738 if let Ok(logs) = h.logs_filter(&service)
739 && logs.len() > last_count
740 {
741 for line in &logs[last_count..] {
742 append_output(line);
743 }
744 last_count = logs.len();
745 }
746 std::thread::sleep(Duration::from_millis(100));
747 }
748 Ok(())
749 },
750 );
751
752 let h = handle.clone();
754 engine.register_fn(
755 "zinit_start_all",
756 move || -> Result<(), Box<EvalAltResult>> {
757 match h.start_all() {
758 Ok(_) => {
759 append_output("Started all services");
760 Ok(())
761 }
762 Err(e) => make_error(format!("zinit_start_all failed: {}", e)),
763 }
764 },
765 );
766
767 let h = handle.clone();
769 engine.register_fn(
770 "zinit_stop_all",
771 move || -> Result<(), Box<EvalAltResult>> {
772 match h.stop_all() {
773 Ok(_) => {
774 append_output("Stopped all services");
775 Ok(())
776 }
777 Err(e) => make_error(format!("zinit_stop_all failed: {}", e)),
778 }
779 },
780 );
781
782 let h = handle.clone();
784 engine.register_fn(
785 "zinit_service_delete_all",
786 move || -> Result<(), Box<EvalAltResult>> {
787 match h.delete_all() {
788 Ok(_) => {
789 append_output("Deleted all services");
790 Ok(())
791 }
792 Err(e) => make_error(format!("zinit_service_delete_all failed: {}", e)),
793 }
794 },
795 );
796
797 let h = handle.clone();
799 engine.register_fn(
800 "zinit_shutdown",
801 move || -> Result<(), Box<EvalAltResult>> {
802 match h.shutdown() {
803 Ok(_) => {
804 append_output("Server shutting down");
805 Ok(())
806 }
807 Err(e) => make_error(format!("zinit_shutdown failed: {}", e)),
808 }
809 },
810 );
811
812 let h = handle.clone();
814 engine.register_fn("zinit_reboot", move || -> Result<(), Box<EvalAltResult>> {
815 match h.reboot() {
816 Ok(_) => {
817 append_output("System rebooting");
818 Ok(())
819 }
820 Err(e) => make_error(format!("zinit_reboot failed: {}", e)),
821 }
822 });
823
824 let h = handle.clone();
828 engine.register_fn(
829 "xinit_list",
830 move || -> Result<Dynamic, Box<EvalAltResult>> {
831 match h.xinit_list() {
832 Ok(proxies) => Ok(Dynamic::from_array(
833 proxies.into_iter().map(Dynamic::from).collect(),
834 )),
835 Err(e) => make_error(format!("xinit_list failed: {}", e)),
836 }
837 },
838 );
839
840 let h = handle.clone();
842 engine.register_fn(
843 "xinit_list_md",
844 move || -> Result<(), Box<EvalAltResult>> {
845 match h.xinit_status_all() {
846 Ok(statuses) => {
847 if statuses.is_empty() {
848 append_output("No xinit proxies registered.");
849 } else {
850 let mut output = String::new();
851 output.push_str(
852 "| Name | Listen | Backend | Service | Running | Connections |\n",
853 );
854 output.push_str(
855 "|------|--------|---------|---------|---------|-------------|\n",
856 );
857 for status in statuses {
858 output.push_str(&format!(
859 "| {} | {} | {} | {} | {} | {} |\n",
860 status.name,
861 status.listen,
862 status.backend,
863 status.service,
864 if status.running { "Yes" } else { "No" },
865 status.active_connections
866 ));
867 }
868 append_output(&output);
869 }
870 Ok(())
871 }
872 Err(e) => make_error(format!("xinit_list_md failed: {}", e)),
873 }
874 },
875 );
876
877 let h = handle.clone();
880 engine.register_fn(
881 "xinit_register",
882 move |name: &str,
883 listen: &str,
884 backend: &str,
885 service: &str|
886 -> Result<(), Box<EvalAltResult>> {
887 let listen_addrs = vec![listen.to_string()];
888 match h.xinit_register(name, &listen_addrs, backend, service, 0, 30) {
889 Ok(_) => {
890 append_output(&format!(
891 "Registered xinit proxy '{}': {} -> {}",
892 name, listen, backend
893 ));
894 Ok(())
895 }
896 Err(e) => make_error(format!("xinit_register '{}' failed: {}", name, e)),
897 }
898 },
899 );
900
901 let h = handle.clone();
903 engine.register_fn(
904 "xinit_unregister",
905 move |name: &str| -> Result<(), Box<EvalAltResult>> {
906 match h.xinit_unregister(name) {
907 Ok(_) => {
908 append_output(&format!("Unregistered xinit proxy '{}'", name));
909 Ok(())
910 }
911 Err(e) => make_error(format!("xinit_unregister '{}' failed: {}", name, e)),
912 }
913 },
914 );
915
916 let h = handle.clone();
918 engine.register_fn(
919 "xinit_status",
920 move |name: &str| -> Result<Dynamic, Box<EvalAltResult>> {
921 match h.xinit_status(name) {
922 Ok(status) => {
923 let mut map = Map::new();
924 map.insert("name".into(), status.name.into());
925 map.insert("listen".into(), status.listen.into());
926 map.insert("backend".into(), status.backend.into());
927 map.insert("service".into(), status.service.into());
928 map.insert("running".into(), status.running.into());
929 map.insert(
930 "total_connections".into(),
931 (status.total_connections as i64).into(),
932 );
933 map.insert(
934 "active_connections".into(),
935 (status.active_connections as i64).into(),
936 );
937 map.insert(
938 "bytes_to_backend".into(),
939 (status.bytes_to_backend as i64).into(),
940 );
941 map.insert(
942 "bytes_from_backend".into(),
943 (status.bytes_from_backend as i64).into(),
944 );
945 Ok(Dynamic::from_map(map))
946 }
947 Err(e) => make_error(format!("xinit_status '{}' failed: {}", name, e)),
948 }
949 },
950 );
951
952 let h = handle.clone();
954 engine.register_fn(
955 "xinit_status_all",
956 move || -> Result<Dynamic, Box<EvalAltResult>> {
957 match h.xinit_status_all() {
958 Ok(statuses) => {
959 let arr: Vec<Dynamic> = statuses
960 .into_iter()
961 .map(|status| {
962 let mut map = Map::new();
963 map.insert("name".into(), status.name.into());
964 map.insert("listen".into(), status.listen.into());
965 map.insert("backend".into(), status.backend.into());
966 map.insert("service".into(), status.service.into());
967 map.insert("running".into(), status.running.into());
968 map.insert(
969 "total_connections".into(),
970 (status.total_connections as i64).into(),
971 );
972 map.insert(
973 "active_connections".into(),
974 (status.active_connections as i64).into(),
975 );
976 Dynamic::from_map(map)
977 })
978 .collect();
979 Ok(Dynamic::from_array(arr))
980 }
981 Err(e) => make_error(format!("xinit_status_all failed: {}", e)),
982 }
983 },
984 );
985 }
986
987 pub fn compile(&self, script: &str) -> Result<AST, Box<EvalAltResult>> {
989 self.engine.compile(script).map_err(|e| {
990 Box::new(EvalAltResult::ErrorRuntime(
991 Dynamic::from(e.to_string()),
992 Position::NONE,
993 ))
994 })
995 }
996
997 pub fn run_ast(&self, ast: &AST) -> Result<Dynamic, Box<EvalAltResult>> {
999 let mut scope = Scope::new();
1000 self.engine.eval_ast_with_scope(&mut scope, ast)
1001 }
1002
1003 pub fn run(&self, script: &str) -> Result<Dynamic, Box<EvalAltResult>> {
1005 let mut scope = Scope::new();
1006 self.engine.eval_with_scope(&mut scope, script)
1007 }
1008
1009 pub fn run_file(&self, path: &Path) -> Result<Dynamic, Box<EvalAltResult>> {
1011 let script = std::fs::read_to_string(path).map_err(|e| {
1012 Box::new(EvalAltResult::ErrorRuntime(
1013 Dynamic::from(format!("Failed to read file: {}", e)),
1014 Position::NONE,
1015 ))
1016 })?;
1017
1018 let script = if script.starts_with("#!") {
1020 script.lines().skip(1).collect::<Vec<_>>().join("\n")
1021 } else {
1022 script
1023 };
1024
1025 self.run(&script)
1026 }
1027
1028 pub fn engine(&self) -> &Engine {
1030 &self.engine
1031 }
1032
1033 pub fn engine_mut(&mut self) -> &mut Engine {
1035 &mut self.engine
1036 }
1037}
1038
1039impl Default for ZinitEngine {
1040 fn default() -> Self {
1041 Self::new()
1042 }
1043}
1044
1045pub fn create_engine() -> Result<ZinitEngine, anyhow::Error> {
1047 let handle = ZinitHandle::new()?;
1048 Ok(ZinitEngine::with_handle(handle))
1049}
1050
1051pub fn run_script(script: &str) -> Result<Dynamic, Box<EvalAltResult>> {
1053 let engine = create_engine().map_err(|e| {
1054 Box::new(EvalAltResult::ErrorRuntime(
1055 Dynamic::from(e.to_string()),
1056 Position::NONE,
1057 ))
1058 })?;
1059 engine.run(script)
1060}
1061
1062pub fn run_script_file(path: &Path) -> Result<Dynamic, Box<EvalAltResult>> {
1064 let engine = create_engine().map_err(|e| {
1065 Box::new(EvalAltResult::ErrorRuntime(
1066 Dynamic::from(e.to_string()),
1067 Position::NONE,
1068 ))
1069 })?;
1070 engine.run_file(path)
1071}