use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
use nomoreide_core::remote::connector::{CommandSink, ConnectorConfig, RelaySnapshot, RelayStatus};
use nomoreide_core::remote::credentials::RemoteCredentials;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum StartOutcome {
Started,
AlreadyRunning,
NotPaired,
Disabled,
}
#[derive(Clone)]
pub(crate) struct RelaySupervisor {
state_dir: PathBuf,
credential: String,
terminal: nomoreide_core::terminal::TerminalManager,
router: Arc<OnceLock<axum::Router>>,
started: Arc<AtomicBool>,
status: Arc<OnceLock<RelayStatus>>,
outbound: nomoreide_core::remote::connector::RelayOutbound,
}
impl RelaySupervisor {
pub(crate) fn new(
state_dir: PathBuf,
credential: String,
terminal: nomoreide_core::terminal::TerminalManager,
) -> Self {
Self {
state_dir,
credential,
terminal,
router: Arc::new(OnceLock::new()),
started: Arc::new(AtomicBool::new(false)),
status: Arc::new(OnceLock::new()),
outbound: nomoreide_core::remote::connector::RelayOutbound::new(),
}
}
pub(crate) fn attach_router(&self, router: axum::Router) {
let _ = self.router.set(router);
}
pub(crate) fn ensure_started(&self) -> StartOutcome {
if super::disabled_by_environment() {
return StartOutcome::Disabled;
}
let credentials = RemoteCredentials::new(&self.state_dir);
let Some(stored) = credentials.load() else {
return StartOutcome::NotPaired;
};
let Some(router) = self.router.get() else {
return StartOutcome::NotPaired;
};
if self.started.swap(true, Ordering::SeqCst) {
return StartOutcome::AlreadyRunning;
}
let mut config = ConnectorConfig::from_credential(&stored);
config.capabilities = super::dispatcher::served_capabilities();
let status = RelayStatus::new(&config, &stored.device_name);
let _ = self.status.set(status.clone());
let sink: Arc<dyn CommandSink> = Arc::new(super::dispatcher::RouterDispatcher::new(
router.clone(),
self.credential.clone(),
stored.device_id,
stored.device_name.clone(),
self.terminal.clone(),
));
tokio::spawn(nomoreide_core::remote::connector::run_forever(
config,
sink,
status,
self.outbound.clone(),
));
StartOutcome::Started
}
pub(crate) fn snapshot(&self) -> Option<RelaySnapshot> {
self.status.get().map(RelayStatus::snapshot)
}
pub(crate) fn retire(&self) -> bool {
use nomoreide_core::remote::protocol::platform_bound::{
DeviceRetire, PlatformBound, RetireReason,
};
self.outbound
.try_send(PlatformBound::DeviceRetire(DeviceRetire {
reason: RetireReason::Unpaired,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn scratch(label: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"nomoreide-supervisor-{label}-{}-{}",
std::process::id(),
uuid::Uuid::new_v4()
));
std::fs::create_dir_all(&dir).expect("scratch");
dir
}
#[test]
fn an_unpaired_machine_does_not_connect() {
let supervisor = RelaySupervisor::new(
scratch("unpaired"),
"cred".into(),
nomoreide_core::terminal::TerminalManager::new(),
);
supervisor.attach_router(axum::Router::new());
assert_eq!(supervisor.ensure_started(), StartOutcome::NotPaired);
assert_eq!(supervisor.snapshot(), None);
}
#[test]
fn nothing_starts_before_the_router_exists() {
let dir = scratch("no-router");
write_credential(&dir);
let supervisor = RelaySupervisor::new(
dir,
"cred".into(),
nomoreide_core::terminal::TerminalManager::new(),
);
assert_eq!(supervisor.ensure_started(), StartOutcome::NotPaired);
}
#[tokio::test]
async fn starting_twice_starts_once() {
let dir = scratch("twice");
write_credential(&dir);
let supervisor = RelaySupervisor::new(
dir,
"cred".into(),
nomoreide_core::terminal::TerminalManager::new(),
);
supervisor.attach_router(axum::Router::new());
assert_eq!(supervisor.ensure_started(), StartOutcome::Started);
assert_eq!(supervisor.ensure_started(), StartOutcome::AlreadyRunning);
assert_eq!(supervisor.ensure_started(), StartOutcome::AlreadyRunning);
}
#[tokio::test]
async fn a_started_connector_reports_itself_before_it_is_connected() {
let dir = scratch("status");
write_credential(&dir);
let supervisor = RelaySupervisor::new(
dir,
"cred".into(),
nomoreide_core::terminal::TerminalManager::new(),
);
supervisor.attach_router(axum::Router::new());
supervisor.ensure_started();
let snapshot = supervisor.snapshot().expect("a status once started");
assert_eq!(snapshot.device_name, "Test Machine");
assert!(!snapshot.connected);
}
fn write_credential(dir: &std::path::Path) {
RemoteCredentials::new(dir)
.store(&nomoreide_core::remote::credentials::StoredCredential {
device_id: "11111111-2222-3333-4444-555555555555".into(),
device_name: "Test Machine".into(),
credential: "c".repeat(64),
platform_base_url: "http://127.0.0.1:1".into(),
web_base_url: String::new(),
paired_at: "2026-09-02T00:00:00Z".into(),
})
.expect("store");
}
}