diff --git a/pkg/cli/cmd/sandboxcmd/sandbox.go b/pkg/cli/cmd/sandboxcmd/sandbox.go index 42043a0..484a4a2 100644 --- a/pkg/cli/cmd/sandboxcmd/sandbox.go +++ b/pkg/cli/cmd/sandboxcmd/sandbox.go @@ -76,7 +76,10 @@ var rolloutCmd = &cobra.Command{ Short: "Build + push + rolling upgrade to sandbox cluster", RunE: func(cmd *cobra.Command, args []string) error { name, _ := cmd.Flags().GetString("name") - return sandbox.Rollout(name) + anyoneClient, _ := cmd.Flags().GetBool("anyone-client") + return sandbox.Rollout(name, sandbox.RolloutFlags{ + AnyoneClient: anyoneClient, + }) }, } @@ -121,6 +124,7 @@ func init() { // rollout flags rolloutCmd.Flags().String("name", "", "Sandbox name (uses active if not specified)") + rolloutCmd.Flags().Bool("anyone-client", false, "Enable Anyone client (SOCKS5 proxy) on all nodes") // ssh flags sshCmd.Flags().String("name", "", "Sandbox name (uses active if not specified)") diff --git a/pkg/cli/production/upgrade/orchestrator.go b/pkg/cli/production/upgrade/orchestrator.go index 459c12f..8c20bdb 100644 --- a/pkg/cli/production/upgrade/orchestrator.go +++ b/pkg/cli/production/upgrade/orchestrator.go @@ -41,7 +41,8 @@ func NewOrchestrator(flags *Flags) *Orchestrator { setup := production.NewProductionSetup(oramaHome, os.Stdout, flags.Force, flags.SkipChecks) setup.SetNameserver(isNameserver) - // Configure Anyone mode (flag > saved preference > auto-detect) + // Configure Anyone mode (explicit flags > saved preferences > auto-detect) + // Explicit flags always win — they represent the user's current intent. if flags.AnyoneRelay { setup.SetAnyoneRelayConfig(&production.AnyoneRelayConfig{ Enabled: true, @@ -55,6 +56,9 @@ func NewOrchestrator(flags *Flags) *Orchestrator { BandwidthPct: flags.AnyoneBandwidth, AccountingMax: flags.AnyoneAccounting, }) + } else if flags.AnyoneClient { + // Explicit --anyone-client flag overrides saved relay prefs and auto-detect. + setup.SetAnyoneClient(true) } else if prefs.AnyoneRelay { // Restore relay config from saved preferences (for firewall rules) orPort := prefs.AnyoneORPort @@ -65,6 +69,8 @@ func NewOrchestrator(flags *Flags) *Orchestrator { Enabled: true, ORPort: orPort, }) + } else if prefs.AnyoneClient { + setup.SetAnyoneClient(true) } else if detectAnyoneRelay(oramaDir) { // Auto-detect: relay is installed but preferences weren't saved. // This happens when upgrading from older versions that didn't persist @@ -79,8 +85,6 @@ func NewOrchestrator(flags *Flags) *Orchestrator { prefs.AnyoneORPort = orPort _ = production.SavePreferences(oramaDir, prefs) fmt.Printf(" Auto-detected Anyone relay (ORPort: %d), saved to preferences\n", orPort) - } else if flags.AnyoneClient || prefs.AnyoneClient { - setup.SetAnyoneClient(true) } return &Orchestrator{ @@ -207,15 +211,15 @@ func (o *Orchestrator) handleBranchPreferences() error { fmt.Printf(" Nameserver mode: enabled (CoreDNS + Caddy)\n") } - // If anyone-client was explicitly provided, update it + // Anyone client and relay are mutually exclusive — setting one clears the other. if o.flags.AnyoneClient { prefs.AnyoneClient = true + prefs.AnyoneRelay = false + prefs.AnyoneORPort = 0 prefsChanged = true - } - - // If anyone-relay was explicitly provided, update it - if o.flags.AnyoneRelay { + } else if o.flags.AnyoneRelay { prefs.AnyoneRelay = true + prefs.AnyoneClient = false prefs.AnyoneORPort = o.flags.AnyoneORPort if prefs.AnyoneORPort == 0 { prefs.AnyoneORPort = 9001 diff --git a/pkg/cli/sandbox/rollout.go b/pkg/cli/sandbox/rollout.go index 396e8f4..8a15385 100644 --- a/pkg/cli/sandbox/rollout.go +++ b/pkg/cli/sandbox/rollout.go @@ -4,14 +4,20 @@ import ( "fmt" "os" "path/filepath" + "strings" "time" "github.com/DeBrosOfficial/network/pkg/cli/remotessh" "github.com/DeBrosOfficial/network/pkg/inspector" ) +// RolloutFlags holds optional flags passed through to `orama node upgrade`. +type RolloutFlags struct { + AnyoneClient bool +} + // Rollout builds, pushes, and performs a rolling upgrade on a sandbox cluster. -func Rollout(name string) error { +func Rollout(name string, flags RolloutFlags) error { cfg, err := LoadConfig() if err != nil { return err @@ -34,6 +40,9 @@ func Rollout(name string) error { info, _ := os.Stat(archivePath) fmt.Printf("Archive: %s (%s)\n\n", filepath.Base(archivePath), formatBytes(info.Size())) + // Build extra flags string for upgrade command + extraFlags := flags.upgradeFlags() + // Step 2: Push archive to all nodes (upload to first, fan out server-to-server) fmt.Println("Pushing archive to all nodes...") if err := fanoutArchive(state.Servers, sshKeyPath, archivePath); err != nil { @@ -54,7 +63,7 @@ func Rollout(name string) error { if i == leaderIdx { continue // skip leader, do it last } - if err := upgradeNode(srv, sshKeyPath, i+1, len(state.Servers)); err != nil { + if err := upgradeNode(srv, sshKeyPath, i+1, len(state.Servers), extraFlags); err != nil { return err } // Wait between nodes @@ -67,7 +76,7 @@ func Rollout(name string) error { // Upgrade leader last if leaderIdx >= 0 { srv := state.Servers[leaderIdx] - if err := upgradeNode(srv, sshKeyPath, len(state.Servers), len(state.Servers)); err != nil { + if err := upgradeNode(srv, sshKeyPath, len(state.Servers), len(state.Servers), extraFlags); err != nil { return err } } @@ -76,6 +85,15 @@ func Rollout(name string) error { return nil } +// upgradeFlags builds the extra CLI flags string for `orama node upgrade`. +func (f RolloutFlags) upgradeFlags() string { + var parts []string + if f.AnyoneClient { + parts = append(parts, "--anyone-client") + } + return strings.Join(parts, " ") +} + // findLeaderIndex returns the index of the RQLite leader node, or -1 if unknown. func findLeaderIndex(state *SandboxState, sshKeyPath string) int { for i, srv := range state.Servers { @@ -92,7 +110,7 @@ func findLeaderIndex(state *SandboxState, sshKeyPath string) int { // It pre-replaces the orama CLI binary before running the upgrade command // to avoid ETXTBSY ("text file busy") errors when the old binary doesn't // have the os.Remove fix in copyBinary(). -func upgradeNode(srv ServerState, sshKeyPath string, current, total int) error { +func upgradeNode(srv ServerState, sshKeyPath string, current, total int, extraFlags string) error { node := inspector.Node{User: "root", Host: srv.IP, SSHKey: sshKeyPath} fmt.Printf(" [%d/%d] Upgrading %s (%s)...\n", current, total, srv.Name, srv.IP) @@ -105,7 +123,11 @@ func upgradeNode(srv ServerState, sshKeyPath string, current, total int) error { return fmt.Errorf("pre-replace orama binary on %s: %w", srv.Name, err) } - if err := remotessh.RunSSHStreaming(node, "orama node upgrade --restart", remotessh.WithNoHostKeyCheck()); err != nil { + upgradeCmd := "orama node upgrade --restart" + if extraFlags != "" { + upgradeCmd += " " + extraFlags + } + if err := remotessh.RunSSHStreaming(node, upgradeCmd, remotessh.WithNoHostKeyCheck()); err != nil { return fmt.Errorf("upgrade %s: %w", srv.Name, err) }