1use std::path::{Path, PathBuf};
31
32use serde::{Deserialize, Serialize};
33
34pub const LOCK_FILE: &str = "orchestrator.lock";
36
37pub const SERVICE_DIR: &str = "service";
39
40pub const DAEMON_ENTRY: &str = "bin/orchestrator.mjs";
42
43pub const SERVICE_NAME: &str = "ai.volter.supercode.orchestrator";
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct Lease {
50 pub pid: u32,
52 pub started_at: String,
54 pub root: PathBuf,
56 #[serde(default, skip_serializing_if = "Option::is_none")]
59 pub host: Option<String>,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub boot: Option<String>,
64}
65
66impl Lease {
67 pub fn is_live(&self) -> bool {
71 let here = this_host();
72 if self
73 .host
74 .as_deref()
75 .is_some_and(|host| Some(host) != here.0.as_deref())
76 {
77 return false;
78 }
79 if let (Some(boot), Some(now)) = (self.boot.as_deref(), here.1.as_deref()) {
80 if boot != now {
81 return false;
82 }
83 }
84 pid_is_live(self.pid)
85 }
86}
87
88pub fn this_host() -> (Option<String>, Option<String>) {
90 let boot = std::fs::read_to_string("/proc/sys/kernel/random/boot_id")
91 .ok()
92 .map(|text| text.trim().to_string())
93 .filter(|text| !text.is_empty());
94 (hostname(), boot)
95}
96
97fn hostname() -> Option<String> {
98 #[cfg(unix)]
99 {
100 let mut buffer = [0u8; 256];
101 let status = unsafe { libc::gethostname(buffer.as_mut_ptr().cast(), buffer.len()) };
103 if status != 0 {
104 return None;
105 }
106 let end = buffer
107 .iter()
108 .position(|byte| *byte == 0)
109 .unwrap_or(buffer.len());
110 String::from_utf8(buffer[..end].to_vec())
111 .ok()
112 .filter(|name| !name.is_empty())
113 }
114 #[cfg(not(unix))]
115 {
116 std::env::var("COMPUTERNAME").ok()
117 }
118}
119
120#[derive(Debug, thiserror::Error)]
122pub enum OrchestratorError {
123 #[error("the orchestrator is not running for `{0}` (no live lease at `{1}`)", root.display(), lock.display())]
125 NotRunning {
126 root: PathBuf,
128 lock: PathBuf,
130 },
131 #[error("the orchestrator is already running for `{}` (pid {pid})", root.display())]
133 AlreadyRunning {
134 root: PathBuf,
136 pid: u32,
138 },
139 #[error("no orchestrator daemon entry found (looked for `{DAEMON_ENTRY}` under: {searched})")]
141 NoDaemonEntry {
142 searched: String,
144 },
145 #[error("orchestrator lease `{}`: {source}", path.display())]
147 Lease {
148 path: PathBuf,
150 source: std::io::Error,
152 },
153 #[error("orchestrator service: {action} failed: {detail}")]
155 Service {
156 action: &'static str,
158 detail: String,
160 },
161}
162
163pub fn lock_path(root: &Path) -> PathBuf {
165 root.join(LOCK_FILE)
166}
167
168pub fn read_lease(root: &Path) -> Option<Lease> {
170 let text = std::fs::read_to_string(lock_path(root)).ok()?;
171 serde_json::from_str(&text).ok()
172}
173
174pub fn write_lease(root: &Path, lease: &Lease) -> Result<(), OrchestratorError> {
176 let path = lock_path(root);
177 if let Some(parent) = path.parent() {
178 std::fs::create_dir_all(parent).map_err(|source| OrchestratorError::Lease {
179 path: path.clone(),
180 source,
181 })?;
182 }
183 let text = serde_json::to_string_pretty(lease).unwrap_or_default();
184 std::fs::write(&path, format!("{text}\n")).map_err(|source| OrchestratorError::Lease {
185 path: path.clone(),
186 source,
187 })
188}
189
190pub fn clear_lease(root: &Path) -> Result<(), OrchestratorError> {
193 let path = lock_path(root);
194 match std::fs::remove_file(&path) {
195 Ok(()) => Ok(()),
196 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
197 Err(source) => Err(OrchestratorError::Lease { path, source }),
198 }
199}
200
201pub fn pid_is_live(pid: u32) -> bool {
207 #[cfg(unix)]
208 {
209 if pid == 0 {
210 return false;
211 }
212 unsafe { libc::kill(pid as libc::pid_t, 0) == 0 }
214 }
215 #[cfg(not(unix))]
216 {
217 let _ = pid;
218 true
219 }
220}
221
222pub fn live_lease(root: &Path) -> Option<Lease> {
224 read_lease(root).filter(Lease::is_live)
225}
226
227pub fn stop(root: &Path) -> Result<Lease, OrchestratorError> {
233 let Some(lease) = live_lease(root) else {
234 return Err(OrchestratorError::NotRunning {
235 root: root.to_path_buf(),
236 lock: lock_path(root),
237 });
238 };
239 #[cfg(unix)]
240 unsafe {
242 libc::kill(lease.pid as libc::pid_t, libc::SIGTERM);
243 }
244 clear_lease(root)?;
245 Ok(lease)
246}
247
248pub fn daemon_entry() -> Result<PathBuf, OrchestratorError> {
258 let mut searched = Vec::new();
259 if let Some(explicit) = std::env::var_os("SUPERCODE_ORCHESTRATOR_ENTRY") {
260 let path = PathBuf::from(explicit);
261 if path.is_file() {
262 return Ok(path);
263 }
264 searched.push(path.display().to_string());
265 }
266 for dir in std::env::var_os("PATH")
269 .iter()
270 .flat_map(std::env::split_paths)
271 {
272 let command = dir.join("supercode-orchestrator");
273 if let Ok(entry) = std::fs::canonicalize(&command) {
274 if entry.is_file() && entry.ends_with(DAEMON_ENTRY) {
275 return Ok(entry);
276 }
277 }
278 }
279 searched.push("supercode-orchestrator on PATH".to_string());
280 let mut roots: Vec<PathBuf> = Vec::new();
281 if let Ok(exe) = std::env::current_exe() {
282 roots.extend(exe.ancestors().skip(1).take(4).map(Path::to_path_buf));
284 }
285 if let Some(workspace) = Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(2) {
290 roots.push(workspace.to_path_buf());
291 }
292 for root in roots {
293 let candidate = root.join("sdk/orchestrator").join(DAEMON_ENTRY);
294 if candidate.is_file() {
295 return Ok(candidate);
296 }
297 searched.push(candidate.display().to_string());
298 }
299 Err(OrchestratorError::NoDaemonEntry {
300 searched: searched.join(", "),
301 })
302}
303
304#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
306pub struct ServiceUnit {
307 pub kind: &'static str,
309 pub path: PathBuf,
311 pub text: String,
313 pub install_command: String,
315 #[serde(skip)]
317 pub files: Vec<(PathBuf, String)>,
318}
319
320pub fn service_unit(root: &Path, entry: &Path, node: &str) -> ServiceUnit {
326 let root_display = root.display().to_string();
327 let entry_display = entry.display().to_string();
328 if cfg!(target_os = "macos") {
329 let path = root.join(SERVICE_DIR).join(format!("{SERVICE_NAME}.plist"));
330 let text = format!(
331 r#"<?xml version="1.0" encoding="UTF-8"?>
332<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
333<plist version="1.0">
334<dict>
335 <key>Label</key><string>{SERVICE_NAME}</string>
336 <key>ProgramArguments</key>
337 <array>
338 <string>{node}</string>
339 <string>{entry_display}</string>
340 <string>--root</string>
341 <string>{root_display}</string>
342 </array>
343 <key>RunAtLoad</key><true/>
344 <key>KeepAlive</key><true/>
345 <key>StandardOutPath</key><string>{root_display}/service/orchestrator.out.log</string>
346 <key>StandardErrorPath</key><string>{root_display}/service/orchestrator.err.log</string>
347</dict>
348</plist>
349"#
350 );
351 let install = format!("launchctl bootstrap gui/$(id -u) {}", path.display());
352 ServiceUnit {
353 kind: "launchd",
354 path,
355 text,
356 install_command: install,
357 files: Vec::new(),
358 }
359 } else {
360 let path = root
361 .join(SERVICE_DIR)
362 .join(format!("{SERVICE_NAME}.service"));
363 let text = format!(
364 "[Unit]\n\
365 Description=supercode orchestrator ({root_display})\n\
366 After=network.target\n\
367 \n\
368 [Service]\n\
369 ExecStart={node} {entry_display} --root {root_display}\n\
370 Restart=on-failure\n\
371 KillSignal=SIGTERM\n\
372 \n\
373 [Install]\n\
374 WantedBy=default.target\n"
375 );
376 let install = format!(
377 "systemctl --user link {} && systemctl --user enable --now {SERVICE_NAME}",
378 path.display()
379 );
380 ServiceUnit {
381 kind: "systemd",
382 path,
383 text,
384 install_command: install,
385 files: Vec::new(),
386 }
387 }
388}
389
390#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
392pub struct ServiceState {
393 pub kind: &'static str,
395 pub label: String,
397 pub installed: bool,
399 pub pid: Option<u32>,
401 pub detail: String,
403}
404
405pub fn absolute_program(program: &str) -> String {
408 resolve_program(program).display().to_string()
409}
410
411pub fn resolve_program(program: &str) -> PathBuf {
415 resolve_program_in(
416 program,
417 std::env::var_os("PATH").as_deref(),
418 std::env::var("PATHEXT").ok().as_deref(),
419 cfg!(windows),
420 )
421}
422
423fn resolve_program_in(
427 program: &str,
428 path: Option<&std::ffi::OsStr>,
429 pathext: Option<&str>,
430 windows: bool,
431) -> PathBuf {
432 if program.contains('/') || (windows && program.contains('\\')) {
433 return PathBuf::from(program);
434 }
435 let extensions: Vec<String> = if windows && Path::new(program).extension().is_none() {
436 pathext
437 .unwrap_or(".COM;.EXE;.BAT;.CMD")
438 .split(';')
439 .filter(|extension| !extension.is_empty())
440 .map(str::to_ascii_lowercase)
441 .collect()
442 } else {
443 vec![String::new()]
444 };
445 for dir in path.map(std::env::split_paths).into_iter().flatten() {
446 for extension in &extensions {
447 let candidate = dir.join(format!("{program}{extension}"));
448 if candidate.is_file() {
449 return candidate;
450 }
451 }
452 }
453 PathBuf::from(program)
454}
455
456fn run_tool(program: &str, args: &[&str]) -> Result<(bool, String), std::io::Error> {
458 let output = std::process::Command::new(program).args(args).output()?;
459 let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
460 text.push_str(&String::from_utf8_lossy(&output.stderr));
461 Ok((output.status.success(), text.trim().to_string()))
462}
463
464#[cfg(target_os = "macos")]
465fn gui_domain() -> String {
466 format!("gui/{}", unsafe { libc::getuid() })
468}
469
470pub fn service_status(root: &Path) -> ServiceState {
474 platform_status(root)
475}
476
477#[cfg(target_os = "macos")]
478fn platform_status(_root: &Path) -> ServiceState {
479 let label = SERVICE_NAME.to_string();
480 let target = format!("{}/{SERVICE_NAME}", gui_domain());
481 match run_tool("launchctl", &["print", &target]) {
482 Ok((true, text)) => ServiceState {
483 kind: "launchd",
484 label,
485 installed: true,
486 pid: field_of(&text, "pid = ").and_then(|value| value.parse().ok()),
487 detail: field_of(&text, "state = ").unwrap_or_else(|| "loaded".into()),
488 },
489 Ok((false, _)) => ServiceState {
490 kind: "launchd",
491 label,
492 installed: false,
493 pid: None,
494 detail: format!("not bootstrapped in {}", gui_domain()),
495 },
496 Err(error) => ServiceState {
497 kind: "launchd",
498 label,
499 installed: false,
500 pid: None,
501 detail: format!("launchctl unavailable: {error}"),
502 },
503 }
504}
505
506#[cfg(all(unix, not(target_os = "macos")))]
507fn platform_status(_root: &Path) -> ServiceState {
508 let label = SERVICE_NAME.to_string();
509 match run_tool("systemctl", &["--user", "is-active", SERVICE_NAME]) {
510 Ok((active, text)) => {
511 let known = run_tool("systemctl", &["--user", "is-enabled", SERVICE_NAME])
512 .map(|(ok, _)| ok)
513 .unwrap_or(false);
514 ServiceState {
515 kind: "systemd",
516 label,
517 installed: active || known,
518 pid: None,
519 detail: if text.is_empty() {
520 "unknown".into()
521 } else {
522 text
523 },
524 }
525 }
526 Err(error) => ServiceState {
527 kind: "systemd",
528 label,
529 installed: false,
530 pid: None,
531 detail: format!("systemctl unavailable: {error}"),
532 },
533 }
534}
535
536#[cfg(not(unix))]
537fn platform_status(_root: &Path) -> ServiceState {
538 ServiceState {
539 kind: "none",
540 label: SERVICE_NAME.to_string(),
541 installed: false,
542 pid: None,
543 detail: "no service manager on this platform".into(),
544 }
545}
546
547#[cfg(target_os = "macos")]
549fn field_of(text: &str, key: &str) -> Option<String> {
550 text.lines()
551 .find_map(|line| line.trim().strip_prefix(key))
552 .map(|value| value.trim().to_string())
553}
554
555pub fn install_service(
561 root: &Path,
562 entry: &Path,
563 node: &str,
564) -> Result<(ServiceUnit, ServiceState), OrchestratorError> {
565 let existing = service_status(root);
566 if existing.installed {
567 return Err(OrchestratorError::Service {
568 action: "install",
569 detail: format!(
570 "`{}` is already installed ({}); `supercode orchestrator setup --uninstall` first",
571 existing.label, existing.detail
572 ),
573 });
574 }
575 let unit = service_unit(root, entry, &absolute_program(node));
576 write_unit(&unit)?;
577 platform_install(&unit)?;
578 Ok((unit, service_status(root)))
579}
580
581#[cfg(target_os = "macos")]
582fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
583 let path = unit.path.display().to_string();
584 let (ok, text) =
585 run_tool("launchctl", &["bootstrap", &gui_domain(), &path]).map_err(|error| {
586 OrchestratorError::Service {
587 action: "install",
588 detail: format!("launchctl: {error}"),
589 }
590 })?;
591 if !ok {
592 return Err(OrchestratorError::Service {
593 action: "install",
594 detail: format!("launchctl bootstrap {}: {text}", gui_domain()),
595 });
596 }
597 Ok(())
598}
599
600#[cfg(all(unix, not(target_os = "macos")))]
603fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
604 let path = unit.path.display().to_string();
605 for args in [
606 vec!["--user", "link", path.as_str()],
607 vec!["--user", "enable", "--now", SERVICE_NAME],
608 ] {
609 let (ok, text) =
610 run_tool("systemctl", &args).map_err(|error| OrchestratorError::Service {
611 action: "install",
612 detail: format!("systemctl: {error}"),
613 })?;
614 if !ok {
615 return Err(OrchestratorError::Service {
616 action: "install",
617 detail: format!("systemctl {}: {text}", args.join(" ")),
618 });
619 }
620 }
621 Ok(())
622}
623
624#[cfg(not(unix))]
625fn platform_install(_unit: &ServiceUnit) -> Result<(), OrchestratorError> {
626 Err(OrchestratorError::Service {
627 action: "install",
628 detail: "no service manager on this platform".into(),
629 })
630}
631
632pub fn uninstall_service(root: &Path) -> Result<ServiceState, OrchestratorError> {
637 platform_uninstall()?;
638 let unit_path = root.join(SERVICE_DIR).join(unit_file_name());
639 match std::fs::remove_file(&unit_path) {
640 Ok(()) => {}
641 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
642 Err(source) => {
643 return Err(OrchestratorError::Lease {
644 path: unit_path,
645 source,
646 })
647 }
648 }
649 let mut state = service_status(root);
652 for _ in 0..40 {
653 if !state.installed {
654 break;
655 }
656 std::thread::sleep(std::time::Duration::from_millis(100));
657 state = service_status(root);
658 }
659 Ok(state)
660}
661
662#[cfg(target_os = "macos")]
663fn platform_uninstall() -> Result<(), OrchestratorError> {
664 let target = format!("{}/{SERVICE_NAME}", gui_domain());
665 let (ok, text) = run_tool("launchctl", &["bootout", &target]).map_err(|error| {
666 OrchestratorError::Service {
667 action: "uninstall",
668 detail: format!("launchctl: {error}"),
669 }
670 })?;
671 if !ok && !text.contains("No such process") && !text.contains("not find") {
673 return Err(OrchestratorError::Service {
674 action: "uninstall",
675 detail: format!("launchctl bootout {target}: {text}"),
676 });
677 }
678 Ok(())
679}
680
681#[cfg(all(unix, not(target_os = "macos")))]
682fn platform_uninstall() -> Result<(), OrchestratorError> {
683 let _ = run_tool("systemctl", &["--user", "disable", "--now", SERVICE_NAME]);
684 Ok(())
685}
686
687#[cfg(not(unix))]
688fn platform_uninstall() -> Result<(), OrchestratorError> {
689 Ok(())
690}
691
692fn unit_file_name() -> String {
694 if cfg!(target_os = "macos") {
695 format!("{SERVICE_NAME}.plist")
696 } else {
697 format!("{SERVICE_NAME}.service")
698 }
699}
700
701pub fn write_unit(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
703 if let Some(parent) = unit.path.parent() {
704 std::fs::create_dir_all(parent).map_err(|source| OrchestratorError::Lease {
705 path: unit.path.clone(),
706 source,
707 })?;
708 }
709 std::fs::write(&unit.path, &unit.text).map_err(|source| OrchestratorError::Lease {
710 path: unit.path.clone(),
711 source,
712 })
713}
714
715#[cfg(test)]
716mod tests {
717 use super::*;
718
719 fn scratch(label: &str) -> PathBuf {
721 let root = std::env::temp_dir().join(format!(
722 "supercode-orchestrator-{label}-{}-{}",
723 std::process::id(),
724 std::time::SystemTime::now()
725 .duration_since(std::time::UNIX_EPOCH)
726 .unwrap()
727 .as_nanos()
728 ));
729 std::fs::create_dir_all(&root).unwrap();
730 root
731 }
732
733 #[test]
734 fn a_lease_round_trips_and_a_missing_one_is_not_running() {
735 let root = &scratch("lease");
736 let root = root.as_path();
737 assert!(read_lease(root).is_none());
738 assert!(live_lease(root).is_none());
739 let lease = Lease {
740 pid: std::process::id(),
741 started_at: "2026-09-04T00:00:00Z".into(),
742 root: root.to_path_buf(),
743 host: None,
744 boot: None,
745 };
746 write_lease(root, &lease).unwrap();
747 assert_eq!(read_lease(root).as_ref(), Some(&lease));
748 assert!(live_lease(root).is_some());
750 clear_lease(root).unwrap();
751 assert!(read_lease(root).is_none());
752 assert!(matches!(
754 stop(root),
755 Err(OrchestratorError::NotRunning { .. })
756 ));
757 std::fs::remove_dir_all(root).ok();
758 }
759
760 #[test]
763 fn a_stale_lease_is_not_live() {
764 let root = &scratch("stale");
765 let root = root.as_path();
766 write_lease(
767 root,
768 &Lease {
769 pid: 0x7FFF_FFFF,
771 started_at: "2026-09-04T00:00:00Z".into(),
772 root: root.to_path_buf(),
773 host: None,
774 boot: None,
775 },
776 )
777 .unwrap();
778 assert!(read_lease(root).is_some(), "the file is still there");
779 assert!(live_lease(root).is_none(), "but nothing is serving it");
780 std::fs::remove_dir_all(root).ok();
781 }
782
783 #[test]
784 fn the_service_unit_names_the_home_the_entry_and_its_install_command() {
785 let root = &scratch("unit");
786 let root = root.as_path();
787 let entry = PathBuf::from("/opt/supercode/sdk/orchestrator/bin/orchestrator.mjs");
788 let unit = service_unit(root, &entry, "/usr/bin/node");
789 assert!(unit.text.contains(&root.display().to_string()));
790 assert!(unit.text.contains("orchestrator.mjs"));
791 assert!(unit.text.contains(SERVICE_NAME));
792 assert!(unit
793 .install_command
794 .contains(&unit.path.display().to_string()));
795 assert!(unit.path.starts_with(root.join(SERVICE_DIR)));
796 assert_eq!(
797 unit.kind,
798 if cfg!(target_os = "macos") {
799 "launchd"
800 } else {
801 "systemd"
802 }
803 );
804 std::fs::remove_dir_all(root).ok();
805 }
806
807 #[test]
810 fn service_status_reports_the_label_and_installs_nothing() {
811 let root = &scratch("service-status");
812 let root = root.as_path();
813 let state = service_status(root);
814 assert_eq!(state.label, SERVICE_NAME);
815 assert!(
816 matches!(state.kind, "launchd" | "systemd" | "none"),
817 "{state:?}"
818 );
819 assert!(
820 !root.join(SERVICE_DIR).exists(),
821 "asking never writes a unit"
822 );
823 std::fs::remove_dir_all(root).ok();
824 }
825
826 #[test]
829 fn a_program_is_resolved_absolutely_for_the_service_manager() {
830 assert_eq!(absolute_program("/usr/bin/env"), "/usr/bin/env");
831 let resolved = absolute_program("sh");
832 assert!(resolved.starts_with('/'), "{resolved}");
833 assert_eq!(
835 absolute_program("definitely-not-a-program"),
836 "definitely-not-a-program"
837 );
838 }
839
840 #[test]
843 fn the_daemon_entry_resolves_in_this_checkout() {
844 let entry = daemon_entry().expect("sdk/orchestrator/bin/orchestrator.mjs");
845 assert!(entry.ends_with(DAEMON_ENTRY));
846 }
847
848 #[test]
849 fn windows_programs_resolve_by_pathext_not_to_npms_shell_scripts() {
850 let dir = std::env::temp_dir().join(format!("supercode-resolve-{}", std::process::id()));
851 std::fs::create_dir_all(&dir).unwrap();
852 for file in ["npm", "npm.cmd", "tool.exe", "tool.cmd"] {
853 std::fs::write(dir.join(file), "").unwrap();
854 }
855 let path = Some(dir.as_os_str());
856 let pathext = Some(".COM;.EXE;.BAT;.CMD");
857 assert_eq!(
858 resolve_program_in("npm", path, pathext, true),
859 dir.join("npm.cmd")
860 );
861 assert_eq!(
862 resolve_program_in("tool", path, pathext, true),
863 dir.join("tool.exe")
864 );
865 assert_eq!(
866 resolve_program_in("npm.cmd", path, pathext, true),
867 dir.join("npm.cmd")
868 );
869 assert_eq!(
870 resolve_program_in("npm", path, pathext, false),
871 dir.join("npm")
872 );
873 assert_eq!(
874 resolve_program_in("absent", path, pathext, true),
875 PathBuf::from("absent")
876 );
877 std::fs::remove_dir_all(&dir).unwrap();
878 }
879}