#[macro_use] extern crate lazy_static; use actix_files; use actix_web::{App, HttpServer}; use structopt::StructOpt; use webterm::WebTermExt; use std::net::TcpListener; use std::process::Command; #[derive(StructOpt, Debug)] #[structopt(name = "webterm-server")] struct Opt { /// The port to listen on #[structopt(short, long, default_value = "8082")] port: u16, /// The host or IP to listen on #[structopt(short, long, default_value = "localhost")] host: String, /// The command to execute #[structopt(short, long, default_value = "/bin/sh")] command: String, } lazy_static! { static ref OPT: Opt = Opt::from_args(); } fn main() { pretty_env_logger::init(); // Normalize common hostnames that sometimes resolve to IPv6-only addresses // which can cause platform-specific bind failures. Mapping `localhost` to // 127.0.0.1 makes behavior predictable on systems where `::1` would otherwise // be selected. let host = if OPT.host == "localhost" { "127.0.0.1".to_string() } else { OPT.host.clone() }; let bind_addr = format!("{}:{}", host, OPT.port); println!("Starting webterm server on http://{}", bind_addr); // Single factory closure variable that we reuse for HttpServer::new. // The closure does not capture any stack variables (it references the static // `OPT`), so it can act as a simple, repeated factory for the server. let factory = || { App::new() .service(actix_files::Files::new("/assets", "./static")) .service(actix_files::Files::new("/static", "./node_modules")) .webterm_socket("/websocket", |_req| { // Use the static OPT inside the handler; this does not make the // outer `factory` closure capture stack variables, so factory // remains a zero-capture closure (a function item/type). let mut cmd = Command::new(OPT.command.clone()); cmd.env("TERM", "xterm"); cmd }) .webterm_ui("/", "/websocket", "/static") }; // Bind a std::net::TcpListener ourselves and hand it to actix via `listen`. // This avoids actix's address parser producing EINVAL on some platforms. let listener = match TcpListener::bind(&bind_addr) { Ok(l) => l, Err(e) => { eprintln!("Failed to bind TcpListener to {}: {}", bind_addr, e); eprintln!("Try `--host 0.0.0.0` or `--host 127.0.0.1` to bind explicitly."); std::process::exit(1); } }; let server = HttpServer::new(factory) .listen(listener) .unwrap_or_else(|e| { eprintln!("Failed to listen on {}: {}", bind_addr, e); std::process::exit(1); }); println!("Listening on http://{}", bind_addr); if let Err(e) = server.run() { eprintln!("Server run failed: {}", e); std::process::exit(1); } }