From 5861e465a2ccf31d87ea25ac145770786f9cc96e Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 24 Jan 2026 09:48:34 -0800 Subject: Rename project from deploy to ship - Rename module to github.com/bdw/ship - Rename cmd/deploy to cmd/ship - Update all import paths - Update config path from ~/.config/deploy to ~/.config/ship - Update VPS env path from /etc/deploy to /etc/ship - Update README, Makefile, and docs --- cmd/ship/host/update.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 cmd/ship/host/update.go (limited to 'cmd/ship/host/update.go') diff --git a/cmd/ship/host/update.go b/cmd/ship/host/update.go new file mode 100644 index 0000000..5f838b6 --- /dev/null +++ b/cmd/ship/host/update.go @@ -0,0 +1,93 @@ +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 +} -- cgit v1.2.3