feat: [US-004] Add SSH tunnel auto-start for Windows services
- Add ssh-tunnel.ps1: Windows SSH tunnel manager (equivalent to ssh-tunnel.sh) - Supports password auth via plink.exe (PuTTY) - Supports ssh_hostkey for non-interactive batch mode - Commands: start, stop, restart, status - Add start-backend-service.ps1: NSSM service wrapper - Starts SSH tunnels before uvicorn - Waits for tunnel ports to be accessible (30s timeout) - Configured by Install-ROA2WEB.ps1 - Add start.ps1: Windows equivalent of start.sh - Orchestrates SSH tunnel + backend + frontend startup - Add backend/shared/ssh_tunnel_manager.py: Python monitoring - Background asyncio task monitors tunnel health every 30s - Auto-restarts tunnels after 2 consecutive failures - Exposes status to /health endpoint - Update ROA2WEB-Console.ps1: - Add Deploy-Scripts function - Update Update-ServiceToUseVenv to use wrapper script - Fix PowerShell reserved variable ($PID -> $tunnelPid) - Fix script path detection (scripts/ vs deployment/windows/scripts/) - Update README.md with ssh_hostkey documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,7 @@ telegram_bot_task = None
|
||||
ocr_job_worker_running = False
|
||||
cleanup_task_running = False
|
||||
email_cache_running = False
|
||||
ssh_tunnel_monitoring = False
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -265,6 +266,40 @@ async def init_email_server_cache():
|
||||
email_cache_running = False
|
||||
|
||||
|
||||
async def init_ssh_tunnel_monitoring():
|
||||
"""Initialize SSH tunnel monitoring with auto-reconnect.
|
||||
|
||||
This does NOT start tunnels - they should already be running
|
||||
(started by start.sh / start.ps1 / start-backend-service.ps1).
|
||||
|
||||
Responsibilities:
|
||||
- Monitor tunnel health via port checks (every 30s)
|
||||
- Auto-restart tunnels if they go down
|
||||
- Expose status for /health endpoint
|
||||
"""
|
||||
global ssh_tunnel_monitoring
|
||||
|
||||
logger.info("[SSH-MONITOR] Initializing tunnel monitoring...")
|
||||
try:
|
||||
from backend.shared.ssh_tunnel_manager import ssh_tunnel_manager
|
||||
|
||||
success = await ssh_tunnel_manager.start_monitoring()
|
||||
ssh_tunnel_monitoring = success
|
||||
|
||||
if success:
|
||||
status = ssh_tunnel_manager.get_status()
|
||||
if status["status"] == "not_configured":
|
||||
logger.info("[SSH-MONITOR] No tunnels configured (direct connection mode)")
|
||||
else:
|
||||
logger.info(f"[SSH-MONITOR] ✅ Monitoring active: {status['status']}")
|
||||
else:
|
||||
logger.warning("[SSH-MONITOR] ⚠️ Failed to start monitoring")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[SSH-MONITOR] ⚠️ Init failed: {e}")
|
||||
ssh_tunnel_monitoring = False
|
||||
|
||||
|
||||
async def run_telegram_bot():
|
||||
"""Run Telegram bot as background task."""
|
||||
logger.info("[TELEGRAM] Starting bot...")
|
||||
@@ -381,7 +416,10 @@ async def startup_event():
|
||||
# Step 5: Initialize email-server cache for multi-Oracle (US-003)
|
||||
await init_email_server_cache()
|
||||
|
||||
# Step 6: Start Telegram bot as background task
|
||||
# Step 6: Initialize SSH tunnel monitoring (auto-reconnect)
|
||||
await init_ssh_tunnel_monitoring()
|
||||
|
||||
# Step 7: Start Telegram bot as background task
|
||||
if settings.telegram_bot_token:
|
||||
telegram_bot_task = asyncio.create_task(run_telegram_bot())
|
||||
logger.info("[STARTUP] ✅ Telegram bot task created")
|
||||
@@ -401,13 +439,24 @@ async def startup_event():
|
||||
@app.on_event("shutdown")
|
||||
async def shutdown_event():
|
||||
"""Application shutdown - Cleanup resources."""
|
||||
global telegram_bot_task, ocr_job_worker_running, cleanup_task_running, email_cache_running
|
||||
global telegram_bot_task, ocr_job_worker_running, cleanup_task_running, email_cache_running, ssh_tunnel_monitoring
|
||||
|
||||
logger.info("=" * 80)
|
||||
logger.info("[SHUTDOWN] Stopping ROA2WEB Unified Backend...")
|
||||
logger.info("=" * 80)
|
||||
|
||||
try:
|
||||
# Stop SSH tunnel monitoring
|
||||
if ssh_tunnel_monitoring:
|
||||
logger.info("[SHUTDOWN] Stopping SSH tunnel monitoring...")
|
||||
try:
|
||||
from backend.shared.ssh_tunnel_manager import ssh_tunnel_manager
|
||||
await ssh_tunnel_manager.stop_monitoring()
|
||||
ssh_tunnel_monitoring = False
|
||||
logger.info("[SHUTDOWN] SSH tunnel monitoring stopped")
|
||||
except Exception as e:
|
||||
logger.error(f"[SHUTDOWN] SSH tunnel monitoring error: {e}")
|
||||
|
||||
# Stop email cache auto-refresh (US-003)
|
||||
if email_cache_running:
|
||||
logger.info("[SHUTDOWN] Stopping email cache auto-refresh...")
|
||||
@@ -609,6 +658,14 @@ async def health_check():
|
||||
except Exception as e:
|
||||
health_status["modules"]["ocr_worker"] = f"error: {str(e)}"
|
||||
|
||||
# Check SSH tunnels
|
||||
global ssh_tunnel_monitoring
|
||||
try:
|
||||
from backend.shared.ssh_tunnel_manager import ssh_tunnel_manager
|
||||
health_status["modules"]["ssh_tunnels"] = ssh_tunnel_manager.get_status()
|
||||
except Exception as e:
|
||||
health_status["modules"]["ssh_tunnels"] = f"error: {str(e)}"
|
||||
|
||||
return health_status
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user