unarr/internal/cmd/self_update.go
Deivid Soto 719429b06e docs: improve CLI help, shell completion, and README
- Add command groups (Getting Started, Search, Downloads, Daemon, System)
- Add shell completion command (bash, zsh, fish, powershell)
- Add flag completions for --type, --quality, --sort, --lang, --genre,
  --country, --method, --player
- Improve Long descriptions and Examples for all commands
- Split doctor disk check into platform-specific files (Unix/Windows)
- Validate infoHash length before truncating (prevent panic)
- Fix references to non-existent 'unarr daemon start' command
- Move stats command to System & Diagnostics group
- Rewrite README with complete documentation, correct config format
  (toml not yaml), all commands, shell completion section
2026-03-28 21:36:27 +01:00

125 lines
2.9 KiB
Go

package cmd
import (
"context"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"syscall"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/upgrade"
)
func newSelfUpdateCmd() *cobra.Command {
var force bool
cmd := &cobra.Command{
Use: "self-update",
Short: "Update unarr to the latest version",
Long: `Download and install the latest version of unarr.
Checks GitHub for the latest release, verifies the checksum, and
replaces the current binary. A backup is kept at <binary>.backup.`,
Example: ` unarr self-update
unarr self-update --force`,
RunE: func(cmd *cobra.Command, args []string) error {
return runSelfUpdate(force)
},
}
cmd.Flags().BoolVarP(&force, "force", "f", false, "reinstall even if already up to date")
return cmd
}
func runSelfUpdate(force bool) error {
bold := color.New(color.Bold)
green := color.New(color.FgGreen)
yellow := color.New(color.FgYellow)
fmt.Println()
bold.Println(" unarr self-update")
fmt.Println()
// Check latest version
fmt.Print(" Checking latest version... ")
ctx := context.Background()
latest, err := upgrade.CheckLatest(ctx)
if err != nil {
fmt.Println()
return fmt.Errorf("could not check latest version: %w", err)
}
currentClean := strings.TrimPrefix(Version, "v")
fmt.Printf("v%s\n", latest)
fmt.Printf(" Current version: v%s\n", currentClean)
if currentClean == latest && !force {
fmt.Println()
green.Println(" ✓ Already up to date!")
fmt.Println()
return nil
}
if currentClean == latest && force {
yellow.Println(" Forcing reinstall...")
}
fmt.Println()
upgrader := &upgrade.Upgrader{
CurrentVersion: currentClean,
OnProgress: func(msg string) {
fmt.Printf(" %s\n", msg)
},
}
result := upgrader.Execute(ctx, latest)
fmt.Println()
if !result.Success {
return fmt.Errorf("upgrade failed: %v", result.Error)
}
green.Printf(" ✓ Upgraded v%s → v%s\n", result.OldVersion, result.NewVersion)
if result.BackupPath != "" {
fmt.Printf(" Backup: %s\n", result.BackupPath)
}
fmt.Println()
// If running as daemon, re-exec to restart with new binary
// For interactive use, just suggest restarting
if isRunningAsDaemon() {
fmt.Println(" Restarting daemon with new version...")
binPath, err := os.Executable()
if err != nil {
return fmt.Errorf("could not determine executable path: %w", err)
}
execErr := syscall.Exec(binPath, os.Args, os.Environ())
if execErr != nil && runtime.GOOS == "windows" {
// Windows doesn't support syscall.Exec — start new process
proc := exec.Command(binPath, os.Args[1:]...)
proc.Stdout = os.Stdout
proc.Stderr = os.Stderr
proc.Stdin = os.Stdin
return proc.Start()
}
return execErr
}
return nil
}
func isRunningAsDaemon() bool {
// Simple heuristic: check if "start" was in the original args
for _, arg := range os.Args {
if arg == "start" {
return true
}
}
return false
}