diff --git a/.github/workflows/docker-rebuild.yml b/.github/workflows/docker-rebuild.yml new file mode 100644 index 0000000..c1634f1 --- /dev/null +++ b/.github/workflows/docker-rebuild.yml @@ -0,0 +1,52 @@ +# Rebuilds and re-pushes the `latest` image without a version bump so newly +# *fixed* Alpine / ffmpeg / Go patches land between tagged releases. Versioned +# tags are immutable and never touched here. Runs weekly and on demand. +name: Docker rebuild + +on: + schedule: + # Mondays 04:17 UTC (off the hour to avoid the scheduler rush) + - cron: "17 4 * * 1" + workflow_dispatch: + +jobs: + rebuild: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + # Stamp the binary with the most recent release tag (not "dev"). + - name: Resolve version + id: ver + run: echo "version=$(git describe --tags --abbrev=0 2>/dev/null || echo dev)" >> "$GITHUB_OUTPUT" + + - uses: docker/setup-qemu-action@v4 + - uses: docker/setup-buildx-action@v4 + + - uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - uses: docker/build-push-action@v7 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + # Refresh the floating tag only — never overwrite a versioned release. + tags: torrentclaw/unarr:latest + build-args: | + VERSION=${{ steps.ver.outputs.version }} + # Force a fresh base pull so apk upgrade picks up new patches. + no-cache: true + + - name: Scan image for fixable CVEs (gate) + uses: docker/scout-action@v1 + with: + command: cves + image: torrentclaw/unarr:latest + only-severities: critical,high + only-fixed: true + exit-code: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8283150..dcb49ce 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,28 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + # Empty when RELEASE_SIGNING_PUBKEY variable is unset — goreleaser + # accepts it and the resulting binary disables signature checks + # (back-compat: pre-signing releases continue to update). Set + # RELEASE_SIGNING_PUBKEY (variable) + RELEASE_SIGNING_KEY (secret) + # to turn verification on. + RELEASE_SIGNING_PUBKEY: ${{ vars.RELEASE_SIGNING_PUBKEY }} + + - name: Sign checksums.txt with ed25519 + # Reference secrets.X directly — step-level env defined in this same + # step is unreliable to read from this step's own if: expression. + if: ${{ vars.RELEASE_SIGNING_PUBKEY != '' && secrets.RELEASE_SIGNING_KEY != '' }} + env: + RELEASE_SIGNING_KEY: ${{ secrets.RELEASE_SIGNING_KEY }} + RELEASE_TAG: ${{ github.ref_name }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + go run ./scripts/sign-checksums \ + -key "$RELEASE_SIGNING_KEY" \ + -in dist/checksums.txt \ + -out dist/checksums.txt.sig + gh release upload "$RELEASE_TAG" dist/checksums.txt.sig --clobber docker: needs: release @@ -62,6 +84,31 @@ jobs: build-args: | VERSION=${{ github.ref_name }} + # CVE gate. Fails the release on FIXABLE critical/high only — unfixed + # upstream ffmpeg codec CVEs are accepted (see SECURITY.md), so the + # codec noise does not block. Runs post-push (image already published); + # a failure here flags that a fixable CVE slipped through. + - name: Scan image for fixable CVEs (gate) + uses: docker/scout-action@v1 + with: + command: cves + image: torrentclaw/unarr:latest + only-severities: critical,high + only-fixed: true + exit-code: true + + # Sync the Docker Hub repo description from DOCKERHUB.md. Non-fatal: a + # description-API auth hiccup must not undo a successful image push. + - name: Update Docker Hub description + uses: peter-evans/dockerhub-description@v4 + continue-on-error: true + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + repository: torrentclaw/unarr + readme-filepath: ./DOCKERHUB.md + short-description: "unarr — the single binary that replaces your *arr stack" + virustotal: needs: release diff --git a/.goreleaser.yml b/.goreleaser.yml index 0a5c821..26ce802 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -26,6 +26,10 @@ builds: - -s -w - -X github.com/torrentclaw/unarr/internal/cmd.Version={{.Version}} - -X github.com/torrentclaw/unarr/internal/sentry.dsn={{ .Env.SENTRY_DSN }} + # Release-signing public key — verified by the self-updater against + # checksums.txt.sig. Empty when not configured; in that case + # signature verification is skipped and a warning is logged. + - -X github.com/torrentclaw/unarr/internal/upgrade.releasePubKeyBase64={{ .Env.RELEASE_SIGNING_PUBKEY }} archives: - formats: [tar.gz] diff --git a/CHANGELOG.md b/CHANGELOG.md index 55bd493..42c34bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.1] - 2026-05-21 + + +### Added + +- **mirror**: update fallback URLs to use IPFS and remove GitHub Pages + +### Fixed + +- **security**: bump golang.org/x deps and add container CVE scan gate +## [0.9.0] - 2026-05-21 + + +### Added + +- **agent**: add mirror failover, agent client refactor, status 401 detection +- **vpn**: local config_file for self-hosted/personal VPN testing +- **vpn**: split-tunnel torrent traffic through managed WireGuard + +### Documentation + +- **docker**: refresh Docker Hub README + sync description in CI + +### Fixed + +- **security**: CORS allowlist, URL scheme guard, state perms, ZIP slip, mirror docs +- **security**: UPnP opt-in, bounded SSE reader, signed self-update +- **security**: harden HLS session IDs, /health disclosure, archive password handling +- **upgrade**: fetch releases from TorrentClaw app, not GitHub + +### Other + +- **release**: 0.9.0 ## [0.8.1] - 2026-05-08 @@ -25,6 +58,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Other - **gitignore**: add dist-ffbinaries to ignored files +- **release**: 0.8.1 ## [0.8.0] - 2026-05-08 @@ -238,16 +272,117 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.4.1] - 2026-04-01 +### Added + +- **cli**: add login command and refactor shared helpers +- **stream**: report watch progress to API via HTTP Range tracking + +### Fixed + +- **ci**: fix lint errors and pin CI to Go 1.25 +- **lint**: remove unused newStubCmd function + +### Other + +- **cli**: remove moreseed stub command +- **cli**: remove redundant stub commands (monitor, open, add, compare) +## [0.4.0] - 2026-03-31 + + +### Added + +- **cli**: upgrade command, rich status, and version cache + +### Fixed + +- **progress**: always report status transitions and poll for control signals +## [0.3.7] - 2026-03-31 + + +### CI/CD + +- **docker**: remove dockerhub-description sync step +## [0.3.6] - 2026-03-31 + + +### CI/CD + +- **deps**: bump docker/metadata-action from 5 to 6 +- **deps**: bump docker/setup-qemu-action from 3 to 4 +- **deps**: bump docker/login-action from 3 to 4 +- **deps**: bump docker/build-push-action from 6 to 7 +- **deps**: bump codecov/codecov-action from 5 to 6 +- **docker**: add Docker Hub description sync and DOCKERHUB.md + +### Fixed + +- **ci**: upgrade golangci-lint to v2.11.3 for Go 1.25 support +- **docker**: upgrade alpine packages to patch CVE-2025-60876 and CVE-2026-27171 +- **lint**: use default:none to disable errcheck, fix all gofmt and exhaustive +- **lint**: disable errcheck, tune gosec/exclusions for codebase state +- **lint**: configure linters for codebase maturity, fix gofmt and ineffassign +- **lint**: exclude common fire-and-forget patterns from errcheck +- **lint**: resolve errcheck and bodyclose warnings for golangci-lint v2 +## [0.3.5] - 2026-03-30 + + +### Changed + +- migrate lint config to v2, remove daemon auto-upgrade, add trust badges +## [0.3.3] - 2026-03-30 + + +### Fixed + +- **ci**: remove go-client checkout steps +## [0.3.2] - 2026-03-30 + + +### Added + +- **init**: add 60s countdown, skip key, and cancel detection to browser auth + +### CI/CD + +- **release**: add Docker Hub publish and VirusTotal scan jobs + +### Documentation + +- add beta notice, fix install URLs to get.torrentclaw.com + +### Fixed + +- **ci**: fix virustotal job condition syntax +- **docker**: simplify Dockerfile for CI builds (no local go-client) +- **release**: disable homebrew tap (needs PAT, not GITHUB_TOKEN) + +### Other + +- re-enable homebrew tap in goreleaser +## [0.3.1] - 2026-03-30 + + +### Fixed + +- **build**: unused variable in Windows process check +- **release**: disable homebrew tap until repo is created + +### Other + +- rename module from torrentclaw-cli to unarr + +### Build + +- remove UPX compression (antivirus false positives, startup penalty) +## [0.3.0] - 2026-03-29 + + ### Added - **agent**: add WebSocket transport with HTTP fallback - **auth**: browser-based CLI authentication (like Claude Code) -- **cli**: add login command and refactor shared helpers -- **cli**: upgrade command, rich status, and version cache - **daemon**: add auto-scan, force start, and stall timeout default - **debrid**: add HTTPS downloader for debrid direct URLs -- **init**: add 60s countdown, skip key, and cancel detection to browser auth -- **stream**: report watch progress to API via HTTP Range tracking - **stream**: UPnP port forwarding for remote video playback - **usenet**: implement full NNTP download pipeline - add migrate command, media server detection, and debrid auto-config @@ -257,61 +392,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - improve daemon resilience, streaming, and usenet downloads - initial commit — unarr CLI -### CI/CD - -- **deps**: bump docker/metadata-action from 5 to 6 -- **deps**: bump docker/setup-qemu-action from 3 to 4 -- **deps**: bump docker/login-action from 3 to 4 -- **deps**: bump docker/build-push-action from 6 to 7 -- **deps**: bump codecov/codecov-action from 5 to 6 -- **docker**: remove dockerhub-description sync step -- **docker**: add Docker Hub description sync and DOCKERHUB.md -- **release**: add Docker Hub publish and VirusTotal scan jobs - ### Changed -- migrate lint config to v2, remove daemon auto-upgrade, add trust badges - extract BuildSyncItems to library package, remove duplication ### Documentation -- add beta notice, fix install URLs to get.torrentclaw.com - improve CLI help, shell completion, and README ### Fixed -- **build**: unused variable in Windows process check -- **ci**: fix lint errors and pin CI to Go 1.25 -- **ci**: upgrade golangci-lint to v2.11.3 for Go 1.25 support -- **ci**: remove go-client checkout steps -- **ci**: fix virustotal job condition syntax -- **docker**: upgrade alpine packages to patch CVE-2025-60876 and CVE-2026-27171 -- **docker**: simplify Dockerfile for CI builds (no local go-client) -- **lint**: remove unused newStubCmd function -- **lint**: use default:none to disable errcheck, fix all gofmt and exhaustive -- **lint**: disable errcheck, tune gosec/exclusions for codebase state -- **lint**: configure linters for codebase maturity, fix gofmt and ineffassign -- **lint**: exclude common fire-and-forget patterns from errcheck -- **lint**: resolve errcheck and bodyclose warnings for golangci-lint v2 -- **progress**: always report status transitions and poll for control signals -- **release**: disable homebrew tap (needs PAT, not GITHUB_TOKEN) -- **release**: disable homebrew tap until repo is created - **torrent**: expand tracker list, add DHT persistence and configurable timeouts - force-start tasks bypass HasCapacity check in dispatch loop - add panic recovery to auto-scan, cap DHT nodes at 200 - harden usenet/debrid downloaders from critico review -### Other - -- **cli**: remove moreseed stub command -- **cli**: remove redundant stub commands (monitor, open, add, compare) -- re-enable homebrew tap in goreleaser -- rename module from torrentclaw-cli to unarr - ### Build -- remove UPX compression (antivirus false positives, startup penalty) - add -s -w -trimpath to Makefile, add build-small target with UPX +[0.9.1]: https://github.com/torrentclaw/unarr/compare/v0.9.0...v0.9.1 +[0.9.0]: https://github.com/torrentclaw/unarr/compare/v0.8.1...v0.9.0 [0.8.1]: https://github.com/torrentclaw/unarr/compare/v0.8.0...v0.8.1 [0.8.0]: https://github.com/torrentclaw/unarr/compare/v0.7.0...v0.8.0 [0.7.0]: https://github.com/torrentclaw/unarr/compare/v0.6.8...v0.7.0 @@ -331,4 +431,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [0.5.1]: https://github.com/torrentclaw/unarr/compare/v0.5.0...v0.5.1 [0.5.0]: https://github.com/torrentclaw/unarr/compare/v0.4.1...v0.5.0 [0.4.1]: https://github.com/torrentclaw/unarr/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/torrentclaw/unarr/compare/v0.3.7...v0.4.0 +[0.3.7]: https://github.com/torrentclaw/unarr/compare/v0.3.6...v0.3.7 +[0.3.6]: https://github.com/torrentclaw/unarr/compare/v0.3.5...v0.3.6 +[0.3.5]: https://github.com/torrentclaw/unarr/compare/v0.3.3...v0.3.5 +[0.3.3]: https://github.com/torrentclaw/unarr/compare/v0.3.2...v0.3.3 +[0.3.2]: https://github.com/torrentclaw/unarr/compare/v0.3.1...v0.3.2 +[0.3.1]: https://github.com/torrentclaw/unarr/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/torrentclaw/unarr/releases/tag/v0.3.0 diff --git a/DOCKERHUB.md b/DOCKERHUB.md index dfa96c4..7a9bc0e 100644 --- a/DOCKERHUB.md +++ b/DOCKERHUB.md @@ -1,12 +1,20 @@ # unarr -Powerful terminal tool for torrent search and management. Search 30+ sources, inspect quality, discover popular content, find streaming providers, and manage downloads — all from your terminal. +**The single binary that replaces your whole *arr stack.** Search 30+ torrent +sources, inspect real quality before you download, grab subtitles, and manage +your media library — all from one terminal tool or a headless daemon. -**[GitHub](https://github.com/torrentclaw/unarr)** | **[Documentation](https://github.com/torrentclaw/unarr#readme)** | **[Releases](https://github.com/torrentclaw/unarr/releases)** +**[Website & docs](https://torrentclaw.com/unarr)** · **[Install guide](https://torrentclaw.com/cli)** · **[Get an API key](https://torrentclaw.com)** -## Quick Start +> Powered by [TorrentClaw](https://torrentclaw.com) — an aggregator that unifies +> YTS, EZTV, Knaben, Torrentio, Bitmagnet and more, enriched with TMDB metadata +> and a 0–100 quality score per release. -### 1. Setup (interactive wizard) +--- + +## Quick start + +### 1. First-time setup (interactive wizard) ```bash docker run -it --rm \ @@ -14,6 +22,9 @@ docker run -it --rm \ torrentclaw/unarr setup ``` +The wizard asks for your TorrentClaw API key (free at +[torrentclaw.com](https://torrentclaw.com)) and your download directory. + ### 2. Run the daemon ```bash @@ -26,6 +37,10 @@ docker run -d --name unarr \ torrentclaw/unarr ``` +That's it — `unarr` now runs headless, watching for jobs and managing downloads. + +--- + ## Docker Compose ```yaml @@ -45,45 +60,54 @@ services: environment: - TZ=UTC # - UNARR_API_KEY=tc_your_key_here + network_mode: host # recommended for full P2P performance deploy: resources: limits: memory: 512M cpus: "2.0" - network_mode: host volumes: unarr-data: ``` +```bash +docker compose run --rm unarr setup # one-time wizard +docker compose up -d # start the daemon +``` + +--- + ## Volumes -| Path | Purpose | -|------|---------| -| `/config` | Configuration file (`config.toml`) | -| `/downloads` | Finished media downloads | -| `/data` | Internal state: torrent metadata, cache | +| Path | Purpose | +|--------------|--------------------------------------------------| +| `/config` | Configuration file (`config.toml`) | +| `/downloads` | Finished media downloads | +| `/data` | Internal state: torrent metadata, cache | -## Environment Variables +## Environment variables -| Variable | Description | Default | -|----------|-------------|---------| -| `TZ` | Timezone | `UTC` | -| `UNARR_API_KEY` | TorrentClaw API key | from config | -| `UNARR_API_URL` | API endpoint | `https://torrentclaw.com` | -| `UNARR_DOWNLOAD_DIR` | Download directory | `/downloads` | -| `UNARR_CONFIG_DIR` | Config directory | `/config` | -| `UNARR_COUNTRY` | Country code (ISO 3166) | `US` | +| Variable | Description | Default | +|------------------------|--------------------------------------|---------------------------| +| `UNARR_API_KEY` | TorrentClaw API key | from config | +| `UNARR_API_URL` | API endpoint | `https://torrentclaw.com` | +| `UNARR_DOWNLOAD_DIR` | Download directory | `/downloads` | +| `UNARR_CONFIG_DIR` | Config directory | `/config` | +| `UNARR_COUNTRY` | Country code (ISO 3166) | `US` | +| `TZ` | Timezone | `UTC` | + +Any config value can be overridden by its matching `UNARR_*` environment variable. ## Networking -**Host mode** (recommended) gives full P2P performance with no port management: +**Host mode (recommended)** — full P2P performance, no port mapping: ```yaml network_mode: host ``` -**Bridge mode** — more isolated, but requires explicit ports: +**Bridge mode** — more isolated, but you must expose the BitTorrent ports: ```yaml ports: @@ -91,7 +115,7 @@ ports: - "6881-6889:6881-6889/udp" ``` -## Running Commands +## Running commands Use `docker exec` for one-off commands while the daemon is running: @@ -99,32 +123,77 @@ Use `docker exec` for one-off commands while the daemon is running: docker exec unarr unarr search "inception" --quality 1080p docker exec unarr unarr popular --limit 10 docker exec unarr unarr status -docker exec unarr unarr doctor +docker exec unarr unarr doctor # diagnose config / connectivity ``` -## Supported Architectures - -| Architecture | Tag | -|-------------|-----| -| `linux/amd64` | `latest`, `0.3`, `0.3.5` | -| `linux/arm64` | `latest`, `0.3`, `0.3.5` | +--- ## Tags -| Tag | Description | -|-----|-------------| -| `latest` | Latest stable release | -| `X.Y.Z` | Specific version (e.g. `0.3.5`) | -| `X.Y` | Latest patch for minor version (e.g. `0.3`) | +| Tag | Description | +|----------|--------------------------------------------------| +| `latest` | Latest stable release | +| `X.Y.Z` | Exact version (e.g. `0.9.0`) | +| `X.Y` | Latest patch within a minor (e.g. `0.9`) | -## Image Details +Pin a tag in production (`torrentclaw/unarr:0.9.0`) for reproducible deploys. -- **Base image:** Alpine 3.22 -- **User:** `unarr` (UID 1000, GID 1000) +## Supported architectures + +Multi-arch image — Docker pulls the right one automatically: + +- `linux/amd64` +- `linux/arm64` (Apple Silicon, Raspberry Pi 4/5, ARM servers) + +## Image details + +- **Base:** Alpine 3.22 (minimal, regularly patched) +- **User:** `unarr` (UID 1000, GID 1000) — runs as **non-root** - **Entrypoint:** `unarr start` (daemon mode) -- **Read-only filesystem** — only mounted volumes are writable -- **No root required** — runs as non-root by default +- **Read-only rootfs** — only mounted volumes are writable +- **Bundled `ffmpeg` / `ffprobe`** for media inspection — nothing else to install +- **Self-contained updates** — binaries are served from TorrentClaw's own + infrastructure, no third-party registry dependency + +--- + +## Other install methods + +Not using Docker? Install the native binary instead: + +```bash +# Linux / macOS +curl -fsSL https://torrentclaw.com/install.sh | sh + +# Windows (PowerShell) +irm https://torrentclaw.com/install.ps1 | iex + +# Go toolchain +go install github.com/torrentclaw/unarr/cmd/unarr@latest +``` + +## Mirrors + +The installer and release binaries are served from every TorrentClaw mirror, so +you can install even if one domain is blocked in your region. Each mirror is +self-contained (it serves its own binaries — no cross-domain dependency): + +| Mirror | Install command | +|--------|-----------------| +| `torrentclaw.com` (primary) | `curl -fsSL https://torrentclaw.com/install.sh \| sh` | +| `torrentclaw.to` | `curl -fsSL https://torrentclaw.to/install.sh \| sh` | +| Tor (`.onion`) | `torsocks sh -c "$(curl http://torrentf3aifidcsaaanmnmuhv2s53r6hqsl3zkmfidiaxainkeqk5id.onion/install.sh)"` | + +The Tor address routes everything (install script + binaries) through the hidden +service, so no clearnet exit is needed. + +## Links + +- **Website & docs:** https://torrentclaw.com/unarr +- **CLI install guide:** https://torrentclaw.com/cli +- **API & account:** https://torrentclaw.com +- **Mirror status:** https://torrentclaw.com/mirrors ## License -MIT License — see [LICENSE](https://github.com/torrentclaw/unarr/blob/main/LICENSE) for details. +MIT. diff --git a/Docs/plans/security-stream-token.md b/Docs/plans/security-stream-token.md new file mode 100644 index 0000000..1a08e21 --- /dev/null +++ b/Docs/plans/security-stream-token.md @@ -0,0 +1,131 @@ +# Phase 2.2 — Per-task stream token (deferred) + +Status: deferred. Requires coordinated change in the web app +(`torrentclaw-web`) and the CLI daemon. Pulled out of the Phase 2 +security pass because the CLI-only fixes (UPnP opt-in, SSE caps, +signed self-update) ship without web-side work; the stream-token +work cannot. + +## Problem + +`/stream`, `/playlist.m3u` and `/hls//...` on the daemon +HTTP server have no authentication. Today, anyone who can reach the +listener and guesses (or learns) the `taskID` (for `/stream`) or +`sessionID` (for `/hls`) can fetch the active file. + +Mitigations already in place after Phase 1+2: + +- `sessionID` is restricted to a safe regex and is a server-issued + UUID v4 (122-bit entropy, not enumerable in practice). +- `/health` no longer leaks the active filename, taskID prefix or + client IP to remote callers (loopback diagnostics preserved). +- UPnP is opt-in, so by default the daemon is not exposed to the + public internet. +- The web client probes `/health` to pick LAN vs Tailscale. + +Residual risk: + +- On a shared LAN (open Wi-Fi, office network, dorm) any device can + reach the listener and brute-force `?id=` against + `/stream`. taskIDs are also UUIDs, so this is high entropy, but + the URL may leak through browser history, sharing, screen capture + or a passive logger and there is no second factor. +- A user who explicitly opts into UPnP exposes the same surface to + the entire internet. + +A per-task secret carried in the URL closes this without breaking +the `