2026-03-28 11:29:42 +01:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
"errors"
|
2026-03-28 11:29:42 +01:00
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"runtime"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/charmbracelet/huh"
|
|
|
|
|
"github.com/fatih/color"
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
|
2026-03-29 16:54:32 +02:00
|
|
|
"github.com/torrentclaw/torrentclaw-cli/internal/arr"
|
2026-03-28 11:29:42 +01:00
|
|
|
"github.com/torrentclaw/torrentclaw-cli/internal/config"
|
2026-03-29 16:54:32 +02:00
|
|
|
"github.com/torrentclaw/torrentclaw-cli/internal/mediaserver"
|
2026-03-28 11:29:42 +01:00
|
|
|
)
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
func newInitCmd() *cobra.Command {
|
2026-03-28 11:29:42 +01:00
|
|
|
var apiURL string
|
|
|
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
Use: "init",
|
|
|
|
|
Aliases: []string{"setup"},
|
|
|
|
|
Short: "First-time configuration wizard",
|
|
|
|
|
Long: `Interactive setup that connects your account, picks a download directory,
|
|
|
|
|
and optionally installs the daemon as a background service.
|
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
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
Opens your browser to create/copy your API key, validates it against the
|
|
|
|
|
server, and saves your configuration.
|
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
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
Run this once after installing unarr. To customize settings later,
|
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
|
|
|
use 'unarr config' or edit ~/.config/unarr/config.toml directly.`,
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
Example: ` unarr init
|
|
|
|
|
unarr init --api-url https://custom.server.com`,
|
2026-03-28 11:29:42 +01:00
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
return runInit(apiURL)
|
2026-03-28 11:29:42 +01:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd.Flags().StringVar(&apiURL, "api-url", "", "API URL override (default: https://torrentclaw.com)")
|
|
|
|
|
|
|
|
|
|
return cmd
|
|
|
|
|
}
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
func runInit(apiURLOverride string) error {
|
|
|
|
|
if !isTerminal() {
|
|
|
|
|
return fmt.Errorf("interactive mode requires a terminal (use UNARR_API_KEY env var instead)")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 11:29:42 +01:00
|
|
|
bold := color.New(color.Bold)
|
|
|
|
|
green := color.New(color.FgGreen)
|
|
|
|
|
cyan := color.New(color.FgCyan)
|
2026-03-29 16:54:32 +02:00
|
|
|
dim := color.New(color.FgHiBlack)
|
2026-03-28 11:29:42 +01:00
|
|
|
|
|
|
|
|
fmt.Println()
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
bold.Println(" unarr init")
|
2026-03-28 11:29:42 +01:00
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
cfg := loadConfig()
|
|
|
|
|
|
|
|
|
|
// Determine API URL
|
|
|
|
|
apiURL := cfg.Auth.APIURL
|
|
|
|
|
if apiURLOverride != "" {
|
|
|
|
|
apiURL = apiURLOverride
|
|
|
|
|
}
|
|
|
|
|
if apiURL == "" {
|
|
|
|
|
apiURL = "https://torrentclaw.com"
|
|
|
|
|
}
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
// ── Step 1/3: Connect account ───────────────────────────────────
|
|
|
|
|
|
2026-03-28 11:29:42 +01:00
|
|
|
keysURL := apiURL + "/profile?tab=apikey"
|
|
|
|
|
fmt.Printf(" Opening %s ...\n", keysURL)
|
|
|
|
|
openBrowser(keysURL)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
apiKey := cfg.Auth.APIKey
|
|
|
|
|
err := huh.NewForm(
|
|
|
|
|
huh.NewGroup(
|
|
|
|
|
huh.NewInput().
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
Title("Step 1/3 — API Key").
|
2026-03-28 11:29:42 +01:00
|
|
|
Description("Copy it from the page that just opened in your browser").
|
|
|
|
|
Placeholder("tc_...").
|
|
|
|
|
Value(&apiKey).
|
|
|
|
|
Validate(func(s string) error {
|
|
|
|
|
s = strings.TrimSpace(s)
|
|
|
|
|
if s == "" {
|
|
|
|
|
return fmt.Errorf("API key is required")
|
|
|
|
|
}
|
|
|
|
|
if !strings.HasPrefix(s, "tc_") {
|
|
|
|
|
return fmt.Errorf("API key should start with tc_")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
).Run()
|
|
|
|
|
if err != nil {
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
if errors.Is(err, huh.ErrUserAborted) {
|
|
|
|
|
fmt.Println("\n Init cancelled.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-03-28 11:29:42 +01:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
apiKey = strings.TrimSpace(apiKey)
|
|
|
|
|
|
|
|
|
|
// Validate API key by registering with the server
|
|
|
|
|
fmt.Print(" Verifying API key... ")
|
|
|
|
|
|
|
|
|
|
agentID := cfg.Agent.ID
|
|
|
|
|
if agentID == "" {
|
|
|
|
|
agentID = uuid.New().String()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hostname, _ := os.Hostname()
|
|
|
|
|
agentName := cfg.Agent.Name
|
|
|
|
|
if agentName == "" {
|
|
|
|
|
agentName = hostname
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ac := agent.NewClient(apiURL, apiKey, "unarr/"+Version)
|
|
|
|
|
resp, err := ac.Register(context.Background(), agent.RegisterRequest{
|
|
|
|
|
AgentID: agentID,
|
|
|
|
|
Name: agentName,
|
|
|
|
|
OS: runtime.GOOS,
|
|
|
|
|
Arch: runtime.GOARCH,
|
|
|
|
|
Version: Version,
|
|
|
|
|
DownloadDir: cfg.Download.Dir,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
color.Red("FAILED")
|
|
|
|
|
fmt.Println()
|
|
|
|
|
return fmt.Errorf("API key validation failed: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
green.Println("OK")
|
|
|
|
|
fmt.Printf(" Connected as %s (%s) [%s]\n", resp.User.Name, resp.User.Email, strings.ToUpper(resp.User.Plan))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
// ── Step 2/3: Download directory ────────────────────────────────
|
|
|
|
|
|
2026-03-28 11:29:42 +01:00
|
|
|
downloadDir := cfg.Download.Dir
|
2026-03-29 16:54:32 +02:00
|
|
|
|
|
|
|
|
// Detect media servers and library paths
|
|
|
|
|
detected := mediaserver.Detect()
|
|
|
|
|
if len(detected.Servers) > 0 {
|
|
|
|
|
for _, s := range detected.Servers {
|
|
|
|
|
cyan.Printf(" Detected %s at %s\n", s.Name, s.URL)
|
|
|
|
|
}
|
|
|
|
|
if len(detected.Paths) > 0 {
|
|
|
|
|
dim.Printf(" Found media libraries: %s\n", strings.Join(detected.Paths, ", "))
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If no dir yet and we detected media paths, offer a Select; otherwise show Input
|
|
|
|
|
needsInput := true
|
|
|
|
|
if downloadDir == "" && len(detected.Paths) > 0 {
|
|
|
|
|
var options []huh.Option[string]
|
|
|
|
|
for _, p := range detected.Paths {
|
|
|
|
|
options = append(options, huh.NewOption(p, p))
|
|
|
|
|
}
|
|
|
|
|
if parent := mediaserver.ParentDir(detected.Paths); parent != "" {
|
|
|
|
|
options = append(options, huh.NewOption(parent+" (parent directory)", parent))
|
|
|
|
|
}
|
|
|
|
|
options = append(options, huh.NewOption(defaultDownloadDir()+" (default)", defaultDownloadDir()))
|
|
|
|
|
options = append(options, huh.NewOption("Custom path...", "__custom__"))
|
|
|
|
|
|
|
|
|
|
downloadDir = detected.Paths[0]
|
|
|
|
|
err = huh.NewForm(
|
|
|
|
|
huh.NewGroup(
|
|
|
|
|
huh.NewSelect[string]().
|
|
|
|
|
Title("Step 2/3 — Download Directory").
|
|
|
|
|
Description("Detected media libraries on your system").
|
|
|
|
|
Options(options...).
|
|
|
|
|
Value(&downloadDir),
|
|
|
|
|
),
|
|
|
|
|
).Run()
|
|
|
|
|
if err != nil {
|
|
|
|
|
if errors.Is(err, huh.ErrUserAborted) {
|
|
|
|
|
fmt.Println("\n Init cancelled.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
needsInput = downloadDir == "__custom__"
|
|
|
|
|
if needsInput {
|
|
|
|
|
downloadDir = defaultDownloadDir()
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-28 11:29:42 +01:00
|
|
|
if downloadDir == "" {
|
|
|
|
|
downloadDir = defaultDownloadDir()
|
|
|
|
|
}
|
2026-03-29 16:54:32 +02:00
|
|
|
if needsInput {
|
|
|
|
|
err = huh.NewForm(
|
|
|
|
|
huh.NewGroup(
|
|
|
|
|
huh.NewInput().
|
|
|
|
|
Title("Step 2/3 — Download Directory").
|
|
|
|
|
Description("Where should downloaded files be saved?").
|
|
|
|
|
Value(&downloadDir),
|
|
|
|
|
),
|
|
|
|
|
).Run()
|
|
|
|
|
if err != nil {
|
|
|
|
|
if errors.Is(err, huh.ErrUserAborted) {
|
|
|
|
|
fmt.Println("\n Init cancelled.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return err
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
}
|
2026-03-28 11:29:42 +01:00
|
|
|
}
|
|
|
|
|
downloadDir = expandHome(strings.TrimSpace(downloadDir))
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
// ── Step 3/3: Install daemon ────────────────────────────────────
|
2026-03-28 11:29:42 +01:00
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
var installDaemon bool
|
2026-03-28 11:29:42 +01:00
|
|
|
err = huh.NewForm(
|
|
|
|
|
huh.NewGroup(
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
huh.NewConfirm().
|
|
|
|
|
Title("Step 3/3 — Install background service?").
|
|
|
|
|
Description("Starts unarr automatically on boot (systemd/launchd)").
|
|
|
|
|
Affirmative("Yes, install and start").
|
|
|
|
|
Negative("No, I'll run it manually").
|
|
|
|
|
Value(&installDaemon),
|
2026-03-28 11:29:42 +01:00
|
|
|
),
|
|
|
|
|
).Run()
|
|
|
|
|
if err != nil {
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
if errors.Is(err, huh.ErrUserAborted) {
|
|
|
|
|
fmt.Println("\n Init cancelled.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2026-03-28 11:29:42 +01:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
// ── Save config ─────────────────────────────────────────────────
|
2026-03-28 11:29:42 +01:00
|
|
|
|
|
|
|
|
cfg.Auth.APIKey = apiKey
|
|
|
|
|
cfg.Auth.APIURL = apiURL
|
|
|
|
|
cfg.Agent.ID = agentID
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
cfg.Agent.Name = agentName
|
2026-03-28 11:29:42 +01:00
|
|
|
cfg.Download.Dir = downloadDir
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
if cfg.Download.PreferredMethod == "" {
|
|
|
|
|
cfg.Download.PreferredMethod = "auto"
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-28 11:29:42 +01:00
|
|
|
if cfg.Organize.MoviesDir == "" {
|
|
|
|
|
cfg.Organize.MoviesDir = filepath.Join(downloadDir, "Movies")
|
|
|
|
|
}
|
|
|
|
|
if cfg.Organize.TVShowsDir == "" {
|
|
|
|
|
cfg.Organize.TVShowsDir = filepath.Join(downloadDir, "TV Shows")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := cfg.ValidatePaths(); err != nil {
|
|
|
|
|
return fmt.Errorf("unsafe configuration: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
configPath := config.FilePath()
|
|
|
|
|
if cfgFile != "" {
|
|
|
|
|
configPath = cfgFile
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := config.Save(cfg, configPath); err != nil {
|
|
|
|
|
return fmt.Errorf("save config: %w", err)
|
|
|
|
|
}
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
appCfg = cfg // update cached config so subsequent calls see the new values
|
|
|
|
|
|
|
|
|
|
// ── Install daemon (if requested) ───────────────────────────────
|
|
|
|
|
|
|
|
|
|
if installDaemon {
|
|
|
|
|
fmt.Println()
|
|
|
|
|
if err := runDaemonInstall(); err != nil {
|
|
|
|
|
color.New(color.FgYellow).Printf(" Could not install daemon: %s\n", err)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Println(" You can install it later with: " + bold.Sprint("unarr daemon install"))
|
|
|
|
|
fmt.Println(" Or run manually with: " + bold.Sprint("unarr start"))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-29 16:54:32 +02:00
|
|
|
// ── Debrid auto-detection from *arr ─────────────────────────────
|
|
|
|
|
|
|
|
|
|
if resp.User.IsPro {
|
|
|
|
|
debridTokens := detectDebridFromArr(dim)
|
|
|
|
|
if len(debridTokens) > 0 {
|
|
|
|
|
fmt.Println()
|
|
|
|
|
cyan.Printf(" Found %d debrid token(s) from your *arr setup:\n", len(debridTokens))
|
|
|
|
|
for _, dt := range debridTokens {
|
|
|
|
|
masked := dt.Token
|
|
|
|
|
if len(masked) > 8 {
|
|
|
|
|
masked = masked[:8] + "..."
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf(" %s (%s) — %s\n", dt.Provider, dt.Name, masked)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
var configureDebrid bool
|
|
|
|
|
err = huh.NewForm(
|
|
|
|
|
huh.NewGroup(
|
|
|
|
|
huh.NewConfirm().
|
|
|
|
|
Title("Configure debrid automatically?").
|
|
|
|
|
Description("Validates and saves the token to your unarr account").
|
|
|
|
|
Affirmative("Yes, configure").
|
|
|
|
|
Negative("No, skip").
|
|
|
|
|
Value(&configureDebrid),
|
|
|
|
|
),
|
|
|
|
|
).Run()
|
|
|
|
|
if err == nil && configureDebrid {
|
|
|
|
|
for _, dt := range debridTokens {
|
|
|
|
|
fmt.Printf(" Configuring %s... ", dt.Provider)
|
|
|
|
|
result, err := ac.ConfigureDebrid(context.Background(), agent.ConfigureDebridRequest{
|
|
|
|
|
Provider: dt.Provider,
|
|
|
|
|
Token: dt.Token,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
color.New(color.FgYellow).Printf("failed: %s\n", err)
|
|
|
|
|
} else if result.Success {
|
|
|
|
|
green.Printf("OK")
|
|
|
|
|
if result.Account.Username != "" {
|
|
|
|
|
fmt.Printf(" (%s", result.Account.Username)
|
|
|
|
|
if result.Account.Premium {
|
|
|
|
|
fmt.Print(", premium")
|
|
|
|
|
}
|
|
|
|
|
fmt.Print(")")
|
|
|
|
|
}
|
|
|
|
|
fmt.Println()
|
|
|
|
|
} else if result.Error != "" {
|
|
|
|
|
color.New(color.FgYellow).Printf("failed: %s\n", result.Error)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
// ── Summary ─────────────────────────────────────────────────────
|
2026-03-28 11:29:42 +01:00
|
|
|
|
|
|
|
|
fmt.Println()
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
green.Println(" ✓ unarr is ready!")
|
2026-03-28 11:29:42 +01:00
|
|
|
fmt.Println()
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
fmt.Printf(" Dashboard: %s/downloads\n", apiURL)
|
|
|
|
|
fmt.Printf(" Config: %s\n", configPath)
|
2026-03-28 11:29:42 +01:00
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// Features summary
|
|
|
|
|
features := []string{}
|
|
|
|
|
if resp.Features.Torrent {
|
|
|
|
|
features = append(features, "Torrent")
|
|
|
|
|
}
|
|
|
|
|
if resp.Features.Debrid {
|
|
|
|
|
features = append(features, "Debrid")
|
|
|
|
|
}
|
|
|
|
|
if resp.Features.Usenet {
|
|
|
|
|
features = append(features, "Usenet")
|
|
|
|
|
}
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
if len(features) > 0 {
|
|
|
|
|
cyan.Printf(" Available: %s\n", strings.Join(features, ", "))
|
2026-03-28 11:29:42 +01:00
|
|
|
}
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
if !installDaemon {
|
|
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Println(" Start the daemon:")
|
|
|
|
|
fmt.Println(" " + bold.Sprint("unarr start") + " foreground (Ctrl+C to stop)")
|
|
|
|
|
fmt.Println(" " + bold.Sprint("unarr daemon install") + " background service (auto-start on boot)")
|
2026-03-28 11:29:42 +01:00
|
|
|
}
|
|
|
|
|
|
feat: replace setup with init wizard + interactive config menu
- `unarr init` (alias: `unarr setup`): streamlined 3-step wizard
(API key, download dir, daemon install). Removed method/name prompts
— auto-configured from defaults.
- `unarr config [category]`: interactive menu with 7 categories
(downloads, organization, notifications, device, region, connection,
advanced). Direct access via `unarr config downloads`, etc.
- Extract shared helpers (openBrowser, expandHome, isTerminal) to
helpers.go. Delete old setup.go and config.go.
- Update all "unarr setup" references to "unarr init" across daemon,
doctor, status, README, install scripts.
2026-03-29 12:09:03 +02:00
|
|
|
fmt.Println()
|
|
|
|
|
fmt.Println(" Customize speed limits, notifications, and more:")
|
|
|
|
|
fmt.Println(" " + bold.Sprint("unarr config"))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
return nil
|
2026-03-28 11:29:42 +01:00
|
|
|
}
|
2026-03-29 16:54:32 +02:00
|
|
|
|
|
|
|
|
// detectDebridFromArr does a lightweight scan for *arr instances and extracts
|
|
|
|
|
// debrid tokens from their download client configs.
|
|
|
|
|
func detectDebridFromArr(dim *color.Color) []arr.DebridToken {
|
|
|
|
|
dim.Println(" Scanning for *arr instances with debrid...")
|
|
|
|
|
|
|
|
|
|
instances := arr.Discover()
|
|
|
|
|
if len(instances) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tokens []arr.DebridToken
|
|
|
|
|
for _, inst := range instances {
|
|
|
|
|
if inst.App == "prowlarr" || inst.APIKey == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
client := arr.NewClient(inst.URL, inst.APIKey)
|
|
|
|
|
dcs, _ := client.DownloadClients()
|
|
|
|
|
if len(dcs) == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
tokens = append(tokens, arr.ExtractDebridTokens(dcs, func(id int) []arr.Field {
|
|
|
|
|
fields, _ := client.DownloadClientDetails(id)
|
|
|
|
|
return fields
|
|
|
|
|
})...)
|
|
|
|
|
}
|
|
|
|
|
return tokens
|
|
|
|
|
}
|