1mod assets;
5pub mod features;
6mod server;
7mod snapshot;
8pub mod tools;
9
10use crate::ipc::WebEndpoint;
11use anyhow::{Context, Result};
12use std::net::{IpAddr, Ipv4Addr, SocketAddr};
13use tokio::net::TcpListener;
14use tokio::task::JoinHandle;
15use uuid::Uuid;
16
17const DEFAULT_LISTEN: &str = "127.0.0.1:7878";
18
19pub async fn start() -> Result<(WebEndpoint, JoinHandle<()>)> {
20 let listener = bind_loopback().await?;
21 start_with_listener(listener).await
22}
23
24pub async fn start_with_listener(listener: TcpListener) -> Result<(WebEndpoint, JoinHandle<()>)> {
25 let token = Uuid::now_v7().simple().to_string();
26 start_with_token(listener, token).await
27}
28
29pub async fn start_with_token(
30 listener: TcpListener,
31 token: String,
32) -> Result<(WebEndpoint, JoinHandle<()>)> {
33 let addr = listener.local_addr()?;
34 let endpoint = endpoint(addr, token);
35 let app = server::router(endpoint.token.clone());
36 let task = tokio::spawn(async move {
37 if let Err(err) = axum::serve(listener, app).await {
38 tracing::warn!(%err, "daemon web app stopped");
39 }
40 });
41 Ok((endpoint, task))
42}
43
44async fn bind_loopback() -> Result<TcpListener> {
45 match TcpListener::bind(DEFAULT_LISTEN).await {
46 Ok(listener) => Ok(listener),
47 Err(err) => bind_fallback()
48 .await
49 .with_context(|| format!("bind daemon web app at {DEFAULT_LISTEN}: {err}")),
50 }
51}
52
53async fn bind_fallback() -> Result<TcpListener> {
54 TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))
55 .await
56 .map_err(Into::into)
57}
58
59fn endpoint(addr: SocketAddr, token: String) -> WebEndpoint {
60 let public = public_addr(addr);
61 WebEndpoint {
62 listen: addr.to_string(),
63 url: format!("http://{public}/?token={token}"),
64 token,
65 }
66}
67
68fn public_addr(addr: SocketAddr) -> SocketAddr {
69 if addr.ip().is_unspecified() {
70 return SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), addr.port());
71 }
72 addr
73}