tty_web/
main.rs

1//! **tty-web** — web-based terminal emulator.
2//!
3//! Opens a real PTY in the browser over WebSocket. Each connection is backed by
4//! a persistent session that survives tab closes and reconnects.
5
6mod config;
7mod pty;
8mod session;
9mod terminal;
10mod web;
11
12use clap::Parser;
13use tokio::net::TcpListener;
14use tracing_subscriber::EnvFilter;
15
16use crate::config::{Config, LogFormat};
17use crate::session::SessionStore;
18
19#[tokio::main]
20async fn main() {
21    let config = Config::parse();
22
23    let filter =
24        EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&config.log_level));
25
26    match config.log_format {
27        LogFormat::Text => tracing_subscriber::fmt().with_env_filter(filter).init(),
28        LogFormat::Json => tracing_subscriber::fmt()
29            .json()
30            .with_env_filter(filter)
31            .init(),
32    };
33
34    let sessions = SessionStore::new();
35    let addr = std::net::SocketAddr::new(config.address, config.port);
36    let app = web::router(
37        config.shell,
38        config.pwd,
39        config.scrollback_limit * 1024,
40        sessions,
41    );
42
43    let listener = TcpListener::bind(addr).await.unwrap_or_else(|e| {
44        tracing::error!("failed to bind to {}: {}", addr, e);
45        std::process::exit(1);
46    });
47
48    tracing::info!("listening on http://{}", addr);
49
50    axum::serve(listener, app)
51        .with_graceful_shutdown(shutdown_signal())
52        .await
53        .unwrap_or_else(|e| {
54            tracing::error!("server error: {}", e);
55            std::process::exit(1);
56        });
57}
58
59async fn shutdown_signal() {
60    let ctrl_c = tokio::signal::ctrl_c();
61    let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
62        .expect("failed to install SIGTERM handler");
63
64    tokio::select! {
65        _ = ctrl_c => {
66            tracing::info!("received Ctrl+C, shutting down");
67        }
68        _ = sigterm.recv() => {
69            tracing::info!("received SIGTERM, shutting down");
70        }
71    }
72}