Hermes SaaS – Web-Portal + Docker-Orchestrierung für Kollegen
  • Go 54.6%
  • HTML 20.7%
  • CSS 13.6%
  • Shell 9.7%
  • Dockerfile 1.4%
Find a file
Jens Lewald b0ed52c22f FIX: FUSE-Capabilities (SYS_ADMIN, /dev/fuse, apparmor) für OpenCloud-davfs2-Mount im Container
manager.go: CapAdd, Devices, SecurityOpt beim Container-Create ergänzt.
BUILD.md: Abschnitt FUSE-Capability-Fix dokumentiert.
2026-07-22 15:10:34 +02:00
container-files Network-Fix: Hostnames → direkte IPs (172.19.0.1) für stabile Container-Verbindung 2026-07-21 13:00:30 +02:00
docker FIX: FUSE-Capabilities (SYS_ADMIN, /dev/fuse, apparmor) für OpenCloud-davfs2-Mount im Container 2026-07-22 15:10:34 +02:00
handlers OpenCloud-Integration: davfs2-Mount, Basic-Auth, Auto-Login-Proxy, Doku 2026-07-12 10:23:06 +02:00
models Verbrauchte Tokens werden deaktiviert (active=0) + aus Admin-Liste gefiltert 2026-07-11 15:14:30 +02:00
scripts Finaler Stand der OpenCloud-Integration: Doku + Script + Fixes 2026-07-12 13:04:42 +02:00
static OpenCloud-Integration: davfs2-Mount, Basic-Auth, Auto-Login-Proxy, Doku 2026-07-12 10:23:06 +02:00
templates OpenCloud-Integration: davfs2-Mount, Basic-Auth, Auto-Login-Proxy, Doku 2026-07-12 10:23:06 +02:00
.gitignore Binary aus Git entfernt + .gitignore 2026-07-10 18:42:22 +02:00
BUILD.md FIX: FUSE-Capabilities (SYS_ADMIN, /dev/fuse, apparmor) für OpenCloud-davfs2-Mount im Container 2026-07-22 15:10:34 +02:00
go.mod Security+UI: bcrypt, Rate-Limit, NULL-Fix, professionelles Interface 2026-07-10 18:57:07 +02:00
go.sum Security+UI: bcrypt, Rate-Limit, NULL-Fix, professionelles Interface 2026-07-10 18:57:07 +02:00
main.go OpenCloud-Integration: davfs2-Mount, Basic-Auth, Auto-Login-Proxy, Doku 2026-07-12 10:23:06 +02:00
README.md Network-Fix: Hostnames → direkte IPs (172.19.0.1) für stabile Container-Verbindung 2026-07-21 13:00:30 +02:00

Hermes SaaS

Web-Portal zur Verwaltung von Hermes-Zugängen für Freunde und Familie. Registrierung nur mit Einmal-Token → automatischer Docker-Container mit ttyd + Hermes. Lokale KI kein Cloud-Anbieter, kein API-Key, alles auf eigener Hardware. Dark-Theme, Golden-Ratio-Typographie, Alpine.js.

Admin-Zugang

Der erste Admin (User admin) wird beim initialen Setup mit bcrypt-Hash angelegt. Tokens erstellen auf /admin → „Token erstellen". Admin wird nach Login direkt auf /admin umgeleitet.

Komponente Wert
KI-Modell Local (JL-Spark auf DGX Spark, kein Cloud-API)
Server 192.168.99.76
URL https://portal.ki-tools.business
Port 8000
DB $HOME/.hermes-saas/portal.db
Service systemctl [start|stop|restart|status] hermes-saas
Binary /opt/hermes-saas/hermes-saas
Templates /opt/hermes-saas/templates/
Static /opt/hermes-saas/static/
Repo git.ki-tools.business/lewald/hermes-saas
Admin-Passwort ki-tools-2026 (bcrypt)
Container-Daten /opt/hermes-saas-data/<user>/ (Volume-Mount)

Architektur

Caddy (10.200.200.1)
  portal.ki-tools.business → 192.168.99.76:8000

Go-Backend (Port 8000)
  ├── /, /login, /register  → Landing + Auth
  ├── /dashboard            → Container-Status + Start/Stop + Terminal-Button
  ├── /admin                → User + Container (unified) + Token-Verwaltung
  ├── /user/{id}/           → Reverse Proxy zu ttyd-Container (WebSocket)
  └── /api/                 → JSON-Endpunkte (Status, Admin-API)

Lokale KI-Modelle (DGX Spark / Spark2)
  api.proxy → 192.168.99.36:8888 (JL-Spark, Custom-Provider)
  Open Source Modelle, rein lokaler Betrieb

Docker-Container (pro User)
  Name:  hermes-user-{name}
  Image: hermes-saas:latest
  Port:  76827699 (ttyd)
  Netz:  kollegen-net (172.19.0.0/16, Bridge, kein LAN-Zugriff)
  Vol.:  /opt/hermes-saas-data/<user>/ → /etc/hermes-saas/
  ExtraHosts: api.proxy, searxng.proxy

socat-Proxies (alpine/socat:latest)
  api-proxy     172.19.0.1:36888 → spark1:8888 (JL-Spark API)
  searxng-proxy 172.19.0.1:36808 → searxng:8080 (SearXNG API)
  Beide auf Host-Netzwerk (kein Bridge), nur für kollegen-net erreichbar

Caddy (ssh.ki-tools.business)
  portal.ki-tools.business  → 192.168.99.76:8000 (Go-Portal)
  ssh.ki-tools.business     → 192.168.99.76:8000 (SSH-Terminal)
  /user1-/user2-            → auth → 192.168.99.76 (BasicAuth)

Features

  • Lokale KI Open-Source-Modelle auf eigener GPU, kein Cloud-Anbieter, keine API-Kosten
  • Login/Registrierung mit bcrypt-Passwörtern, Session-Cookies (24h, httpOnly, Secure, SameSite=Lax)
  • Einladungstoken (UUIDv4, once-only, max_uses=1)
  • Container-Management per Docker-API (start/stop/restart, kein CLI)
  • ttyd-Proxy inkl. WebSocket für interaktives Terminal, Basic-Auth-Injection (Auto-Login)
  • Container-Volume Sessions, Memory, Logs überleben Container-Neustart
  • Wöchentliches Update hermes update -y läuft 1× pro Woche beim Container-Start
  • Admin-Redirect Admin wird nach Login direkt auf /admin umgeleitet
  • Rate-Limiting 5 Login-Versuche/Minute/IP
  • NULL-sichere Queries sql.NullString für nullable DB-Felder
  • Passwort-Generierung mit crypto/rand.Read für Container-Passwörter

Container-Image: hermes-saas:latest

Das Custom-Image baut auf ttyd-custom:latest auf.

Komponente Details
Basis ttyd-custom:latest (ttyd 1.7.7 + openssh-client + sshpass auf Ubuntu 24.04)
Python 3.12 (für Hermes Agent)
Node.js 22.23 (NodeSource setup_22)
Hermes Agent v0.18.x (aktuellste per hermes update)
MCP SearXNG mcp-searxng v1.11.0 → 172.19.0.1:36808
Persona SOUL.md Such-Priorität SearXNG-MCP, Date-Check, Skills-Hinweis
Preload-Skills date-verification, weather-queries
ttyd Port 7681, --yolo --tui, trap '' INT, copyOnSelect=true
Scrollbalken Ausgeblendet via eigenem ttyd-index.html (-I Flag)
Basis-Config /etc/hermes-saas.base/ → bei erstem Start ins Volume kopiert

Pfad im Container: HERMES_HOME=/etc/hermes-saas

Erststart-Seed

Beim ersten Start eines Containers (leeres Volume):

  1. SOUL.md aus /etc/hermes-saas.base/ kopieren
  2. config.yaml mit MCP-SearXNG und Skill-Preload generieren
  3. hermes update -y für neueste Version
  4. ttyd starten mit -I /etc/hermes-saas/ttyd-index.html

Volumes

Pro User ein Volume:

Host: /opt/hermes-saas-data/<user>/  →  Container: /etc/hermes-saas/

Enthält: config.yaml, SOUL.md, ttyd-index.html, Sessions, Memory, Logs.

Wöchentliches Update

start-hermes.sh prüft Kalenderwoche via /etc/hermes-saas/.last_update. Nur 1× pro Woche wird hermes update -y ausgeführt non-interactive (kein hängen).

Container-Proxies (socat)

Container Image Port Ziel Zweck
api-proxy alpine/socat:latest 172.19.0.1:36888 192.168.99.36:8888 (spark1) JL-Spark API für Hermes
searxng-proxy alpine/socat:latest 172.19.0.1:36808 192.168.99.8:8080 SearXNG Suche

Keine eigenen Images direkt alpine/socat:latest aus dem Docker-Hub. socat lauscht auf der Bridge-Gateway-IP 172.19.0.1, Container in kollegen-net erreichen die Proxies über ExtraHosts.

Custom Images

Das Portal baut auf einem Basis-Image auf (separates Projekt/Repo):

Image Basis Enthalten Repo
hermes-saas:latest ttyd-custom:latest Python 3.12, Node 22, Hermes Agent, mcp-searxng dieses Repo

ttyd-custom:latest (ttyd 1.7.7 + openssh-client + sshpass auf Ubuntu 24.04, ~231 MB) wird in einem separaten Projekt gebaut und ist keine Datei in diesem Repo.

Projektstruktur

hermes-saas/
├── main.go                   # Router, Session-Middleware, Server-Start
├── handlers/
│   ├── auth.go               # Login, Logout, Register, Rate-Limiting
│   ├── dashboard.go          # Landing, Dashboard, ProxyToContainer
│   ├── admin.go              # Admin-Seite (UserWithContainer-Struktur)
│   └── api.go                # JSON-API (Container-Start/Stop, Status)
├── models/
│   ├── models.go             # SQLite-Modelle (User, Session, Token, Container), bcrypt
│   └── tokens.go             # Token-Generierung (UUIDv4)
├── docker/
│   └── manager.go            # Docker-API-Manager via Unix Socket + Volume-Mount
├── templates/                # html/template (mit Alpine.js)
│   ├── base.html             # Shell + Navigation (dynamischer Admin-Link)
│   ├── landing.html          # Hero mit Icon
│   ├── login.html            # Glaskarte
│   ├── register.html         # Mit Passwort-Stärke
│   ├── dashboard.html        # 3er-Grid, Status-Dots, Terminal-Button
│   └── admin.html            # User+Container unified, Token-Grid
├── static/
│   └── style.css             # Eigenes Design-System (kein Framework)
├── container-files/          # Build-Artefakte für das Docker-Image
│   ├── Dockerfile            # FROM ttyd-custom -> hermes-saas:latest
│   ├── SOUL.md               # Persona (SearXNG-Suche, Date-Check, Skills)
│   ├── start-hermes.sh       # Entrypoint (Seed, Update, ttyd-Start)
│   └── ttyd-index.html       # Custom index.html (Scrollbalken aus)
└── README.md

UI-Design

  • Dark-Theme mit Glas-Effekten (backdrop-filter blur, border-glow)
  • Animierter Grid-Hintergrund + radiale Glows
  • Golden-Ratio-Typographie: 15px Basis → 13/15/20/24/32/44/60px, line-height 1.7
  • Kein Tailwind/Framework eigenes CSS (Zero-CDN, nur Google Fonts: Inter + JetBrains Mono)
  • Navigation: Username dezenter Text (kein Button-Look) mit Admin-Link für Admins
  • Terminal-Button öffnet /user/{id}/ im neuen Tab, Proxy injectt Basic-Auth (Auto-Login)

Technologie-Stack

Komponente Technologie
KI-Modell Lokal (JL-Spark, Open Source, DGX Spark GPU)
Backend Go (Single Binary, ~22 MB)
Datenbank SQLite
Templates html/template + Alpine.js 3.x
CSS Eigenes Design-System
Docker Docker-API via Unix Socket
Passwörter bcrypt (golang.org/x/crypto/bcrypt)
Fonts Inter (UI) + JetBrains Mono (Terminal)
Reverse Proxy Caddy (SSL-Terminierung)
Container-Image Ubuntu 24.04, Python 3.12, Node 22, Hermes Agent
Suche (MCP) mcp-searxng (Node.js) via SearXNG-Proxy

Umgebungsvariablen

Variable Zweck Fallback
HERMES_SAAS_PORT Server-Port KOLPORT_PORT8000
HERMES_SAAS_DB_PATH SQLite-DB-Pfad KOLPORT_DB_PATH~/.hermes-saas/portal.db

Migration (2026-07-11)

  • Projekt umbenannt von kollegen-portalhermes-saas
  • Datenbank umgezogen von ~/.kollegen-portal/portal.db~/.hermes-saas/portal.db
  • Umgebungsvariablen: KOLPORT_* wird als Fallback weiter unterstützt
  • Service-Env auf HERMES_SAAS_PORT aktualisiert
  • Passwort-Hashing: bcrypt (vorher SHA256, Umstellung 2026-07-10)
  • Image-Umstellung: hermes-kollege:latesthermes-saas:latest (2026-07-11)
  • Volumes pro User: /opt/hermes-saas-data/<user>/ (2026-07-11)

Image-Build (Server)

cd /tmp/hermes-saas-build
docker build -t hermes-saas:latest .
docker stop hermes-user-{user}; docker rm hermes-user-{user}
docker run -d \
  --name hermes-user-{user} \
  --network kollegen-net \
  --add-host api.proxy:172.19.0.1 \
  --add-host searxng.proxy:172.19.0.1 \
  -e SEARXNG_URL=http://searxng.proxy:36808 \
  -v /opt/hermes-saas-data/{user}:/etc/hermes-saas \
  -p {port}:7681 \
  hermes-saas:latest \
  /start-hermes.sh user{id} {user} {password}