Phase 6a: per-container web UI (axum); per-agent port hashed from name
This commit is contained in:
parent
14cb107125
commit
d0f954bbc1
9 changed files with 112 additions and 4 deletions
|
|
@ -8,6 +8,7 @@ workspace = true
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
axum.workspace = true
|
||||
clap.workspace = true
|
||||
hive-sh4re.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::time::Duration;
|
|||
|
||||
use anyhow::{Result, bail};
|
||||
use clap::{Parser, Subcommand};
|
||||
use hive_ag3nt::{DEFAULT_SOCKET, client};
|
||||
use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, web_ui};
|
||||
use hive_sh4re::{AgentRequest, AgentResponse};
|
||||
use tokio::process::Command;
|
||||
|
||||
|
|
@ -44,7 +44,19 @@ async fn main() -> Result<()> {
|
|||
|
||||
let cli = Cli::parse();
|
||||
match cli.cmd {
|
||||
Cmd::Serve { poll_ms } => serve(&cli.socket, Duration::from_millis(poll_ms)).await,
|
||||
Cmd::Serve { poll_ms } => {
|
||||
let port = std::env::var("HIVE_PORT")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u16>().ok())
|
||||
.unwrap_or(DEFAULT_WEB_PORT);
|
||||
let label = std::env::var("HIVE_LABEL").unwrap_or_else(|_| "hive-ag3nt".into());
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = web_ui::serve(label, port).await {
|
||||
tracing::error!(error = ?e, "web ui failed");
|
||||
}
|
||||
});
|
||||
serve(&cli.socket, Duration::from_millis(poll_ms)).await
|
||||
}
|
||||
Cmd::Send { to, body } => {
|
||||
let resp: AgentResponse =
|
||||
client::request(&cli.socket, &AgentRequest::Send { to, body }).await?;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::time::Duration;
|
|||
|
||||
use anyhow::{Result, bail};
|
||||
use clap::{Parser, Subcommand};
|
||||
use hive_ag3nt::{DEFAULT_SOCKET, client};
|
||||
use hive_ag3nt::{DEFAULT_SOCKET, DEFAULT_WEB_PORT, client, web_ui};
|
||||
use hive_sh4re::{ManagerRequest, ManagerResponse};
|
||||
|
||||
#[derive(Parser)]
|
||||
|
|
@ -52,7 +52,19 @@ async fn main() -> Result<()> {
|
|||
|
||||
let cli = Cli::parse();
|
||||
match cli.cmd {
|
||||
Cmd::Serve { poll_ms } => serve(&cli.socket, Duration::from_millis(poll_ms)).await,
|
||||
Cmd::Serve { poll_ms } => {
|
||||
let port = std::env::var("HIVE_PORT")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u16>().ok())
|
||||
.unwrap_or(DEFAULT_WEB_PORT);
|
||||
let label = std::env::var("HIVE_LABEL").unwrap_or_else(|_| "hm1nd".into());
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = web_ui::serve(label, port).await {
|
||||
tracing::error!(error = ?e, "web ui failed");
|
||||
}
|
||||
});
|
||||
serve(&cli.socket, Duration::from_millis(poll_ms)).await
|
||||
}
|
||||
Cmd::Send { to, body } => one_shot(&cli.socket, ManagerRequest::Send { to, body }).await,
|
||||
Cmd::Recv => one_shot(&cli.socket, ManagerRequest::Recv).await,
|
||||
Cmd::Spawn { name } => one_shot(&cli.socket, ManagerRequest::Spawn { name }).await,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
//! `hive-m1nd` (manager) binaries.
|
||||
|
||||
pub mod client;
|
||||
pub mod web_ui;
|
||||
|
||||
/// Default socket path inside the container — bind-mounted by `hive-c0re`.
|
||||
pub const DEFAULT_SOCKET: &str = "/run/hive/mcp.sock";
|
||||
|
||||
/// Default web UI port — used when `HIVE_PORT` env is unset.
|
||||
pub const DEFAULT_WEB_PORT: u16 = 8042;
|
||||
|
|
|
|||
40
hive-ag3nt/src/web_ui.rs
Normal file
40
hive-ag3nt/src/web_ui.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//! Per-container HTTP UI. Phase 6 minimum — a status page on a host port.
|
||||
//! Containers share the host's network namespace (privateNetwork = false), so
|
||||
//! each instance must bind a distinct port. `HIVE_PORT` is set per agent by
|
||||
//! `hive-c0re`'s generated per-agent flake (deterministic from agent name).
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use axum::{Router, extract::State, response::Html, routing::get};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
label: String,
|
||||
}
|
||||
|
||||
pub async fn serve(label: String, port: u16) -> Result<()> {
|
||||
let state = AppState { label };
|
||||
let app = Router::new().route("/", get(index)).with_state(state);
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
let listener = tokio::net::TcpListener::bind(addr)
|
||||
.await
|
||||
.with_context(|| format!("bind web UI on port {port}"))?;
|
||||
tracing::info!(%port, "web UI listening");
|
||||
axum::serve(listener, app).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn index(State(state): State<AppState>) -> Html<String> {
|
||||
Html(format!(
|
||||
"<!doctype html>\n\
|
||||
<html>\n\
|
||||
<head><title>{label}</title></head>\n\
|
||||
<body>\n\
|
||||
<h1>{label}</h1>\n\
|
||||
<p>hyperhive harness placeholder. Phase 6a: this page exists.</p>\n\
|
||||
</body>\n\
|
||||
</html>\n",
|
||||
label = state.label,
|
||||
))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue