Compare commits

..
14 changed files with 124 additions and 403 deletions

114
Cargo.lock generated
View file

@ -135,6 +135,15 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "archery"
version = "1.2.2"
@ -981,6 +990,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "derive_more"
version = "1.0.0"
@ -1256,6 +1276,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
"zlib-rs",
]
[[package]]
@ -1660,6 +1681,7 @@ dependencies = [
"url",
"utoipa",
"utoipa-axum",
"utoipa-swagger-ui",
]
[[package]]
@ -3902,6 +3924,41 @@ dependencies = [
"smallvec",
]
[[package]]
name = "rust-embed"
version = "8.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097"
dependencies = [
"mime_guess",
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.119",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0"
dependencies = [
"sha2 0.11.0",
"walkdir",
]
[[package]]
name = "rustc-hash"
version = "2.1.3"
@ -5021,6 +5078,31 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "utoipa-swagger-ui"
version = "9.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55"
dependencies = [
"axum",
"base64",
"mime_guess",
"regex",
"rust-embed",
"serde",
"serde_json",
"url",
"utoipa",
"utoipa-swagger-ui-vendored",
"zip",
]
[[package]]
name = "utoipa-swagger-ui-vendored"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2eebbbfe4093922c2b6734d7c679ebfebd704a0d7e56dfcb0d05818ce28977d"
[[package]]
name = "uuid"
version = "1.24.0"
@ -5663,8 +5745,40 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "zip"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308"
dependencies = [
"arbitrary",
"crc32fast",
"flate2",
"indexmap",
"memchr",
"zopfli",
]
[[package]]
name = "zlib-rs"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]]
name = "zopfli"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
dependencies = [
"bumpalo",
"crc32fast",
"log",
"simd-adler32",
]

View file

@ -116,3 +116,8 @@ hmac = "0.13"
sha2 = "0.11"
utoipa = { version = "5", features = ["axum_extras"] }
utoipa-axum = "0.2"
# "vendored" bundles a pinned Swagger UI dist via utoipa-swagger-ui-vendored
# instead of the crate's default curl-download build script — required in
# the nix sandbox, which has no network at build time outside the
# Cargo.lock-pinned dependency fetch.
utoipa-swagger-ui = { version = "9", features = ["axum", "vendored"] }

View file

@ -142,9 +142,6 @@
agentBaseToplevel = lib.mkDefault self.packages.x86_64-linux.agent-base-toplevel;
managerToplevel = lib.mkDefault self.packages.x86_64-linux.ruth-toplevel;
};
services.hyperhive.gateway.swaggerUiTheme =
lib.mkDefault
self.packages.${pkgs.stdenv.hostPlatform.system}.swagger-ui-theme;
};
hive-ci = ./nix/host-modules/hive-ci.nix;
hive-forge = ./nix/host-modules/hive-forge;

View file

@ -53,6 +53,7 @@ tracing-subscriber.workspace = true
problem_details = { version = "0.9.0", features = ["axum"] }
utoipa.workspace = true
utoipa-axum.workspace = true
utoipa-swagger-ui.workspace = true
[dev-dependencies]
tempfile = "3"

View file

@ -7,23 +7,18 @@ use std::sync::Arc;
use anyhow::{Context, Result};
use axum::{
Json,
http::StatusCode,
response::{IntoResponse, Response},
routing::get,
};
use utoipa::OpenApi;
use utoipa_axum::{router::OpenApiRouter, routes};
use utoipa_swagger_ui::SwaggerUi;
use crate::coordinator::Coordinator;
use crate::lifecycle;
/// Root of the auto-generated `OpenAPI` spec, served raw at
/// `/api/openapi.json` — see [`utoipa`]. Swagger UI itself (browsable at
/// `/api/docs`) is nginx-hosted straight from the nix store now (see
/// `nix/host-modules/hive-gateway/vhosts.nix`'s `swaggerUiLocations`);
/// c0re only needs to serve the JSON this route generates, not the whole
/// vendored UI. Only routes carrying a
/// Root of the auto-generated `OpenAPI` spec (`/api/openapi.json`, browsable
/// at `/api/docs`) — see [`utoipa`]. Only routes carrying a
/// `#[utoipa::path(...)]` annotation show up; the rest of the (much
/// larger) route table below is undocumented for now. Deliberately
/// incremental: an unannotated route just doesn't appear in the spec,
@ -211,15 +206,8 @@ pub async fn serve(
.routes(routes!(state_snapshot::dashboard_stream))
.routes(routes!(state_snapshot::dashboard_history))
.split_for_parts();
// Just the JSON, not the UI — Swagger UI itself is nginx-hosted from
// the nix store (see the module doc comment above `ApiDoc`). `api`
// is `Clone`; each request gets its own owned copy for `Json` to
// serialize.
let app = router
.route(
"/api/openapi.json",
get(move || async move { Json(api.clone()) }),
)
.merge(SwaggerUi::new("/api/docs").url("/api/openapi.json", api))
.with_state(AppState {
coord,
webhook_secret,

View file

@ -24,13 +24,6 @@ let
# container's).
dashboardDist = "${config.services.hyperhive.c0re.servedFrontend}/dashboard";
# Full hyperhive-themed Swagger UI dist — nginx serves this whole
# tree straight from the store at /api/docs/, no hive-c0re fallback
# (see vhosts.nix's `swaggerUiLocations` and
# nix/packages/swagger-ui-{dist,theme}.nix). Own option under this
# module (not c0re's) — hive-c0re has no relationship to it.
swaggerUiTheme = cfg.swaggerUiTheme;
# Self-signed TLS is the implicit floor: when neither an operator cert
# (`tls.certDir`) nor ACME (`tls.acme.enable`) is configured, the gateway
# generates + serves a hive-CA-signed leaf (see hive-tls.nix). There is no
@ -268,7 +261,6 @@ in
matrixCfg
hyperhiveDomain
dashboardDist
swaggerUiTheme
tlsCert
tlsKey
;

View file

@ -254,21 +254,6 @@ in
};
};
swaggerUiTheme = lib.mkOption {
type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.swagger-ui-theme";
description = ''
Full Swagger UI static dist, hyperhive-themed (see
`nix/packages/swagger-ui-theme.nix`, built on
`nix/packages/swagger-ui-dist.nix`). The gateway serves this
whole tree directly at `/api/docs/` hive-c0re hosts none of
it, only the dynamic `/api/openapi.json` route (proxied
through, unaffected by this option). Override to ship a
custom theme (or the plain vendored dist) without a gateway
rebuild.
'';
};
hsts = {
enable = lib.mkOption {
type = lib.types.bool;

View file

@ -11,7 +11,6 @@
matrixCfg,
hyperhiveDomain,
dashboardDist,
swaggerUiTheme, # nix/packages/swagger-ui-theme.nix: has index.html + hyperhive-theme.css
errorPages, # ./error-pages.nix: { notFound, unreachable, unauthorized }
tlsCert,
tlsKey,
@ -305,40 +304,6 @@ let
proxyPass = "http://${cfg.upstreamHost}:${toString cfg.upstreamPort}";
};
};
# Swagger UI: nginx hosts the FULL themed dist (`swaggerUiTheme` —
# vendored Swagger UI + our overlay, see nix/packages/swagger-ui-
# dist.nix + swagger-ui-theme.nix) straight from the store, with NO
# fallback to hive-c0re at all — per the operator's shape: "core
# should not need the swagger ui at all if it is hosted in gateway"
# / "core only hosts the json". Only `/api/openapi.json`
# (the live-generated spec `index.html` fetches; not under this
# prefix) still proxies to c0re via "/api/" below — that's the one
# thing that has to stay dynamic.
#
# Prefix location, not exact-match: wins over "/api/" on plain
# prefix length (no ordering/`=` needed), and now needs to cover
# every file in the tree (bundle.js, maps, favicons, …), not just
# our 2 override files — hive-c0re no longer serves any of this as
# a fallback once its own `utoipa-swagger-ui` mount is removed.
# `= /api/docs` (no trailing slash) issues the same redirect
# `utoipa-swagger-ui`'s router used to: that mount is going away
# too, so nginx has to own it now, or the H0M3 hub's own `/api/docs`
# link (no trailing slash) would 404 once hive-c0re drops the route.
swaggerUiLocations = {
"= /api/docs" = {
extraConfig = ''
return 301 /api/docs/;
'';
};
"/api/docs/" = {
alias = "${swaggerUiTheme}/";
extraConfig = ''
index index.html;
${dashboardAuth}
'';
};
};
in
{
# Accept-header SPA map for the matrix GUI only (see docs/gateway.md
@ -360,7 +325,6 @@ in
// wellKnownLocations
// agentLocations
// dashboardProxyLocation
// swaggerUiLocations
// lib.optionalAttrs cfg.auth.enable {
# Internal-only target for the 401 error_page above.
# `internal` prevents direct client access; `alias` serves

View file

@ -18,13 +18,6 @@ let
inherit (nixpkgs.lib) nixosSystem;
};
# Plain vendored Swagger UI dist (./swagger-ui-dist.nix) + the
# hyperhive-themed overlay on top (./swagger-ui-theme.nix) — bound
# here (not just inline in the attrset below) so the theme
# derivation can take the dist derivation as an explicit input.
swagger-ui-dist = pkgs.callPackage ./swagger-ui-dist.nix { };
swagger-ui-theme = pkgs.callPackage ./swagger-ui-theme.nix { inherit swagger-ui-dist; };
# Every per-binary package: name → description. The single source of
# truth for the bin list — it drives the per-bin extractor packages
# and the `default` bundle, so adding a binary is one entry here.
@ -165,13 +158,6 @@ in
xdg-icons = pkgs.callPackage ./hive-xdg-icons.nix {
hyperhiveSvg = ../../branding/hyperhive.svg;
};
# Swagger UI: plain vendored dist + the hyperhive-themed overlay —
# see ./swagger-ui-dist.nix / ./swagger-ui-theme.nix (both computed
# above, in `let`). The gateway serves `swagger-ui-theme`'s full
# tree straight from the store at /api/docs/; a theme tweak is a
# gateway config change, not a hive-c0re rebuild. `swagger-ui-dist`
# is exposed too since it's independently useful/inspectable.
inherit swagger-ui-dist swagger-ui-theme;
# Pre-built per-container system closures. Exposed as packages
# so operators can `nix build .#agent-base-toplevel` (or wire

View file

@ -1,47 +0,0 @@
{
stdenv,
fetchurl,
}:
# Pure vendored Swagger UI 5.17.14 static dist — no hyperhive theming
# (see ./swagger-ui-theme.nix for that layer). Deliberately NOT sourced
# via the Rust `utoipa-swagger-ui-vendored` crate / Cargo.lock (per the
# operator's ask: a nix package that just contains the plain static
# dist, not fetched via cargo) — hive-c0re no longer needs to know this
# exists at all once the gateway hosts it directly.
#
# Sourced straight from the `swagger-ui-dist` npm package, which ships
# exactly the built `dist/` files (no source, no build step needed) at
# the same release as the Rust ecosystem's vendored copy — verified by
# comparing this tarball's recorded `gitHead` (in its `package.json`,
# `74ed0adebfc9c8dd0de2bf8e81495b022a66c083`) against the commit the
# `utoipa-swagger-ui-vendored` crate's own pinned zip was built from
# (same hash, printed by `unzip -l` on that zip's first entry).
stdenv.mkDerivation {
pname = "swagger-ui-dist";
version = "5.17.14";
src = fetchurl {
url = "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz";
hash = "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==";
};
sourceRoot = "package";
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out
# The npm package also carries a few node-consumption conveniences
# (package.json, index.js, absolute-path.js, README/LICENSE/NOTICE)
# alongside the actual browser dist — only copy what a web server
# should ever expose.
cp *.html *.css *.css.map *.js *.js.map *.png $out/
runHook postInstall
'';
meta = {
description = "Vendored Swagger UI 5.17.14 static dist (unthemed), from the swagger-ui-dist npm package";
homepage = "https://forge.darkest.space/hyperhive/hyperhive";
};
}

View file

@ -1,52 +0,0 @@
{
stdenv,
swagger-ui-dist,
}:
# hyperhive's Catppuccin Mocha re-theme of Swagger UI: overlays our 3
# hand-authored override files (`swagger-ui-theme/`, top-level — not
# under `hive-c0re/`, which has no relationship to these files) onto
# the plain vendored dist (./swagger-ui-dist.nix). This is the FULL
# tree hive-gateway's nginx serves at `/api/docs/` — hive-c0re hosts
# none of it, only the dynamic `/api/openapi.json` (see
# nix/host-modules/hive-gateway/vhosts.nix).
#
# Output layout: every file from `swagger-ui-dist`, with 3 replaced:
# index.html adds colors.css (live stylix theme) +
# hyperhive-theme.css <link>s
# hyperhive-theme.css new; the re-theme, consumes --baseNN vars
# swagger-initializer.js hardcodes `url: "/api/openapi.json"` —
# `utoipa-swagger-ui`'s `serve()` normally
# rewrites this per-request from a
# `{{config}}` placeholder its OWN build.rs
# injects (`replace_default_url_with_config`
# + `format_config`); hive-c0re doesn't serve
# this file at all anymore, so that rewrite
# is baked in statically here instead —
# hand-verified byte-for-byte against what
# `format_config` would produce, by applying
# the same transform to the real vendored
# file rather than typing it from scratch.
stdenv.mkDerivation {
pname = "hyperhive-swagger-ui-theme";
version = "0.1.0";
dontUnpack = true;
dontBuild = true;
installPhase = ''
runHook preInstall
mkdir -p $out
cp -r ${swagger-ui-dist}/. $out/
chmod -R u+w $out
install -m644 ${../../swagger-ui-theme/index.html} $out/index.html
install -m644 ${../../swagger-ui-theme/hyperhive-theme.css} $out/hyperhive-theme.css
install -m644 ${../../swagger-ui-theme/swagger-initializer.js} $out/swagger-initializer.js
runHook postInstall
'';
meta = {
description = "hyperhive Swagger UI Catppuccin Mocha re-theme full static dist, nginx-served at /api/docs/";
homepage = "https://forge.darkest.space/hyperhive/hyperhive";
};
}

View file

@ -1,161 +0,0 @@
/* Partial hyperhive reskin for the vendored Swagger UI at /api/docs.
Recolors chrome (background, topbar, borders, links, inputs) to
match the dashboard. Swagger's own semantic HTTP-method badge colours
and execute/cancel button colours are left alone those carry
meaning independent of hyperhive branding.
Loaded after swagger-ui.css (see index.html) so these rules win the
cascade without `!important`, except where noted below.
`--baseNN` values come from `/static/colors.css` (linked in
index.html, same origin as the dashboard) this follows the
operator's live stylix theme, not a hardcoded copy. See
frontend/packages/shared/src/colors.css for the base16 contract. */
/* Page chrome */
html,
body {
background: var(--base00);
}
.swagger-ui {
color: var(--base05);
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code",
"Source Code Pro", monospace;
}
/* Topbar */
.swagger-ui .topbar {
background: var(--base01);
border-bottom: 1px solid var(--base02);
}
.swagger-ui .topbar .download-url-wrapper input[type="text"] {
border-color: var(--base02);
background: var(--base00);
color: var(--base05);
}
.swagger-ui .topbar .download-url-wrapper .download-url-button {
background: var(--base0E);
color: var(--base01);
}
/* Info block + links */
.swagger-ui .info .title,
.swagger-ui .info li,
.swagger-ui .info p,
.swagger-ui .info table {
color: var(--base05);
}
.swagger-ui a.nostyle,
.swagger-ui a.nostyle:visited,
.swagger-ui .info a {
color: var(--base0D);
}
.swagger-ui .opblock-tag {
color: var(--base05);
border-bottom-color: var(--base02);
}
.swagger-ui .opblock-tag:hover {
background: color-mix(in srgb, var(--base0E) 8%, transparent);
}
/* Scheme/server selector bar. */
.swagger-ui .scheme-container {
background: var(--base01);
box-shadow: none;
border-bottom: 1px solid var(--base02);
}
/* Endpoint rows method-colour left border stays; only the card body
is recoloured. */
.swagger-ui .opblock {
background: var(--base01);
border-color: var(--base02);
}
.swagger-ui .opblock .opblock-summary-description,
.swagger-ui .opblock .opblock-summary-path,
.swagger-ui .opblock .opblock-summary-path__deprecated {
color: var(--base05);
}
.swagger-ui .opblock .opblock-section-header {
background: var(--base00);
}
.swagger-ui .opblock-description-wrapper p,
.swagger-ui .opblock-external-docs-wrapper p,
.swagger-ui .opblock-title_normal p,
.swagger-ui .parameter__name,
.swagger-ui .parameter__type,
.swagger-ui .parameter__deprecated,
.swagger-ui .parameter__in,
.swagger-ui table.model tr.property-row td {
color: var(--base05);
}
.swagger-ui .tab li,
.swagger-ui .opblock .opblock-section-header h4,
.swagger-ui .opblock .opblock-section-header label {
color: var(--base05);
}
/* Response / model tables. */
.swagger-ui table thead tr td,
.swagger-ui table thead tr th {
color: var(--base05);
border-bottom-color: var(--base02);
}
.swagger-ui .responses-inner h4,
.swagger-ui .responses-inner h5 {
color: var(--base05);
}
.swagger-ui .response-col_status {
color: var(--base05);
}
.swagger-ui .model-box,
.swagger-ui section.models {
background: var(--base01);
}
.swagger-ui section.models {
border-color: var(--base02);
}
.swagger-ui section.models h4 {
color: var(--base05);
}
.swagger-ui .model {
color: var(--base05);
}
/* Form controls */
.swagger-ui select,
.swagger-ui input[type="text"],
.swagger-ui input[type="password"],
.swagger-ui input[type="search"],
.swagger-ui input[type="email"],
.swagger-ui input[type="file"],
.swagger-ui textarea {
background: var(--base00);
color: var(--base05);
border-color: var(--base02);
}
/* Generic buttons (Try it out / Reset / Authorize etc) execute/cancel
keep Swagger's own colours. */
.swagger-ui .btn {
color: var(--base05);
border-color: var(--base02);
background: var(--base00);
}
.swagger-ui .btn.authorize {
color: var(--base0E);
border-color: var(--base0E);
}
.swagger-ui .btn.authorize svg {
fill: var(--base0E);
}
/* `!important`: highlight.js re-asserts this per-token inline, so a
plain override loses. Scoped to the code-sample block background
only token colours stay as highlight.js's theme provides. */
.swagger-ui .highlight-code {
background: var(--base00) !important;
}
.swagger-ui .microlight {
color: var(--base05);
}

View file

@ -1,30 +0,0 @@
<!-- Served by the gateway's nginx — one of 3 files (see also
hyperhive-theme.css, swagger-initializer.js) that
nix/packages/swagger-ui-theme.nix overlays onto the plain vendored
dist (swagger-ui-dist.nix), and the whole result is what
vhosts.nix's `swaggerUiLocations` serves at /api/docs/, wholesale,
with NO hive-c0re fallback for any file in the tree. Byte-identical
to the vendored Swagger UI's dist/index.html except two added
<link>s: `colors.css` (same origin as the dashboard — so this
resolves the operator's live stylix theme, not a frozen copy) and
hyperhive-theme.css, which consumes its `--baseNN` vars. -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="stylesheet" type="text/css" href="/static/colors.css" />
<link rel="stylesheet" type="text/css" href="hyperhive-theme.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
</body>
</html>

View file

@ -1,21 +0,0 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
"dom_id": "#swagger-ui",
"url": "/api/openapi.json",
"deepLinking": true,
"layout": "StandaloneLayout",
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
});
//</editor-fold>
};