agent icon: add hyperhive.icon option + GET /icon endpoint

Foundation for the per-agent icon feature (#137).

- harness-base.nix: new hyperhive.icon option (nullable path to an
  SVG). An agent commits an SVG into its config repo and references
  it as ./icon.svg; when set it lands at /etc/hyperhive/icon.svg.
- web_ui.rs: GET /icon serves the configured SVG, falling back to the
  bundled hyperhive logo when none is set — so it always returns an
  image and consumers can hit it unconditionally.

Closes #139
This commit is contained in:
iris 2026-05-21 01:06:34 +02:00
parent a4cb66bffe
commit 39bd46b244
2 changed files with 35 additions and 0 deletions

View file

@ -118,6 +118,7 @@ pub async fn serve(
.route("/api/stats", get(api_stats))
.route("/screen", get(serve_screen))
.route("/screen/ws", get(screen_ws))
.route("/icon", get(serve_icon))
.with_state(state);
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let listener = bind_with_retry(addr, "web UI").await?;
@ -230,6 +231,18 @@ async fn serve_screen() -> impl IntoResponse {
)
}
/// This agent's icon. Serves the operator-configured SVG from
/// `/etc/hyperhive/icon.svg` (set via the `hyperhive.icon` agent.nix
/// option) when present, otherwise the bundled default hyperhive logo.
/// Always returns an image, so consumers (dashboard, favicon) can hit
/// `/icon` unconditionally without probing whether one is configured.
async fn serve_icon() -> impl IntoResponse {
const DEFAULT_ICON: &str = include_str!("../../branding/hyperhive.svg");
let body = std::fs::read_to_string("/etc/hyperhive/icon.svg")
.unwrap_or_else(|_| DEFAULT_ICON.to_string());
([("content-type", "image/svg+xml")], body)
}
/// Read `/etc/hyperhive/gui.json` and extract the `vnc_port` field.
/// Returns `None` if the file is absent or unparseable — GUI not enabled.
fn read_gui_json() -> Option<u16> {