use std::path::Path;
use std::sync::Arc;
use plecto_control::Control;
use plecto_server::serve_with_shutdown;
use tokio::net::TcpListener;
mod cli;
mod dev_cmd;
mod dev_key;
mod new_filter;
fn main() -> anyhow::Result<()> {
plecto_server::cap_malloc_arenas();
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?
.block_on(run())
}
async fn run() -> anyhow::Result<()> {
let _logging = tracing_subscriber::fmt()
.json()
.with_target(true)
.try_init();
const USAGE: &str = "usage: plecto <manifest.toml> [listen_addr] | plecto validate <manifest.toml> | plecto conformance <component.wasm> [--json] | plecto new-filter --lang rust <name> | plecto dev <filter-dir> | plecto schema | plecto --version";
let mut args = std::env::args().skip(1);
let manifest = match args.next().ok_or_else(|| anyhow::anyhow!(USAGE))?.as_str() {
"--version" | "-V" => {
println!(
"plecto {} (profile: {})",
env!("CARGO_PKG_VERSION"),
capability_profile()
);
return Ok(());
}
"schema" => {
println!("{}", plecto_control::manifest_json_schema()?);
return Ok(());
}
"validate" => {
let path = args
.next()
.ok_or_else(|| anyhow::anyhow!("usage: plecto validate <manifest.toml>"))?;
return cli::validate(&path);
}
"conformance" => return cli::conformance(args.collect()),
"new-filter" => return cli::new_filter(args.collect()),
#[cfg(unix)]
"dev" => {
let filter_dir = args
.next()
.ok_or_else(|| anyhow::anyhow!("usage: plecto dev <filter-dir>"))?;
let project_root = std::env::current_dir()
.map_err(|e| anyhow::anyhow!("resolve the current directory: {e}"))?;
dev_cmd::run(Path::new(&filter_dir), &project_root).await?;
return Ok(());
}
#[cfg(not(unix))]
"dev" => {
anyhow::bail!("plecto dev requires unix (it reloads via SIGHUP, like plecto serve)")
}
manifest => manifest.to_string(),
};
let listen_arg = args.next();
let control = Arc::new(
Control::from_manifest_path(Path::new(&manifest))
.map_err(|e| anyhow::anyhow!(plecto_control::diagnosed_message(&e)))?,
);
let listen = listen_arg
.or_else(|| control.listen_addr().map(str::to_string))
.unwrap_or_else(|| "127.0.0.1:8080".to_string());
#[cfg(unix)]
spawn_sighup_reload(control.clone());
plecto_server::raise_nofile_limit();
let listener = TcpListener::bind(&listen).await?;
tracing::info!(%listen, version = %control.config_version(), "plecto fast path listening");
serve_with_shutdown(control, listener, shutdown_signal()).await?;
tracing::info!("plecto fast path stopped");
Ok(())
}
fn capability_profile() -> String {
let compiled: Vec<&str> = [
(cfg!(feature = "outbound-http"), "outbound-http"),
(cfg!(feature = "outbound-tcp"), "outbound-tcp"),
(cfg!(feature = "fat-guest"), "fat-guest"),
]
.into_iter()
.filter_map(|(on, name)| on.then_some(name))
.collect();
match compiled.as_slice() {
[] => "minimal".to_owned(),
["outbound-http", "outbound-tcp", "fat-guest"] => {
"capabilities (outbound-http, outbound-tcp, fat-guest)".to_owned()
}
partial => format!("custom ({})", partial.join(", ")),
}
}
#[cfg(unix)]
pub(crate) fn spawn_sighup_reload(control: Arc<plecto_control::Control>) {
std::thread::spawn(move || match plecto_control::SignalReloadSource::sighup() {
Ok(mut source) => plecto_control::serve_reloads(&control, &mut source),
Err(e) => {
tracing::error!(error = %e, "cannot register SIGHUP handler; hot reload disabled")
}
});
}
async fn shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
match signal(SignalKind::terminate()) {
Ok(mut term) => {
tokio::select! {
_ = term.recv() => {}
_ = tokio::signal::ctrl_c() => {}
}
}
Err(e) => {
tracing::error!(error = %e, "cannot register SIGTERM handler; ctrl-c only");
let _ = tokio::signal::ctrl_c().await;
}
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
}