Skip to main content

arcbox_helper/
client.rs

1//! High-level client for communicating with the arcbox-helper daemon.
2//!
3//! Wraps the raw tarpc `HelperServiceClient` with ergonomic methods and
4//! a unified error type. Consumers (arcbox-core, arcbox-daemon) use this
5//! instead of managing tarpc connections directly.
6
7use crate::HelperServiceClient;
8
9/// Errors from helper client operations.
10#[derive(Debug, thiserror::Error)]
11pub enum ClientError {
12    /// Cannot connect to the helper socket (daemon not running).
13    #[error("helper not reachable: {0}")]
14    Connection(#[from] std::io::Error),
15    /// tarpc transport or RPC-level failure.
16    #[error("helper rpc failed: {0}")]
17    Rpc(#[from] tarpc::client::RpcError),
18    /// The helper executed the operation but it returned an error.
19    #[error("helper error: {0}")]
20    Helper(String),
21}
22
23/// Client for the arcbox-helper privileged daemon.
24pub struct Client {
25    inner: HelperServiceClient,
26}
27
28impl Client {
29    /// Connects to the helper daemon via Unix socket.
30    ///
31    /// Uses launchd-managed socket by default (`/var/run/arcbox-helper.sock`),
32    /// overridable via `ARCBOX_HELPER_SOCKET` env var.
33    pub async fn connect() -> Result<Self, ClientError> {
34        let inner = crate::connect().await?;
35        Ok(Self { inner })
36    }
37
38    /// Connects to the helper daemon at an explicit socket path.
39    ///
40    /// Unlike [`connect()`](Self::connect), this does not read the
41    /// `ARCBOX_HELPER_SOCKET` env var, making it safe for parallel tests.
42    pub async fn connect_to(path: &str) -> Result<Self, ClientError> {
43        let transport = tarpc::serde_transport::unix::connect(
44            path,
45            tarpc::tokio_serde::formats::Bincode::default,
46        )
47        .await?;
48        let inner =
49            crate::HelperServiceClient::new(tarpc::client::Config::default(), transport).spawn();
50        Ok(Self { inner })
51    }
52
53    /// Adds a host route for `subnet` via `iface`.
54    pub async fn route_add(&self, subnet: &str, iface: &str) -> Result<(), ClientError> {
55        self.inner
56            .route_add(tarpc::context::current(), subnet.into(), iface.into())
57            .await?
58            .map_err(ClientError::Helper)
59    }
60
61    /// Removes the host route for `subnet`.
62    pub async fn route_remove(&self, subnet: &str) -> Result<(), ClientError> {
63        self.inner
64            .route_remove(tarpc::context::current(), subnet.into())
65            .await?
66            .map_err(ClientError::Helper)
67    }
68
69    /// Installs a DNS resolver file for `domain` on port `port`.
70    pub async fn dns_install(&self, domain: &str, port: u16) -> Result<(), ClientError> {
71        self.inner
72            .dns_install(tarpc::context::current(), domain.into(), port)
73            .await?
74            .map_err(ClientError::Helper)
75    }
76
77    /// Removes the DNS resolver file for `domain`.
78    pub async fn dns_uninstall(&self, domain: &str) -> Result<(), ClientError> {
79        self.inner
80            .dns_uninstall(tarpc::context::current(), domain.into())
81            .await?
82            .map_err(ClientError::Helper)
83    }
84
85    /// Checks if a DNS resolver file is installed for `domain`.
86    pub async fn dns_status(&self, domain: &str) -> Result<bool, ClientError> {
87        self.inner
88            .dns_status(tarpc::context::current(), domain.into())
89            .await?
90            .map_err(ClientError::Helper)
91    }
92
93    /// Appends the fixed `127.0.0.1 ArcBox` alias to `/etc/hosts`.
94    pub async fn hosts_alias_install(&self) -> Result<(), ClientError> {
95        self.inner
96            .hosts_alias_install(tarpc::context::current())
97            .await?
98            .map_err(ClientError::Helper)
99    }
100
101    /// Removes the ArcBox alias line from `/etc/hosts`.
102    pub async fn hosts_alias_uninstall(&self) -> Result<(), ClientError> {
103        self.inner
104            .hosts_alias_uninstall(tarpc::context::current())
105            .await?
106            .map_err(ClientError::Helper)
107    }
108
109    /// Checks whether the ArcBox `/etc/hosts` alias is installed.
110    pub async fn hosts_alias_status(&self) -> Result<bool, ClientError> {
111        self.inner
112            .hosts_alias_status(tarpc::context::current())
113            .await?
114            .map_err(ClientError::Helper)
115    }
116
117    /// Creates the `/var/run/docker.sock` → `target` symlink.
118    pub async fn socket_link(&self, target: &str) -> Result<(), ClientError> {
119        self.inner
120            .socket_link(tarpc::context::current(), target.into())
121            .await?
122            .map_err(ClientError::Helper)
123    }
124
125    /// Removes the `/var/run/docker.sock` symlink.
126    pub async fn socket_unlink(&self) -> Result<(), ClientError> {
127        self.inner
128            .socket_unlink(tarpc::context::current())
129            .await?
130            .map_err(ClientError::Helper)
131    }
132
133    /// Creates `/usr/local/bin/{name}` → `target` symlink.
134    pub async fn cli_link(&self, name: &str, target: &str) -> Result<(), ClientError> {
135        self.inner
136            .cli_link(tarpc::context::current(), name.into(), target.into())
137            .await?
138            .map_err(ClientError::Helper)
139    }
140
141    /// Removes `/usr/local/bin/{name}` symlink if ArcBox-owned.
142    pub async fn cli_unlink(&self, name: &str) -> Result<(), ClientError> {
143        self.inner
144            .cli_unlink(tarpc::context::current(), name.into())
145            .await?
146            .map_err(ClientError::Helper)
147    }
148
149    /// Returns the helper daemon version.
150    pub async fn version(&self) -> Result<String, ClientError> {
151        Ok(self.inner.version(tarpc::context::current()).await?)
152    }
153}