Files
zzet--gortex/cmd/gortex/proxy_cmd.go
T
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

247 lines
7.4 KiB
Go

package main
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/spf13/cobra"
"github.com/zzet/gortex/internal/daemon"
)
// proxyCmd is the canonical surface for managing the remote roster the
// local daemon federates/proxies to. `gortex daemon server add/remove`
// remain as hidden deprecated aliases that delegate here.
var proxyCmd = &cobra.Command{
Use: "proxy",
Short: "Manage the remote Gortex daemons this daemon federates with",
Long: `Maintain ~/.gortex/servers.toml — the roster of remote Gortex daemons
the local daemon federates read queries to and proxies scoped calls to.
gortex proxy add <slug> <url> register a remote
gortex proxy on|off <slug> enable / disable federation to a remote
gortex proxy list|status inspect the roster
Toggling a remote persists across restarts (` + "`off`" + ` writes
enabled=false; ` + "`on`" + ` clears the key, back to default-on) and is
applied to a running daemon without a restart when possible.`,
}
var proxyListCmd = &cobra.Command{
Use: "list",
Short: "Print the remote roster",
RunE: runProxyList,
SilenceUsage: true,
}
var proxyStatusCmd = &cobra.Command{
Use: "status",
Short: "Show each remote's enabled state and reachability",
RunE: runProxyStatus,
SilenceUsage: true,
}
var proxyOnCmd = &cobra.Command{
Use: "on <slug>",
Short: "Enable federation/proxy to a remote (default-on; clears the key)",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error { return runProxyToggle(args[0], true) },
SilenceUsage: true,
}
var proxyOffCmd = &cobra.Command{
Use: "off <slug>",
Short: "Disable federation/proxy to a remote (persists across restarts)",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error { return runProxyToggle(args[0], false) },
SilenceUsage: true,
}
var proxyAddCmd = &cobra.Command{
Use: "add <slug> <url>",
Short: "Add a remote to the roster (http://, https://, or unix:///path)",
Args: cobra.ExactArgs(2),
RunE: runProxyAdd,
SilenceUsage: true,
}
var proxyRemoveCmd = &cobra.Command{
Use: "remove <slug>",
Aliases: []string{"rm"},
Short: "Remove a remote from the roster",
Args: cobra.ExactArgs(1),
RunE: runProxyRemove,
SilenceUsage: true,
}
func init() {
// proxy add reuses the daemon-server-add flag vars + logic so the
// two surfaces stay in lockstep on this canonical form.
proxyAddCmd.Flags().BoolVar(&daemonServerAddDefault, "default", false, "mark this remote as the default")
proxyAddCmd.Flags().StringVar(&daemonServerAddAuthToken, "auth-token", "", "literal bearer token (prefer --auth-token-env)")
proxyAddCmd.Flags().StringVar(&daemonServerAddAuthTokenEnv, "auth-token-env", "", "env-var name the daemon reads at request time")
proxyAddCmd.Flags().StringSliceVar(&daemonServerAddWorkspaces, "workspaces", nil, "pre-declared workspace slugs (comma-separated)")
proxyAddCmd.Flags().BoolVar(&daemonServerAddReadOnly, "read-only", false, "mark this remote read-only (no write tools routed to it)")
proxyCmd.AddCommand(proxyListCmd, proxyStatusCmd, proxyOnCmd, proxyOffCmd, proxyAddCmd, proxyRemoveCmd)
rootCmd.AddCommand(proxyCmd)
}
func runProxyList(_ *cobra.Command, _ []string) error {
cfg, err := daemon.LoadServersConfig("")
if err != nil {
return err
}
if cfg == nil || len(cfg.Server) == 0 {
fmt.Fprintf(os.Stderr, "[gortex] no remotes configured (%s is empty or missing)\n", daemon.ServersConfigPath())
return nil
}
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleLight)
t.Style().Format.Header = text.FormatDefault
t.AppendHeader(table.Row{"slug", "url", "enabled", "read-only", "default", "auth"})
for _, s := range cfg.Server {
t.AppendRow(table.Row{
s.Slug,
s.URL,
yesNo(s.IsEnabled()),
yesNo(s.ReadOnly),
yesNo(s.Default),
authLabel(s),
})
}
t.Render()
return nil
}
func runProxyStatus(_ *cobra.Command, _ []string) error {
cfg, err := daemon.LoadServersConfig("")
if err != nil {
return err
}
if cfg == nil || len(cfg.Server) == 0 {
fmt.Fprintf(os.Stderr, "[gortex] no remotes configured (%s is empty or missing)\n", daemon.ServersConfigPath())
return nil
}
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleLight)
t.Style().Format.Header = text.FormatDefault
t.AppendHeader(table.Row{"slug", "url", "enabled", "reachable", "advertised"})
for _, s := range cfg.Server {
reach, advert := probeRemote(s)
t.AppendRow(table.Row{s.Slug, s.URL, yesNo(s.IsEnabled()), reach, advert})
}
t.Render()
return nil
}
// probeRemote does a short best-effort /v1/health fetch so `proxy status`
// can show liveness and the remote's advertised posture. Failures are
// reported, never fatal.
func probeRemote(s daemon.ServerEntry) (reachable, advertised string) {
cli, err := daemon.NewServerClient(s)
if err != nil {
return "no (" + err.Error() + ")", ""
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
h, err := cli.FetchHealth(ctx)
if err != nil {
return "no", ""
}
posture := "rw"
if h.EffectiveReadOnly() {
posture = "ro"
}
caps := ""
if len(h.Capabilities) > 0 {
caps = " [" + strings.Join(h.Capabilities, ",") + "]"
}
return "yes", posture + caps
}
func runProxyToggle(slug string, enabled bool) error {
cfg, err := daemon.LoadServersConfig("")
if err != nil {
return err
}
if cfg == nil {
cfg = &daemon.ServersConfig{}
}
changed, err := cfg.SetEnabled(slug, enabled)
if err != nil {
return err // unknown slug — surfaced to the user
}
if err := cfg.Save(""); err != nil {
return err
}
state := "disabled"
if enabled {
state = "enabled"
}
if !changed {
fmt.Fprintf(os.Stderr, "[gortex] remote %q already %s\n", slug, state)
return nil
}
fmt.Fprintf(os.Stderr, "[gortex] remote %q %s in %s\n", slug, state, daemon.ServersConfigPath())
proxyApplyToRunningDaemon()
return nil
}
func runProxyAdd(cmd *cobra.Command, args []string) error {
// Positional <url> rides into the shared add path via its flag var.
daemonServerAddURL = args[1]
return runDaemonServerAdd(cmd, args[:1])
}
func runProxyRemove(cmd *cobra.Command, args []string) error {
return runDaemonServerRemove(cmd, args)
}
// proxyApplyToRunningDaemon best-effort applies a roster change to a
// running daemon via the ControlProxy live-reload RPC. When the daemon
// is down or does not yet understand the verb, it degrades to the
// restart hint — never an error (the durable config write already
// succeeded).
func proxyApplyToRunningDaemon() {
if !daemon.IsRunning() {
return
}
c, err := daemonControlClient()
if err != nil {
fmt.Fprintln(os.Stderr, "[gortex] note: run `gortex daemon restart` to apply to the running daemon")
return
}
defer c.Close()
resp, err := c.Control(daemon.ControlProxy, nil)
if err != nil || !resp.OK {
fmt.Fprintln(os.Stderr, "[gortex] note: run `gortex daemon restart` to apply to the running daemon")
return
}
fmt.Fprintln(os.Stderr, "[gortex] applied to the running daemon (no restart needed)")
}
func yesNo(b bool) string {
if b {
return "yes"
}
return ""
}
func authLabel(s daemon.ServerEntry) string {
switch {
case s.AuthTokenEnv != "":
return "env:" + s.AuthTokenEnv
case s.AuthToken != "":
return "literal"
}
return ""
}