Headless Python/FastAPI reimplementation of Iris server-side, for the Linux/Docker migration.
  • Python 86%
  • Jupyter Notebook 7.8%
  • Shell 4%
  • Dockerfile 2.2%
Find a file
User fa8e5b7b62 fix(hermes): Fork-Repo-URL nicht mehr separat hartcodiert
hermes/Dockerfile trug die Forgejo-Fork-URL (192.168.178.92:3000) als eigenen
Literal, unabhängig von deployment/build-and-push.sh's REGISTRY_HOST/REGISTRY_ORG
(docker:3000/iris) — beide meinen dieselbe Instanz, waren aber bereits
auseinandergedriftet. Jetzt ein Build-ARG (HERMES_AGENT_REPO_URL), das
build-and-push.sh explizit aus REGISTRY_HOST/REGISTRY_ORG ableitet — nur noch eine
Stelle kennt die Registry-Adresse.

Behebt nicht den in Testserver-2 sichtbaren "diverging branches"-Fehler selbst
(git-Historie dieser einen Instanz vs. Fork-main) — das ist ein separates,
instanzspezifisches Problem, kein Hardcoding-Bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RA7qAhGpR1Ct2sABqNNhNo
2026-09-13 23:41:22 +02:00
deployment fix(hermes): Fork-Repo-URL nicht mehr separat hartcodiert 2026-09-13 23:41:22 +02:00
iris-server fix(hermes): Fork-Repo-URL nicht mehr separat hartcodiert 2026-09-13 23:41:22 +02:00
scripts fix(deployment): hermes-sandbox-Image fehlte bei jedem Deploy komplett (Exit-Code 125) 2026-08-29 18:00:08 +02:00
.gitignore fix(gitignore): obsidian-docs-Symlink wurde durch Trailing-Slash-Pattern nicht erfasst 2026-08-29 21:06:16 +02:00
CLAUDE.md fix(toolcall-proxy): OBSIDIAN_VAULT_PATH im Sandbox-Container setzen 2026-09-13 13:39:14 +02:00
CLIENT-LOCAL-EXECUTION.md feat(vault-git,vault-maintenance,backup): port remaining Companion-Panel features, add toolcall-proxy attachment support 2026-08-17 10:14:08 +02:00
PLAN.md docs: reflect the three closed route gaps in ROUTES.md/PLAN.md/CLAUDE.md 2026-09-09 12:26:47 +02:00
README.md docs(readme): update fresh-install steps for .env + non-root server 2026-09-05 10:37:34 +02:00
ROUTES.md docs(routes): note the two new /backup/retention routes 2026-09-09 15:16:13 +02:00
SERVER-ADMIN.md docs(server-admin): add tip for verifying a running job's actual reasoning_effort 2026-09-05 17:17:55 +02:00

Iris Linux-Server — Installation

Dieses Dokument beschreibt, wie ein komplett neuer Iris-Linux-Server aus den Quellen in diesem Verzeichnis aufgesetzt wird — z. B. auf einem zweiten Docker-Host oder als Ersatz für den bestehenden. Für Hintergrund/Architektur siehe CLAUDE.md, für den Rollout-/Feature-Stand PLAN.md, für die tägliche Redeploy-Routine gegen den bereits laufenden Server den entsprechenden Abschnitt in CLAUDE.md.

1. Voraussetzungen auf dem Zielhost

  • Docker + Docker-Compose-Plugin (docker compose, nicht das alte docker-compose).
  • Optional qrencode (apt-get install qrencode) — für den QR-Code beim iOS-Pairing (Schritt 6). Fehlt es, gibt pairing-info.sh das Pairing-JSON stattdessen als reinen Text aus, den man von Hand ins iOS-Pairing-Feld eintippt/kopiert.
  • Ein SSH-Zugang zum Host für die Einrichtung selbst — danach läuft die Administration über den IrisClient, siehe SERVER-ADMIN.md.

2. Code auf den Host bringen

Entweder direkt klonen:

git clone http://<forgejo-host>:3000/iris/iris-linux-server.git /docker/iris-server

oder von einer lokalen Entwicklungskopie synchronisieren (identisch zum bestehenden Redeploy-Workflow in CLAUDE.md) — --exclude='.env' ist Pflicht, sobald auf dem Zielhost schon eine .env existiert (Schritt 3), sonst überschreibt ein späterer Redeploy die host-spezifischen Werte:

rsync -av --exclude='.git' --exclude='.env' \
  ~/Documents/2ndbrain/linux-server/iris-server/ \
  <user>@<neuer-host>:/docker/iris-server/

<user> ist ein gewöhnlicher, per usermod -aG docker in die Docker-Gruppe aufgenommener Nutzer, kein root — siehe SERVER-ADMIN.md Punkt 4. docker-compose.ymls Bind-Mount-Quellen sind seit dem .env-Umbau (siehe Schritt 3) nicht mehr auf /docker/iris-server/ fest verdrahtet — nur pairing-info.sh leitet seinen Datenpfad weiterhin relativ zur eigenen Skript-Position ab, funktioniert also unabhängig vom gewählten Basisverzeichnis.

3. Host-spezifische Werte in .env setzen

Host-spezifische Werte liegen NICHT mehr direkt in docker-compose.yml (Stand vor 2026-08-31), sondern in einer pro Host individuellen .env (Docker Compose liest sie automatisch aus demselben Verzeichnis). Einmalig aus der committeten Vorlage anlegen:

cd /docker/iris-server
cp .env.example .env

Dann die Werte für diesen Host setzen (siehe .env.example's Kopfkommentar für Details):

Variable Bedeutung
IRIS_SERVER_HOST Echte LAN-IP des neuen Hosts — geht als SubjectAlternativeName ins selbstsignierte TLS-Leaf-Zertifikat (app/certs.py). Muss die Adresse sein, unter der Clients den Server tatsächlich erreichen.
IRIS_DOCKER_DIR Basisverzeichnis für Container-Daten auf diesem Host (Standard: /docker/iris-server — muss zu Schritt 2 passen).
IRIS_VAULT_DIR Obsidian-Vault-Wurzel — bewusst UNABHÄNGIG von IRIS_DOCKER_DIR, kann z. B. auf einem separaten NAS-Mount liegen.
APP_UID/APP_GID/DOCKER_GID Nicht von Hand setzen — siehe Schritt 3a.

LLM_BASE_URL/LLM_MODEL (Backend für vault-admin enrich, LLM-gestützte Kurzbeschreibungen — unabhängig vom eigentlichen Agent-Backend, das später per POST /config gesetzt wird) stehen weiterhin direkt in docker-compose.yml (Services hermes/hermes-scheduled), zeigen aktuell auf ein bestimmtes Ollama im LAN — ggf. auf ein für den neuen Host erreichbares Backend ändern.

3a. Host-User/Docker-GID ermitteln

Der server-Container läuft als non-root (iris, UID/GID passend zum echten Host-Login) und braucht dafür zusätzlich APP_UID/APP_GID/DOCKER_GID in .envniemals von Hand auf einen festen Wert wie 1000 setzen, das bricht auf jedem Host mit einem anderen Admin-Login. Stattdessen live ermitteln (läuft idempotent, auch bei jedem späteren Redeploy erneut ausführbar):

./scripts/detect-host-env.sh

Siehe CLAUDE.md, Abschnitt "server läuft als non-root", für die volle Begründung — u. a. warum hermes/hermes-scheduled/tunnel bewusst weiterhin als root laufen.

4. Erststart

cd /docker/iris-server
docker compose up -d --build

Auf einem leeren /data-Volume greift entrypoint.sh/entrypoint.pys "erster Start"-Zweig:

  • docker-entrypoint.sh bringt /data in einen für den non-root iris-Prozess beschreibbaren Zustand (Root→gosu-Privilegien-Drop, siehe Schritt 3a) — läuft bei jedem Start, kein Sonderfall fürs erste Mal,
  • eigene CA + Leaf-Zertifikat werden erzeugt (app/certs.py),
  • ein Bearer-Token wird generiert und persistiert (app/secrets_store.py),
  • Hermes' Basis-Config/Skills sowie SOUL.md werden aus dem Image-Seed kopiert (in BEIDE Hermes-Container, iris-hermes und iris-hermes-scheduled),
  • die idempotenten Hermes-Quellcode-Patches laufen (hermes/patch_hermes.py).

Alle fünf Container sollten danach laufen (iris-server, iris-hermes, iris-hermes-scheduled, iris-tunnel, iris-bgutil-provider):

docker compose ps

5. Verifizieren

curl -sk https://<neuer-host>:8765/health

Erwartete Antwort (Shape muss zu RemoteServerClient.swifts Erwartung passen):

{"status":"ok","version":"0.1.0","job_count":0,"jobs_version":0,"notifications_version":0,
 "vault_root":"","available_agents":{"hermes":false,"claudeCode":false,"opencode":false},
 "companion_chat_version":0,"qwen_voice_version":0,"platform":"linux"}

https, nicht http — der Server terminiert TLS selbst über uvicorn (-k unterdrückt die Zertifikatsprüfung für den selbstsignierten Leaf beim reinen Health-Check).

6. Clients pairen

Auf dem Host selbst (nicht im Container):

./scripts/pairing-info.sh

Erzeugt beim ersten Aufruf ein dediziertes SSH-Schlüsselpaar für die spätere Admin-Shell/Toolcall-Proxy-Nutzung, zeigt den QR-Code fürs iOS-Pairing an und schreibt /docker/iris-server/data/pairing-macos.json für den macOS-IrisClient (Zahnrad → „+" → Datei-Drop, siehe AddConnectionSheetView).

7. Vault befüllen

/docker/iris-server/vault/ startet leer. Der Nutzer befüllt ihn selbst — per rsync, git clone, Syncthing o. Ä. — das ist bewusst außerhalb der Verantwortung der App (siehe SERVER-ADMIN.md Punkt 3). Ohne Inhalt verhalten sich alle /vault/*-Routen korrekt wie bei einem leeren Vault (leere Listen, kein Fehler).

Alternative: Bestehendes Backup restaurieren statt neu einrichten

Wer keinen komplett leeren Server will, sondern eine Kopie eines bereits laufenden Servers (Vault + Hermes-Daten + Chat-/Job-Historie in einem Schritt statt Schritt 6+7 von Hand), kann nach Schritt 5 stattdessen ein vorhandenes Backup restaurieren:

curl -sk -X POST https://<neuer-host>:8765/backup/restore \
  -H "Authorization: Bearer <bearer-token-des-NEUEN-servers>" \
  -H "Content-Type: application/json" \
  -d '{"path": "<pfad-zur-hochgeladenen-zip>", "scope": "everything"}'

In der Praxis läuft das über den IrisClient (Einstellungen → Backup & Restore → „Von lokaler Datei wiederherstellen"), der das Hochladen selbst übernimmt. Das Backup-Manifest-Format ist cross-plattform-kompatibel (Mac ↔ Linux, siehe BackupUnit-Dokumentation in BackupRestoreManager.swift bzw. app/backup_manager.pys UNIT_MAPPING) — ein auf dem bestehenden Server erstelltes Backup lässt sich so direkt auf dem neuen wiederherstellen. scope: "everything" übernimmt auch die Backend-Konfiguration des Ursprungsservers; "dataOnly" lässt sie unangetastet, falls der neue Host ein anderes LLM-Backend nutzen soll.

Danach: Administration ohne SSH

Ab hier läuft alles über den IrisClient (Agent-Backend wählen, API-Keys hinterlegen, Systemprompts, Job-Hooks, Hintergrund-Jobs, Vault-Wartung) — kein weiterer SSH-Zugriff nötig. Details: SERVER-ADMIN.md.