Files
sultan/pkg/cli/cli.go
T

146 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package cli implementiert die Befehlszeile von sultan:
//
// sultan server Registry, Stream-Hub, Policy-Engine, Traefik-Provider
// sultan agent Agent: Enrollment, Stream, Docker-Watcher
// sultan token Mandanten, Einladungen, Knoten, Lizenzen verwalten
// sultan accountant Zentrale für Inventar und Abrechnung
// sultan version
//
// Run ist ohne Prozessgrenzen testbar; main ruft nur Run auf.
package cli
import (
"context"
"errors"
"flag"
"fmt"
"io"
"log/slog"
"os"
"strings"
"time"
"sultan/pkg/server"
)
// Version wird beim Bauen gesetzt: -ldflags "-X sultan/pkg/cli.Version=1.2.3".
var Version = "dev"
const usage = `sultan – verteiltes Ingress-Routing mit ZeroTrust-Service-Discovery
Befehle:
sultan server Server starten (Registry, Agent-Port, Traefik-Provider)
sultan agent Agent starten (Docker-Host oder Bare-Metal)
sultan token Mandanten, Einladungen, Knoten und Lizenzen verwalten
sultan accountant Zentrale für Inventar und Abrechnung starten bzw. verwalten
sultan dump-landing eingebaute Lander-Seiten als Vorlagen ausgeben
sultan version Version ausgeben
sultan help diese Hilfe
Hilfe zu einem Befehl: sultan <befehl> --help
`
// Run führt die Befehlszeile aus und liefert den Exit-Code (0 ok, 1 Fehler,
// 2 Bedienfehler). ctx endet z. B. bei SIGTERM.
func Run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
fmt.Fprint(stderr, usage)
return 2
}
switch args[0] {
case "help", "-h", "--help":
fmt.Fprint(stdout, usage)
return 0
case "version":
fmt.Fprintf(stdout, "sultan %s\nZeitzone: %s\n", Version, time.Local)
return 0
case "server":
return runServer(ctx, args[1:], stdout, stderr)
case "token":
return runToken(ctx, args[1:], stdout, stderr)
case "agent":
return runAgent(ctx, args[1:], stderr)
case "accountant":
return runAccountant(ctx, args[1:], stdout, stderr)
case "dump-landing":
return runDump(args[1:], stdout, stderr)
default:
fmt.Fprintf(stderr, "unbekannter Befehl %q\n\n%s", args[0], usage)
return 2
}
}
func runServer(ctx context.Context, args []string, stdout, stderr io.Writer) int {
cfg, err := parseServerFlags(args, stderr, os.Getenv)
if errors.Is(err, flag.ErrHelp) {
return 0
}
if err != nil {
fmt.Fprintln(stderr, err)
return 2
}
cfg.Logger = slog.New(slog.NewTextHandler(stderr, nil))
cfg.Version = Version
s, err := server.New(ctx, cfg)
if err != nil {
fmt.Fprintln(stderr, "Start fehlgeschlagen:", err)
return 1
}
if err := s.Run(ctx); err != nil {
fmt.Fprintln(stderr, "Server beendet mit Fehler:", err)
return 1
}
return 0
}
// parseServerFlags liest die Flags von `sultan server`; Defaults wie in
// server.Config. Jedes Flag ist auch als SULTAN_<FLAG> setzbar (Flags gewinnen).
func parseServerFlags(args []string, output io.Writer, getenv func(string) string) (server.Config, error) {
fs := flag.NewFlagSet("sultan server", flag.ContinueOnError)
fs.SetOutput(output)
var cfg server.Config
fs.StringVar(&cfg.DataDir, "data-dir", "./suldata", "Datenverzeichnis (sultan.db, server.key/.crt, master.key)")
fs.StringVar(&cfg.AgentAddr, "agent-addr", ":8443", "Adresse des Agent-Ports (nur TLS)")
fs.StringVar(&cfg.ProviderAddr, "provider-addr", "127.0.0.1:8080",
"Adresse für Traefik (/v1/traefik-config, Klartext – im Container :8080 und nur auf 127.0.0.1 veröffentlichen)")
fs.Var((*stringList)(&cfg.PublicURLs), "public-url", "Adresse, unter der Agents den Server erreichen (z. B. https://ingress.example.com:8443); mehrfach angebbar (öffentlich, NetBird …); wird gemerkt, die erste steckt in Einladungen")
fs.StringVar(&cfg.LanderAddr, "lander-addr", "127.0.0.1:8081", `Adresse des eingebauten Landers (Hinweis-/Wartungsseiten); "none" = externer Lander`)
fs.StringVar(&cfg.InterceptorURL, "interceptor-url", "", "URL, unter der Traefik den Lander erreicht (Default: --lander-addr)")
fs.StringVar(&cfg.HosterName, "hoster-name", "", "Name des Hosting-Anbieters (Einladungsseiten)")
fs.StringVar(&cfg.HosterURL, "hoster-url", "", "Angebotsseite des Hosting-Anbieters")
fs.StringVar(&cfg.ProURL, "pro-url", "", "Pro-Angebot, verlinkt auf Community-Wartungsseiten")
fs.DurationVar(&cfg.RefreshInterval, "refresh", time.Minute, "Intervall der Neubewertung aller Knoten")
fs.Var((*stringList)(&cfg.SlackWebhooks), "notify-slack", "Slack-kompatibler Webhook für Benachrichtigungen (mehrfach angebbar)")
fs.Var((*stringList)(&cfg.NtfyURLs), "notify-ntfy", "ntfy-Topic-URL für Benachrichtigungen, z. B. https://ntfy.sh/sultan (mehrfach angebbar)")
fs.StringVar(&cfg.NtfyToken, "notify-ntfy-token", "", "Access-Token für ntfy")
fs.DurationVar(&cfg.OfflineAlertAfter, "offline-alert-after", 15*time.Minute, "Alarm, wenn ein Knoten so lange getrennt ist (nicht bei Wartung)")
fs.IntVar(&cfg.EnrollLimit, "enroll-limit", 10, "Enrollment-Versuche pro Minute und Client-IP (-1 = unbegrenzt)")
fs.DurationVar(&cfg.LeaseDuration, "lease-duration", 0, "feste Lease-Laufzeit für alle Knoten (0 = je Tier: Community 14 Tage, kommerziell 7 Tage)")
fs.StringVar(&cfg.AccountantToken, "accountant-token", "", "Einladung von `sultan accountant invite` (nur beim ersten Start; danach accountant.json)")
fs.StringVar(&cfg.AccountantURL, "accountant", "", "Accountant-Adresse, z. B. https://accountant.example.de:8444 (Override; sonst aus der Einladung)")
annotateEnv(fs)
if err := fs.Parse(args); err != nil {
return server.Config{}, err
}
if err := applyEnv(fs, getenv); err != nil {
return server.Config{}, err
}
if fs.NArg() > 0 {
return server.Config{}, fmt.Errorf("unerwartetes Argument %q", fs.Arg(0))
}
return cfg, nil
}
// stringList ist ein mehrfach angebbares String-Flag.
type stringList []string
func (l *stringList) String() string { return strings.Join(*l, ",") }
// Set ignoriert leere Werte (z. B. eine leere Variable in Compose).
func (l *stringList) Set(v string) error {
if v = strings.TrimSpace(v); v != "" {
*l = append(*l, v)
}
return nil
}