//! The jac-serve binary: an in-memory jacquard host on one port.
//!
//! ```text
//! cargo run -p jac-serve # empty state, port 8787
//! cargo run -p jac-serve -- --seed-demo # start with the walkthrough seeded
//! JAC_SERVE_PORT=9000 cargo run -p jac-serve
//!
//! # Import a shelf of git repositories and write them to disk, then exit.
//! cargo run -p jac-serve -- --import-dir ~/github/one --out ~/jack/one
//! cargo run -p jac-serve -- --import-dir ~/github/one --out ~/jack/one \
//! --commits 25 --paths 400
//! ```
use std::sync::Arc;
use jac_serve::state::AppState;
use jac_telemetry::{S10Config, Telemetry};
/// `--name value`, absent when the flag is not given.
fn flag(args: &[String], name: &str) -> Option<String> {
args.iter()
.position(|a| a == name)
.and_then(|i| args.get(i + 1))
.cloned()
}
/// A leading `~` is the shell's job, and this may not run under one.
fn expand(path: &str) -> String {
match path.strip_prefix("~/") {
Some(rest) => {
std::env::var("HOME").map_or_else(|_| path.to_owned(), |home| format!("{home}/{rest}"))
}
None => path.to_owned(),
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let telemetry = Telemetry::start(S10Config::from_env(), "jac-serve");
if telemetry.enabled() {
println!("telemetry: reporting to s10");
} else {
println!("telemetry: disabled (set S10_INGEST_URL and S10_INGEST_KEY to enable)");
}
let state = Arc::new(AppState::with_telemetry(telemetry));
// Batch import: does its work and exits without ever binding a port.
let args: Vec<String> = std::env::args().collect();
if let Some(root) = flag(&args, "--import-dir") {
let out = flag(&args, "--out").unwrap_or_else(|| "./jack".to_owned());
let bounds = jac_serve::gitimport::Bounds {
commits: flag(&args, "--commits")
.and_then(|v| v.parse().ok())
.unwrap_or(20),
paths: flag(&args, "--paths")
.and_then(|v| v.parse().ok())
.unwrap_or(300),
};
let root = expand(&root);
let out = expand(&out);
println!("importing every git repo under {root}");
println!(
" bounds: {} commits, {} paths each",
bounds.commits, bounds.paths
);
let index = jac_serve::batch::shelf(
std::path::Path::new(&root),
std::path::Path::new(&out),
&bounds,
)?;
println!("{}", serde_json::to_string_pretty(&index["provenance"])?);
println!(
"imported {} repos ({} failed) -> {out}",
index["imported"], index["failed"]
);
return Ok(());
}
if std::env::args().any(|a| a == "--seed-demo") {
jac_serve::demo::seed(&state).map_err(|e| anyhow::anyhow!("demo seed failed: {e}"))?;
println!("seeded the demo arc: meridian-systems + halcyon-works");
}
let port = std::env::var("JAC_SERVE_PORT")
.or_else(|_| std::env::var("PORT"))
.ok()
.and_then(|p| p.parse::<u16>().ok())
.unwrap_or(8787);
let app = jac_serve::app(state);
let listener = tokio::net::TcpListener::bind(("0.0.0.0", port)).await?;
println!("jac-serve listening on http://localhost:{port}");
println!("honest scope: identities are declared, not authenticated; nothing persists");
axum::serve(listener, app)
.with_graceful_shutdown(async {
let _ = tokio::signal::ctrl_c().await;
})
.await?;
Ok(())
}