👑 sultan
Verteiltes Ingress-Routing mit ZeroTrust-Service-Discovery – geschrieben in Go.
Schluss mit Klick-Konfiguration im Proxy-GUI und toten Routen 🪦: Bei Sultan sagen deine Dienste selbst, wie sie erreichbar sein wollen. Ob Docker-Container mit Traefik-Labels 🐳 oder klassischer Bare-Metal-/LAMP-Server mit Drop-in-Config 🖥️ – ein kleiner Agent meldet das Routing sicher an den zentralen Sultan-Server, und Traefik holt sich daraus live seine Konfiguration. Ohne Neustart, ohne Portweiterleitung.
✨ Was Sultan kann
- 🏷️ Routing am Dienst deklarieren – native
traefik.*-Labels in Docker oder Dateien unter/etc/sultan.d/ - 📡 Firewall-freundlich – der Agent baut nur ausgehende Verbindungen auf; funktioniert hinter NAT, CGNAT und DS-Lite
- ⚡ Echtzeit-Updates – Container starten oder stoppen, Traefik weiß es sofort
- 🔐 Sicher von Haus aus – TLS mit Fingerprint-Pinning (wie bei SSH), Ed25519-Schlüssel, die den Host nie verlassen
- 🛡️ Mandantenfähig – jeder Mandant darf nur seine eigenen Domains beanspruchen
- 🌐 ZeroTrust-Overlay – optional über ein WireGuard-Mesh (z. B. NetBird), auch für Hosts ohne öffentliche IP
- 📊 Monitoring – Checkmk-Integration für Knoten- und Lizenzstatus
🧭 So funktioniert's
Internet ──▶ Traefik ──(pollt)──▶ sultan server ◀──(TLS-Stream)── sultan agent (Docker / Bare-Metal)
Ein einziges Binary, drei Rollen:
| Befehl | Aufgabe |
|---|---|
sultan server |
Registry, Policy-Engine und Konfigurationsquelle für Traefik |
sultan agent |
Beobachtet Docker bzw. /etc/sultan.d/ und meldet an den Server |
sultan token |
Schlüssel, Einladungs-Tokens und Lizenzen verwalten |
Die ausführliche Architektur steht in .ai/sultan-system-architecture.md.
🚀 Installation
🚧 Hinweis: Sultan befindet sich noch am Anfang der Entwicklung. Die folgenden Schritte zeigen, wie die Installation aussehen wird – Befehle und Flags können sich noch ändern.
Voraussetzungen
- Zum Bauen: Go (Version laut
go.mod) bzw. Docker mit buildx - Für den Ingress-Knoten: Traefik v3
- Für Docker-Hosts: Zugriff auf
/var/run/docker.sock
1. Quellen holen
git clone ssh://git@git.everdc.eu:10022/chmln-app-dev/sultan.git
cd sultan
2. Binary bauen (amd64 + arm64)
go build -o sultan . && ./sultan version # schnell, nur für die eigene Architektur
deploy/build.sh # Release-Binaries für linux/amd64 + linux/arm64
# → dist/<version>/sultan_linux_amd64, sultan_linux_arm64, SHA256SUMS
sudo install -m 0755 dist/<version>/sultan_linux_amd64 /usr/local/bin/sultan
Die Version kommt aus git describe (Tag, sonst Commit-Hash; -dirty bei uncommitteten Änderungen) und steht in sultan version.
3. Docker-Image bauen (ein Image für Server und Agent, amd64 + arm64)
docker build -t sultan:latest . # lokal, eigene Architektur
docker run --rm sultan:latest sultan version
Das Image ist distroless (nur das statische Binary, ~17 MB) und läuft als nonroot-User 65532. Die Rolle wählt der Befehl: sultan server … bzw. sultan agent ….
4. Image in die Registry schieben (Zot, pod.chmln.de)
cp deploy/.env.example deploy/.env # einmalig; gitignored – nie einchecken!
$EDITOR deploy/.env # REGISTRY_PASSWORD eintragen (Pflicht)
deploy/push-image.sh # → pod.chmln.de/sultan:<version>
LATEST=1 deploy/push-image.sh # zusätzlich :latest (nur für Releases)
Das Skript liest deploy/.env (Defaults: REGISTRY=pod.chmln.de, REGISTRY_USER=sultan, IMAGE_NAME=sultan; ohne REGISTRY_PASSWORD Abbruch), meldet sich per docker login --password-stdin an und baut per docker buildx gleichzeitig für linux/amd64 und linux/arm64 (Go kompiliert quer, kein QEMU nötig). Versionen mit -dirty werden verweigert. Weitere Stellschrauben: PLATFORMS, ENV_FILE.
Danach auf den Hosts: SULTAN_IMAGE=pod.chmln.de/sultan:<version> in der Compose-.env und docker compose pull && docker compose up -d.
5. Binaries auf den statischen Webhost legen
TARGET=deploy@webdocker11:/srv/inst/htdocs deploy/publish-static.sh
STABLE=1 TARGET=… deploy/publish-static.sh # zusätzlich sultan/releases/stable setzen
Ablage per rsync unter sultan/releases/<version>/ (Binaries + SHA256SUMS); ältere Versionen bleiben herunterladbar. Grundlage für das spätere curl … | bash.
6. Mit Docker Compose betreiben
Vorlagen: deploy/compose/server (Ingress-Knoten neben Traefik) und deploy/compose/agent (Docker-Host).
cp -r deploy/compose/server ~/sultan-server && cd ~/sultan-server
cp .env.example .env && $EDITOR .env # SULTAN_IMAGE, SULTAN_PUBLIC_URL
sudo install -d -m 0700 -o 65532 -g 65532 suldata # Datenverzeichnis für den nonroot-User
docker compose up -d
docker compose exec sultan-server sultan token tenant add --id=… --domains=…
docker compose exec sultan-server sultan token invite --tenant=…
Der Agent läuft mit network_mode: host. Nur so meldet er die Host-IP als Backend-Adresse (statt der Container-IP) und den Hostnamen als Knoten-ID. Für den Docker-Socket braucht er DOCKER_GID in der .env (getent group docker | cut -d: -f3).
Server starten (Ingress-Knoten)
sultan server --public-url=https://ingress.example.com:8443 # Daten landen in ./suldata
sultan server --help # alle Optionen
Alle Optionen gehen auch als Umgebungsvariable, z. B. SULTAN_PUBLIC_URL=… sultan server (praktisch für Docker und systemd). Der Server lauscht auf :8443 für Agents und auf 127.0.0.1:8080 für Traefik (beides per --agent-addr / --provider-addr änderbar). Im Verzeichnis ./suldata liegen Datenbank und Schlüssel – bitte sichern 🔑 und nie einchecken. Traefik bindest du über den HTTP-Provider an:
--providers.http.endpoint=http://127.0.0.1:8080/v1/traefik-config
--providers.http.pollInterval=3s
Agent verbinden (Backend-Host)
- Auf dem Server einen Mandanten anlegen und eine Einladung erzeugen 🎟️
sultan token tenant add --id=homelab --name="Mein Homelab" --email=ich@example.com --domains="example.com,*.example.com"
sultan token invite --tenant=homelab # gibt die Einladung aus, 24 h gültig
sultan token --help # alle Verwaltungsbefehle
- Auf dem Backend-Host den Agent starten:
SULTAN_TOKEN=<einladung> sultan agent # Adresse steckt in der Einladung (sultan server --public-url=…)
sultan agent --server=https://ingress.example.com:8443 --token=<einladung> # sonst Adresse angeben
# danach genügt:
sultan agent
Der Agent liest die Container mit traefik.enable=true aus Docker und meldet sie an den Server. Wichtig: Der Ingress erreicht nur veröffentlichte Ports (ports: "8081:80"), nicht solche, die nur auf 127.0.0.1 veröffentlicht sind. Ohne Docker: --docker-socket=none. Über welche Adresse der Ingress den Host erreicht, ermittelt der Agent automatisch; für NetBird z. B. --backend-interface=wt0.
Ohne Docker (Bare-Metal, LAMP …) 🖥️ beschreibst du deine Dienste in /etc/sultan.d/*.yaml:
services:
- name: kunde-a
url: http://127.0.0.1:8080
hosts: [kunde-a.de]
https: true
redirect_http_to_https: true
redirect_www: to_non_www
healthcheck: { expect_title: "KundeA - Willkommen" } # bei Ausfall: Wartungsseite statt Fehler
Beim ersten Verbinden merkt sich der Agent den Fingerprint des Servers – ab dann läuft alles automatisch. 🎉
Wartung 🔧
sultan agent maintenance on --for 2h --public-reason "Wir spielen ein Sicherheitsupdate ein." # auf dem Knoten
sultan token node maintenance --id=pi --for 2h # oder auf dem Server
sultan token node resume --id=pi # beenden
Besucher sehen währenddessen die Wartungsseite mit Grund und voraussichtlichem Ende – auch, wenn der Knoten gerade neu startet.
Monitoring & Benachrichtigungen 📊
sultan server --notify-slack=https://hooks.slack.com/… --notify-ntfy=https://ntfy.sh/mein-sultan
curl -s http://127.0.0.1:8080/metrics/checkmk # Checkmk Local Checks: Verträge, Zahlungen, Knoten
Sultan meldet ablaufende Verträge, Mahnstufen, ausgefallene Knoten und Policy-Verstöße – jedes Ereignis genau einmal.
Hinweis- und Wartungsseiten 🚧
Fällt ein Dienst aus oder läuft ein Abo ab, zeigt Sultan statt eines Fehlers eine freundliche Seite (Deutsch und Englisch). Eigene Gestaltung:
sultan dump-landing # Vorlagen nach ./suldata/landing – anpassen, wirkt sofort
sultan server --hoster-name="Mein Hosting" --hoster-url=https://hosting.example
Fertige Docker-Compose-Vorlagen für Ingress und Backend-Host liegen unter deploy/compose.