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}
57
58#[derive(Debug, thiserror::Error)]
60pub enum OrchestratorError {
61 #[error("the orchestrator is not running for `{0}` (no live lease at `{1}`)", root.display(), lock.display())]
63 NotRunning {
64 root: PathBuf,
66 lock: PathBuf,
68 },
69 #[error("the orchestrator is already running for `{}` (pid {pid})", root.display())]
71 AlreadyRunning {
72 root: PathBuf,
74 pid: u32,
76 },
77 #[error("no orchestrator daemon entry found (looked for `{DAEMON_ENTRY}` under: {searched})")]
79 NoDaemonEntry {
80 searched: String,
82 },
83 #[error("orchestrator lease `{}`: {source}", path.display())]
85 Lease {
86 path: PathBuf,
88 source: std::io::Error,
90 },
91 #[error("orchestrator service: {action} failed: {detail}")]
93 Service {
94 action: &'static str,
96 detail: String,
98 },
99}
100
101pub fn lock_path(root: &Path) -> PathBuf {
103 root.join(LOCK_FILE)
104}
105
106pub fn read_lease(root: &Path) -> Option<Lease> {
108 let text = std::fs::read_to_string(lock_path(root)).ok()?;
109 serde_json::from_str(&text).ok()
110}
111
112pub fn write_lease(root: &Path, lease: &Lease) -> Result<(), OrchestratorError> {
114 let path = lock_path(root);
115 if let Some(parent) = path.parent() {
116 std::fs::create_dir_all(parent).map_err(|source| OrchestratorError::Lease {
117 path: path.clone(),
118 source,
119 })?;
120 }
121 let text = serde_json::to_string_pretty(lease).unwrap_or_default();
122 std::fs::write(&path, format!("{text}\n")).map_err(|source| OrchestratorError::Lease {
123 path: path.clone(),
124 source,
125 })
126}
127
128pub fn clear_lease(root: &Path) -> Result<(), OrchestratorError> {
131 let path = lock_path(root);
132 match std::fs::remove_file(&path) {
133 Ok(()) => Ok(()),
134 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
135 Err(source) => Err(OrchestratorError::Lease { path, source }),
136 }
137}
138
139pub fn pid_is_live(pid: u32) -> bool {
145 #[cfg(unix)]
146 {
147 if pid == 0 {
148 return false;
149 }
150 unsafe { libc::kill(pid as libc::pid_t, 0) == 0 }
152 }
153 #[cfg(not(unix))]
154 {
155 let _ = pid;
156 true
157 }
158}
159
160pub fn live_lease(root: &Path) -> Option<Lease> {
162 read_lease(root).filter(|lease| pid_is_live(lease.pid))
163}
164
165pub fn stop(root: &Path) -> Result<Lease, OrchestratorError> {
171 let Some(lease) = live_lease(root) else {
172 return Err(OrchestratorError::NotRunning {
173 root: root.to_path_buf(),
174 lock: lock_path(root),
175 });
176 };
177 #[cfg(unix)]
178 unsafe {
180 libc::kill(lease.pid as libc::pid_t, libc::SIGTERM);
181 }
182 clear_lease(root)?;
183 Ok(lease)
184}
185
186pub fn daemon_entry() -> Result<PathBuf, OrchestratorError> {
194 let mut searched = Vec::new();
195 if let Some(explicit) = std::env::var_os("SUPERCODE_ORCHESTRATOR_ENTRY") {
196 let path = PathBuf::from(explicit);
197 if path.is_file() {
198 return Ok(path);
199 }
200 searched.push(path.display().to_string());
201 }
202 for dir in std::env::var_os("PATH")
205 .iter()
206 .flat_map(std::env::split_paths)
207 {
208 let command = dir.join("supercode-orchestrator");
209 if let Ok(entry) = std::fs::canonicalize(&command) {
210 if entry.is_file() && entry.ends_with(DAEMON_ENTRY) {
211 return Ok(entry);
212 }
213 }
214 }
215 searched.push("supercode-orchestrator on PATH".to_string());
216 let mut roots: Vec<PathBuf> = Vec::new();
217 if let Ok(exe) = std::env::current_exe() {
218 roots.extend(exe.ancestors().skip(1).take(4).map(Path::to_path_buf));
220 }
221 if let Ok(cwd) = std::env::current_dir() {
222 roots.push(cwd);
223 }
224 if let Some(workspace) = Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(2) {
229 roots.push(workspace.to_path_buf());
230 }
231 for root in roots {
232 let candidate = root.join("sdk/orchestrator").join(DAEMON_ENTRY);
233 if candidate.is_file() {
234 return Ok(candidate);
235 }
236 searched.push(candidate.display().to_string());
237 }
238 Err(OrchestratorError::NoDaemonEntry {
239 searched: searched.join(", "),
240 })
241}
242
243#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
245pub struct ServiceUnit {
246 pub kind: &'static str,
248 pub path: PathBuf,
250 pub text: String,
252 pub install_command: String,
254}
255
256pub fn service_unit(root: &Path, entry: &Path, node: &str) -> ServiceUnit {
262 let root_display = root.display().to_string();
263 let entry_display = entry.display().to_string();
264 if cfg!(target_os = "macos") {
265 let path = root.join(SERVICE_DIR).join(format!("{SERVICE_NAME}.plist"));
266 let text = format!(
267 r#"<?xml version="1.0" encoding="UTF-8"?>
268<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
269<plist version="1.0">
270<dict>
271 <key>Label</key><string>{SERVICE_NAME}</string>
272 <key>ProgramArguments</key>
273 <array>
274 <string>{node}</string>
275 <string>{entry_display}</string>
276 <string>--root</string>
277 <string>{root_display}</string>
278 </array>
279 <key>RunAtLoad</key><true/>
280 <key>KeepAlive</key><true/>
281 <key>StandardOutPath</key><string>{root_display}/service/orchestrator.out.log</string>
282 <key>StandardErrorPath</key><string>{root_display}/service/orchestrator.err.log</string>
283</dict>
284</plist>
285"#
286 );
287 let install = format!("launchctl bootstrap gui/$(id -u) {}", path.display());
288 ServiceUnit {
289 kind: "launchd",
290 path,
291 text,
292 install_command: install,
293 }
294 } else {
295 let path = root
296 .join(SERVICE_DIR)
297 .join(format!("{SERVICE_NAME}.service"));
298 let text = format!(
299 "[Unit]\n\
300 Description=supercode orchestrator ({root_display})\n\
301 After=network.target\n\
302 \n\
303 [Service]\n\
304 ExecStart={node} {entry_display} --root {root_display}\n\
305 Restart=on-failure\n\
306 KillSignal=SIGTERM\n\
307 \n\
308 [Install]\n\
309 WantedBy=default.target\n"
310 );
311 let install = format!(
312 "systemctl --user link {} && systemctl --user enable --now {SERVICE_NAME}",
313 path.display()
314 );
315 ServiceUnit {
316 kind: "systemd",
317 path,
318 text,
319 install_command: install,
320 }
321 }
322}
323
324#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
326pub struct ServiceState {
327 pub kind: &'static str,
329 pub label: String,
331 pub installed: bool,
333 pub pid: Option<u32>,
335 pub detail: String,
337}
338
339pub fn absolute_program(program: &str) -> String {
342 if program.contains('/') {
343 return program.to_string();
344 }
345 if let Some(path) = std::env::var_os("PATH") {
346 for dir in std::env::split_paths(&path) {
347 let candidate = dir.join(program);
348 if candidate.is_file() {
349 return candidate.display().to_string();
350 }
351 }
352 }
353 program.to_string()
354}
355
356fn run_tool(program: &str, args: &[&str]) -> Result<(bool, String), std::io::Error> {
358 let output = std::process::Command::new(program).args(args).output()?;
359 let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
360 text.push_str(&String::from_utf8_lossy(&output.stderr));
361 Ok((output.status.success(), text.trim().to_string()))
362}
363
364#[cfg(target_os = "macos")]
365fn gui_domain() -> String {
366 format!("gui/{}", unsafe { libc::getuid() })
368}
369
370pub fn service_status(root: &Path) -> ServiceState {
374 platform_status(root)
375}
376
377#[cfg(target_os = "macos")]
378fn platform_status(_root: &Path) -> ServiceState {
379 let label = SERVICE_NAME.to_string();
380 let target = format!("{}/{SERVICE_NAME}", gui_domain());
381 match run_tool("launchctl", &["print", &target]) {
382 Ok((true, text)) => ServiceState {
383 kind: "launchd",
384 label,
385 installed: true,
386 pid: field_of(&text, "pid = ").and_then(|value| value.parse().ok()),
387 detail: field_of(&text, "state = ").unwrap_or_else(|| "loaded".into()),
388 },
389 Ok((false, _)) => ServiceState {
390 kind: "launchd",
391 label,
392 installed: false,
393 pid: None,
394 detail: format!("not bootstrapped in {}", gui_domain()),
395 },
396 Err(error) => ServiceState {
397 kind: "launchd",
398 label,
399 installed: false,
400 pid: None,
401 detail: format!("launchctl unavailable: {error}"),
402 },
403 }
404}
405
406#[cfg(all(unix, not(target_os = "macos")))]
407fn platform_status(_root: &Path) -> ServiceState {
408 let label = SERVICE_NAME.to_string();
409 match run_tool("systemctl", &["--user", "is-active", SERVICE_NAME]) {
410 Ok((active, text)) => {
411 let known = run_tool("systemctl", &["--user", "is-enabled", SERVICE_NAME])
412 .map(|(ok, _)| ok)
413 .unwrap_or(false);
414 ServiceState {
415 kind: "systemd",
416 label,
417 installed: active || known,
418 pid: None,
419 detail: if text.is_empty() {
420 "unknown".into()
421 } else {
422 text
423 },
424 }
425 }
426 Err(error) => ServiceState {
427 kind: "systemd",
428 label,
429 installed: false,
430 pid: None,
431 detail: format!("systemctl unavailable: {error}"),
432 },
433 }
434}
435
436#[cfg(not(unix))]
437fn platform_status(_root: &Path) -> ServiceState {
438 ServiceState {
439 kind: "none",
440 label: SERVICE_NAME.to_string(),
441 installed: false,
442 pid: None,
443 detail: "no service manager on this platform".into(),
444 }
445}
446
447#[cfg(target_os = "macos")]
449fn field_of(text: &str, key: &str) -> Option<String> {
450 text.lines()
451 .find_map(|line| line.trim().strip_prefix(key))
452 .map(|value| value.trim().to_string())
453}
454
455pub fn install_service(
461 root: &Path,
462 entry: &Path,
463 node: &str,
464) -> Result<(ServiceUnit, ServiceState), OrchestratorError> {
465 let existing = service_status(root);
466 if existing.installed {
467 return Err(OrchestratorError::Service {
468 action: "install",
469 detail: format!(
470 "`{}` is already installed ({}); `supercode orchestrator setup --uninstall` first",
471 existing.label, existing.detail
472 ),
473 });
474 }
475 let unit = service_unit(root, entry, &absolute_program(node));
476 write_unit(&unit)?;
477 platform_install(&unit)?;
478 Ok((unit, service_status(root)))
479}
480
481#[cfg(target_os = "macos")]
482fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
483 let path = unit.path.display().to_string();
484 let (ok, text) =
485 run_tool("launchctl", &["bootstrap", &gui_domain(), &path]).map_err(|error| {
486 OrchestratorError::Service {
487 action: "install",
488 detail: format!("launchctl: {error}"),
489 }
490 })?;
491 if !ok {
492 return Err(OrchestratorError::Service {
493 action: "install",
494 detail: format!("launchctl bootstrap {}: {text}", gui_domain()),
495 });
496 }
497 Ok(())
498}
499
500#[cfg(all(unix, not(target_os = "macos")))]
503fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
504 let path = unit.path.display().to_string();
505 for args in [
506 vec!["--user", "link", path.as_str()],
507 vec!["--user", "enable", "--now", SERVICE_NAME],
508 ] {
509 let (ok, text) =
510 run_tool("systemctl", &args).map_err(|error| OrchestratorError::Service {
511 action: "install",
512 detail: format!("systemctl: {error}"),
513 })?;
514 if !ok {
515 return Err(OrchestratorError::Service {
516 action: "install",
517 detail: format!("systemctl {}: {text}", args.join(" ")),
518 });
519 }
520 }
521 Ok(())
522}
523
524#[cfg(not(unix))]
525fn platform_install(_unit: &ServiceUnit) -> Result<(), OrchestratorError> {
526 Err(OrchestratorError::Service {
527 action: "install",
528 detail: "no service manager on this platform".into(),
529 })
530}
531
532pub fn uninstall_service(root: &Path) -> Result<ServiceState, OrchestratorError> {
537 platform_uninstall()?;
538 let unit_path = root.join(SERVICE_DIR).join(unit_file_name());
539 match std::fs::remove_file(&unit_path) {
540 Ok(()) => {}
541 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
542 Err(source) => {
543 return Err(OrchestratorError::Lease {
544 path: unit_path,
545 source,
546 })
547 }
548 }
549 let mut state = service_status(root);
552 for _ in 0..40 {
553 if !state.installed {
554 break;
555 }
556 std::thread::sleep(std::time::Duration::from_millis(100));
557 state = service_status(root);
558 }
559 Ok(state)
560}
561
562#[cfg(target_os = "macos")]
563fn platform_uninstall() -> Result<(), OrchestratorError> {
564 let target = format!("{}/{SERVICE_NAME}", gui_domain());
565 let (ok, text) = run_tool("launchctl", &["bootout", &target]).map_err(|error| {
566 OrchestratorError::Service {
567 action: "uninstall",
568 detail: format!("launchctl: {error}"),
569 }
570 })?;
571 if !ok && !text.contains("No such process") && !text.contains("not find") {
573 return Err(OrchestratorError::Service {
574 action: "uninstall",
575 detail: format!("launchctl bootout {target}: {text}"),
576 });
577 }
578 Ok(())
579}
580
581#[cfg(all(unix, not(target_os = "macos")))]
582fn platform_uninstall() -> Result<(), OrchestratorError> {
583 let _ = run_tool("systemctl", &["--user", "disable", "--now", SERVICE_NAME]);
584 Ok(())
585}
586
587#[cfg(not(unix))]
588fn platform_uninstall() -> Result<(), OrchestratorError> {
589 Ok(())
590}
591
592fn unit_file_name() -> String {
594 if cfg!(target_os = "macos") {
595 format!("{SERVICE_NAME}.plist")
596 } else {
597 format!("{SERVICE_NAME}.service")
598 }
599}
600
601pub fn write_unit(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
603 if let Some(parent) = unit.path.parent() {
604 std::fs::create_dir_all(parent).map_err(|source| OrchestratorError::Lease {
605 path: unit.path.clone(),
606 source,
607 })?;
608 }
609 std::fs::write(&unit.path, &unit.text).map_err(|source| OrchestratorError::Lease {
610 path: unit.path.clone(),
611 source,
612 })
613}
614
615#[cfg(test)]
616mod tests {
617 use super::*;
618
619 fn scratch(label: &str) -> PathBuf {
621 let root = std::env::temp_dir().join(format!(
622 "supercode-orchestrator-{label}-{}-{}",
623 std::process::id(),
624 std::time::SystemTime::now()
625 .duration_since(std::time::UNIX_EPOCH)
626 .unwrap()
627 .as_nanos()
628 ));
629 std::fs::create_dir_all(&root).unwrap();
630 root
631 }
632
633 #[test]
634 fn a_lease_round_trips_and_a_missing_one_is_not_running() {
635 let root = &scratch("lease");
636 let root = root.as_path();
637 assert!(read_lease(root).is_none());
638 assert!(live_lease(root).is_none());
639 let lease = Lease {
640 pid: std::process::id(),
641 started_at: "2026-09-04T00:00:00Z".into(),
642 root: root.to_path_buf(),
643 };
644 write_lease(root, &lease).unwrap();
645 assert_eq!(read_lease(root).as_ref(), Some(&lease));
646 assert!(live_lease(root).is_some());
648 clear_lease(root).unwrap();
649 assert!(read_lease(root).is_none());
650 assert!(matches!(
652 stop(root),
653 Err(OrchestratorError::NotRunning { .. })
654 ));
655 std::fs::remove_dir_all(root).ok();
656 }
657
658 #[test]
661 fn a_stale_lease_is_not_live() {
662 let root = &scratch("stale");
663 let root = root.as_path();
664 write_lease(
665 root,
666 &Lease {
667 pid: 0x7FFF_FFFF,
669 started_at: "2026-09-04T00:00:00Z".into(),
670 root: root.to_path_buf(),
671 },
672 )
673 .unwrap();
674 assert!(read_lease(root).is_some(), "the file is still there");
675 assert!(live_lease(root).is_none(), "but nothing is serving it");
676 std::fs::remove_dir_all(root).ok();
677 }
678
679 #[test]
680 fn the_service_unit_names_the_home_the_entry_and_its_install_command() {
681 let root = &scratch("unit");
682 let root = root.as_path();
683 let entry = PathBuf::from("/opt/supercode/sdk/orchestrator/bin/orchestrator.mjs");
684 let unit = service_unit(root, &entry, "/usr/bin/node");
685 assert!(unit.text.contains(&root.display().to_string()));
686 assert!(unit.text.contains("orchestrator.mjs"));
687 assert!(unit.text.contains(SERVICE_NAME));
688 assert!(unit
689 .install_command
690 .contains(&unit.path.display().to_string()));
691 assert!(unit.path.starts_with(root.join(SERVICE_DIR)));
692 assert_eq!(
693 unit.kind,
694 if cfg!(target_os = "macos") {
695 "launchd"
696 } else {
697 "systemd"
698 }
699 );
700 std::fs::remove_dir_all(root).ok();
701 }
702
703 #[test]
706 fn service_status_reports_the_label_and_installs_nothing() {
707 let root = &scratch("service-status");
708 let root = root.as_path();
709 let state = service_status(root);
710 assert_eq!(state.label, SERVICE_NAME);
711 assert!(
712 matches!(state.kind, "launchd" | "systemd" | "none"),
713 "{state:?}"
714 );
715 assert!(
716 !root.join(SERVICE_DIR).exists(),
717 "asking never writes a unit"
718 );
719 std::fs::remove_dir_all(root).ok();
720 }
721
722 #[test]
725 fn a_program_is_resolved_absolutely_for_the_service_manager() {
726 assert_eq!(absolute_program("/usr/bin/env"), "/usr/bin/env");
727 let resolved = absolute_program("sh");
728 assert!(resolved.starts_with('/'), "{resolved}");
729 assert_eq!(
731 absolute_program("definitely-not-a-program"),
732 "definitely-not-a-program"
733 );
734 }
735
736 #[test]
739 fn the_daemon_entry_resolves_in_this_checkout() {
740 let entry = daemon_entry().expect("sdk/orchestrator/bin/orchestrator.mjs");
741 assert!(entry.ends_with(DAEMON_ENTRY));
742 }
743}