1pub mod log_stream;
2pub mod routes;
3pub mod ui;
4
5use dashmap::DashMap;
6use log_stream::LogMessage;
7use meow_config::{
8 proxy_provider::ProxyProvider, raw::RawConfig, rule_provider::RuleProvider, NamedListener,
9};
10use meow_tunnel::Tunnel;
11use parking_lot::RwLock;
12use std::collections::HashMap;
13use std::net::SocketAddr;
14use std::path::PathBuf;
15use std::sync::Arc;
16use tokio::sync::broadcast;
17use tracing::{info, warn};
18
19pub const TUN_STARTUP_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(300);
34
35#[cfg(feature = "listener-tun")]
39pub fn tun_config_to_listener_config(
40 tun: &meow_config::TunConfig,
41) -> meow_listener::TunListenerConfig {
42 meow_listener::TunListenerConfig {
43 device: tun.device.clone(),
44 mtu: tun.mtu,
45 inet4_address: tun.inet4_address,
46 auto_route: tun.auto_route,
47 dns_hijack: tun.dns_hijack,
48 udp_timeout: tun.udp_timeout,
49 }
50}
51
52pub struct ApiServer {
53 tunnel: Tunnel,
54 listen_addr: SocketAddr,
55 secret: Option<String>,
56 config_path: String,
57 raw_config: Arc<RwLock<RawConfig>>,
58 log_tx: broadcast::Sender<LogMessage>,
59 proxy_providers: Arc<DashMap<String, Arc<ProxyProvider>>>,
60 rule_providers: Arc<RwLock<HashMap<String, Arc<RuleProvider>>>>,
61 listeners: Vec<NamedListener>,
62 external_ui: Option<PathBuf>,
63}
64
65impl ApiServer {
66 #[allow(clippy::too_many_arguments)]
67 pub fn new(
68 tunnel: Tunnel,
69 listen_addr: SocketAddr,
70 secret: Option<String>,
71 config_path: String,
72 raw_config: Arc<RwLock<RawConfig>>,
73 log_tx: broadcast::Sender<LogMessage>,
74 proxy_providers: Arc<DashMap<String, Arc<ProxyProvider>>>,
75 rule_providers: Arc<RwLock<HashMap<String, Arc<RuleProvider>>>>,
76 listeners: Vec<NamedListener>,
77 external_ui: Option<PathBuf>,
78 ) -> Self {
79 Self {
80 tunnel,
81 listen_addr,
82 secret,
83 config_path,
84 raw_config,
85 log_tx,
86 proxy_providers,
87 rule_providers,
88 listeners,
89 external_ui,
90 }
91 }
92
93 pub async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
94 let state = Arc::new(routes::AppState {
95 tunnel: self.tunnel.clone(),
96 secret: self.secret.clone(),
97 config_path: self.config_path.clone(),
98 raw_config: Arc::clone(&self.raw_config),
99 log_tx: self.log_tx.clone(),
100 proxy_providers: Arc::clone(&self.proxy_providers),
101 rule_providers: Arc::clone(&self.rule_providers),
102 listeners: self.listeners.clone(),
103 external_ui: self.resolve_external_ui(),
104 config_mutation_lock: tokio::sync::Mutex::new(()),
105 });
106
107 let app = routes::create_router(state);
108
109 let listener = tokio::net::TcpListener::bind(self.listen_addr).await?;
110 info!("REST API listening on {}", self.listen_addr);
111 info!("Web UI available at http://{}/ui", self.listen_addr);
112 axum::serve(listener, app).await?;
113 Ok(())
114 }
115
116 fn resolve_external_ui(&self) -> Option<PathBuf> {
120 let dir = self.external_ui.as_ref()?;
121 if dir.is_dir() {
122 info!("Serving external Web UI from {}", dir.display());
123 Some(dir.clone())
124 } else {
125 warn!(
126 "external-ui directory {} not found; serving the built-in panel instead",
127 dir.display()
128 );
129 None
130 }
131 }
132}