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(30);
26
27#[cfg(feature = "listener-tun")]
31pub fn tun_config_to_listener_config(
32 tun: &meow_config::TunConfig,
33) -> meow_listener::TunListenerConfig {
34 meow_listener::TunListenerConfig {
35 device: tun.device.clone(),
36 mtu: tun.mtu,
37 inet4_address: tun.inet4_address,
38 auto_route: tun.auto_route,
39 dns_hijack: tun.dns_hijack,
40 udp_timeout: tun.udp_timeout,
41 }
42}
43
44pub struct ApiServer {
45 tunnel: Tunnel,
46 listen_addr: SocketAddr,
47 secret: Option<String>,
48 config_path: String,
49 raw_config: Arc<RwLock<RawConfig>>,
50 log_tx: broadcast::Sender<LogMessage>,
51 proxy_providers: Arc<DashMap<String, Arc<ProxyProvider>>>,
52 rule_providers: Arc<RwLock<HashMap<String, Arc<RuleProvider>>>>,
53 listeners: Vec<NamedListener>,
54 external_ui: Option<PathBuf>,
55}
56
57impl ApiServer {
58 #[allow(clippy::too_many_arguments)]
59 pub fn new(
60 tunnel: Tunnel,
61 listen_addr: SocketAddr,
62 secret: Option<String>,
63 config_path: String,
64 raw_config: Arc<RwLock<RawConfig>>,
65 log_tx: broadcast::Sender<LogMessage>,
66 proxy_providers: Arc<DashMap<String, Arc<ProxyProvider>>>,
67 rule_providers: Arc<RwLock<HashMap<String, Arc<RuleProvider>>>>,
68 listeners: Vec<NamedListener>,
69 external_ui: Option<PathBuf>,
70 ) -> Self {
71 Self {
72 tunnel,
73 listen_addr,
74 secret,
75 config_path,
76 raw_config,
77 log_tx,
78 proxy_providers,
79 rule_providers,
80 listeners,
81 external_ui,
82 }
83 }
84
85 pub async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
86 let state = Arc::new(routes::AppState {
87 tunnel: self.tunnel.clone(),
88 secret: self.secret.clone(),
89 config_path: self.config_path.clone(),
90 raw_config: Arc::clone(&self.raw_config),
91 log_tx: self.log_tx.clone(),
92 proxy_providers: Arc::clone(&self.proxy_providers),
93 rule_providers: Arc::clone(&self.rule_providers),
94 listeners: self.listeners.clone(),
95 external_ui: self.resolve_external_ui(),
96 config_mutation_lock: tokio::sync::Mutex::new(()),
97 });
98
99 let app = routes::create_router(state);
100
101 let listener = tokio::net::TcpListener::bind(self.listen_addr).await?;
102 info!("REST API listening on {}", self.listen_addr);
103 info!("Web UI available at http://{}/ui", self.listen_addr);
104 axum::serve(listener, app).await?;
105 Ok(())
106 }
107
108 fn resolve_external_ui(&self) -> Option<PathBuf> {
112 let dir = self.external_ui.as_ref()?;
113 if dir.is_dir() {
114 info!("Serving external Web UI from {}", dir.display());
115 Some(dir.clone())
116 } else {
117 warn!(
118 "external-ui directory {} not found; serving the built-in panel instead",
119 dir.display()
120 );
121 None
122 }
123 }
124}