package host import ( "bufio" "fmt" "os" "strings" "github.com/bdw/ship/internal/ssh" "github.com/bdw/ship/internal/state" "github.com/spf13/cobra" ) var updateCmd = &cobra.Command{ Use: "update", Short: "Update VPS packages", Long: "Run apt update && apt upgrade on the VPS", RunE: runUpdate, } func init() { updateCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt") } func runUpdate(cmd *cobra.Command, args []string) error { st, err := state.Load() if err != nil { return fmt.Errorf("error loading state: %w", err) } host, _ := cmd.Flags().GetString("host") if host == "" { host = st.GetDefaultHost() } if host == "" { return fmt.Errorf("--host is required (no default host set)") } yes, _ := cmd.Flags().GetBool("yes") if !yes { fmt.Printf("This will run apt update && apt upgrade on %s\n", host) fmt.Print("Continue? [Y/n]: ") reader := bufio.NewReader(os.Stdin) response, _ := reader.ReadString('\n') response = strings.TrimSpace(response) if response == "n" || response == "N" { fmt.Println("Aborted.") return nil } } fmt.Printf("Connecting to %s...\n", host) client, err := ssh.Connect(host) if err != nil { return fmt.Errorf("error connecting to VPS: %w", err) } defer client.Close() fmt.Println("\n-> Running apt update...") if err := client.RunSudoStream("apt update"); err != nil { return fmt.Errorf("error running apt update: %w", err) } fmt.Println("\n-> Running apt upgrade...") if err := client.RunSudoStream("DEBIAN_FRONTEND=noninteractive apt upgrade -y"); err != nil { return fmt.Errorf("error running apt upgrade: %w", err) } fmt.Println() if output, err := client.Run("[ -f /var/run/reboot-required ] && echo 'yes' || echo 'no'"); err == nil { if strings.TrimSpace(output) == "yes" { fmt.Print("A reboot is required to complete the update. Reboot now? [Y/n]: ") reader := bufio.NewReader(os.Stdin) response, _ := reader.ReadString('\n') response = strings.TrimSpace(response) if response == "" || response == "y" || response == "Y" { fmt.Println("Rebooting...") if _, err := client.RunSudo("reboot"); err != nil { // reboot command often returns an error as connection drops // this is expected behavior } fmt.Println("Reboot initiated. The host will be back online shortly.") return nil } fmt.Println("Skipping reboot. Run 'sudo reboot' manually when ready.") } } fmt.Println("Update complete") return nil }