- 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
91 lines
2.9 KiB
Rust
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);
|
|
}
|
|
}
|