package cli import ( "context" "errors" "flag" "fmt" "io" "log/slog" "os" "sultan/pkg/agent" ) func runAgent(ctx context.Context, args []string, stderr io.Writer) int { if len(args) > 0 && args[0] == "maintenance" { return runAgentMaintenance(ctx, args[1:], stderr) } cfg, err := parseAgentFlags(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)) a, err := agent.New(cfg) if err != nil { fmt.Fprintln(stderr, err) return 2 } if err := a.Run(ctx); err != nil { fmt.Fprintln(stderr, "Agent beendet mit Fehler:", err) return 1 } return 0 } // parseAgentFlags liest die Flags von `sultan agent`. SULTAN_TOKEN und // SULTAN_SERVER dienen als Defaults (curl | sh, Compose; Argumente wären über // ps für andere Benutzer sichtbar) – Flags gewinnen. func parseAgentFlags(args []string, output io.Writer, getenv func(string) string) (agent.Config, error) { fs := flag.NewFlagSet("sultan agent", flag.ContinueOnError) fs.SetOutput(output) var cfg agent.Config fs.StringVar(&cfg.ServerURL, "server", "", "Server-Adresse, z. B. https://ingress.example.com:8443 (Override; sonst aus der Einladung)") fs.StringVar(&cfg.Token, "token", "", "Einladung von `sultan token invite` (nur beim ersten Start)") fs.StringVar(&cfg.DataDir, "data-dir", "./suldata", "Datenverzeichnis (node.key, node.json)") fs.StringVar(&cfg.NodeID, "node-id", "", "Knoten-ID (Default: aus dem Hostnamen)") fs.StringVar(&cfg.DockerSocket, "docker-socket", "/var/run/docker.sock", `Docker-Socket ("none" = ohne Docker)`) fs.StringVar(&cfg.BackendHost, "backend-host", "", "Adresse, unter der der Ingress die Dienste erreicht (Default: automatisch)") fs.StringVar(&cfg.BackendInterface, "backend-interface", "", "alternativ: IP dieses Interfaces verwenden (z. B. wt0 für NetBird)") fs.StringVar(&cfg.ConfigDir, "config-dir", "/etc/sultan.d", `Dienste als YAML (*.yaml) für Hosts ohne Docker-Labels ("none" = aus)`) fs.StringVar(&cfg.FQDN, "fqdn", "", "vollständiger Name dieses Hosts fürs Inventar (Default: automatisch über DNS)") annotateEnv(fs) if err := fs.Parse(args); err != nil { return agent.Config{}, err } if err := applyEnv(fs, getenv); err != nil { return agent.Config{}, err } if fs.NArg() > 0 { return agent.Config{}, fmt.Errorf("unerwartetes Argument %q", fs.Arg(0)) } return cfg, nil } // runAgentMaintenance: `sultan agent maintenance on|off [--for] [--reason] // [--public-reason]` – meldet Wartung dieses Knotens beim Server an oder ab, // z. B. aus einem Patch- oder Reboot-Skript. func runAgentMaintenance(ctx context.Context, args []string, stderr io.Writer) int { if len(args) == 0 || (args[0] != "on" && args[0] != "off") { fmt.Fprintln(stderr, "Aufruf: sultan agent maintenance on|off [--for 4h|0] [--reason TEXT] [--public-reason TEXT] [--data-dir DIR]") return 2 } on := args[0] == "on" fs := flag.NewFlagSet("sultan agent maintenance", flag.ContinueOnError) fs.SetOutput(stderr) dataDir := fs.String("data-dir", "./suldata", "Datenverzeichnis des Agenten") server := fs.String("server", "", "Server-Adresse (Override)") forRaw := fs.String("for", "4h", "Dauer (z. B. 30m, 4h, 2d; 0 = unbefristet)") reason := fs.String("reason", "", "interner Grund (Log, Monitoring)") publicReason := fs.String("public-reason", "", "öffentlicher Grund – ersetzt den Standardtext der Wartungsseite") annotateEnv(fs) if err := fs.Parse(args[1:]); err != nil { if errors.Is(err, flag.ErrHelp) { return 0 } return 2 } if err := applyEnv(fs, os.Getenv); err != nil { fmt.Fprintln(stderr, err) return 2 } d, err := parseMaintenanceFor(*forRaw) if err != nil { fmt.Fprintln(stderr, "--for:", err) return 2 } a, err := agent.New(agent.Config{DataDir: *dataDir, ServerURL: *server, Logger: slog.New(slog.NewTextHandler(stderr, nil))}) if err != nil { fmt.Fprintln(stderr, err) return 2 } if err := a.SetMaintenance(ctx, on, d, *reason, *publicReason); err != nil { fmt.Fprintln(stderr, "Fehler:", err) return 1 } if on { fmt.Fprintln(stderr, "Wartung angemeldet – Besucher sehen jetzt die Wartungsseite.") } else { fmt.Fprintln(stderr, "Wartung beendet.") } return 0 }