mod control;
mod filesystem;
mod icon;
mod kubernetes;
mod machine;
#[cfg(target_os = "macos")]
mod macos;
mod migration;
mod process;
mod sandbox_cleanup;
mod sandbox_locks;
mod snapshot;
mod stats;
mod system;
mod template;
use std::sync::Arc;
use std::time::Duration;
use std::sync::OnceLock;
use arcbox_core::Runtime;
use arcbox_core::vm_lifecycle::DEFAULT_MACHINE_NAME;
use connectrpc::{ConnectError, RequestContext};
use tokio_stream::{Stream, StreamExt as _};
pub use control::SandboxServiceImpl;
pub use filesystem::SandboxFilesystemServiceImpl;
pub use icon::IconServiceImpl;
pub use kubernetes::KubernetesServiceImpl;
pub use machine::MachineServiceImpl;
#[cfg(target_os = "macos")]
pub use macos::MacosServiceImpl;
pub use migration::MigrationServiceImpl;
pub use process::SandboxProcessServiceImpl;
pub use sandbox_cleanup::{
initialize as initialize_sandbox_cleanup, spawn as spawn_sandbox_cleanup,
};
pub use snapshot::SandboxSnapshotServiceImpl;
pub use stats::StatsServiceImpl;
pub use system::{SetupState, SystemServiceImpl};
pub use template::TemplateServiceImpl;
pub type SharedRuntime = Arc<OnceLock<Arc<Runtime>>>;
const KEEPALIVE_INTERVAL: Duration = Duration::from_secs(15);
fn with_keepalive<S, T>(
stream: S,
keepalive: fn() -> T,
) -> impl Stream<Item = Result<T, ConnectError>>
where
S: Stream<Item = Result<T, ConnectError>>,
{
stream
.timeout(KEEPALIVE_INTERVAL)
.map(move |item| item.unwrap_or_else(|_elapsed| Ok(keepalive())))
}
#[cfg(target_os = "macos")]
pub(crate) async fn run_macos_blocking<T, Fut, F>(f: F) -> Result<T, ConnectError>
where
T: Send + 'static,
Fut: std::future::Future<Output = arcbox_core::Result<T>>,
F: FnOnce() -> Fut + Send + 'static,
{
tokio::task::spawn_blocking(move || {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map_err(|e| ConnectError::internal(format!("macOS runtime: {e}")))?
.block_on(f())
.map_err(|e| ConnectError::internal(e.to_string()))
})
.await
.map_err(|e| ConnectError::internal(format!("macOS task join: {e}")))?
}
pub(crate) trait ConnectRuntimeExt {
fn ready(&self) -> Result<&Arc<Runtime>, ConnectError>;
}
impl ConnectRuntimeExt for SharedRuntime {
fn ready(&self) -> Result<&Arc<Runtime>, ConnectError> {
self.get()
.ok_or_else(|| ConnectError::unavailable("daemon is starting, runtime not ready yet"))
}
}
pub(crate) trait ContextExt {
fn machine_id(&self) -> Result<String, ConnectError>;
fn sandbox_machine_id(&self) -> Result<String, ConnectError>;
}
impl ContextExt for RequestContext {
fn machine_id(&self) -> Result<String, ConnectError> {
match self.header("x-machine") {
None => Ok(DEFAULT_MACHINE_NAME.to_owned()),
Some(value) => match value.to_str() {
Ok("") => Ok(DEFAULT_MACHINE_NAME.to_owned()),
Ok(s) => Ok(s.to_string()),
Err(_) => Err(ConnectError::invalid_argument(
"invalid x-machine header: must be valid UTF-8",
)),
},
}
}
fn sandbox_machine_id(&self) -> Result<String, ConnectError> {
let machine = self.machine_id()?;
if machine != DEFAULT_MACHINE_NAME {
return Err(ConnectError::invalid_argument(
"Sandbox V1 is available only on the System VM",
));
}
Ok(machine)
}
}
fn protocol_key(protocol: arcbox_connect::sandbox_v1::PortProtocol) -> &'static str {
use arcbox_connect::sandbox_v1::PortProtocol;
match protocol {
PortProtocol::Udp => "udp",
_ => "tcp",
}
}
fn wire_protocol(
protocol: arcbox_connect::sandbox_v1::PortProtocol,
) -> arcbox_connect::v1::SandboxPortProtocol {
use arcbox_connect::sandbox_v1::PortProtocol;
match protocol {
PortProtocol::Udp => arcbox_connect::v1::SandboxPortProtocol::Udp,
_ => arcbox_connect::v1::SandboxPortProtocol::Tcp,
}
}
#[must_use]
pub fn router(runtime: SharedRuntime) -> connectrpc::Router {
let clone = || Arc::clone(&runtime);
let sandbox_operations = Arc::new(sandbox_locks::SandboxOperationLocks::default());
let router = connectrpc::Router::new()
.add_service(Arc::new(SandboxServiceImpl::new(
clone(),
Arc::clone(&sandbox_operations),
)))
.add_service(Arc::new(SandboxProcessServiceImpl::new(clone())))
.add_service(Arc::new(SandboxFilesystemServiceImpl::new(clone())))
.add_service(Arc::new(SandboxSnapshotServiceImpl::new(
clone(),
Arc::clone(&sandbox_operations),
)))
.add_service(Arc::new(TemplateServiceImpl::new()))
.add_service(Arc::new(IconServiceImpl::new()))
.add_service(Arc::new(StatsServiceImpl::new(clone())))
.add_service(Arc::new(KubernetesServiceImpl::new(clone())))
.add_service(Arc::new(MigrationServiceImpl::new(clone())))
.add_service(Arc::new(MachineServiceImpl::new(clone())));
#[cfg(target_os = "macos")]
let router = router.add_service(Arc::new(MacosServiceImpl::new(clone())));
router
}
#[must_use]
pub fn router_with_system(runtime: SharedRuntime, system: SystemServiceImpl) -> connectrpc::Router {
router(runtime).add_service(Arc::new(system))
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx_with(header: Option<&str>) -> RequestContext {
let mut headers = http::HeaderMap::new();
if let Some(value) = header {
headers.insert("x-machine", value.parse().expect("valid header value"));
}
RequestContext::new(headers)
}
#[test]
fn machine_id_defaults_to_the_system_vm() {
assert_eq!(
ctx_with(None).machine_id().expect("absent header is valid"),
DEFAULT_MACHINE_NAME
);
assert_eq!(
ctx_with(Some(""))
.machine_id()
.expect("empty header is valid"),
DEFAULT_MACHINE_NAME
);
assert_eq!(
ctx_with(Some("other-vm"))
.machine_id()
.expect("explicit header is valid"),
"other-vm"
);
}
#[test]
fn sandbox_machine_id_accepts_only_the_system_vm() {
assert_eq!(DEFAULT_MACHINE_NAME, "default");
for header in [None, Some(""), Some(DEFAULT_MACHINE_NAME)] {
assert_eq!(
ctx_with(header)
.sandbox_machine_id()
.expect("System VM routing should be accepted"),
DEFAULT_MACHINE_NAME
);
}
let error = ctx_with(Some("other-vm"))
.sandbox_machine_id()
.expect_err("other machines must be rejected");
assert_eq!(error.code, connectrpc::ErrorCode::InvalidArgument);
}
}