diff --git a/.forgejo/workflows/docker-rebuild.yml b/.forgejo/workflows/docker-rebuild.yml deleted file mode 100644 index 34cc3d6..0000000 --- a/.forgejo/workflows/docker-rebuild.yml +++ /dev/null @@ -1,61 +0,0 @@ -# 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: docker - container: - image: docker.io/library/docker:27-cli - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install build deps - run: apk add --no-cache curl git bash - - - name: Install buildx - run: | - mkdir -p ~/.docker/cli-plugins - curl -sSL https://github.com/docker/buildx/releases/latest/download/buildx-linux-amd64 \ - -o ~/.docker/cli-plugins/docker-buildx - chmod +x ~/.docker/cli-plugins/docker-buildx - - - name: Set up qemu - run: docker run --rm --privileged tonistiigi/binfmt --install all - - # Stamp the binary with the most recent release tag (not "dev"). - - name: Resolve version - id: ver - run: | - v=$(git describe --tags --abbrev=0 2>/dev/null || echo dev) - echo "version=$v" >> "$GITHUB_OUTPUT" - - - name: Login to Docker Hub - env: - DH_USER: ${{ secrets.DOCKERHUB_USERNAME }} - DH_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - run: echo "$DH_TOKEN" | docker login -u "$DH_USER" --password-stdin - - - name: Build + push (refresh latest) - env: - VERSION: ${{ steps.ver.outputs.version }} - run: | - docker buildx create --name builder --use --driver docker-container - # Refresh the floating tag only — never overwrite a versioned release. - # Force a fresh base pull so apk upgrade picks up new patches. - docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --build-arg "VERSION=$VERSION" \ - --tag "torrentclaw/unarr:latest" \ - --no-cache \ - --push \ - . diff --git a/.forgejo/workflows/release.yml b/.forgejo/workflows/release.yml deleted file mode 100644 index d757612..0000000 --- a/.forgejo/workflows/release.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: Release - -on: - push: - tags: - - "v*" - workflow_dispatch: - -permissions: - contents: write - -jobs: - release: - runs-on: docker - container: - image: docker.io/library/golang:1.25 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install build deps (bash, curl, jq, ffmpeg fetch deps) - run: | - apt-get update - apt-get install -y --no-install-recommends bash curl ca-certificates jq xz-utils unzip - - - name: Install goreleaser - run: | - curl -sSfL https://github.com/goreleaser/goreleaser/releases/latest/download/goreleaser_Linux_x86_64.tar.gz \ - | tar -xz -C /usr/local/bin goreleaser - - - name: Run goreleaser - env: - # Forgejo runner auto-injects GITHUB_TOKEN (a per-job, instance-scoped - # token usable against the Forgejo REST API). goreleaser only accepts - # one token; with both GITHUB_TOKEN + GITEA_TOKEN set it errors out - # ("multiple tokens"). Unset GITHUB_TOKEN before invoking goreleaser so - # it picks the Gitea code path + the gitea_urls block in .goreleaser.yml. - GITEA_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 }} - run: | - unset GITHUB_TOKEN - goreleaser release --clean - - - name: Sign checksums.txt with ed25519 - if: ${{ vars.RELEASE_SIGNING_PUBKEY != '' && secrets.RELEASE_SIGNING_KEY != '' }} - env: - RELEASE_SIGNING_KEY: ${{ secrets.RELEASE_SIGNING_KEY }} - RELEASE_TAG: ${{ github.ref_name }} - FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Tailscale IP — domain-agnostic; the runner shares the dokploy-network with - # forgejo (hostname `forgejo`), so the in-cluster hostname is fastest, but the - # Tailscale IP is the documented fallback. - FORGEJO_API: http://forgejo:3000/api/v1 - REPO: torrentclaw/unarr - run: | - set -euo pipefail - go run ./scripts/sign-checksums \ - -key "$RELEASE_SIGNING_KEY" \ - -in dist/checksums.txt \ - -out dist/checksums.txt.sig - - # Find the release ID for this tag, then upload the sig as an asset. - rel_id=$(curl -sSf "$FORGEJO_API/repos/$REPO/releases/tags/$RELEASE_TAG" \ - -H "Authorization: token $FORGEJO_TOKEN" | jq -r '.id') - curl -sSf -X POST \ - "$FORGEJO_API/repos/$REPO/releases/$rel_id/assets?name=checksums.txt.sig" \ - -H "Authorization: token $FORGEJO_TOKEN" \ - -F "attachment=@dist/checksums.txt.sig" - - docker: - needs: release - runs-on: docker - container: - # Docker-in-Docker capable image — buildx + qemu pre-installed. - image: docker.io/library/docker:27-cli - steps: - - uses: actions/checkout@v4 - - - name: Install buildx - run: | - apk add --no-cache curl - mkdir -p ~/.docker/cli-plugins - curl -sSL https://github.com/docker/buildx/releases/latest/download/buildx-linux-amd64 \ - -o ~/.docker/cli-plugins/docker-buildx - chmod +x ~/.docker/cli-plugins/docker-buildx - - - name: Login to Docker Hub - env: - DH_USER: ${{ secrets.DOCKERHUB_USERNAME }} - DH_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} - run: echo "$DH_TOKEN" | docker login -u "$DH_USER" --password-stdin - - - name: Set up qemu - run: docker run --rm --privileged tonistiigi/binfmt --install all - - - name: Build + push multi-arch image - env: - VERSION: ${{ github.ref_name }} - run: | - set -euo pipefail - VERSION_SEMVER="${VERSION#v}" - MAJOR_MINOR="${VERSION_SEMVER%.*}" - docker buildx create --name builder --use --driver docker-container - docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --build-arg "VERSION=$VERSION" \ - --tag "torrentclaw/unarr:$VERSION_SEMVER" \ - --tag "torrentclaw/unarr:$MAJOR_MINOR" \ - --tag "torrentclaw/unarr:latest" \ - --push \ - . diff --git a/.forgejo/workflows/ci.yml b/.github/workflows/ci.yml similarity index 61% rename from .forgejo/workflows/ci.yml rename to .github/workflows/ci.yml index 82ee799..7dabcc4 100644 --- a/.forgejo/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,26 +12,35 @@ permissions: jobs: test: name: Test - runs-on: docker - container: - image: docker.io/library/golang:1.25 + runs-on: ubuntu-latest + strategy: + matrix: + go-version: ["1.25"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: ${{ matrix.go-version }} - name: Run tests run: go test -v -race -count=1 ./... build: name: Build - runs-on: docker - container: - image: docker.io/library/golang:1.25 + runs-on: ubuntu-latest strategy: matrix: goos: [linux, darwin, windows] goarch: [amd64, arm64] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.25" - name: Build env: @@ -41,30 +50,30 @@ jobs: lint: name: Lint - runs-on: docker - container: - image: docker.io/library/golang:1.25 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - name: Install golangci-lint - run: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/v2.11.4/install.sh \ - | sh -s -- -b /usr/local/bin v2.11.4 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.25" - name: Run golangci-lint - run: golangci-lint run ./... + uses: golangci/golangci-lint-action@v9 + with: + version: v2.11.4 coverage: name: Coverage - runs-on: docker - container: - image: docker.io/library/golang:1.25 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - name: Install python3 - run: apt-get update && apt-get install -y --no-install-recommends python3 + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.25" - name: Run tests with coverage (all packages) run: | @@ -93,13 +102,24 @@ jobs: print('OK: Coverage meets minimum threshold') " + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v6 + with: + files: ./coverage.out + fail_ci_if_error: false + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + vet: name: Vet - runs-on: docker - container: - image: docker.io/library/golang:1.25 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: "1.25" - name: Run go vet run: go vet ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..8283150 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,163 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - uses: goreleaser/goreleaser-action@v6 + with: + version: "~> v2" + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + + docker: + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v6 + with: + images: torrentclaw/unarr + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest + + - 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 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ github.ref_name }} + + + virustotal: + needs: release + runs-on: ubuntu-latest + if: vars.VT_ENABLED == 'true' + steps: + - name: Get release tag + id: tag + run: echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" + + - name: Download release assets + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir -p assets + gh release download "${{ steps.tag.outputs.tag }}" \ + --repo "${{ github.repository }}" \ + --dir assets \ + --pattern '*.tar.gz' \ + --pattern '*.zip' \ + --pattern 'checksums.txt' + + - name: Scan assets with VirusTotal + env: + VT_API_KEY: ${{ secrets.VT_API_KEY }} + run: | + mkdir -p results + for file in assets/*; do + filename=$(basename "$file") + echo "Uploading $filename to VirusTotal..." + + response=$(curl -s --request POST \ + --url https://www.virustotal.com/api/v3/files \ + --header "x-apikey: $VT_API_KEY" \ + --form "file=@$file") + + analysis_id=$(echo "$response" | jq -r '.data.id // empty') + if [ -z "$analysis_id" ]; then + echo "::warning::Failed to upload $filename: $response" + continue + fi + + echo "$filename=$analysis_id" >> results/scans.txt + echo " Analysis ID: $analysis_id" + + # Rate limit: VT free tier allows 4 req/min + sleep 16 + done + + - name: Wait for analysis completion + env: + VT_API_KEY: ${{ secrets.VT_API_KEY }} + run: | + echo "Waiting 60s for VirusTotal analysis to complete..." + sleep 60 + + vt_report="## 🛡️ VirusTotal Scan Results\n\n" + vt_report+="| File | Result | Link |\n" + vt_report+="|------|--------|------|\n" + + while IFS='=' read -r filename analysis_id; do + result=$(curl -s --request GET \ + --url "https://www.virustotal.com/api/v3/analyses/$analysis_id" \ + --header "x-apikey: $VT_API_KEY") + + malicious=$(echo "$result" | jq -r '.data.attributes.stats.malicious // 0') + undetected=$(echo "$result" | jq -r '.data.attributes.stats.undetected // 0') + sha256=$(echo "$result" | jq -r '.meta.file_info.sha256 // empty') + + if [ "$malicious" = "0" ]; then + status="✅ Clean ($undetected engines)" + else + status="⚠️ $malicious detections" + fi + + link="https://www.virustotal.com/gui/file/$sha256" + vt_report+="| \`$filename\` | $status | [View]($link) |\n" + + sleep 16 + done < results/scans.txt + + echo -e "$vt_report" > results/report.md + cat results/report.md + + - name: Append scan results to release notes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + current_body=$(gh release view "${{ steps.tag.outputs.tag }}" \ + --repo "${{ github.repository }}" \ + --json body --jq '.body') + + new_body="${current_body} + + $(cat results/report.md)" + + gh release edit "${{ steps.tag.outputs.tag }}" \ + --repo "${{ github.repository }}" \ + --notes "$new_body" diff --git a/.gitignore b/.gitignore index 8015bab..81f1284 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,4 @@ dist-ffbinaries/ # Docker tmp/ config/ -dist-ffbinaries/ - -# Claude Code: keep entirely local, do not track -.claude/ \ No newline at end of file +dist-ffbinaries/ \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index 6bc4a51..0a5c821 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -26,10 +26,6 @@ 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] @@ -59,22 +55,6 @@ changelog: - "^test:" - "^chore:" -# Self-hosted Forgejo at git.torrentclaw.com. goreleaser detects GITEA_TOKEN + -# these URLs and publishes the release there instead of GitHub. Reachable via -# `forgejo` hostname inside the dokploy-network (the runner shares it); for -# local goreleaser runs outside the network, override via env GITEA_API_URL. -# -# In goreleaser v2 `gitea_urls` is a top-level key (was nested under `release` -# in v1). -gitea_urls: - api: http://forgejo:3000/api/v1 - download: https://git.torrentclaw.com - skip_tls_verify: false - -release: - draft: false - prerelease: auto - # Homebrew tap — requires PAT with repo scope (not GITHUB_TOKEN) # Enable when torrentclaw/homebrew-tap PAT is configured as HOMEBREW_TAP_TOKEN # brews: diff --git a/.nojekyll b/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/CHANGELOG.md b/CHANGELOG.md index de1dd6e..55bd493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,174 +5,6 @@ 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.15] - 2026-05-27 - - -### Added - -- **sentry**: enhance error handling by skipping user input errors in CaptureError - -### Changed - -- **ci**: point Forgejo URLs at torrentclaw org (post-transfer) -- **sentry**: decouple agent import via string-match, rename predicate - -### Documentation - -- **positioning**: reframe unarr around download/stream/transcode, drop misleading search-first wording - -### Fixed - -- **ci**: unset GITHUB_TOKEN so goreleaser uses GITEA_TOKEN -- **sentry**: skip "daemon not running" stop/reload errors - -### Other - -- **scripts**: harden release.sh against double-release and inline version bumps -- untrack .claude/ (private local config) -## [0.9.14] - 2026-05-27 - - -### Added - -- **vaapi**: hybrid CPU-scale + hwupload encode path (QW2, 0.9.14) - -### CI/CD - -- port workflows from .github/ to .forgejo/ (Forgejo Actions) - -### Fixed - -- **daemon**: defensive IsClosed check in watchSessionReady poll loop -- **daemon**: use parent ctx for MarkSessionReady so cancel propagates -- **release**: move gitea_urls to top-level (goreleaser v2 schema) -## [0.9.13] - 2026-05-27 - - -### Added - -- **agent**: session-ready webhook for SSE-driven player handshake (0.9.13) -- **agent**: send full transcoder diagnostic in register payload (0.9.12) - -### Fixed - -- **daemon**: defer probeCancel so a panic mid-diagnostic still releases ctx - -### Other - -- **release**: add ship.sh end-to-end pipeline as GH Actions backup -- **skills**: add /publish slash command + allow .claude/ in git -## [0.9.11] - 2026-05-27 - - -### Added - -- **hls**: pre-segmentación delantada — 2 s segments + async session start (0.9.10) -- **hls**: faster first-start — probe cache + tighter encoder presets (0.9.9) - -### Changed - -- **hls**: critico-driven hardening of fase 3.2 - -### Fixed - -- **cors**: allow play from .to / staging / onion mirrors -- **library**: classify resolution by width + height, not height alone -- **transcode**: make preset libx264-only + restore quality opt-in - -### Other - -- **release**: 0.9.11 -## [0.9.8] - 2026-05-27 - - -### Fixed - -- **upgrade**: break auto-apply restart loop (0.9.8) -## [0.9.7] - 2026-05-26 - - -### Added - -- **hls**: persistent fMP4 segment cache + integrity + stats (0.9.7) -## [0.9.6] - 2026-05-26 - - -### Added - -- **daemon**: auto-apply upgrades when server signals (0.9.6) -## [0.9.5] - 2026-05-26 - - -### Added - -- **funnel**: cloudflare quick tunnel embedded subprocess (0.9.5) -## [0.9.4] - 2026-05-26 - - -### Added - -- **stream**: retire WebRTC, HLS-only, bump 0.9.4 (**BREAKING**) -## [0.9.3] - 2026-05-26 - - -### Added - -- **usenet**: warn at startup when par2 or extractor is missing - -### Fixed - -- **engine**: truncate errorMessage before reporting status -- **hls**: clamp ffmpeg bitrate to the level we derive from outputHeight -## [0.9.2] - 2026-05-22 - - -### Added - -- **vpn**: unarr vpn command + report/arbitrate the WireGuard slot -## [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 - -### Other - -- **release**: 0.9.1 -## [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 - -### CI/CD - -- deploy install scripts to GitHub Pages - -### 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 - -- **pages**: add .nojekyll to disable Jekyll processing -- **pages**: set custom domain unarr.torrentclaw.com -- **release**: 0.9.0 ## [0.8.1] - 2026-05-08 @@ -193,7 +25,6 @@ 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 @@ -407,117 +238,16 @@ 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 @@ -527,37 +257,61 @@ 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.15]: https://github.com/torrentclaw/unarr/compare/v0.9.14...v0.9.15 -[0.9.14]: https://github.com/torrentclaw/unarr/compare/v0.9.13...v0.9.14 -[0.9.13]: https://github.com/torrentclaw/unarr/compare/v0.9.11...v0.9.13 -[0.9.11]: https://github.com/torrentclaw/unarr/compare/v0.9.8...v0.9.11 -[0.9.8]: https://github.com/torrentclaw/unarr/compare/v0.9.7...v0.9.8 -[0.9.7]: https://github.com/torrentclaw/unarr/compare/v0.9.6...v0.9.7 -[0.9.6]: https://github.com/torrentclaw/unarr/compare/v0.9.5...v0.9.6 -[0.9.5]: https://github.com/torrentclaw/unarr/compare/v0.9.4...v0.9.5 -[0.9.4]: https://github.com/torrentclaw/unarr/compare/v0.9.3...v0.9.4 -[0.9.3]: https://github.com/torrentclaw/unarr/compare/v0.9.2...v0.9.3 -[0.9.2]: https://github.com/torrentclaw/unarr/compare/v0.9.1...v0.9.2 -[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 @@ -577,12 +331,4 @@ 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/CNAME b/CNAME deleted file mode 100644 index d892572..0000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -unarr.torrentclaw.com \ No newline at end of file diff --git a/DOCKERHUB.md b/DOCKERHUB.md index 3df5b70..dfa96c4 100644 --- a/DOCKERHUB.md +++ b/DOCKERHUB.md @@ -1,21 +1,12 @@ # unarr -**The single binary that replaces your whole *arr stack.** Built-in torrent, -debrid, and usenet engines. Stream, transcode, and organize your library from -one terminal — or run it as a headless daemon with a web dashboard, WireGuard -split-tunnel, and Cloudflare Funnel remote access. +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. -**[Website & docs](https://torrentclaw.com/unarr)** · **[Install guide](https://torrentclaw.com/cli)** · **[Get an API key](https://torrentclaw.com)** +**[GitHub](https://github.com/torrentclaw/unarr)** | **[Documentation](https://github.com/torrentclaw/unarr#readme)** | **[Releases](https://github.com/torrentclaw/unarr/releases)** -> 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. +## Quick Start ---- - -## Quick start - -### 1. First-time setup (interactive wizard) +### 1. Setup (interactive wizard) ```bash docker run -it --rm \ @@ -23,9 +14,6 @@ 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 @@ -38,10 +26,6 @@ docker run -d --name unarr \ torrentclaw/unarr ``` -That's it — `unarr` now runs headless, watching for jobs and managing downloads. - ---- - ## Docker Compose ```yaml @@ -61,54 +45,45 @@ 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 | -|------------------------|--------------------------------------|---------------------------| -| `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. +| 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` | ## Networking -**Host mode (recommended)** — full P2P performance, no port mapping: +**Host mode** (recommended) gives full P2P performance with no port management: ```yaml network_mode: host ``` -**Bridge mode** — more isolated, but you must expose the BitTorrent ports: +**Bridge mode** — more isolated, but requires explicit ports: ```yaml ports: @@ -116,7 +91,7 @@ ports: - "6881-6889:6881-6889/udp" ``` -## Running commands +## Running Commands Use `docker exec` for one-off commands while the daemon is running: @@ -124,77 +99,32 @@ 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 # diagnose config / connectivity +docker exec unarr unarr doctor ``` ---- +## 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` | Exact version (e.g. `0.9.0`) | -| `X.Y` | Latest patch within a minor (e.g. `0.9`) | +| 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`) | -Pin a tag in production (`torrentclaw/unarr:0.9.0`) for reproducible deploys. +## Image Details -## 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** +- **Base image:** Alpine 3.22 +- **User:** `unarr` (UID 1000, GID 1000) - **Entrypoint:** `unarr start` (daemon mode) -- **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 +- **Read-only filesystem** — only mounted volumes are writable +- **No root required** — runs as non-root by default ## License -MIT. +MIT License — see [LICENSE](https://github.com/torrentclaw/unarr/blob/main/LICENSE) for details. diff --git a/Dockerfile b/Dockerfile index 64ea4e2..1773622 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,23 +21,10 @@ FROM alpine:3.22 # Use Alpine's native musl ffmpeg + ffprobe instead of the johnvansickle / # BtbN static glibc builds — those need a glibc shim on Alpine and the # vector-math symbols the GPL builds reference are not satisfiable by -# gcompat. Alpine ships ffmpeg ~7.x which is fine for the HLS transcoding -# pipeline (libx264 + libfdk-aac alternatives included). +# gcompat. Alpine ships ffmpeg ~7.x which is fine for the WebRTC +# transcoding pipeline (libx264 + libfdk-aac alternatives included). RUN apk upgrade --no-cache && \ - apk add --no-cache ca-certificates tzdata ffmpeg wget - -# Bundle cloudflared so `unarr funnel on` (default: on, see config defaults) -# Just Works on a headless container with no first-run network round-trip. -# TARGETARCH is set automatically by Docker buildx during cross-builds. -ARG TARGETARCH=amd64 -RUN case "$TARGETARCH" in \ - amd64) CF_ARCH=amd64 ;; \ - arm64) CF_ARCH=arm64 ;; \ - arm) CF_ARCH=armhf ;; \ - *) echo "unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \ - esac && \ - wget -qO /usr/local/bin/cloudflared "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-$CF_ARCH" && \ - chmod +x /usr/local/bin/cloudflared + apk add --no-cache ca-certificates tzdata ffmpeg # Non-root user (UID 1000 matches typical host user for volume permissions) RUN addgroup -g 1000 unarr && adduser -u 1000 -G unarr -D -h /home/unarr unarr diff --git a/Docs/plans/library-sync.md b/Docs/plans/library-sync.md deleted file mode 100644 index 509e87a..0000000 --- a/Docs/plans/library-sync.md +++ /dev/null @@ -1,170 +0,0 @@ -# Plan: Sincronización bidireccional de biblioteca (CLI ↔ Web) - -## Context -La biblioteca web solo muestra descargas completadas (download_task + debrid). El `unarr scan` escanea ficheros con ffprobe y los sube al servidor, pero solo soporta un path, no detecta borrados del disco, y no permite borrar ficheros desde la web. El usuario quiere una biblioteca unificada que refleje el estado real de su colección y se sincronice en ambas direcciones. - -## Protocolo de sincronización - -### Forward Sync (Disco → Web) -1. CLI escanea todos los `ScanPaths` configurados -2. Para cada path: descubre ficheros, compara con cache (skip ffprobe si no cambió), sube a `/library-sync` -3. En `isLastBatch=true`: el servidor elimina items con ese `scanPath` que no estén en el batch (ficheros borrados del disco desaparecen de la web) - -### Reverse Sync (Web → Disco) -1. CLI llama a `GET /agent/library-deletions` — items que el usuario soft-deleted desde la web -2. Si `AutoDelete=true` o `--yes`: borra ficheros del disco -3. Si no: muestra lista y pide confirmación interactiva -4. Llama a `POST /agent/library-deletions/confirm` con los IDs confirmados → hard-delete en DB - -### Resolución de conflictos -- Fichero en disco pero no en web → forward sync lo añade -- Fichero en web pero no en disco → forward sync lo elimina (isLastBatch) -- Soft-deleted en web, aún en disco → reverse sync lo borra del disco y confirma -- Soft-deleted en web, ya borrado del disco → reverse sync confirma directamente -- Race condition (user borra en web mientras CLI escanea) → forward sync skippea rows con `deleted_at IS NOT NULL` - ---- - -## Fase 1: Multi-path + Forward Sync mejorado - -### 1.1 CLI — Config multi-path -**Archivo:** `torrentclaw-cli/internal/config/config.go` -- Añadir `ScanPaths []string` a `LibraryConfig` -- Migrar `ScanPath` → `ScanPaths[0]` en `Load()` si `ScanPaths` está vacío -- Añadir `AutoDelete bool` (default false) - -### 1.2 CLI — Cache v2 -**Archivo:** `torrentclaw-cli/internal/library/types.go` -- Cambiar `LibraryCache` a version 2: `Paths map[string][]LibraryItem` -- Migración v1→v2: `Path`+items → `Paths[Path]` - -**Archivo:** `torrentclaw-cli/internal/library/cache.go` -- `LoadCache` detecta versión y migra -- `SaveCache` siempre guarda v2 - -### 1.3 CLI — Scan multi-path -**Archivo:** `torrentclaw-cli/internal/cmd/scan.go` -- `unarr scan` sin args → escanea todos los `ScanPaths` -- `unarr scan /path/a /path/b` → escanea paths específicos y los recuerda en config -- Loop: para cada path, scan + sync con su `scanPath` - -### 1.4 CLI — Nuevo comando `unarr sync` -**Archivo nuevo:** `torrentclaw-cli/internal/cmd/sync.go` -- Forward sync: scan ligero (sin ffprobe para ficheros sin cambios) + upload -- Sin reverse sync todavía (Fase 3) -- Flags: `--dry-run`, `--paths` - -### 1.5 Web — Columna `scan_path` en `library_item` -**Archivo:** `torrentclaw-web/src/lib/db/schema.ts` -- Añadir `scanPath: varchar(2048)` a tabla `libraryItem` -- Generar migración con `pnpm db:generate` - -**Archivo:** `torrentclaw-web/src/lib/services/library-upgrade.ts` -- `syncLibraryItems()`: persistir `scanPath` en cada row al hacer upsert - -### 1.6 CLI — Daemon multi-path -**Archivo:** `torrentclaw-cli/internal/cmd/daemon.go` -- `runAutoScan()` itera sobre todos los `ScanPaths` - ---- - -## Fase 2: Reverse Sync (Web → Disco) - -### 2.1 Web — Soft-delete -**Archivo:** `torrentclaw-web/src/lib/db/schema.ts` -- Añadir `deletedAt: timestamp` a tabla `libraryItem` -- Generar migración - -### 2.2 Web — Endpoints de borrado -**Archivo nuevo:** `torrentclaw-web/src/app/api/internal/library/items/route.ts` -- `DELETE` — session auth, recibe `{itemIds: number[]}`, hace soft-delete (`deletedAt = NOW()`) - -**Archivo nuevo:** `torrentclaw-web/src/app/api/internal/agent/library-deletions/route.ts` -- `GET` — agent auth, devuelve items con `deletedAt IS NOT NULL` para ese usuario -- `POST` — agent auth, recibe `{confirmedIds: number[]}`, hard-delete los rows - -### 2.3 Web — Heartbeat con pendingDeletions -**Archivo:** endpoint de heartbeat del agente -- Añadir `pendingDeletions: number` al response (count de items con `deletedAt IS NOT NULL`) - -### 2.4 Web — Forward sync respeta soft-deletes -**Archivo:** `torrentclaw-web/src/lib/services/library-upgrade.ts` -- `syncLibraryItems()` en `isLastBatch`: la query de DELETE excluye rows con `deletedAt IS NOT NULL` - -### 2.5 CLI — Agent client nuevos métodos -**Archivo:** `torrentclaw-cli/internal/agent/client.go` -- `GetLibraryDeletions(ctx) → []DeletionItem` -- `ConfirmLibraryDeletions(ctx, ids []int) → error` - -**Archivo:** `torrentclaw-cli/internal/agent/types.go` -- `DeletionItem {ID int, FilePath string, DeletedAt string}` - -### 2.6 CLI — Sync reverse -**Archivo:** `torrentclaw-cli/internal/cmd/sync.go` -- Después del forward sync: llama a `GetLibraryDeletions()` -- Valida que cada fichero está dentro de un `ScanPaths` conocido (seguridad) -- Si `AutoDelete` o `--yes`: borra y confirma -- Si no: muestra lista interactiva, pide confirmación -- Flag `--no-delete` para skip reverse sync -- Si `BackupDir` configurado: mover a backup en vez de borrar - -### 2.7 CLI — Daemon auto-delete -**Archivo:** `torrentclaw-cli/internal/cmd/daemon.go` -- Al final de `runAutoSync()`: si `AutoDelete=true`, procesa deletions automáticamente -- Si no: log warning "N files pending deletion, run `unarr sync`" - ---- - -## Fase 3: Web UI (brief) - -- Botón "Eliminar" en items de biblioteca → llama `DELETE /library/items` -- Badge "Pendiente de borrar" en items soft-deleted -- Posibilidad de cancelar el borrado (clear `deletedAt`) -- Vista unificada: scanned items + downloaded items en la misma vista - ---- - -## Archivos clave - -### CLI (Go) -| Archivo | Cambio | -|---------|--------| -| `internal/config/config.go` | ScanPaths, AutoDelete, migración | -| `internal/library/types.go` | Cache v2 con Paths map | -| `internal/library/cache.go` | Load/Save v2, migración v1 | -| `internal/library/sync.go` | BuildSyncItems (sin cambios) | -| `internal/cmd/scan.go` | Multi-path loop | -| `internal/cmd/sync.go` | **Nuevo** — comando sync bidireccional | -| `internal/cmd/daemon.go` | runAutoSync multi-path + reverse | -| `internal/agent/client.go` | GetLibraryDeletions, ConfirmLibraryDeletions | -| `internal/agent/types.go` | DeletionItem type | - -### Web (TypeScript) -| Archivo | Cambio | -|---------|--------| -| `src/lib/db/schema.ts` | scanPath + deletedAt en library_item | -| `src/lib/services/library-upgrade.ts` | persistir scanPath, respetar soft-deletes | -| `src/app/api/internal/agent/library-deletions/route.ts` | **Nuevo** — GET + POST | -| `src/app/api/internal/library/items/route.ts` | **Nuevo** — DELETE soft-delete | -| Endpoint heartbeat del agente | pendingDeletions en response | - ---- - -## Verificación - -### Fase 1 -1. `go build ./cmd/unarr/ && go test ./...` -2. Configurar 2 scan paths en config.toml, ejecutar `unarr scan` → ambos se escanean -3. Borrar un fichero del disco, ejecutar `unarr scan` → desaparece de la web -4. `pnpm build` en torrentclaw-web para verificar tipos - -### Fase 2 -1. Desde la web: borrar un item de la biblioteca -2. Ejecutar `unarr sync` → muestra el fichero pendiente de borrar, pedir confirmación -3. Confirmar → fichero se borra del disco y desaparece de la web -4. `unarr sync --dry-run` → muestra lo que haría sin hacer nada -5. Con `auto_delete = true` en config: el daemon borra automáticamente - -### Fase 3 -1. Verificar visualmente en Chrome DevTools la UI de borrado -2. Verificar que el badge "pendiente" aparece y desaparece correctamente diff --git a/Docs/plans/security-stream-token.md b/Docs/plans/security-stream-token.md deleted file mode 100644 index 1a08e21..0000000 --- a/Docs/plans/security-stream-token.md +++ /dev/null @@ -1,131 +0,0 @@ -# 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 `