Files
sultan/README.md
T

12 KiB
Raw Blame History

👑 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.

📚 Betriebsdoku

  • 🏠 docs/community.md – Server neben Traefik (host-Netz oder Docker-Netz) und Agent einrichten, aktualisieren
  • 🔧 docs/wartung.md – maintenance, drain, stop vs. down, Reboot-Ablauf
  • 💼 docs/hoster.md – Kunden-Onboarding für Hoster (pro/enterprise), Mahnwesen, Vertragsende

🚀 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, zot.chmln.de)

cp deploy/.env.example deploy/.env                # einmalig; gitignored – nie einchecken!
$EDITOR deploy/.env                               # REGISTRY_PASSWORD eintragen (Pflicht)
deploy/push-image.sh                              # → zot.chmln.de/sultan:<version>
LATEST=1 deploy/push-image.sh                     # zusätzlich :latest (nur für Releases)

Das Skript liest deploy/.env (Defaults: REGISTRY=zot.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=zot.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), deploy/compose/agent (Docker-Host) und deploy/compose/accountant (zentrales Inventar). Alle Einstellungen stehen in der .env; welche es gibt und was ihre Defaults sind, zeigt die jeweilige .env.example.

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)

  1. 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
  1. 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.

Für Inventar und Störfall meldet der Agent zusätzlich, was Docker ohnehin weiß – ohne weitere Labels: Compose-Projekt und -Verzeichnis, Startzeit und Health-Status je Container sowie FQDN, Betriebssystem, Docker-Version und NetBird-IP des Hosts. Den FQDN ermittelt er per DNS; abweichend: --fqdn=webdocker11.lan.example.de. Am Routing ändern diese Angaben nichts.

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.

Zentrales Inventar (Accountant) 📒

Mehrere Ingress-Server melden ihr Inventar an eine Zentrale: welche URL bei welchem Mandanten auf welchem Backend-Host (<fqdn>:<port>) und in welchem Compose-Verzeichnis läuft.

sultan accountant --public-url=https://accountant.example.de:8444      # Zentrale starten (Daten in ./accdata)
sultan accountant invite --payee=webproxy11                             # Einladung für einen Ingress-Server
SULTAN_ACCOUNTANT_TOKEN=<einladung> sultan server …                     # auf dem Ingress-Server (nur beim ersten Start)
sultan accountant payee list                                            # wer meldet, letzter Bericht
sultan accountant payee report --id=webproxy11                          # letzter Bericht als JSON
sultan accountant where kunde-a.de                                      # Störfall: Backend, Container, Compose-Verzeichnis, seit wann, wo vorher
sultan accountant routes --tenant=kunde-a                               # alle aktuellen Routen als Tabelle
curl -s http://127.0.0.1:8082/metrics/checkmk                           # Checkmk: je URL und je Ingress-Server
sultan accountant dump-checkmk                                          # Checkmk-Text als Vorlage anpassen

Der Server meldet Änderungen innerhalb von 10 s und sonst stündlich. Ist der Accountant nicht erreichbar, läuft der Ingress unbeeinträchtigt weiter und meldet später.

Abrechnung 💶 – Kunden entstehen automatisch aus den Mandanten; abrechenbare Services markierst du mit dem Label sultan.accountant.billing=<produkt> oder per assign:

sultan accountant customer set --id=kunde-a --cycle=q --flat-rate=89,00 --group=extern
sultan accountant product add --id=olat --name=OLAT --class=billable --price=49,00
sultan accountant assign --host=olat.kunde-a.de --product=olat
sultan accountant billing --month=2026-10 --out=2026-10.csv          # CSV für die Buchhaltung

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.