move avatar-PNG helpers to hive-core, configurable org avatar

core_avatar_png() and config_org_avatar_png() were hive-sh4re
functions used only by hive-c0re::forge::users. Move them there as
private path-resolution helpers (core_avatar_png_path,
config_org_avatar_png_path), following the existing
std::env::var(...).expect(...) required-env-var style used elsewhere
in hive-c0re.

The org avatar is now independently configurable:
services.hyperhive.c0re.orgAvatarPng (nullable path, default null)
lets an operator override just the agent-configs org avatar PNG
without replacing the whole assets package. Wired via a new
HIVE_ORG_AVATAR_PNG env var that falls back to the bundled PNG when
the option is unset. The core avatar stays under HIVE_ASSETS_DIR,
unchanged.
This commit is contained in:
iris 2026-08-09 23:04:18 +02:00 committed by mara
commit 3e388d2d46
4 changed files with 55 additions and 25 deletions

View file

@ -24,11 +24,36 @@ use crate::paths::FORGE_CORE_TOKEN as CORE_TOKEN_PATH;
// in `crate::paths` — one-shot guards: the upload/align runs once, the // in `crate::paths` — one-shot guards: the upload/align runs once, the
// marker is written, subsequent startups skip. Delete one to force its // marker is written, subsequent startups skip. Delete one to force its
// step to re-run. // step to re-run.
// Avatar PNGs are loaded at runtime from // Avatar PNG paths are resolved by `core_avatar_png_path` /
// `$HIVE_ASSETS_DIR/branding/{hyperhive,agent-configs}.png` via the // `config_org_avatar_png_path` below — only-used-here, so they live
// helpers in `hive_sh4re::assets`. The `agent-configs.png` is // in this module rather than the shared `hive_sh4re::assets` helpers
// rendered from its SVG during the `hyperhive-assets` derivation's // (which stay for paths every crate needs, e.g. `branding_svg`).
// build.
/// `$HIVE_ASSETS_DIR/branding/hyperhive.png` — the core mark,
/// rasterised. Not independently configurable (unlike the org avatar
/// below) — override the whole `services.hyperhive.c0re.assets`
/// package to change it.
fn core_avatar_png_path() -> std::path::PathBuf {
let dir = std::env::var("HIVE_ASSETS_DIR").expect(
"HIVE_ASSETS_DIR is unset — hive-c0re.nix sets it unconditionally, \
so this process was started outside the NixOS module",
);
std::path::PathBuf::from(dir).join("branding/hyperhive.png")
}
/// Path to the `agent-configs` org's avatar PNG. Independently
/// configurable via `services.hyperhive.c0re.orgAvatarPng` — the nix
/// module resolves `HIVE_ORG_AVATAR_PNG` to that option's value, or
/// the bundled `agent-configs.png` from `assets` when unset, so an
/// operator can override just this image without replacing the whole
/// `assets` package.
fn config_org_avatar_png_path() -> std::path::PathBuf {
std::path::PathBuf::from(std::env::var("HIVE_ORG_AVATAR_PNG").expect(
"HIVE_ORG_AVATAR_PNG is unset — hive-c0re.nix sets it unconditionally \
(from services.hyperhive.c0re.orgAvatarPng or the bundled default), \
so this process was started outside the NixOS module",
))
}
/// Per-agent token scopes (broad-but-not-admin) for tokens hive-c0re /// Per-agent token scopes (broad-but-not-admin) for tokens hive-c0re
/// mints itself on the **internal** forge. See `docs/forge.md::Token /// mints itself on the **internal** forge. See `docs/forge.md::Token
/// scopes` for the per-scope rationale. Not `pub(super)` — external /// scopes` for the per-scope rationale. Not `pub(super)` — external
@ -400,7 +425,7 @@ pub(super) async fn ensure_core_avatar(token: &str) -> Result<()> {
if marker.exists() { if marker.exists() {
return Ok(()); return Ok(());
} }
let png_path = hive_sh4re::assets::core_avatar_png(); let png_path = core_avatar_png_path();
let png_bytes = tokio::fs::read(&png_path) let png_bytes = tokio::fs::read(&png_path)
.await .await
.with_context(|| format!("read core avatar PNG from {}", png_path.display()))?; .with_context(|| format!("read core avatar PNG from {}", png_path.display()))?;
@ -432,7 +457,7 @@ pub(super) async fn ensure_config_org_avatar(token: &str) -> Result<()> {
if marker.exists() { if marker.exists() {
return Ok(()); return Ok(());
} }
let png_path = hive_sh4re::assets::config_org_avatar_png(); let png_path = config_org_avatar_png_path();
let png_bytes = tokio::fs::read(&png_path) let png_bytes = tokio::fs::read(&png_path)
.await .await
.with_context(|| format!("read {CONFIG_ORG} avatar PNG from {}", png_path.display()))?; .with_context(|| format!("read {CONFIG_ORG} avatar PNG from {}", png_path.display()))?;

View file

@ -50,23 +50,6 @@ pub fn branding_svg() -> PathBuf {
dir().join("branding/hyperhive.svg") dir().join("branding/hyperhive.svg")
} }
/// `$HIVE_ASSETS_DIR/branding/hyperhive.png` — the same mark
/// rasterised, used by `hive-c0re::forge::push_avatar` to upload
/// the forge org avatar.
#[must_use]
pub fn core_avatar_png() -> PathBuf {
dir().join("branding/hyperhive.png")
}
/// `$HIVE_ASSETS_DIR/branding/agent-configs.png` — secondary org
/// mark for the `agent-configs/` mirror org. Rendered from
/// `agent-configs.svg` at asset-build time (was rendered in
/// `hive-c0re/build.rs`).
#[must_use]
pub fn config_org_avatar_png() -> PathBuf {
dir().join("branding/agent-configs.png")
}
/// `$HIVE_ASSETS_DIR/prompts/system.md` — the claude system prompt /// `$HIVE_ASSETS_DIR/prompts/system.md` — the claude system prompt
/// template. `hive-agent::prompt::render` filters the role markers /// template. `hive-agent::prompt::render` filters the role markers
/// inside it per agent / manager flavor. /// inside it per agent / manager flavor.

View file

@ -39,8 +39,17 @@ in
HIVE_AGENT_FRONTEND_DIR = "${cfg.servedFrontend}/agent"; HIVE_AGENT_FRONTEND_DIR = "${cfg.servedFrontend}/agent";
# Path to the static runtime asset tree (branding + claude # Path to the static runtime asset tree (branding + claude
# prompts). `hive_sh4re::assets::*` reads paths underneath. # prompts). `hive_sh4re::assets::*` reads paths underneath.
# `forge.rs` reads the avatar PNGs from here on startup. # `forge/users.rs` reads the core avatar PNG from here on startup.
HIVE_ASSETS_DIR = "${cfg.assets}/share/hyperhive"; HIVE_ASSETS_DIR = "${cfg.assets}/share/hyperhive";
# `agent-configs` org avatar PNG — independently overridable via
# `orgAvatarPng` without replacing the whole `assets` package.
# Falls back to the bundled PNG under HIVE_ASSETS_DIR when unset.
# Read by `forge::users::config_org_avatar_png_path`.
HIVE_ORG_AVATAR_PNG =
if cfg.orgAvatarPng != null then
"${cfg.orgAvatarPng}"
else
"${cfg.assets}/share/hyperhive/branding/agent-configs.png";
# Whether this hive runs ruthless — no root/manager agent at all # Whether this hive runs ruthless — no root/manager agent at all
# (`auto_update::ensure_root_agent`). Default false = root # (`auto_update::ensure_root_agent`). Default false = root
# auto-managed; true makes the sweep a no-op. # auto-managed; true makes the sweep a no-op.

View file

@ -55,6 +55,19 @@
rust derivation. rust derivation.
''; '';
}; };
orgAvatarPng = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
description = ''
PNG uploaded once as the `agent-configs` Forgejo org's avatar
(`forge::users::ensure_config_org_avatar`, one-shot,
marker-guarded delete `forge/agent-configs-avatar-set` under
the state dir to force a re-upload after changing this).
Defaults (`null`) to the bundled `agent-configs.png` from
`assets`. Set this to override just the org avatar without
replacing the whole `assets` package.
'';
};
xdgIcons = lib.mkOption { xdgIcons = lib.mkOption {
type = lib.types.package; type = lib.types.package;
defaultText = lib.literalExpression "hyperhive.packages.\${system}.xdg-icons"; defaultText = lib.literalExpression "hyperhive.packages.\${system}.xdg-icons";