socktop-webterm/src/server.rs
jasonwitty 6e48c095ab Initial commit: Socktop WebTerm with k3s deployment
- Multi-architecture Docker image (ARM64 + AMD64)
- Kubernetes manifests for 3-replica deployment
- Traefik ingress configuration
- NGINX Proxy Manager integration
- ConfigMap-based configuration
- Automated build and deployment scripts
- Session monitoring tools
2025-11-28 01:31:33 -08:00

91 lines
2.9 KiB
Rust

#[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);
}
}