chore: rename module from torrentclaw-cli to unarr

- Rename Go module path github.com/torrentclaw/torrentclaw-cli → github.com/torrentclaw/unarr
- Update all imports, ldflags, scripts, docs, and Docker config
- Add GitHub Actions release workflow (goreleaser on tag push)
This commit is contained in:
Deivid Soto 2026-03-30 13:06:07 +02:00
parent 9cc806d11f
commit 5a7449b9e6
58 changed files with 166 additions and 141 deletions

View file

@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions & Discussion
url: https://github.com/torrentclaw/torrentclaw-cli/discussions
url: https://github.com/torrentclaw/unarr/discussions
about: Ask questions or start a discussion

View file

@ -10,30 +10,20 @@ permissions:
jobs:
release:
name: GoReleaser
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Checkout go-client (local replace dependency)
run: git clone --depth 1 https://github.com/torrentclaw/go-client "$GITHUB_WORKSPACE/../go-client"
- name: Set up Go
uses: actions/setup-go@v6
- uses: actions/setup-go@v5
with:
go-version: "1.25"
go-version-file: go.mod
- name: Install UPX
uses: crazy-max/ghaction-upx@v4
- uses: goreleaser/goreleaser-action@v6
with:
install-only: true
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v7
with:
version: latest
version: "~> v2"
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}

View file

@ -16,8 +16,8 @@ builds:
- arm64
ldflags:
- -s -w
- -X github.com/torrentclaw/torrentclaw-cli/internal/cmd.Version={{.Version}}
- -X github.com/torrentclaw/torrentclaw-cli/internal/sentry.dsn={{ .Env.SENTRY_DSN }}
- -X github.com/torrentclaw/unarr/internal/cmd.Version={{.Version}}
- -X github.com/torrentclaw/unarr/internal/sentry.dsn={{ .Env.SENTRY_DSN }}
archives:
- format: tar.gz
@ -42,7 +42,7 @@ brews:
owner: torrentclaw
name: homebrew-tap
name: unarr
homepage: https://github.com/torrentclaw/torrentclaw-cli
homepage: https://github.com/torrentclaw/unarr
description: "unarr — replaces the entire *arr stack"
license: MIT
install: |

View file

@ -53,5 +53,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- CI pipeline (test, build, lint, vet)
- Lefthook git hooks (gofmt, go vet, conventional commits)
[Unreleased]: https://github.com/torrentclaw/torrentclaw-cli/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/torrentclaw/torrentclaw-cli/releases/tag/v0.1.0
[Unreleased]: https://github.com/torrentclaw/unarr/compare/v0.1.0...HEAD
[0.1.0]: https://github.com/torrentclaw/unarr/releases/tag/v0.1.0

View file

@ -7,15 +7,15 @@ Thank you for your interest in contributing! This guide will help you get starte
1. **Fork** the repository on GitHub
2. **Clone** your fork locally:
```bash
git clone https://github.com/YOUR-USERNAME/torrentclaw-cli.git
cd torrentclaw-cli
git clone https://github.com/YOUR-USERNAME/unarr.git
cd unarr
```
3. **Set up the Go client** (local dependency):
```bash
# Clone the go-client next to the CLI
cd ..
git clone https://github.com/torrentclaw/go-client.git
cd torrentclaw-cli
cd unarr
```
4. **Create a branch** for your change:
```bash
@ -23,7 +23,7 @@ Thank you for your interest in contributing! This guide will help you get starte
```
5. **Make your changes**, write tests, and ensure everything passes
6. **Commit** with a clear message (see [Commit Messages](#commit-messages))
7. **Push** to your fork and [open a Pull Request](https://github.com/torrentclaw/torrentclaw-cli/compare)
7. **Push** to your fork and [open a Pull Request](https://github.com/torrentclaw/unarr/compare)
## Development Setup
@ -76,7 +76,7 @@ make install-hooks # Install lefthook git hooks
## Project Structure
```
torrentclaw-cli/
unarr/
├── cmd/unarr/ # Entry point
│ └── main.go
├── internal/
@ -176,7 +176,7 @@ chore: update CI matrix to Go 1.24
## Reporting Bugs
[Open an issue](https://github.com/torrentclaw/torrentclaw-cli/issues/new?labels=bug) with:
[Open an issue](https://github.com/torrentclaw/unarr/issues/new?labels=bug) with:
- **Description** — what went wrong
- **Steps to reproduce** — minimal commands to trigger the bug

View file

@ -1,6 +1,6 @@
# ---- Build stage ----
# Build context must be the parent directory containing both torrentclaw-cli/
# and go-client/. Use: docker build -f torrentclaw-cli/Dockerfile .
# Build context must be the parent directory containing both unarr/
# and go-client/. Use: docker build -f unarr/Dockerfile .
# Or use the provided docker-build.sh script.
FROM golang:1.24-alpine AS builder
@ -12,12 +12,12 @@ COPY go-client/ /deps/go-client/
# Copy go.mod/go.sum first for layer caching
WORKDIR /src
COPY torrentclaw-cli/go.mod torrentclaw-cli/go.sum ./
COPY unarr/go.mod unarr/go.sum ./
RUN go mod edit -replace github.com/torrentclaw/go-client=/deps/go-client
RUN go mod download
# Copy source (changes here won't invalidate mod cache)
COPY torrentclaw-cli/ .
COPY unarr/ .
RUN go mod edit -replace github.com/torrentclaw/go-client=/deps/go-client
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /unarr ./cmd/unarr/

View file

@ -2,7 +2,7 @@
BINARY = unarr
SENTRY_DSN ?=
LDFLAGS = -s -w -X github.com/torrentclaw/torrentclaw-cli/internal/sentry.dsn=$(SENTRY_DSN)
LDFLAGS = -s -w -X github.com/torrentclaw/unarr/internal/sentry.dsn=$(SENTRY_DSN)
all: fmt vet lint test build

View file

@ -1,9 +1,9 @@
# unarr
[![CI](https://github.com/torrentclaw/torrentclaw-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/torrentclaw/torrentclaw-cli/actions/workflows/ci.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/torrentclaw/torrentclaw-cli)](https://goreportcard.com/report/github.com/torrentclaw/torrentclaw-cli)
[![CI](https://github.com/torrentclaw/unarr/actions/workflows/ci.yml/badge.svg)](https://github.com/torrentclaw/unarr/actions/workflows/ci.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/torrentclaw/unarr)](https://goreportcard.com/report/github.com/torrentclaw/unarr)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Go Version](https://img.shields.io/github/go-mod/go-version/torrentclaw/torrentclaw-cli)](go.mod)
[![Go Version](https://img.shields.io/github/go-mod/go-version/torrentclaw/unarr)](go.mod)
Powerful terminal tool for torrent search and management.
@ -35,18 +35,18 @@ brew install torrentclaw/tap/unarr
### Go install
```bash
go install github.com/torrentclaw/torrentclaw-cli/cmd/unarr@latest
go install github.com/torrentclaw/unarr/cmd/unarr@latest
```
### GitHub Releases
Download prebuilt binaries for Linux, macOS, and Windows from [GitHub Releases](https://github.com/torrentclaw/torrentclaw-cli/releases).
Download prebuilt binaries for Linux, macOS, and Windows from [GitHub Releases](https://github.com/torrentclaw/unarr/releases).
### Build from source
```bash
git clone https://github.com/torrentclaw/torrentclaw-cli.git
cd torrentclaw-cli
git clone https://github.com/torrentclaw/unarr.git
cd unarr
make build
```

View file

@ -15,7 +15,7 @@ Only the latest release receives security updates.
Instead, report them via **GitHub Security Advisories**:
1. Go to [Security Advisories](https://github.com/torrentclaw/torrentclaw-cli/security/advisories)
1. Go to [Security Advisories](https://github.com/torrentclaw/unarr/security/advisories)
2. Click **"Report a vulnerability"**
3. Fill in the details

View file

@ -1,8 +1,8 @@
package main
import (
"github.com/torrentclaw/torrentclaw-cli/internal/cmd"
"github.com/torrentclaw/torrentclaw-cli/internal/sentry"
"github.com/torrentclaw/unarr/internal/cmd"
"github.com/torrentclaw/unarr/internal/sentry"
)
func main() {

View file

@ -1,12 +1,12 @@
#!/bin/sh
# Build the unarr Docker image.
# Must be run from the torrentclaw-cli directory (or its parent).
# Must be run from the unarr directory (or its parent).
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PARENT_DIR="$(dirname "$SCRIPT_DIR")"
# Build from parent dir so both torrentclaw-cli/ and go-client/ are in context
# Build from parent dir so both unarr/ and go-client/ are in context
docker build \
-f "$SCRIPT_DIR/Dockerfile" \
-t torrentclaw/unarr:latest \

View file

@ -2,7 +2,7 @@ services:
unarr:
build:
context: ..
dockerfile: torrentclaw-cli/Dockerfile
dockerfile: unarr/Dockerfile
image: torrentclaw/unarr:latest
container_name: unarr
restart: unless-stopped

6
go.mod
View file

@ -1,11 +1,13 @@
module github.com/torrentclaw/torrentclaw-cli
module github.com/torrentclaw/unarr
go 1.25.0
require (
github.com/BurntSushi/toml v1.6.0
github.com/anacrolix/dht/v2 v2.23.0
github.com/anacrolix/log v0.17.1-0.20251118025802-918f1157b7bb
github.com/anacrolix/torrent v1.61.0
github.com/anacrolix/upnp v0.1.4
github.com/charmbracelet/huh v1.0.0
github.com/fatih/color v1.19.0
github.com/getsentry/sentry-go v0.44.1
@ -22,7 +24,6 @@ require (
github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
github.com/anacrolix/btree v0.1.1 // indirect
github.com/anacrolix/chansync v0.7.0 // indirect
github.com/anacrolix/dht/v2 v2.23.0 // indirect
github.com/anacrolix/envpprof v1.5.0 // indirect
github.com/anacrolix/generics v0.2.0 // indirect
github.com/anacrolix/go-libutp v1.4.0 // indirect
@ -33,7 +34,6 @@ require (
github.com/anacrolix/multiless v0.4.0 // indirect
github.com/anacrolix/stm v0.5.0 // indirect
github.com/anacrolix/sync v0.6.0 // indirect
github.com/anacrolix/upnp v0.1.4 // indirect
github.com/anacrolix/utp v0.2.0 // indirect
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect

View file

@ -1,6 +1,6 @@
# unarr — Windows installer (PowerShell 5.1+)
# Usage: irm https://get.unarr.com/install.ps1 | iex
# or: irm https://raw.githubusercontent.com/torrentclaw/torrentclaw-cli/main/install.ps1 | iex
# or: irm https://raw.githubusercontent.com/torrentclaw/unarr/main/install.ps1 | iex
#
# Options (env vars):
# $env:INSTALL_DIR = "C:\path" — where to place the binary
@ -15,7 +15,7 @@ param(
$ErrorActionPreference = "Stop"
$Repo = "torrentclaw/torrentclaw-cli"
$Repo = "torrentclaw/unarr"
$Binary = "unarr.exe"
# ---- Helpers ----

View file

@ -1,7 +1,7 @@
#!/bin/sh
# unarr — cross-platform installer (Linux / macOS)
# Usage: curl -fsSL https://get.unarr.com/install.sh | sh
# or: curl -fsSL https://raw.githubusercontent.com/torrentclaw/torrentclaw-cli/main/install.sh | sh
# or: curl -fsSL https://raw.githubusercontent.com/torrentclaw/unarr/main/install.sh | sh
#
# Options (env vars):
# INSTALL_DIR=/usr/local/bin — where to place the binary (default: /usr/local/bin or ~/.local/bin)
@ -9,7 +9,7 @@
# METHOD=binary|docker — force install method (default: auto-detect)
set -e
REPO="torrentclaw/torrentclaw-cli"
REPO="torrentclaw/unarr"
BINARY="unarr"
# ---- Colors (only if terminal) ----
@ -217,7 +217,7 @@ install_docker() {
torrentclaw/unarr
# Or use the provided docker-compose.yml:
# curl -fsSL https://raw.githubusercontent.com/torrentclaw/torrentclaw-cli/main/docker-compose.yml > docker-compose.yml
# curl -fsSL https://raw.githubusercontent.com/torrentclaw/unarr/main/docker-compose.yml > docker-compose.yml
# docker compose up -d
DOCKER_USAGE

View file

@ -6,7 +6,7 @@ import (
"path/filepath"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/unarr/internal/config"
)
// DaemonState is written to disk every heartbeat for external tools to read.

View file

@ -10,9 +10,9 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/torrentclaw-cli/internal/ui"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/config"
"github.com/torrentclaw/unarr/internal/ui"
)
func newCleanCmd() *cobra.Command {

View file

@ -11,7 +11,7 @@ import (
"github.com/charmbracelet/huh"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/unarr/internal/config"
)
var configCategories = []string{"downloads", "organization", "notifications", "device", "region", "connection", "advanced"}

View file

@ -13,12 +13,12 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/torrentclaw-cli/internal/engine"
"github.com/torrentclaw/torrentclaw-cli/internal/library"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/download"
"github.com/torrentclaw/torrentclaw-cli/internal/upgrade"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/config"
"github.com/torrentclaw/unarr/internal/engine"
"github.com/torrentclaw/unarr/internal/library"
"github.com/torrentclaw/unarr/internal/usenet/download"
"github.com/torrentclaw/unarr/internal/upgrade"
)
// newStartCmd creates the top-level `unarr start` command.

View file

@ -9,8 +9,8 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/config"
)
func newDoctorCmd() *cobra.Command {

View file

@ -12,9 +12,9 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/engine"
"github.com/torrentclaw/torrentclaw-cli/internal/parser"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/engine"
"github.com/torrentclaw/unarr/internal/parser"
)
func newDownloadCmd() *cobra.Command {

View file

@ -13,10 +13,10 @@ import (
"github.com/fatih/color"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/arr"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/torrentclaw-cli/internal/mediaserver"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/arr"
"github.com/torrentclaw/unarr/internal/config"
"github.com/torrentclaw/unarr/internal/mediaserver"
)
func newInitCmd() *cobra.Command {

View file

@ -9,8 +9,8 @@ import (
"github.com/spf13/cobra"
tc "github.com/torrentclaw/go-client"
"github.com/torrentclaw/torrentclaw-cli/internal/parser"
"github.com/torrentclaw/torrentclaw-cli/internal/ui"
"github.com/torrentclaw/unarr/internal/parser"
"github.com/torrentclaw/unarr/internal/ui"
)
func newInspectCmd() *cobra.Command {

View file

@ -11,10 +11,10 @@ import (
"github.com/charmbracelet/huh"
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/arr"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/torrentclaw-cli/internal/mediaserver"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/arr"
"github.com/torrentclaw/unarr/internal/config"
"github.com/torrentclaw/unarr/internal/mediaserver"
)
func newMigrateCmd() *cobra.Command {

View file

@ -9,7 +9,7 @@ import (
"github.com/spf13/cobra"
tc "github.com/torrentclaw/go-client"
"github.com/torrentclaw/torrentclaw-cli/internal/ui"
"github.com/torrentclaw/unarr/internal/ui"
)
func newPopularCmd() *cobra.Command {

View file

@ -9,7 +9,7 @@ import (
"github.com/spf13/cobra"
tc "github.com/torrentclaw/go-client"
"github.com/torrentclaw/torrentclaw-cli/internal/ui"
"github.com/torrentclaw/unarr/internal/ui"
)
func newRecentCmd() *cobra.Command {

View file

@ -9,8 +9,8 @@ import (
"syscall"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/config"
)
// ReloadableConfig holds a reference to the daemon for hot-reload.

View file

@ -2,7 +2,7 @@
package cmd
import "github.com/torrentclaw/torrentclaw-cli/internal/agent"
import "github.com/torrentclaw/unarr/internal/agent"
// ReloadableConfig holds a reference to the daemon for hot-reload.
type ReloadableConfig struct {

View file

@ -6,8 +6,8 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/torrentclaw-cli/internal/sentry"
"github.com/torrentclaw/unarr/internal/config"
"github.com/torrentclaw/unarr/internal/sentry"
tc "github.com/torrentclaw/go-client"
)
@ -37,7 +37,7 @@ Get started:
unarr start Start the download daemon
Documentation: https://torrentclaw.com/cli
Source: https://github.com/torrentclaw/torrentclaw-cli`,
Source: https://github.com/torrentclaw/unarr`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if noColor || os.Getenv("NO_COLOR") != "" {
color.NoColor = true

View file

@ -12,9 +12,9 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/torrentclaw-cli/internal/library"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/config"
"github.com/torrentclaw/unarr/internal/library"
)
func newScanCmd() *cobra.Command {

View file

@ -10,7 +10,7 @@ import (
"github.com/spf13/cobra"
tc "github.com/torrentclaw/go-client"
"github.com/torrentclaw/torrentclaw-cli/internal/ui"
"github.com/torrentclaw/unarr/internal/ui"
)
func newSearchCmd() *cobra.Command {

View file

@ -11,7 +11,7 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/upgrade"
"github.com/torrentclaw/unarr/internal/upgrade"
)
func newSelfUpdateCmd() *cobra.Command {

View file

@ -8,7 +8,7 @@ import (
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/ui"
"github.com/torrentclaw/unarr/internal/ui"
)
func newStatsCmd() *cobra.Command {

View file

@ -12,9 +12,9 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/torrentclaw/torrentclaw-cli/internal/engine"
"github.com/torrentclaw/torrentclaw-cli/internal/parser"
"github.com/torrentclaw/torrentclaw-cli/internal/ui"
"github.com/torrentclaw/unarr/internal/engine"
"github.com/torrentclaw/unarr/internal/parser"
"github.com/torrentclaw/unarr/internal/ui"
)
func newStreamCmd() *cobra.Command {

View file

@ -8,10 +8,10 @@ import (
"sync"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/torrentclaw-cli/internal/engine"
"github.com/torrentclaw/torrentclaw-cli/internal/ui"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/config"
"github.com/torrentclaw/unarr/internal/engine"
"github.com/torrentclaw/unarr/internal/ui"
)
// streamRegistry tracks active stream tasks and servers for cancellation.

View file

@ -15,7 +15,7 @@ func newStubCmd(name, short string) *cobra.Command {
fmt.Println()
color.New(color.FgYellow).Printf(" ⚠️ '%s' is coming in a future release.\n", name)
fmt.Println()
fmt.Println(" Follow progress at: https://github.com/torrentclaw/torrentclaw-cli")
fmt.Println(" Follow progress at: https://github.com/torrentclaw/unarr")
fmt.Println()
},
}

View file

@ -10,7 +10,7 @@ import (
"github.com/spf13/cobra"
tc "github.com/torrentclaw/go-client"
"github.com/torrentclaw/torrentclaw-cli/internal/ui"
"github.com/torrentclaw/unarr/internal/ui"
)
func newWatchCmd() *cobra.Command {

View file

@ -12,7 +12,7 @@ import (
"testing"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/unarr/internal/agent"
)
func TestDebridAvailable(t *testing.T) {

View file

@ -5,7 +5,7 @@ import (
"log"
"sync"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/unarr/internal/agent"
)
// ManagerConfig holds download manager settings.
@ -24,6 +24,7 @@ type Manager struct {
activeMu sync.RWMutex
active map[string]*Task
cancels map[string]context.CancelFunc // per-task cancel functions
sem chan struct{}
wg sync.WaitGroup
@ -45,6 +46,7 @@ func NewManager(cfg ManagerConfig, reporter *ProgressReporter, downloaders ...Do
reporter: reporter,
downloaders: dlMap,
active: make(map[string]*Task),
cancels: make(map[string]context.CancelFunc),
sem: make(chan struct{}, cfg.MaxConcurrent),
}
}
@ -53,8 +55,12 @@ func NewManager(cfg ManagerConfig, reporter *ProgressReporter, downloaders ...Do
func (m *Manager) Submit(ctx context.Context, at agent.Task) {
task := NewTaskFromAgent(at)
// Per-task cancellable context so CancelTask can unblock the goroutine
taskCtx, taskCancel := context.WithCancel(ctx)
m.activeMu.Lock()
m.active[task.ID] = task
m.cancels[task.ID] = taskCancel
m.activeMu.Unlock()
m.reporter.Track(task)
@ -65,7 +71,8 @@ func (m *Manager) Submit(ctx context.Context, at agent.Task) {
m.wg.Add(1)
go func() {
defer m.wg.Done()
m.processTask(ctx, task)
defer taskCancel()
m.processTask(taskCtx, task)
}()
return
}
@ -74,6 +81,7 @@ func (m *Manager) Submit(ctx context.Context, at agent.Task) {
select {
case m.sem <- struct{}{}:
case <-ctx.Done():
taskCancel()
return
}
@ -81,7 +89,8 @@ func (m *Manager) Submit(ctx context.Context, at agent.Task) {
go func() {
defer m.wg.Done()
defer func() { <-m.sem }()
m.processTask(ctx, task)
defer taskCancel()
m.processTask(taskCtx, task)
}()
}
@ -119,12 +128,19 @@ func (m *Manager) ActiveTasks() []*Task {
func (m *Manager) CancelTask(taskID string) {
m.activeMu.RLock()
task, ok := m.active[taskID]
cancel := m.cancels[taskID]
m.activeMu.RUnlock()
if !ok {
return
}
// Cancel the task's context first — this unblocks the goroutine
// (e.g. stuck waiting for metadata) so it exits and releases the semaphore slot.
if cancel != nil {
cancel()
}
if dl, exists := m.downloaders[task.ResolvedMethod]; exists {
dl.Pause(taskID) // stop download, keep files
}
@ -141,12 +157,17 @@ func (m *Manager) CancelTask(taskID string) {
func (m *Manager) PauseTask(taskID string) {
m.activeMu.RLock()
task, ok := m.active[taskID]
cancel := m.cancels[taskID]
m.activeMu.RUnlock()
if !ok {
return
}
if cancel != nil {
cancel()
}
if dl, exists := m.downloaders[task.ResolvedMethod]; exists {
dl.Pause(taskID) // stop download, keep files for resume
}
@ -159,12 +180,17 @@ func (m *Manager) PauseTask(taskID string) {
func (m *Manager) CancelAndDeleteFiles(taskID string) {
m.activeMu.RLock()
task, ok := m.active[taskID]
cancel := m.cancels[taskID]
m.activeMu.RUnlock()
if !ok {
return
}
if cancel != nil {
cancel()
}
if dl, exists := m.downloaders[task.ResolvedMethod]; exists {
dl.Cancel(taskID) // stop download + delete files
}
@ -204,8 +230,12 @@ func (m *Manager) Shutdown(ctx context.Context) {
}
}
// Clean active map
// Clean active map and cancel functions
m.activeMu.Lock()
for id, cancel := range m.cancels {
cancel()
delete(m.cancels, id)
}
m.active = make(map[string]*Task)
m.activeMu.Unlock()
}
@ -214,6 +244,7 @@ func (m *Manager) processTask(ctx context.Context, task *Task) {
defer func() {
m.activeMu.Lock()
delete(m.active, task.ID)
delete(m.cancels, task.ID)
m.activeMu.Unlock()
}()

View file

@ -5,7 +5,7 @@ import (
"testing"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/unarr/internal/agent"
)
func TestManagerSubmitAndWait(t *testing.T) {

View file

@ -6,7 +6,7 @@ import (
"sync"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/unarr/internal/agent"
)
// ActionFunc is called when the server signals an action on a task.

View file

@ -8,7 +8,7 @@ import (
"testing"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/unarr/internal/agent"
)
// ---------------------------------------------------------------------------

View file

@ -5,7 +5,7 @@ import (
"sync"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/unarr/internal/agent"
)
// TaskStatus represents the current state of a download task.

View file

@ -3,7 +3,7 @@ package engine
import (
"testing"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/unarr/internal/agent"
)
func TestNewTaskFromAgent(t *testing.T) {

View file

@ -16,7 +16,7 @@ import (
"github.com/anacrolix/dht/v2/krpc"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/storage"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/unarr/internal/config"
"golang.org/x/time/rate"
)

View file

@ -20,11 +20,13 @@ type UPnPMapping struct {
// setupUPnP discovers the gateway, maps the port, and gets the public IP.
// Returns nil if UPnP is not available or fails.
func setupUPnP(internalPort int) (*UPnPMapping, error) {
devices := upnp.Discover(0, 5*time.Second, alog.Logger{})
log.Println("stream: discovering UPnP gateway (10s timeout)...")
devices := upnp.Discover(0, 10*time.Second, alog.Logger{})
if len(devices) == 0 {
return nil, fmt.Errorf("no UPnP devices found")
return nil, fmt.Errorf("no UPnP devices found (is UPnP enabled on your router?)")
}
log.Printf("stream: found %d UPnP device(s), using %s", len(devices), devices[0].ID())
device := devices[0]
// Get public IP
@ -32,13 +34,15 @@ func setupUPnP(internalPort int) (*UPnPMapping, error) {
if err != nil {
return nil, fmt.Errorf("get external IP: %w", err)
}
log.Printf("stream: public IP via UPnP: %s", externalIP)
// Map port (0 = let router choose external port, 2h lease)
// Map port (same internal/external, 2h lease)
mappedPort, err := device.AddPortMapping(upnp.TCP, internalPort, internalPort, "unarr stream", 2*time.Hour)
if err != nil {
return nil, fmt.Errorf("add port mapping: %w", err)
return nil, fmt.Errorf("add port mapping %d: %w", internalPort, err)
}
log.Printf("stream: UPnP port mapped %s:%d -> local:%d (2h lease)", externalIP, mappedPort, internalPort)
return &UPnPMapping{
ExternalIP: externalIP.String(),
ExternalPort: mappedPort,

View file

@ -10,12 +10,12 @@ import (
"sync"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/agent"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/download"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/nntp"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/nzb"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/postprocess"
"github.com/torrentclaw/unarr/internal/agent"
"github.com/torrentclaw/unarr/internal/config"
"github.com/torrentclaw/unarr/internal/usenet/download"
"github.com/torrentclaw/unarr/internal/usenet/nntp"
"github.com/torrentclaw/unarr/internal/usenet/nzb"
"github.com/torrentclaw/unarr/internal/usenet/postprocess"
)
// activeDownload holds the state for a single in-progress usenet download.

View file

@ -6,7 +6,7 @@ import (
"os"
"path/filepath"
"github.com/torrentclaw/torrentclaw-cli/internal/config"
"github.com/torrentclaw/unarr/internal/config"
)
// CachePath returns the default library cache file path.

View file

@ -4,7 +4,7 @@ import (
"regexp"
"strings"
"github.com/torrentclaw/torrentclaw-cli/internal/library/mediainfo"
"github.com/torrentclaw/unarr/internal/library/mediainfo"
)
var (

View file

@ -3,7 +3,7 @@ package library
import (
"testing"
"github.com/torrentclaw/torrentclaw-cli/internal/library/mediainfo"
"github.com/torrentclaw/unarr/internal/library/mediainfo"
)
func TestResolveResolution(t *testing.T) {

View file

@ -11,8 +11,8 @@ import (
"sync/atomic"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/library/mediainfo"
"github.com/torrentclaw/torrentclaw-cli/internal/parser"
"github.com/torrentclaw/unarr/internal/library/mediainfo"
"github.com/torrentclaw/unarr/internal/parser"
)
// videoExts are file extensions considered as video files.

View file

@ -1,6 +1,6 @@
package library
import "github.com/torrentclaw/torrentclaw-cli/internal/agent"
import "github.com/torrentclaw/unarr/internal/agent"
// BuildSyncItems converts cached library items to sync request items.
// Shared between unarr scan (cmd/scan.go) and auto-scan (cmd/daemon.go).

View file

@ -1,6 +1,6 @@
package library
import "github.com/torrentclaw/torrentclaw-cli/internal/library/mediainfo"
import "github.com/torrentclaw/unarr/internal/library/mediainfo"
// LibraryItem represents a single scanned media file.
type LibraryItem struct {

View file

@ -10,7 +10,7 @@ import (
)
// dsn is injected at build time via ldflags. If empty, Sentry is disabled.
// Set via: -ldflags "-X github.com/torrentclaw/torrentclaw-cli/internal/sentry.dsn=..."
// Set via: -ldflags "-X github.com/torrentclaw/unarr/internal/sentry.dsn=..."
var dsn string
const flushTimeout = 2 * time.Second

View file

@ -12,9 +12,9 @@ import (
"sync/atomic"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/nntp"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/nzb"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/yenc"
"github.com/torrentclaw/unarr/internal/usenet/nntp"
"github.com/torrentclaw/unarr/internal/usenet/nzb"
"github.com/torrentclaw/unarr/internal/usenet/yenc"
)
// Progress is emitted during download.

View file

@ -8,10 +8,10 @@ import (
"testing"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/download"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/nntp"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/nzb"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/postprocess"
"github.com/torrentclaw/unarr/internal/usenet/download"
"github.com/torrentclaw/unarr/internal/usenet/nntp"
"github.com/torrentclaw/unarr/internal/usenet/nzb"
"github.com/torrentclaw/unarr/internal/usenet/postprocess"
)
// TestE2EDownload is a real end-to-end test that downloads from Usenet.

View file

@ -11,7 +11,7 @@ import (
"sync/atomic"
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/nzb"
"github.com/torrentclaw/unarr/internal/usenet/nzb"
)
// Binary progress file format:

View file

@ -8,7 +8,7 @@ import (
"time"
"github.com/torrentclaw/torrentclaw-cli/internal/usenet/nzb"
"github.com/torrentclaw/unarr/internal/usenet/nzb"
)
var fixedPast = time.Now().Add(-30 * 24 * time.Hour)