Add serve command to quire-ci
Adds a  subcommand that starts an axum HTTP server
with  and  endpoints. Moves axum to a workspace dependency
and adds tracing-subscriber for structured logging. This is the
foundation for the CI server — endpoints will be added incrementally.

Assisted-by: Owl Alpha via pi
change xuumstlrrsszmxluxmvowvvwwuupzmuq
commit 436171f927688eae21cf3dc21de3746fe900549e
author Alpha Chen <alpha@kejadlen.dev>
date
parent wlwzzqwq
diff --git a/Cargo.lock b/Cargo.lock
index 51628a3..e87f313 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2548,6 +2548,7 @@ dependencies = [
 name = "quire-ci"
 version = "0.1.0"
 dependencies = [
+ "axum",
  "facet",
  "figue",
  "fs-err",
@@ -2566,6 +2567,7 @@ dependencies = [
  "tokio",
  "tracing",
  "tracing-opentelemetry",
+ "tracing-subscriber",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index e88dde7..d5ea1c2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,6 +3,7 @@ members = ["quire-ci", "quire-core", "quire-server"]
 resolver = "3"
 
 [workspace.dependencies]
+axum = "*"
 clap = { version = "*", features = ["derive", "env"] }
 facet = "*"
 fs-err = "*"
diff --git a/quire-ci/Cargo.toml b/quire-ci/Cargo.toml
index 76a758b..5fedb85 100644
--- a/quire-ci/Cargo.toml
+++ b/quire-ci/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
 edition = "2024"
 
 [dependencies]
+axum = { workspace = true }
 facet = { workspace = true }
 figue = "*"
 fs-err = { workspace = true }
@@ -17,8 +18,9 @@ serde = { workspace = true }
 serde_json = { workspace = true }
 tempfile = { workspace = true }
 thiserror = { workspace = true }
-tokio = { workspace = true, features = ["rt-multi-thread"] }
+tokio = { workspace = true, features = ["full"] }
 opentelemetry = { workspace = true }
 opentelemetry_sdk = { workspace = true }
 tracing = { workspace = true }
 tracing-opentelemetry = { workspace = true }
+tracing-subscriber = { workspace = true }
diff --git a/quire-ci/src/main.rs b/quire-ci/src/main.rs
index 5179ad3..7d5ed03 100644
--- a/quire-ci/src/main.rs
+++ b/quire-ci/src/main.rs
@@ -1,3 +1,4 @@
+mod server;
 mod sink;
 
 use std::cell::RefCell;
@@ -120,6 +121,13 @@ enum Commands {
         #[facet(args::named, default)]
         git_dir: Option<PathBuf>,
     },
+
+    /// Start the HTTP server.
+    Serve {
+        /// Port to listen on.
+        #[facet(args::named, default = 3000)]
+        port: u16,
+    },
 }
 
 /// RAII wrapper around a tempdir holding captured sh logs. On drop,
@@ -288,6 +296,13 @@ fn main() -> Result<()> {
 
     match cli.command {
         Commands::Validate => validate(workspace),
+        Commands::Serve { port } => {
+            let rt = tokio::runtime::Builder::new_multi_thread()
+                .enable_all()
+                .build()
+                .into_diagnostic()?;
+            rt.block_on(server::run(port))
+        }
         Commands::Run {
             events,
             out_dir,
diff --git a/quire-ci/src/server.rs b/quire-ci/src/server.rs
new file mode 100644
index 0000000..9ac7a0d
--- /dev/null
+++ b/quire-ci/src/server.rs
@@ -0,0 +1,49 @@
+use std::net::SocketAddr;
+
+use axum::Router;
+use axum::routing::get;
+use miette::{IntoDiagnostic, Result};
+use tracing_subscriber::EnvFilter;
+use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
+use tracing_subscriber::util::SubscriberInitExt;
+
+const VERSION: &str = env!("QUIRE_VERSION");
+
+async fn health() -> &'static str {
+    "ok"
+}
+
+async fn index() -> String {
+    format!("quire-ci {VERSION}\n")
+}
+
+pub async fn run(port: u16) -> Result<()> {
+    let filter = EnvFilter::builder()
+        .with_env_var("QUIRE_LOG")
+        .from_env()
+        .into_diagnostic()?;
+
+    let fmt_layer = tracing_subscriber::fmt::layer().with_writer(std::io::stderr);
+
+    tracing_subscriber::registry()
+        .with(fmt_layer)
+        .with(filter)
+        .init();
+
+    let app = Router::new()
+        .route("/health", get(health))
+        .route("/", get(index));
+
+    let addr = SocketAddr::from(([0, 0, 0, 0], port));
+    tracing::info!(%addr, "starting HTTP server");
+
+    let listener = tokio::net::TcpListener::bind(addr)
+        .await
+        .into_diagnostic()?;
+
+    axum::serve(listener, app).await.into_diagnostic()?;
+
+    tracing::info!(version = %VERSION, "server shutdown complete");
+
+    Ok(())
+}