Add Hermes WebUI and Athena operator skill

This commit is contained in:
Mikei386
2026-08-24 21:50:23 +02:00
parent 335c9a8501
commit 71775891b4
15 changed files with 446 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
FROM ghcr.io/nesquena/hermes-webui@sha256:48ba6ee4a837079955c00e997b751065cc0324ae6eaa0f2fec592c8f4b2a746e
COPY hermes_requirements.py /usr/local/bin/hermes_requirements.py
COPY patch_entrypoint.py /tmp/patch_entrypoint.py
RUN python3 /tmp/patch_entrypoint.py /hermeswebui_init.bash \
&& python3 /tmp/patch_entrypoint.py /apptoo/docker_init.bash \
&& rm /tmp/patch_entrypoint.py \
&& chmod 0555 /usr/local/bin/hermes_requirements.py
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Export Hermes dependencies without attempting to build Hermes itself."""
from __future__ import annotations
import re
import sys
import tomllib
from pathlib import Path
def main() -> None:
document = tomllib.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
project = document["project"]
optional = project.get("optional-dependencies", {})
pending = list(project.get("dependencies", [])) + list(optional.get("all", []))
emitted: set[str] = set()
while pending:
requirement = pending.pop(0)
match = re.fullmatch(r"hermes-agent(?:\[([^]]+)\])?", requirement.strip())
if match:
extras = [item.strip() for item in (match.group(1) or "").split(",") if item.strip()]
for extra in extras:
if extra not in optional:
raise SystemExit(f"Unknown Hermes extra: {extra}")
pending[:0] = optional[extra]
continue
if requirement not in emitted:
print(requirement)
emitted.add(requirement)
if __name__ == "__main__":
main()
@@ -0,0 +1,22 @@
#!/usr/bin/env python3
"""Patch WebUI dependency bootstrap for modern non-wheel Hermes releases."""
from __future__ import annotations
import sys
from pathlib import Path
path = Path(sys.argv[1])
content = path.read_text(encoding="utf-8")
old = ''' uv pip install "$_stage_src[all]" --trusted-host pypi.org --trusted-host files.pythonhosted.org \\
|| error_exit "Failed to install hermes-agent's requirements"'''
new = ''' python3 /usr/local/bin/hermes_requirements.py "$_stage_src/pyproject.toml" \\
> /tmp/hermes-agent-requirements.txt \\
|| error_exit "Failed to export hermes-agent's requirements"
uv pip install -r /tmp/hermes-agent-requirements.txt \\
--trusted-host pypi.org --trusted-host files.pythonhosted.org \\
|| error_exit "Failed to install hermes-agent's requirements"'''
if content.count(old) != 1:
raise SystemExit(f"Expected dependency bootstrap exactly once in {path}")
path.write_text(content.replace(old, new), encoding="utf-8")
+1
View File
@@ -59,4 +59,5 @@ fi
install -m 0600 "$STACK_DIR/platform/hermes/config.yaml" "$HERMES_DATA_DIR/config.yaml"
install -m 0600 "$STACK_DIR/platform/hermes/SOUL.md" "$HERMES_DATA_DIR/SOUL.md"
chown -R 10000:10000 "$HERMES_DATA_DIR"
"$STACK_DIR/platform/hermes/install-skills.sh"
printf 'HERMES_CONFIG_OK %s\n' "$HERMES_DATA_DIR"
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -Eeuo pipefail
HERMES_CONTAINER=${HERMES_CONTAINER:-mike-ai-hermes}
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
docker inspect "$HERMES_CONTAINER" >/dev/null 2>&1 || \
die "Hermes-Container fehlt: $HERMES_CONTAINER"
deadline=$((SECONDS + 180))
until [[ $(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \
"$HERMES_CONTAINER" 2>/dev/null || true) == healthy ]]; do
(( SECONDS < deadline )) || die "Hermes wurde nicht rechtzeitig gesund."
sleep 2
done
create_profile() {
local name=$1 model=$2 context=$3 description=$4
if ! docker exec "$HERMES_CONTAINER" hermes profile show "$name" >/dev/null 2>&1; then
docker exec "$HERMES_CONTAINER" hermes profile create "$name" \
--clone-from default --description "$description"
fi
docker exec "$HERMES_CONTAINER" hermes -p "$name" config set model.default "$model"
docker exec "$HERMES_CONTAINER" hermes -p "$name" config set model.context_length "$context"
[[ $(docker exec "$HERMES_CONTAINER" hermes -p "$name" config get model.default) == "$model" ]] || \
die "Modellalias von Profil $name konnte nicht verifiziert werden."
[[ $(docker exec "$HERMES_CONTAINER" hermes -p "$name" config get model.context_length) == "$context" ]] || \
die "Kontext von Profil $name konnte nicht verifiziert werden."
}
create_profile fast qwen-fast 76800 \
"Schnelles Qwen3.8-27B-Profil mit 76,8K Kontext fuer kurze Chats und schnelle Aufgaben."
create_profile medium qwen-medium 160000 \
"Ausgewogenes Qwen3.8-27B-Standardprofil mit 160K Kontext fuer Alltag und agentische Aufgaben."
create_profile large qwen-large 192000 \
"Grosses Qwen3.8-27B-Profil mit 192K Kontext fuer umfangreiche Dokumente und lange Aufgaben."
create_profile ultra qwen-ultra 262144 \
"Maximales Qwen3.8-27B-Profil mit 262K Kontext fuer sehr grosse Kontexte; langsamer als die Standardprofile."
create_profile uncensored qwen-uncensored 80000 \
"Unzensiertes Qwen3.8-27B-Profil mit 80K Kontext fuer spezielle Anfragen."
"${STACK_DIR:-/opt/mike-ai/stack}/platform/hermes/install-skills.sh"
docker exec "$HERMES_CONTAINER" hermes profile list
printf 'HERMES_PROFILES_OK\n'
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -Eeuo pipefail
STACK_DIR=${STACK_DIR:-/opt/mike-ai/stack}
HERMES_DATA_DIR=${HERMES_DATA_DIR:-/data/hermes}
SKILL_SOURCE=$STACK_DIR/platform/hermes/skills/athena-operator/SKILL.md
PROFILES=(fast medium large ultra uncensored)
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
[[ -s $SKILL_SOURCE ]] || die "Skill-Quelle fehlt: $SKILL_SOURCE"
grep -Fxq -- 'name: athena-operator' "$SKILL_SOURCE" || \
die "Skill-Quelle hat kein gültiges Athena-Operator-Frontmatter."
install_skill() {
local root=$1 target=$1/platform/athena-operator/SKILL.md
install -d -o 10000 -g 10000 -m 0750 "${target%/*}"
if [[ -s $target ]] && ! cmp -s "$SKILL_SOURCE" "$target"; then
cp -a "$target" "$target.before-managed-update-$(date +%Y%m%d-%H%M%S)"
fi
install -o 10000 -g 10000 -m 0640 "$SKILL_SOURCE" "$target"
cmp -s "$SKILL_SOURCE" "$target" || die "Skill-Synchronisierung fehlgeschlagen: $target"
}
install_skill "$HERMES_DATA_DIR/skills"
for profile in "${PROFILES[@]}"; do
[[ -d $HERMES_DATA_DIR/profiles/$profile ]] || continue
install_skill "$HERMES_DATA_DIR/profiles/$profile/skills"
done
printf 'HERMES_ATHENA_OPERATOR_SKILL_OK\n'
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
STACK_DIR=${STACK_DIR:-/opt/mike-ai/stack}
DATA_DIR=${HERMES_WEBUI_DATA_DIR:-/data/hermes-webui}
HERMES_ENV=${HERMES_ENV:-/data/hermes/.env}
SECRETS_DIR=${SECRETS_DIR:-/etc/mike-ai}
STACK_ENV=$SECRETS_DIR/stack.env
WEBUI_IMAGE_DEFAULT=mike-ai/hermes-webui:0.52.113-hermes-source-v1
die() { printf 'FEHLER: %s\n' "$*" >&2; exit 1; }
[[ $EUID -eq 0 ]] || die "Bitte als root ausführen."
[[ -s $HERMES_ENV ]] || die "Hermes-Umgebung fehlt: $HERMES_ENV"
[[ -s $STACK_ENV ]] || die "Stack-Umgebung fehlt: $STACK_ENV"
[[ $(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \
mike-ai-hermes 2>/dev/null || true) == healthy ]] || \
die "Hermes-Gateway ist nicht gesund; WebUI wird nicht gestartet."
[[ $(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' \
mike-ai-wireguard-gateway 2>/dev/null || true) == healthy ]] || \
die "WireGuard-Gateway ist nicht gesund; WebUI-VPN-Proxy wird nicht gestartet."
read_env() {
local key=$1
sed -n "s/^${key}=//p" "$HERMES_ENV" | head -n 1
}
gateway_key=$(read_env API_SERVER_KEY)
router_key=$(read_env ROUTER_API_KEY)
[[ ${#gateway_key} -ge 16 ]] || die "Hermes-Gateway-Key fehlt oder ist zu kurz."
[[ -n $router_key ]] || die "Router-Key fehlt in der Hermes-Umgebung."
install -d -m 0700 "$DATA_DIR" "$DATA_DIR/state"
hermes_image=$(sed -n 's/^HERMES_IMAGE=//p' "$STACK_ENV" | head -n 1)
if [[ -z $hermes_image ]]; then
hermes_image=$(docker inspect --format '{{.Config.Image}}' mike-ai-hermes 2>/dev/null || true)
fi
[[ -n $hermes_image ]] || die "Hermes-Image fehlt in $STACK_ENV und im laufenden Container."
image_id=$(docker image inspect --format '{{.Id}}' "$hermes_image" 2>/dev/null) || \
die "Hermes-Image ist lokal nicht verfügbar: $hermes_image"
webui_image=$(sed -n 's/^HERMES_WEBUI_IMAGE=//p' "$STACK_ENV" | head -n 1)
webui_image=${webui_image:-$WEBUI_IMAGE_DEFAULT}
docker build --pull=false --tag "$webui_image" "$STACK_DIR/platform/docker/hermes-webui"
agent_dir=$DATA_DIR/hermes-agent
agent_marker=$DATA_DIR/hermes-agent-image-id
if [[ ! -s $agent_marker || $(<"$agent_marker") != "$image_id" || ! -s $agent_dir/cli.py ]]; then
source_tmp=$(mktemp -d "$DATA_DIR/hermes-agent.new.XXXXXX")
source_container=$(docker create "$hermes_image")
cleanup_source_container() {
[[ -z ${source_container:-} ]] || docker rm -f "$source_container" >/dev/null 2>&1 || true
}
trap cleanup_source_container EXIT
docker cp "$source_container:/opt/hermes/." "$source_tmp/"
docker rm "$source_container" >/dev/null
source_container=
chown -R 10000:10000 "$source_tmp"
if [[ -e $agent_dir ]]; then
mv "$agent_dir" "$DATA_DIR/hermes-agent.previous-$(date +%Y%m%d-%H%M%S)"
fi
mv "$source_tmp" "$agent_dir"
printf '%s\n' "$image_id" >"$agent_marker"
chmod 0600 "$agent_marker"
trap - EXIT
fi
[[ -s $SECRETS_DIR/hermes-webui-password ]] || \
openssl rand -base64 24 >"$SECRETS_DIR/hermes-webui-password"
chmod 0600 "$SECRETS_DIR/hermes-webui-password"
cat >"$DATA_DIR/.env" <<EOF
HERMES_WEBUI_PASSWORD=$(<"$SECRETS_DIR/hermes-webui-password")
HERMES_WEBUI_GATEWAY_API_KEY=$gateway_key
ROUTER_API_KEY=$router_key
EOF
chmod 0600 "$DATA_DIR/.env"
chown 10000:10000 "$DATA_DIR" "$DATA_DIR/.env" "$agent_marker"
chown -R 10000:10000 "$DATA_DIR/state"
cd "$STACK_DIR"
HERMES_WEBUI_IMAGE=$webui_image docker compose --env-file "$STACK_ENV" --profile hermes-webui \
up -d --no-deps hermes-webui hermes-webui-vpn-proxy
printf 'HERMES_WEBUI_OK http://192.168.1.212:8787\n'
printf 'Passwort lokal: %s\n' "$SECRETS_DIR/hermes-webui-password"
@@ -0,0 +1,115 @@
---
name: athena-operator
description: Operate and extend the Athena AI platform safely.
license: MIT
metadata:
hermes:
version: 0.1.0
author: Michael Roll, Hermes Agent
platforms: [linux, macos, windows]
tags: [athena, operations, docker, mcp, models, recovery]
related_skills: []
---
# Athena Operator Skill
Operate, diagnose, extend, and recover the Athena AI platform through its
platform-context and operator MCPs. Keep durable truth in the versioned Athena
repository and use live measurements only as evidence of current state.
## When to Use
- Use for Athena, MikeAI, Docker-stack, router, inference-profile, model,
benchmark, MCP, Hermes, Open WebUI, TTS, STT, image, Git-deploy, and recovery
work on the Athena host.
- Use when a user asks how Athena is built or whether an Athena component is
running, configured, documented, reproducible, or recoverable.
- Do not use as the primary tool for Home Assistant, Unraid, Sonarr, Radarr,
Navidrome, or GitHub data when their specialist MCP is available.
## Prerequisites
- Require `Athena Plattformwissen` for architecture and versioned knowledge.
- Require `Athena Operator` for live host inspection and execution.
- Treat missing or failed tools as missing evidence. Never invent state,
output, files, logs, or completed actions.
- Never request, reveal, copy into chat, or commit secret values.
## Tool Selection
1. Identify the target system before calling a tool.
2. For Athena itself, begin with `athena_operator_inspect`. Use
`athena_get_overview` when architectural context is needed.
3. For external services, prefer the narrow specialist MCP. Use
`athena_get_external_services` before planning a duplicate service.
4. Use `athena_operator_search_source` and `athena_operator_read_source` for
deployed code. Use `athena_search_knowledge` and `athena_read_source` for
documentation. Do not guess paths or configuration.
5. Use `athena_operator_terminal` as the broad Athena escape hatch only when a
structured tool is too narrow. Keep commands focused and outputs bounded.
## Procedure
1. **Establish evidence.** Inspect only the relevant live subject and read the
smallest authoritative source section. Completion: runtime facts and source
facts are separately identified.
2. **Check drift.** Compare live state with versioned source and current
reference documentation. Completion: any mismatch is named before changes.
3. **Protect the active workload.** Check jobs and active containers. Do not
restart, recreate, switch profiles, alter shared configuration, or consume
required GPU capacity while an important request or benchmark is active.
Completion: work is either proven idle or the change is staged only.
4. **Plan rollback.** Name the files, services, validation, rollback artifact,
and expected user-visible effect. Completion: rollback is possible without
relying on chat history.
5. **Change the source of truth.** Modify repository sources, not only a live
container. Prefer `athena_operator_prepare`; show its full preview and stop
for the exact user confirmation before `athena_operator_execute`.
Completion: the approved ticket matches the intended content.
6. **Deploy narrowly.** Change only named services. Never restart the entire
stack merely to activate one component. Completion: unrelated containers
and the active inference request remain undisturbed.
7. **Verify behavior.** Run syntax/config checks, focused tests, service health,
and one bounded functional test. A running container alone is not proof.
Completion: expected behavior and rollback path are both verified.
8. **Close the maintenance loop.** Update relevant docs, publish the Git
change, create a newer recovery bundle, then check maintenance status.
Completion: source commit, deployed state, docs, and recovery agree.
## Persistent Versus Temporary Work
- "Use" a missing helper for one task: place it in a task-specific temporary
location or ephemeral container and remove it afterward.
- "Install", "add", "deploy", or "make permanent": implement it in repository
source, documentation, installation flow, and recovery.
- Do not create a second backend merely because an existing service is stopped,
inaccessible, or absent from one tool catalogue.
## Remote-Safety Boundary
- Athena has no physical console or KVM. Never attempt power control or changes
to Athena SSH, LAN, WireGuard, firewall, boot, kernel, drivers, mounts, or
partitions through this workflow.
- Do not stop or restart the WireGuard gateway as a side effect of ordinary
deployment. Bind user services to the VPN path; keep them unavailable from
the university LAN.
- Inside the trusted VPN, normal service communication and Internet access are
allowed. Do not add extra egress restrictions unless the user requests them.
## Pitfalls
- Profile names are not simultaneous models; exactly one text profile is active.
- A profile switch can terminate active generation and invalidate prompt cache.
- A healthy container can still expose the wrong model, route, or tool set.
- `/opt/mike-ai/stack` is deployed source, not automatically the canonical Git
worktree. Complete durable changes through the documented Git workflow.
- New skills are loaded at the next Hermes session; absence in the current
session is expected.
## Verification
- State the tools that supplied each important live claim.
- List every modified source file and every deployed service.
- Report focused test and health results, not vague success language.
- If Git publication or recovery creation is incomplete, call it unfinished
maintenance rather than declaring the task fully complete.