mirror of
https://codeberg.org/muon/home.git
synced 2026-07-03 23:49:35 +00:00
171 lines
5.4 KiB
Nix
171 lines
5.4 KiB
Nix
{
|
|
pkgs,
|
|
lib,
|
|
config,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.mods.server.hermes;
|
|
auth2apiCfg = config.mods.server.auth2api;
|
|
|
|
# auth2api exposes an OpenAI-compatible endpoint; point Hermes at it.
|
|
auth2apiUrl = "http://${auth2apiCfg.host}:${toString auth2apiCfg.port}/v1";
|
|
in
|
|
with lib; {
|
|
options.mods.server.hermes = {
|
|
enable = mkEnableOption "Hermes AI agent (NousResearch)";
|
|
|
|
model = mkOption {
|
|
type = types.str;
|
|
default = "claude-sonnet-4-6";
|
|
description = ''
|
|
Model identifier as exposed by auth2api. Available aliases:
|
|
sonnet, haiku, opus, claude-sonnet-4-6, claude-haiku-4-5, claude-opus-4-6, etc.
|
|
Run: curl http://127.0.0.1:8317/v1/models
|
|
'';
|
|
};
|
|
|
|
container = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Run Hermes inside a persistent Ubuntu container.";
|
|
};
|
|
|
|
hostUsers = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ config.mods.user.name ];
|
|
description = ''
|
|
Users that get a ~/.hermes symlink to the service stateDir and are
|
|
added to the hermes group for shared file access.
|
|
'';
|
|
};
|
|
|
|
extraVolumes = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ ];
|
|
description = "Extra volume mounts (host:container:mode) for the container.";
|
|
};
|
|
};
|
|
|
|
addToSystemPackages = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = ''
|
|
Put the hermes CLI on the system PATH and set HERMES_HOME so the
|
|
interactive CLI shares state with the gateway service.
|
|
'';
|
|
};
|
|
|
|
extraPackages = mkOption {
|
|
type = types.listOf types.package;
|
|
default = [ ];
|
|
description = "Extra Nix packages available to the agent at runtime.";
|
|
};
|
|
|
|
stateDir = mkOption {
|
|
type = types.str;
|
|
default = "/var/lib/hermes";
|
|
description = "State directory (HERMES_HOME parent).";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
# Write the nix-store overlay script into the stateDir so it's available
|
|
# at /data/nix-store-overlay inside the container. Run it once with:
|
|
# docker exec hermes-agent sudo /data/nix-store-overlay
|
|
# This mounts a writable overlay over the read-only /nix/store so the Nix
|
|
# daemon can build new derivations without touching the host store.
|
|
# Upper/work dirs live on /data (persistent volume) so builds survive restarts.
|
|
system.activationScripts.hermes-nix-overlay-script = {
|
|
text = ''
|
|
install -m 0755 -o root -g root /dev/stdin \
|
|
'${cfg.stateDir}/nix-store-overlay' << 'OVERLAY_EOF'
|
|
#!/bin/sh
|
|
set -e
|
|
UPPER=/data/nix-store-upper
|
|
WORK=/data/nix-store-work
|
|
if ! touch /nix/store/.rw-test 2>/dev/null; then
|
|
mkdir -p "$UPPER" "$WORK"
|
|
mount -t overlay overlay \
|
|
-o lowerdir=/nix/store,upperdir="$UPPER",workdir="$WORK" \
|
|
/nix/store
|
|
echo "nix-store overlay mounted (upper=$UPPER)"
|
|
else
|
|
rm -f /nix/store/.rw-test
|
|
echo "nix-store already writable, skipping overlay"
|
|
fi
|
|
OVERLAY_EOF
|
|
'';
|
|
deps = [ "users" "groups" ];
|
|
};
|
|
|
|
# Hermes needs auth2api running to have a backend to call.
|
|
assertions = [
|
|
{
|
|
assertion = auth2apiCfg.enable;
|
|
message = "mods.server.hermes requires mods.server.auth2api.enable = true";
|
|
}
|
|
];
|
|
|
|
services.hermes-agent = {
|
|
enable = true;
|
|
|
|
inherit (cfg) stateDir addToSystemPackages extraPackages;
|
|
|
|
# auth2api runs unauthenticated on localhost (api-keys: []), but Hermes's
|
|
# provider resolver requires a non-empty key env var for any provider.
|
|
# AUTH2API_KEY is a dummy — auth2api ignores Bearer tokens when api-keys: [].
|
|
environment.AUTH2API_KEY = "auth2api";
|
|
|
|
# ── Model ──────────────────────────────────────────────────────────────
|
|
# Declare auth2api as a named custom provider in the providers: section.
|
|
# Hermes resolves it via resolve_user_provider, which accepts arbitrary
|
|
# base_url + key_env without needing a built-in provider slug.
|
|
settings = {
|
|
providers.auth2api = {
|
|
name = "auth2api (Claude via OAuth)";
|
|
api = auth2apiUrl;
|
|
key_env = "AUTH2API_KEY";
|
|
transport = "openai_chat";
|
|
};
|
|
|
|
model = {
|
|
default = cfg.model;
|
|
provider = "auth2api";
|
|
};
|
|
|
|
toolsets = [ "all" ];
|
|
max_turns = 100;
|
|
|
|
terminal = {
|
|
backend = "local";
|
|
timeout = 180;
|
|
};
|
|
|
|
memory = {
|
|
memory_enabled = true;
|
|
user_profile_enabled = true;
|
|
};
|
|
|
|
display = {
|
|
compact = false;
|
|
show_reasoning = true;
|
|
};
|
|
};
|
|
|
|
# ── Container ──────────────────────────────────────────────────────────
|
|
container = mkIf cfg.container.enable {
|
|
enable = true;
|
|
backend = "docker";
|
|
inherit (cfg.container) hostUsers extraVolumes;
|
|
|
|
# SYS_ADMIN is required for the overlay mount over /nix/store.
|
|
# The nix-store-overlay script (written to stateDir by tmpfiles) mounts
|
|
# a writable overlay so the Nix daemon can build inside the container
|
|
# without touching the host store.
|
|
extraOptions = [ "--cap-add=SYS_ADMIN" ];
|
|
};
|
|
};
|
|
};
|
|
}
|