feat(agent): add mirror failover, agent client refactor, status 401 detection

- Mirror pool with health tracking and exponential backoff for failed hosts
- Agent client routes requests through mirror pool with retry semantics
- New `unarr mirrors` command to inspect mirror state and force failover
- `unarr status` now detects 401 from /agent/register and suggests `unarr login`
  instead of the generic "Could not fetch account info" message
- Config supports multiple ScanPaths for upcoming multi-path library scan
- Draft plan for bidirectional library sync (CLI ↔ Web) under Docs/plans/
This commit is contained in:
Deivid Soto 2026-05-15 16:26:43 +02:00
parent bf18812a3d
commit a73e1a7756
12 changed files with 972 additions and 76 deletions

View file

@ -2,6 +2,7 @@ package cmd
import (
"context"
"errors"
"fmt"
"runtime"
"strings"
@ -58,7 +59,7 @@ func runStatus() error {
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
ac := agent.NewClient(cfg.Auth.APIURL, cfg.Auth.APIKey, "unarr/"+Version)
ac := newAgentClientFromConfig(cfg, "unarr/"+Version)
resp, err := ac.Register(ctx, agent.RegisterRequest{
AgentID: cfg.Agent.ID,
Name: cfg.Agent.Name,
@ -74,7 +75,17 @@ func runStatus() error {
cyan.Println(" Account")
ar := <-accountCh
if ar.err != nil {
dim.Println(" Could not fetch account info")
var httpErr *agent.HTTPError
switch {
case errors.As(ar.err, &httpErr) && httpErr.StatusCode == 401:
yellow.Println(" API key invalid or revoked")
fmt.Printf(" Run %s to re-authenticate\n", cyan.Sprint("unarr login"))
case errors.As(ar.err, &httpErr) && httpErr.StatusCode == 403:
yellow.Println(" API key lacks permission for this server")
fmt.Printf(" Check plan or run %s\n", cyan.Sprint("unarr login"))
default:
dim.Printf(" Could not fetch account info (%v)\n", ar.err)
}
} else {
fmt.Printf(" User: %s\n", ar.user.Name)
fmt.Printf(" Email: %s\n", ar.user.Email)