frontend: wire static-dir env var + per-agent extraFiles option
Phase 3 of #273. Container plumbing for the bundled frontend dist: - flake.nix overlay: `pkgs.hyperhive-frontend` exposed for the agent / manager containers (mirrors the existing `pkgs.hyperhive` pattern); module argument `hyperhiveFrontend = system: self .packages.${system}.frontend` threads the package into the host hive-c0re module without forcing operators to apply the overlay on their host pkgs. - `services.hive-c0re.frontend` option: pinned to the flake's frontend package by default, overridable for custom dashboard SPAs. The hive-c0re systemd service gets `HIVE_STATIC_DIR = ${cfg.frontend}/dashboard` — the Rust binary will pick it up in Phase 4. - `hyperhive.frontend.dist` option: per-container, defaults to `pkgs.hyperhive-frontend`. Override to ship a fully custom agent SPA (advanced; the default + extraFiles flow handles the common 'add files' case). - `hyperhive.frontend.extraFiles` option: attrsOf submodule (mirroring the `hyperhive.extraMcpServers` shape per damocles' request so existing #322-style assertions keep their grip). Each entry has `source` (path relative to agent.nix) and `target` (URL/disk prefix within the merged static tree, defaulting to the attribute name). Operator-named example: the bitburner agent drops `bitburner-dist` into `/bitburner/` alongside the default agent UI at `/`. - `hyperhive.frontend.mergedDist` (readOnly): the runCommand derivation that composes `agent/` from the default dist plus every `extraFiles` entry. Aborts on overwrite so a filename collision becomes a build error rather than a silent dist swap. agent-base.nix + manager.nix set their respective systemd service `HIVE_STATIC_DIR` to this merged path. Until Phase 4 lands, the env var is set but unused — the Rust binaries still serve assets via `include_str!`. The cutover happens in the next commit on this branch. Refs #273.
This commit is contained in:
parent
c8af7bc70c
commit
892e034908
5 changed files with 145 additions and 3 deletions
|
|
@ -69,6 +69,13 @@
|
||||||
overlays = {
|
overlays = {
|
||||||
default = final: prev: {
|
default = final: prev: {
|
||||||
hyperhive = self.packages.${prev.stdenv.hostPlatform.system}.default;
|
hyperhive = self.packages.${prev.stdenv.hostPlatform.system}.default;
|
||||||
|
# Bundled frontend dist (see ./nix/frontend.nix). Output is
|
||||||
|
# $out/{dashboard,agent}/; consumers pick the surface they
|
||||||
|
# need. Exposed via the overlay so containers' nix evaluations
|
||||||
|
# can reach it as `pkgs.hyperhive-frontend` once the overlay
|
||||||
|
# is applied (manager + agent containers both apply it via
|
||||||
|
# `mkContainer` further down).
|
||||||
|
hyperhive-frontend = self.packages.${prev.stdenv.hostPlatform.system}.frontend;
|
||||||
};
|
};
|
||||||
claude-unstable =
|
claude-unstable =
|
||||||
final: prev:
|
final: prev:
|
||||||
|
|
@ -102,6 +109,7 @@
|
||||||
# builds (already applied internally in `nixosConfigurations`).
|
# builds (already applied internally in `nixosConfigurations`).
|
||||||
hive-c0re = import ./nix/modules/hive-c0re.nix {
|
hive-c0re = import ./nix/modules/hive-c0re.nix {
|
||||||
hyperhivePackage = system: self.packages.${system}.default;
|
hyperhivePackage = system: self.packages.${system}.default;
|
||||||
|
hyperhiveFrontend = system: self.packages.${system}.frontend;
|
||||||
hyperhiveFlake = "${self}";
|
hyperhiveFlake = "${self}";
|
||||||
};
|
};
|
||||||
hive-forge = ./nix/modules/hive-forge.nix;
|
hive-forge = ./nix/modules/hive-forge.nix;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
{
|
{
|
||||||
hyperhivePackage,
|
hyperhivePackage,
|
||||||
|
hyperhiveFrontend,
|
||||||
hyperhiveFlake,
|
hyperhiveFlake,
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
|
@ -25,6 +26,19 @@ in
|
||||||
defaultText = lib.literalExpression "hyperhive.packages.\${system}.default";
|
defaultText = lib.literalExpression "hyperhive.packages.\${system}.default";
|
||||||
description = "Package that provides /bin/hive-c0re.";
|
description = "Package that provides /bin/hive-c0re.";
|
||||||
};
|
};
|
||||||
|
frontend = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = hyperhiveFrontend pkgs.stdenv.hostPlatform.system;
|
||||||
|
defaultText = lib.literalExpression "hyperhive.packages.\${system}.frontend";
|
||||||
|
description = ''
|
||||||
|
Bundled frontend dist (see `./nix/frontend.nix`). Output has
|
||||||
|
`dashboard/` and `agent/` subdirectories — hive-c0re serves
|
||||||
|
`dashboard/` via `tower_http::ServeDir` from the path passed
|
||||||
|
in `HIVE_STATIC_DIR`. Override to ship a custom dashboard SPA;
|
||||||
|
the JSON contract (`/api/state`, the SSE streams, the action
|
||||||
|
endpoints) is the source of truth for any replacement.
|
||||||
|
'';
|
||||||
|
};
|
||||||
hyperhiveFlake = lib.mkOption {
|
hyperhiveFlake = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = hyperhiveFlake;
|
default = hyperhiveFlake;
|
||||||
|
|
@ -114,6 +128,10 @@ in
|
||||||
];
|
];
|
||||||
environment = {
|
environment = {
|
||||||
HYPERHIVE_GIT = "${pkgs.git}/bin/git";
|
HYPERHIVE_GIT = "${pkgs.git}/bin/git";
|
||||||
|
# Path to the dashboard static dist. The hive-c0re axum router
|
||||||
|
# serves this via `tower_http::ServeDir` for any path it doesn't
|
||||||
|
# match against an API/action route.
|
||||||
|
HIVE_STATIC_DIR = "${cfg.frontend}/dashboard";
|
||||||
} // lib.optionalAttrs config.hyperhive.forge.enable {
|
} // lib.optionalAttrs config.hyperhive.forge.enable {
|
||||||
# Agents poll this URL for Forgejo notifications. Derived from
|
# Agents poll this URL for Forgejo notifications. Derived from
|
||||||
# hyperhive.forge.{domain,httpPort} so it tracks forge config changes.
|
# hyperhive.forge.{domain,httpPort} so it tracks forge config changes.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{ pkgs, ... }:
|
{ pkgs, config, ... }:
|
||||||
{
|
{
|
||||||
imports = [ ./harness-base.nix ];
|
imports = [ ./harness-base.nix ];
|
||||||
|
|
||||||
|
|
@ -13,7 +13,15 @@
|
||||||
# anything an agent adds to its own `agent.nix` — without having to
|
# anything an agent adds to its own `agent.nix` — without having to
|
||||||
# touch the service definition.
|
# touch the service definition.
|
||||||
path = [ "/run/current-system/sw" ];
|
path = [ "/run/current-system/sw" ];
|
||||||
environment.SHELL = "${pkgs.bashInteractive}/bin/bash";
|
environment = {
|
||||||
|
SHELL = "${pkgs.bashInteractive}/bin/bash";
|
||||||
|
# Path to the merged agent static dist. The harness serves this
|
||||||
|
# via `tower_http::ServeDir` for any request it doesn't route to
|
||||||
|
# an API endpoint. `mergedDist` is the agent-default dist with
|
||||||
|
# `hyperhive.frontend.extraFiles` layered on top — both come
|
||||||
|
# from harness-base.nix.
|
||||||
|
HIVE_STATIC_DIR = "${config.hyperhive.frontend.mergedDist}";
|
||||||
|
};
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
ExecStart = "${pkgs.hyperhive}/bin/hive-ag3nt serve";
|
ExecStart = "${pkgs.hyperhive}/bin/hive-ag3nt serve";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,88 @@
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options.hyperhive.frontend.dist = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = pkgs.hyperhive-frontend;
|
||||||
|
defaultText = lib.literalExpression "pkgs.hyperhive-frontend";
|
||||||
|
description = ''
|
||||||
|
The shipped frontend dist (built by `nix/frontend.nix`). Output
|
||||||
|
layout: `dashboard/` (used by hive-c0re on the host) and
|
||||||
|
`agent/` (used here, layered with `extraFiles` below at
|
||||||
|
activation time). Override to ship a fully custom per-agent SPA;
|
||||||
|
the JSON contract (`/api/state`, `/events/stream`, the action
|
||||||
|
endpoints) is the source of truth for any replacement.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
options.hyperhive.frontend.mergedDist = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
readOnly = true;
|
||||||
|
description = ''
|
||||||
|
Computed: the merged static tree consumed by the harness via
|
||||||
|
`HIVE_STATIC_DIR`. Composed at evaluation time by copying
|
||||||
|
`hyperhive.frontend.dist`'s `agent/` subdir as the base, then
|
||||||
|
layering each `extraFiles` entry on top. Read-only —
|
||||||
|
consumers (`agent-base.nix`, `manager.nix`) reference this in
|
||||||
|
their systemd service environment; do not set directly.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
options.hyperhive.frontend.extraFiles = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
source = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
description = ''
|
||||||
|
Source file or directory to layer over the default
|
||||||
|
agent dist. A path (relative to `agent.nix` or
|
||||||
|
absolute) — nix copies its contents into the merged
|
||||||
|
static tree.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
target = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = name;
|
||||||
|
defaultText = lib.literalMD "the attribute name";
|
||||||
|
description = ''
|
||||||
|
Destination path within the merged static tree, used
|
||||||
|
as both the served URL prefix (`/<target>/...`) and
|
||||||
|
the on-disk layout in the merged derivation. Defaults
|
||||||
|
to the attribute name. Use forward slashes for
|
||||||
|
nested layouts (e.g. `"games/bitburner"`).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
bitburner = {
|
||||||
|
source = ./bitburner-dist;
|
||||||
|
# served at GET /bitburner/...
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
Per-agent additions layered on top of the default frontend
|
||||||
|
dist. Each entry copies its `source` into the served static
|
||||||
|
tree under `target`. Useful for shipping a self-contained
|
||||||
|
agent-specific surface alongside the standard agent UI (e.g.
|
||||||
|
the bitburner agent's game page at `/bitburner/`).
|
||||||
|
|
||||||
|
The default agent UI remains served at `/`; entries here only
|
||||||
|
add new routes and never replace the default. Path collisions
|
||||||
|
with default-dist filenames are a configuration error and
|
||||||
|
will surface as build failures (the merge derivation aborts
|
||||||
|
on overwrite).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
options.hyperhive.forge.url = lib.mkOption {
|
options.hyperhive.forge.url = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "http://localhost:3000";
|
default = "http://localhost:3000";
|
||||||
|
|
@ -385,6 +467,28 @@
|
||||||
environment.etc."hyperhive/claude-plugins-auto-update.json".text =
|
environment.etc."hyperhive/claude-plugins-auto-update.json".text =
|
||||||
builtins.toJSON config.hyperhive.claudePluginsAutoUpdate;
|
builtins.toJSON config.hyperhive.claudePluginsAutoUpdate;
|
||||||
|
|
||||||
|
# Merged frontend static tree. Base = `${frontend.dist}/agent/`,
|
||||||
|
# then each `extraFiles` entry is laid on top at its `target`
|
||||||
|
# path. The runCommand derivation aborts on overwrite so a
|
||||||
|
# filename collision with the default dist surfaces as a build
|
||||||
|
# failure rather than a silent override (operator gets a clear
|
||||||
|
# nix error rather than a confusing 404 / silent dist swap).
|
||||||
|
hyperhive.frontend.mergedDist = pkgs.runCommand "hyperhive-agent-frontend-merged" { } (
|
||||||
|
''
|
||||||
|
mkdir -p $out
|
||||||
|
cp -r ${config.hyperhive.frontend.dist}/agent/. $out/
|
||||||
|
chmod -R u+w $out
|
||||||
|
''
|
||||||
|
+ lib.concatMapStrings (entry: ''
|
||||||
|
mkdir -p $(dirname $out/${entry.target})
|
||||||
|
if [ -e $out/${entry.target} ]; then
|
||||||
|
echo "hyperhive.frontend.extraFiles: refusing to overwrite existing path '${entry.target}' in the default dist" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cp -r ${entry.source} $out/${entry.target}
|
||||||
|
'') (lib.attrValues config.hyperhive.frontend.extraFiles)
|
||||||
|
);
|
||||||
|
|
||||||
# HIVE_DEFAULT_MODEL seeds the initial model selection when no persisted
|
# HIVE_DEFAULT_MODEL seeds the initial model selection when no persisted
|
||||||
# model choice exists in the state dir. SHELL must be set so claude's
|
# model choice exists in the state dir. SHELL must be set so claude's
|
||||||
# Bash tool finds a POSIX shell.
|
# Bash tool finds a POSIX shell.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{ pkgs, ... }:
|
{ pkgs, config, ... }:
|
||||||
{
|
{
|
||||||
imports = [ ./harness-base.nix ];
|
imports = [ ./harness-base.nix ];
|
||||||
|
|
||||||
|
|
@ -24,6 +24,10 @@
|
||||||
HIVE_PORT = "8000";
|
HIVE_PORT = "8000";
|
||||||
HIVE_LABEL = "hm1nd";
|
HIVE_LABEL = "hm1nd";
|
||||||
SHELL = "${pkgs.bashInteractive}/bin/bash";
|
SHELL = "${pkgs.bashInteractive}/bin/bash";
|
||||||
|
# Manager runs the same hive-m1nd harness binary that serves
|
||||||
|
# the per-agent web UI; point it at the merged agent static dist
|
||||||
|
# (same shape as for sub-agents).
|
||||||
|
HIVE_STATIC_DIR = "${config.hyperhive.frontend.mergedDist}";
|
||||||
};
|
};
|
||||||
# See note in agent-base.nix — `/run/current-system/sw` makes the
|
# See note in agent-base.nix — `/run/current-system/sw` makes the
|
||||||
# harness service PATH track `environment.systemPackages` so anything
|
# harness service PATH track `environment.systemPackages` so anything
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue