This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/crates/maestro-web/src/main.rs
T
naps62-yoloandClaude Opus 4.7 bc3237287f feat(maestro): maestro-web standalone binary + reverse-proxy (NAP-241)
New crate crates/maestro-web/: axum server that

* embeds the web/dist SPA bundle via include_dir! (graceful placeholder when
  pnpm build hasn't run yet — keeps fresh checkouts compiling)
* reverse-proxies /api/* HTTP to a co-located daemon (default
  http://127.0.0.1:7070) via reqwest, stripping hop-by-hop headers
* reverse-proxies /api/attach/:id and /api/events WebSockets via
  tokio_tungstenite::connect_async + a tokio task pumping both directions
* reads bind_addr / daemon_url / dev_proxy_target from the new [web]
  config section; env-var overrides MAESTRO_WEB_BIND_ADDR / _DAEMON_URL /
  _DEV_PROXY_TARGET

Integration tests spin up a mock daemon + maestro-web pair on random ports
and verify HTTP forward, SPA fallback, WS echo, and 502 on unreachable
daemon.

Adds a top-level Justfile (`just web` = pnpm install + pnpm build + cargo
build), crates/maestro-web/README.md (config + trust model + dev workflow),
and a docs/INSTALL.md section on optional web UI install.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:23:14 +00:00

71 lines
2.1 KiB
Rust

//! `maestro-web` binary entry point. See `lib.rs` for the architecture.
use std::error::Error;
use std::path::PathBuf;
use std::process::ExitCode;
use clap::Parser;
use maestro_web::config::{default_config_path, EnvOverrides};
use maestro_web::{load_config, router};
use tokio::net::TcpListener;
use tracing_subscriber::EnvFilter;
#[derive(Debug, Parser)]
#[command(
name = "maestro-web",
version,
about = "Serves the Maestro web UI and reverse-proxies to a co-located maestrod"
)]
struct Cli {
/// Override the unified Maestro config-file path.
#[arg(long, env = "MAESTRO_CONFIG")]
config: Option<PathBuf>,
}
#[tokio::main]
async fn main() -> ExitCode {
let cli = Cli::parse();
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::fmt().with_env_filter(filter).init();
match run(cli.config).await {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
tracing::error!(error = %e, "maestro-web failed");
eprintln!("maestro-web: {e}");
ExitCode::FAILURE
}
}
}
async fn run(config_override: Option<PathBuf>) -> Result<(), Box<dyn Error>> {
let path = match config_override {
Some(p) => p,
None => default_config_path()?,
};
let cfg = load_config(&path, EnvOverrides::from_env())?;
tracing::info!(
bind = %cfg.bind_addr,
daemon = %cfg.daemon_url,
dev_proxy = ?cfg.dev_proxy_target,
"loaded config",
);
if cfg.dev_proxy_target.is_some() {
tracing::warn!(
"dev_proxy_target is set in config but not yet honored by the server \
(NAP-241 v1 ships only the embedded-bundle path)"
);
}
let router = router(&cfg);
let listener = TcpListener::bind(cfg.bind_addr).await?;
tracing::info!(addr = %listener.local_addr()?, "serving");
axum::serve(listener, router)
.with_graceful_shutdown(async {
let _ = tokio::signal::ctrl_c().await;
tracing::info!("ctrl-c received — shutting down");
})
.await?;
Ok(())
}