This commit is contained in:
Michael Neale
2025-11-11 17:57:37 +11:00
parent c45d3552f9
commit fa5870fb3f
5 changed files with 20 additions and 56 deletions
+2 -5
View File
@@ -60,13 +60,10 @@ pub async fn run() -> Result<()> {
let local_addr = listener.local_addr()?;
info!("listening on {}", local_addr);
let port = local_addr.port();
*app_state.server_port.write().await = Some(port);
let app_state_clone = app_state.clone();
tokio::spawn(async move {
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; // a tiny pause to not flood
app_state_clone.auto_start_tunnel(port).await;
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
app_state_clone.auto_start_tunnel().await;
});
axum::serve(listener, app)
+2
View File
@@ -1,4 +1,6 @@
pub mod auth;
pub mod configuration;
pub mod error;
pub mod openapi;
pub mod routes;
pub mod state;
+1 -15
View File
@@ -27,21 +27,7 @@ pub struct ErrorResponse {
)]
#[axum::debug_handler]
pub async fn start_tunnel(State(state): State<Arc<AppState>>) -> Response {
let port = match *state.server_port.read().await {
Some(p) => p,
None => {
tracing::error!("Server port not set");
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: "Server port not initialized".to_string(),
}),
)
.into_response();
}
};
match state.tunnel_manager.start(port).await {
match state.tunnel_manager.start().await {
Ok(_info) => {
let status = state.tunnel_manager.get_status().await;
(StatusCode::OK, Json(status)).into_response()
+5 -8
View File
@@ -5,7 +5,7 @@ use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use tokio::sync::Mutex;
use crate::tunnel::TunnelManager;
@@ -17,15 +17,13 @@ pub struct AppState {
/// Tracks sessions that have already emitted recipe telemetry to prevent double counting.
recipe_session_tracker: Arc<Mutex<HashSet<String>>>,
pub tunnel_manager: Arc<TunnelManager>,
pub server_port: Arc<RwLock<Option<u16>>>,
}
impl AppState {
pub async fn new() -> anyhow::Result<Arc<AppState>> {
let agent_manager = AgentManager::instance().await?;
let tunnel_config = crate::tunnel::config::load_config().await;
let tunnel_manager = Arc::new(TunnelManager::new(tunnel_config.clone()));
let tunnel_manager = Arc::new(TunnelManager::new(tunnel_config));
Ok(Arc::new(Self {
agent_manager,
@@ -33,16 +31,15 @@ impl AppState {
session_counter: Arc::new(AtomicUsize::new(0)),
recipe_session_tracker: Arc::new(Mutex::new(HashSet::new())),
tunnel_manager,
server_port: Arc::new(RwLock::new(None)),
}))
}
pub async fn auto_start_tunnel(&self, port: u16) {
pub async fn auto_start_tunnel(&self) {
let status = self.tunnel_manager.get_status().await;
if status.auto_start && status.state == crate::tunnel::TunnelState::Idle {
tracing::info!("Auto-starting tunnel on port {}", port);
match self.tunnel_manager.start(port).await {
tracing::info!("Auto-starting tunnel");
match self.tunnel_manager.start().await {
Ok(info) => {
tracing::info!("Tunnel auto-started successfully: {}", info.url);
}
+10 -28
View File
@@ -1,10 +1,16 @@
pub mod config;
pub mod lapstone;
use crate::configuration::Settings;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::{mpsc, RwLock};
fn get_server_port() -> anyhow::Result<u16> {
let settings = Settings::new()?;
Ok(settings.port)
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum TunnelState {
@@ -89,7 +95,7 @@ impl TunnelManager {
}
}
pub async fn start(&self, port: u16) -> anyhow::Result<TunnelInfo> {
pub async fn start(&self) -> anyhow::Result<TunnelInfo> {
let mut state = self.state.write().await;
if *state != TunnelState::Idle {
anyhow::bail!("Tunnel is already running or starting");
@@ -98,6 +104,7 @@ impl TunnelManager {
drop(state);
let config = self.config.read().await.clone();
let server_port = get_server_port()?;
let tunnel_secret = config.secret.clone().unwrap_or_else(generate_secret);
let server_secret =
@@ -114,7 +121,7 @@ impl TunnelManager {
*self.restart_tx.write().await = Some(restart_tx.clone());
let result = lapstone::start(
port,
server_port,
tunnel_secret,
server_secret,
agent_id,
@@ -156,7 +163,7 @@ impl TunnelManager {
*restart_tx_clone.write().await = Some(new_restart_tx.clone());
match lapstone::start(
port,
server_port,
tunnel_secret,
server_secret,
agent_id,
@@ -221,28 +228,3 @@ fn generate_agent_id() -> String {
let bytes: [u8; 16] = rand::random();
hex::encode(bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_secret() {
let secret = generate_secret();
assert_eq!(secret.len(), 64); // 32 bytes = 64 hex chars
}
#[test]
fn test_generate_agent_id() {
let agent_id = generate_agent_id();
assert_eq!(agent_id.len(), 32); // 16 bytes = 32 hex chars
}
#[tokio::test]
async fn test_tunnel_manager_initial_state() {
let manager = TunnelManager::new(TunnelConfig::default());
let status = manager.get_status().await;
assert_eq!(status.state, TunnelState::Idle);
assert!(status.info.is_none());
}
}