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> {
192 let mut searched = Vec::new();
193 if let Some(explicit) = std::env::var_os("SUPERCODE_ORCHESTRATOR_ENTRY") {
194 let path = PathBuf::from(explicit);
195 if path.is_file() {
196 return Ok(path);
197 }
198 searched.push(path.display().to_string());
199 }
200 let mut roots: Vec<PathBuf> = Vec::new();
201 if let Ok(exe) = std::env::current_exe() {
202 roots.extend(exe.ancestors().skip(1).take(4).map(Path::to_path_buf));
204 }
205 if let Ok(cwd) = std::env::current_dir() {
206 roots.push(cwd);
207 }
208 if let Some(workspace) = Path::new(env!("CARGO_MANIFEST_DIR")).ancestors().nth(2) {
213 roots.push(workspace.to_path_buf());
214 }
215 for root in roots {
216 let candidate = root.join("sdk/orchestrator").join(DAEMON_ENTRY);
217 if candidate.is_file() {
218 return Ok(candidate);
219 }
220 searched.push(candidate.display().to_string());
221 }
222 Err(OrchestratorError::NoDaemonEntry {
223 searched: searched.join(", "),
224 })
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
229pub struct ServiceUnit {
230 pub kind: &'static str,
232 pub path: PathBuf,
234 pub text: String,
236 pub install_command: String,
238}
239
240pub fn service_unit(root: &Path, entry: &Path, node: &str) -> ServiceUnit {
246 let root_display = root.display().to_string();
247 let entry_display = entry.display().to_string();
248 if cfg!(target_os = "macos") {
249 let path = root.join(SERVICE_DIR).join(format!("{SERVICE_NAME}.plist"));
250 let text = format!(
251 r#"<?xml version="1.0" encoding="UTF-8"?>
252<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
253<plist version="1.0">
254<dict>
255 <key>Label</key><string>{SERVICE_NAME}</string>
256 <key>ProgramArguments</key>
257 <array>
258 <string>{node}</string>
259 <string>{entry_display}</string>
260 <string>--root</string>
261 <string>{root_display}</string>
262 </array>
263 <key>RunAtLoad</key><true/>
264 <key>KeepAlive</key><true/>
265 <key>StandardOutPath</key><string>{root_display}/service/orchestrator.out.log</string>
266 <key>StandardErrorPath</key><string>{root_display}/service/orchestrator.err.log</string>
267</dict>
268</plist>
269"#
270 );
271 let install = format!("launchctl bootstrap gui/$(id -u) {}", path.display());
272 ServiceUnit {
273 kind: "launchd",
274 path,
275 text,
276 install_command: install,
277 }
278 } else {
279 let path = root
280 .join(SERVICE_DIR)
281 .join(format!("{SERVICE_NAME}.service"));
282 let text = format!(
283 "[Unit]\n\
284 Description=supercode orchestrator ({root_display})\n\
285 After=network.target\n\
286 \n\
287 [Service]\n\
288 ExecStart={node} {entry_display} --root {root_display}\n\
289 Restart=on-failure\n\
290 KillSignal=SIGTERM\n\
291 \n\
292 [Install]\n\
293 WantedBy=default.target\n"
294 );
295 let install = format!(
296 "systemctl --user link {} && systemctl --user enable --now {SERVICE_NAME}",
297 path.display()
298 );
299 ServiceUnit {
300 kind: "systemd",
301 path,
302 text,
303 install_command: install,
304 }
305 }
306}
307
308#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
310pub struct ServiceState {
311 pub kind: &'static str,
313 pub label: String,
315 pub installed: bool,
317 pub pid: Option<u32>,
319 pub detail: String,
321}
322
323pub fn absolute_program(program: &str) -> String {
326 if program.contains('/') {
327 return program.to_string();
328 }
329 if let Some(path) = std::env::var_os("PATH") {
330 for dir in std::env::split_paths(&path) {
331 let candidate = dir.join(program);
332 if candidate.is_file() {
333 return candidate.display().to_string();
334 }
335 }
336 }
337 program.to_string()
338}
339
340fn run_tool(program: &str, args: &[&str]) -> Result<(bool, String), std::io::Error> {
342 let output = std::process::Command::new(program).args(args).output()?;
343 let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
344 text.push_str(&String::from_utf8_lossy(&output.stderr));
345 Ok((output.status.success(), text.trim().to_string()))
346}
347
348#[cfg(target_os = "macos")]
349fn gui_domain() -> String {
350 format!("gui/{}", unsafe { libc::getuid() })
352}
353
354pub fn service_status(root: &Path) -> ServiceState {
358 platform_status(root)
359}
360
361#[cfg(target_os = "macos")]
362fn platform_status(_root: &Path) -> ServiceState {
363 let label = SERVICE_NAME.to_string();
364 let target = format!("{}/{SERVICE_NAME}", gui_domain());
365 match run_tool("launchctl", &["print", &target]) {
366 Ok((true, text)) => ServiceState {
367 kind: "launchd",
368 label,
369 installed: true,
370 pid: field_of(&text, "pid = ").and_then(|value| value.parse().ok()),
371 detail: field_of(&text, "state = ").unwrap_or_else(|| "loaded".into()),
372 },
373 Ok((false, _)) => ServiceState {
374 kind: "launchd",
375 label,
376 installed: false,
377 pid: None,
378 detail: format!("not bootstrapped in {}", gui_domain()),
379 },
380 Err(error) => ServiceState {
381 kind: "launchd",
382 label,
383 installed: false,
384 pid: None,
385 detail: format!("launchctl unavailable: {error}"),
386 },
387 }
388}
389
390#[cfg(all(unix, not(target_os = "macos")))]
391fn platform_status(_root: &Path) -> ServiceState {
392 let label = SERVICE_NAME.to_string();
393 match run_tool("systemctl", &["--user", "is-active", SERVICE_NAME]) {
394 Ok((active, text)) => {
395 let known = run_tool("systemctl", &["--user", "is-enabled", SERVICE_NAME])
396 .map(|(ok, _)| ok)
397 .unwrap_or(false);
398 ServiceState {
399 kind: "systemd",
400 label,
401 installed: active || known,
402 pid: None,
403 detail: if text.is_empty() {
404 "unknown".into()
405 } else {
406 text
407 },
408 }
409 }
410 Err(error) => ServiceState {
411 kind: "systemd",
412 label,
413 installed: false,
414 pid: None,
415 detail: format!("systemctl unavailable: {error}"),
416 },
417 }
418}
419
420#[cfg(not(unix))]
421fn platform_status(_root: &Path) -> ServiceState {
422 ServiceState {
423 kind: "none",
424 label: SERVICE_NAME.to_string(),
425 installed: false,
426 pid: None,
427 detail: "no service manager on this platform".into(),
428 }
429}
430
431#[cfg(target_os = "macos")]
433fn field_of(text: &str, key: &str) -> Option<String> {
434 text.lines()
435 .find_map(|line| line.trim().strip_prefix(key))
436 .map(|value| value.trim().to_string())
437}
438
439pub fn install_service(
445 root: &Path,
446 entry: &Path,
447 node: &str,
448) -> Result<(ServiceUnit, ServiceState), OrchestratorError> {
449 let existing = service_status(root);
450 if existing.installed {
451 return Err(OrchestratorError::Service {
452 action: "install",
453 detail: format!(
454 "`{}` is already installed ({}); `supercode orchestrator setup --uninstall` first",
455 existing.label, existing.detail
456 ),
457 });
458 }
459 let unit = service_unit(root, entry, &absolute_program(node));
460 write_unit(&unit)?;
461 platform_install(&unit)?;
462 Ok((unit, service_status(root)))
463}
464
465#[cfg(target_os = "macos")]
466fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
467 let path = unit.path.display().to_string();
468 let (ok, text) =
469 run_tool("launchctl", &["bootstrap", &gui_domain(), &path]).map_err(|error| {
470 OrchestratorError::Service {
471 action: "install",
472 detail: format!("launchctl: {error}"),
473 }
474 })?;
475 if !ok {
476 return Err(OrchestratorError::Service {
477 action: "install",
478 detail: format!("launchctl bootstrap {}: {text}", gui_domain()),
479 });
480 }
481 Ok(())
482}
483
484#[cfg(all(unix, not(target_os = "macos")))]
487fn platform_install(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
488 let path = unit.path.display().to_string();
489 for args in [
490 vec!["--user", "link", path.as_str()],
491 vec!["--user", "enable", "--now", SERVICE_NAME],
492 ] {
493 let (ok, text) =
494 run_tool("systemctl", &args).map_err(|error| OrchestratorError::Service {
495 action: "install",
496 detail: format!("systemctl: {error}"),
497 })?;
498 if !ok {
499 return Err(OrchestratorError::Service {
500 action: "install",
501 detail: format!("systemctl {}: {text}", args.join(" ")),
502 });
503 }
504 }
505 Ok(())
506}
507
508#[cfg(not(unix))]
509fn platform_install(_unit: &ServiceUnit) -> Result<(), OrchestratorError> {
510 Err(OrchestratorError::Service {
511 action: "install",
512 detail: "no service manager on this platform".into(),
513 })
514}
515
516pub fn uninstall_service(root: &Path) -> Result<ServiceState, OrchestratorError> {
521 platform_uninstall()?;
522 let unit_path = root.join(SERVICE_DIR).join(unit_file_name());
523 match std::fs::remove_file(&unit_path) {
524 Ok(()) => {}
525 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
526 Err(source) => {
527 return Err(OrchestratorError::Lease {
528 path: unit_path,
529 source,
530 })
531 }
532 }
533 let mut state = service_status(root);
536 for _ in 0..40 {
537 if !state.installed {
538 break;
539 }
540 std::thread::sleep(std::time::Duration::from_millis(100));
541 state = service_status(root);
542 }
543 Ok(state)
544}
545
546#[cfg(target_os = "macos")]
547fn platform_uninstall() -> Result<(), OrchestratorError> {
548 let target = format!("{}/{SERVICE_NAME}", gui_domain());
549 let (ok, text) = run_tool("launchctl", &["bootout", &target]).map_err(|error| {
550 OrchestratorError::Service {
551 action: "uninstall",
552 detail: format!("launchctl: {error}"),
553 }
554 })?;
555 if !ok && !text.contains("No such process") && !text.contains("not find") {
557 return Err(OrchestratorError::Service {
558 action: "uninstall",
559 detail: format!("launchctl bootout {target}: {text}"),
560 });
561 }
562 Ok(())
563}
564
565#[cfg(all(unix, not(target_os = "macos")))]
566fn platform_uninstall() -> Result<(), OrchestratorError> {
567 let _ = run_tool("systemctl", &["--user", "disable", "--now", SERVICE_NAME]);
568 Ok(())
569}
570
571#[cfg(not(unix))]
572fn platform_uninstall() -> Result<(), OrchestratorError> {
573 Ok(())
574}
575
576fn unit_file_name() -> String {
578 if cfg!(target_os = "macos") {
579 format!("{SERVICE_NAME}.plist")
580 } else {
581 format!("{SERVICE_NAME}.service")
582 }
583}
584
585pub fn write_unit(unit: &ServiceUnit) -> Result<(), OrchestratorError> {
587 if let Some(parent) = unit.path.parent() {
588 std::fs::create_dir_all(parent).map_err(|source| OrchestratorError::Lease {
589 path: unit.path.clone(),
590 source,
591 })?;
592 }
593 std::fs::write(&unit.path, &unit.text).map_err(|source| OrchestratorError::Lease {
594 path: unit.path.clone(),
595 source,
596 })
597}
598
599#[cfg(test)]
600mod tests {
601 use super::*;
602
603 fn scratch(label: &str) -> PathBuf {
605 let root = std::env::temp_dir().join(format!(
606 "supercode-orchestrator-{label}-{}-{}",
607 std::process::id(),
608 std::time::SystemTime::now()
609 .duration_since(std::time::UNIX_EPOCH)
610 .unwrap()
611 .as_nanos()
612 ));
613 std::fs::create_dir_all(&root).unwrap();
614 root
615 }
616
617 #[test]
618 fn a_lease_round_trips_and_a_missing_one_is_not_running() {
619 let root = &scratch("lease");
620 let root = root.as_path();
621 assert!(read_lease(root).is_none());
622 assert!(live_lease(root).is_none());
623 let lease = Lease {
624 pid: std::process::id(),
625 started_at: "2026-09-04T00:00:00Z".into(),
626 root: root.to_path_buf(),
627 };
628 write_lease(root, &lease).unwrap();
629 assert_eq!(read_lease(root).as_ref(), Some(&lease));
630 assert!(live_lease(root).is_some());
632 clear_lease(root).unwrap();
633 assert!(read_lease(root).is_none());
634 assert!(matches!(
636 stop(root),
637 Err(OrchestratorError::NotRunning { .. })
638 ));
639 std::fs::remove_dir_all(root).ok();
640 }
641
642 #[test]
645 fn a_stale_lease_is_not_live() {
646 let root = &scratch("stale");
647 let root = root.as_path();
648 write_lease(
649 root,
650 &Lease {
651 pid: 0x7FFF_FFFF,
653 started_at: "2026-09-04T00:00:00Z".into(),
654 root: root.to_path_buf(),
655 },
656 )
657 .unwrap();
658 assert!(read_lease(root).is_some(), "the file is still there");
659 assert!(live_lease(root).is_none(), "but nothing is serving it");
660 std::fs::remove_dir_all(root).ok();
661 }
662
663 #[test]
664 fn the_service_unit_names_the_home_the_entry_and_its_install_command() {
665 let root = &scratch("unit");
666 let root = root.as_path();
667 let entry = PathBuf::from("/opt/supercode/sdk/orchestrator/bin/orchestrator.mjs");
668 let unit = service_unit(root, &entry, "/usr/bin/node");
669 assert!(unit.text.contains(&root.display().to_string()));
670 assert!(unit.text.contains("orchestrator.mjs"));
671 assert!(unit.text.contains(SERVICE_NAME));
672 assert!(unit
673 .install_command
674 .contains(&unit.path.display().to_string()));
675 assert!(unit.path.starts_with(root.join(SERVICE_DIR)));
676 assert_eq!(
677 unit.kind,
678 if cfg!(target_os = "macos") {
679 "launchd"
680 } else {
681 "systemd"
682 }
683 );
684 std::fs::remove_dir_all(root).ok();
685 }
686
687 #[test]
690 fn service_status_reports_the_label_and_installs_nothing() {
691 let root = &scratch("service-status");
692 let root = root.as_path();
693 let state = service_status(root);
694 assert_eq!(state.label, SERVICE_NAME);
695 assert!(
696 matches!(state.kind, "launchd" | "systemd" | "none"),
697 "{state:?}"
698 );
699 assert!(
700 !root.join(SERVICE_DIR).exists(),
701 "asking never writes a unit"
702 );
703 std::fs::remove_dir_all(root).ok();
704 }
705
706 #[test]
709 fn a_program_is_resolved_absolutely_for_the_service_manager() {
710 assert_eq!(absolute_program("/usr/bin/env"), "/usr/bin/env");
711 let resolved = absolute_program("sh");
712 assert!(resolved.starts_with('/'), "{resolved}");
713 assert_eq!(
715 absolute_program("definitely-not-a-program"),
716 "definitely-not-a-program"
717 );
718 }
719
720 #[test]
723 fn the_daemon_entry_resolves_in_this_checkout() {
724 let entry = daemon_entry().expect("sdk/orchestrator/bin/orchestrator.mjs");
725 assert!(entry.ends_with(DAEMON_ENTRY));
726 }
727}