This commit is contained in:
Michael Neale
2025-11-12 12:27:06 +11:00
parent 34ba02f3e6
commit 897fa13028
3 changed files with 32 additions and 73 deletions
+3 -5
View File
@@ -57,14 +57,12 @@ pub async fn run() -> Result<()> {
.layer(cors);
let listener = tokio::net::TcpListener::bind(settings.socket_addr()).await?;
let local_addr = listener.local_addr()?;
info!("listening on {}", local_addr);
info!("listening on {}", listener.local_addr()?);
let app_state_clone = app_state.clone();
let tunnel_manager = app_state.tunnel_manager.clone();
tokio::spawn(async move {
// a very short pause for goosed to start, I found without this, reconnections may struggle
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
app_state_clone.auto_start_tunnel().await;
tunnel_manager.check_auto_start().await;
});
axum::serve(listener, app)
+1 -22
View File
@@ -22,9 +22,7 @@ pub struct AppState {
impl AppState {
pub async fn new() -> anyhow::Result<Arc<AppState>> {
let agent_manager = AgentManager::instance().await?;
// Initialize tunnel manager with default config - will lazy-load actual config when needed
// This prevents blocking server startup if keyring access requires password prompt
let tunnel_manager = Arc::new(TunnelManager::new_uninitialized());
let tunnel_manager = Arc::new(TunnelManager::new());
Ok(Arc::new(Self {
agent_manager,
@@ -35,25 +33,6 @@ impl AppState {
}))
}
pub async fn auto_start_tunnel(&self) {
// Ensure config is loaded before checking status
self.tunnel_manager.ensure_initialized().await;
let status = self.tunnel_manager.get_status().await;
if status.auto_start && status.state == crate::tunnel::TunnelState::Idle {
tracing::info!("Auto-starting tunnel");
match self.tunnel_manager.start().await {
Ok(info) => {
tracing::info!("Tunnel auto-started successfully: {}", info.url);
}
Err(e) => {
tracing::error!("Failed to auto-start tunnel: {}", e);
}
}
}
}
pub async fn scheduler(&self) -> Result<Arc<dyn SchedulerTrait>, anyhow::Error> {
self.agent_manager.scheduler().await
}
+28 -46
View File
@@ -55,58 +55,50 @@ pub struct TunnelStatus {
pub struct TunnelManager {
state: Arc<RwLock<TunnelState>>,
info: Arc<RwLock<Option<TunnelInfo>>>,
config: Arc<RwLock<Option<TunnelConfig>>>,
config_initialized: Arc<RwLock<bool>>,
config: Arc<RwLock<TunnelConfig>>,
lapstone_handle: Arc<RwLock<Option<tokio::task::JoinHandle<()>>>>,
restart_tx: Arc<RwLock<Option<mpsc::Sender<()>>>>,
watchdog_handle: Arc<RwLock<Option<tokio::task::JoinHandle<()>>>>,
}
impl TunnelManager {
pub fn new(config: TunnelConfig) -> Self {
pub fn new() -> Self {
TunnelManager {
state: Arc::new(RwLock::new(TunnelState::Idle)),
info: Arc::new(RwLock::new(None)),
config: Arc::new(RwLock::new(Some(config))),
config_initialized: Arc::new(RwLock::new(true)),
config: Arc::new(RwLock::new(TunnelConfig::default())),
lapstone_handle: Arc::new(RwLock::new(None)),
restart_tx: Arc::new(RwLock::new(None)),
watchdog_handle: Arc::new(RwLock::new(None)),
}
}
pub fn new_uninitialized() -> Self {
TunnelManager {
state: Arc::new(RwLock::new(TunnelState::Idle)),
info: Arc::new(RwLock::new(None)),
config: Arc::new(RwLock::new(None)),
config_initialized: Arc::new(RwLock::new(false)),
lapstone_handle: Arc::new(RwLock::new(None)),
restart_tx: Arc::new(RwLock::new(None)),
watchdog_handle: Arc::new(RwLock::new(None)),
}
async fn load_config(&self) {
let loaded = config::load_config().await;
*self.config.write().await = loaded;
}
pub async fn ensure_initialized(&self) {
let initialized = *self.config_initialized.read().await;
if !initialized {
let loaded_config = config::load_config().await;
*self.config.write().await = Some(loaded_config);
*self.config_initialized.write().await = true;
pub async fn check_auto_start(&self) {
self.load_config().await;
let status = self.get_status().await;
if status.auto_start && status.state == TunnelState::Idle {
tracing::info!("Auto-starting tunnel");
match self.start().await {
Ok(info) => {
tracing::info!("Tunnel auto-started successfully: {}", info.url);
}
Err(e) => {
tracing::error!("Failed to auto-start tunnel: {}", e);
}
}
}
}
pub async fn get_status(&self) -> TunnelStatus {
self.ensure_initialized().await;
let state = self.state.read().await.clone();
let info = self.info.read().await.clone();
let auto_start = self
.config
.read()
.await
.as_ref()
.map(|c| c.auto_start)
.unwrap_or(false);
let auto_start = self.config.read().await.auto_start;
TunnelStatus {
state,
info,
@@ -118,19 +110,14 @@ impl TunnelManager {
where
F: FnOnce(&mut TunnelConfig),
{
self.ensure_initialized().await;
let mut cfg_opt = self.config.write().await;
if let Some(cfg) = cfg_opt.as_mut() {
f(cfg);
if let Err(e) = config::save_config(cfg).await {
tracing::error!("Failed to save tunnel config: {}", e);
}
let mut cfg = self.config.write().await;
f(&mut cfg);
if let Err(e) = config::save_config(&cfg).await {
tracing::error!("Failed to save tunnel config: {}", e);
}
}
pub async fn start(&self) -> anyhow::Result<TunnelInfo> {
self.ensure_initialized().await;
let mut state = self.state.write().await;
if *state != TunnelState::Idle {
anyhow::bail!("Tunnel is already running or starting");
@@ -138,7 +125,7 @@ impl TunnelManager {
*state = TunnelState::Starting;
drop(state);
let config = self.config.read().await.clone().unwrap_or_default();
let config = self.config.read().await.clone();
let server_port = get_server_port()?;
let tunnel_secret = config.secret.clone().unwrap_or_else(generate_secret);
@@ -179,12 +166,7 @@ impl TunnelManager {
let watchdog = tokio::spawn(async move {
while restart_rx.recv().await.is_some() {
let auto_start = config
.read()
.await
.as_ref()
.map(|c| c.auto_start)
.unwrap_or(false);
let auto_start = config.read().await.auto_start;
if auto_start {
tracing::warn!("Tunnel connection lost, initiating restart...");
@@ -194,7 +176,7 @@ impl TunnelManager {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
*state.write().await = TunnelState::Starting;
let cfg = config.read().await.clone().unwrap_or_default();
let cfg = config.read().await.clone();
let tunnel_secret = cfg.secret.clone().unwrap_or_else(generate_secret);
let server_secret = std::env::var("GOOSE_SERVER__SECRET_KEY")
.unwrap_or_else(|_| "test".to_string());