Commit graph

68 commits

Author SHA1 Message Date
Deivid Soto
eb2548f9a6 feat(streaming): seek-restart, single-session, idle sweeper, probe.json
Follow-ups on the daemon HLS pipeline (0fc0e1c):

- engine/hls.go HLSSession.Register now closes every other active
  session in the registry. Modeled as "one viewer == one transcode" so
  repeated quality switches or page reloads don't leave orphan ffmpegs
  saturating the CPU until the idle sweeper reaps them 30 min later.
- engine/hls.go restartFromSegment kills + respawns ffmpeg with
  -ss / -output_ts_offset / -start_number when the browser asks for a
  segment far ahead of the writer head. Segments already on disk stay
  cached. Without this, a user dragging the scrubber to minute 30 of a
  fresh stream blocks until the encoder reaches minute 30 in real time.
- engine/hls.go subtitle disambiguation: never set DEFAULT=YES on any
  rendition (anime forced "signs only" tracks were autoselected and
  rendered nothing during opening dialogue, looking broken). Names get
  numeric suffixes when language is duplicated; FORCED tracks get a
  "(forzados)" suffix.
- engine/hls.go ProbeInfo() exposes codec / audio / subtitle metadata
  to the new GET /hls/<id>/probe.json endpoint for the player's info
  badge + bandwidth logic.
- engine/hls.go scale chain fix: chains a trunc(iw/2)*2 scale after
  the height cap so libx264 stops rejecting odd widths (853x480 etc.).
- engine/hls.go HW encoder tuning: NVENC -preset p4 -rc vbr -tune hq,
  QSV -preset medium.
- engine/stream_server.go routes /hls/<id>/probe.json to the session.
- cmd/daemon.go runs an idle sweeper goroutine every 5 min, reaping
  sessions whose last segment fetch was >30 min ago.
2026-05-07 23:55:05 +02:00
Deivid Soto
0fc0e1c21a feat(streaming): add HLS transport pipeline (daemon side)
Introduces an HLS-over-HTTP path as Plan B for in-browser streaming. The
WebRTC + MSE pipeline keeps working untouched; the new path is selected
when the backend sets transport="hls" on a streaming session.

Daemon scope:
- engine/hls.go: HLSSession + HLSSessionRegistry. Spawns ffmpeg with
  -f hls -hls_segment_type fmp4 + force_key_frames aligned with 4 s
  segments. Pre-renders master + media playlists from the probe duration
  so the browser knows the total timeline before any segment exists,
  fixing seek/duration/pause/multi-track issues seen with the live fMP4
  pipe.
- engine/probe.go: enumerate every audio + subtitle track instead of
  collapsing to a single default audio track.
- engine/stream_server.go: route /hls/<id>/{master.m3u8,video/...,
  subs/...} to the matching session. Emit a synthesised single-VTT
  subtitle playlist per text track; bitmap subs (PGS/DVB) skip silently.
- cmd/daemon.go: branch on WebRTCSession.Transport == "hls" to register
  an HLS session instead of running the legacy DataChannel pump.
- agent/types.go: WebRTCSession.Transport + AudioIndex fields.

Backend + web sides land in a follow-up commit.
2026-05-07 16:10:22 +02:00
Deivid Soto
70f7337226 feat(stream): per-session quality cap from web
Adds WebRTCSession.Quality to the sync payload so the daemon can pick a
MaxHeight + bitrate per session instead of using the global config cap.

resolveQualityCap() maps the label to a (height, b:v) pair and
buildStreamSource() promotes a passthrough decision to ActionTranscodeVideo
when the source resolution exceeds the cap (4K source on a phone client
with quality="720p" must transcode, not pass-through).

Also lands the transcode-on-by-default fix for legacy configs without a
[downloads.transcode] section so existing installs pick up h264+aac
fallback for HEVC/AC3 content without re-running setup.
2026-05-07 10:13:45 +02:00
Deivid Soto
66ac79664b feat(stream): real-time transcoding for non-browser-decodable codecs
Source files in HEVC, AV1, AC3, DTS, EAC3, etc. now transcode through ffmpeg
to fragmented MP4 (h264 + aac) on-the-fly when the browser would otherwise
play silent black. Decision matrix lives in engine.DecideAction:
passthrough → remux → audio-transcode → full video-transcode.

Architecture — temp file + growing-size source:
- engine.streamSource interface abstracts byte source. Two impls:
  * diskFileSource: passthrough when codecs are already browser-friendly.
  * transcodeSource: spawns ffmpeg writing to a /tmp/tc-stream-*.mp4 file.
    A ticker polls file size and wakes blocked ReadAt callers as ffmpeg
    produces output. Estimate of final size (bitrate × duration) is
    announced over the wire so the browser's scrubber has something to
    anchor on.
- dataChannelPump now reads from streamSource instead of *os.File. HELLO
  carries Transcoding=true + an estimated total size; Seekable=true (we
  read random-access from the temp file even while writing).
- Transcoder runtime resolved per session by buildTranscodeRuntime in
  cmd/daemon: ffmpeg/ffprobe path lookup + HWAccel auto-detection
  (NVENC/QSV/VAAPI/VideoToolbox).
- New [downloads.transcode] TOML section: enabled (default true), hw_accel
  (auto), preset (veryfast), video_bitrate (5M), audio_bitrate (192k),
  max_height (optional downscale), max_concurrent (safety cap).

Falls back to passthrough if ffprobe is missing, fails, or codecs are
already browser-friendly. tmp file is cleaned up on session shutdown.
2026-05-07 09:26:05 +02:00
Deivid Soto
4314c06c5c feat(stream): pion-based WebRTC byte streamer for browser playback
Replaces the broken anacrolix WebTorrent path with a custom WebRTC peer
that the browser drives directly. Architecture matches plan/clever-
weaving-dove.md (Fase 2 + 3 + 6 of the streaming pivot).

- engine/wire: shared 12-byte binary frame format (Hello / RangeReq /
  RangeData / RangeEnd / Cancel / Ping / Pong / SeekHint). Roundtrip +
  oversized-frame rejection tests.
- agent/signal_client: SSE consumer + POST sender for SDP/ICE relay
  through /api/internal/stream/signal/<id>; auto-reconnects.
- engine/webrtc_stream: pion v4 PeerConnection + DataChannel pump.
  Reads file via os.ReadAt, chunks RangeData at 16 KiB, honours app-
  level backpressure with SetBufferedAmountLowThreshold.
- cmd/daemon dispatcher learns mode webrtc_stream + new
  webrtcSessionRegistry tracks per-session cancel funcs for clean
  shutdown.
- engine/probe + hwaccel + transcoder: foundation for Fase 2.5
  (codec detection, NVENC/QSV/VAAPI/VideoToolbox autodetection,
  ffmpeg pipe wrapper to fragmented MP4). Integration into
  webrtc_stream still pending.
- pion/webrtc/v4 promoted from indirect to direct dep.

End-to-end against unarr-dev confirms a 122 MB 1080p H.264 / AAC MP4
plays in Chrome with the new pipeline.
2026-05-06 23:12:38 +02:00
Deivid Soto
e50dd17a00 feat(seed-file): unarr-side handler for browser-on-demand seeding (Fase 4.7.c)
Closes the agent half of in-browser playback for arbitrary files. When
the web app inserts a download_task with mode="seed_file", the daemon
now wraps the on-disk file as a single-file torrent, adds it to the
existing WebRTC-enabled torrent client, and reports the generated
info_hash back so the browser can target /stream/<hash>.

Pieces:

- internal/agent/types.go: Task.FilePath (received from claim) +
  StatusUpdate.InfoHash (sent back). Both serialise compatibly with
  the matching Zod schemas in the Next.js sync route.

- internal/engine/seed_file.go: SeedFile(client, filePath, trackers)
  builds the metainfo via metainfo.Info.BuildFromFilePath +
  bencode.Marshal, then AddTorrent + DownloadAll() so anacrolix
  hashes the file and flips pieces to "have" as it goes. The
  libtorrent piece-size ladder is mirrored from wstracker-probe so
  generated torrents are interoperable with mainstream clients.
  SeedFileOnDownloader is the daemon-facing convenience wrapper —
  bails loud when [downloads.webrtc].enabled = false instead of
  silently producing a torrent no browser can find.

- internal/cmd/seed_file_handler.go: handleSeedFileTask invoked from
  the existing OnTasksClaimed dispatcher in daemon.go for mode=
  seed_file. Validates filePath, calls the engine helper, and pushes
  the resulting info_hash via Client.ReportStatus. Failures (missing
  file, WebRTC disabled, ffmpeg-style oddities) report status="failed"
  + errorMessage so the browser's WatchInBrowserButton can show the
  reason instead of timing out at 60 s.

- internal/cmd/daemon.go: dispatcher learns the seed_file branch in
  the same shape as the existing stream branch.

Tests (6 unit, all green):
- SeedFile rejects missing files + directories.
- SeedFile yields a deterministic info_hash for the same payload across
  fresh clients (web client polls expecting this).
- SeedFileOnDownloader errors when WebRTC is disabled.
- chooseSeedPieceLength matches the ladder breakpoints.
- makeAnnounceList handles nil/empty/partial inputs.

Web side compatible: mode=seed_file is already accepted by the sync
schema; agent.Task.filePath + StatusUpdate.infoHash now propagate
through the existing claim/report endpoints. End-to-end browser ↔
unarr smoke is the next concrete verification step (needs a running
unarr-dev daemon plus library scan + a file with no source torrent).
2026-05-06 16:28:01 +02:00
Deivid Soto
f6117ddeb9 feat(torrent): act as WebTorrent peer for browser ↔ unarr P2P streaming
Wires anacrolix/torrent's built-in webtorrent package so a browser
running webtorrent.js can fetch pieces from this CLI via WebRTC data
channels. The daemon stays the seeder; we never relay bytes through
TorrentClaw infrastructure — same legal posture as today.

Changes:
- internal/config: new [downloads.webrtc] section
  (enabled/trackers/stun_servers/turn_servers/turn_user/turn_pass).
  Disabled by default, opt-in via config.toml. When enabled but
  trackers / STUN slices are empty, defaults are reapplied on Load() so
  users get a working setup with a single `enabled = true`.
- internal/engine: TorrentConfig gains WebRTCEnabled / WebRTCTrackers
  / ICEServers; NewTorrentDownloader populates ClientConfig.ICEServerList
  and forces NoUpload=false when WebRTC is on (browsers can't pull
  otherwise). buildMagnet now accepts variadic extra trackers and the
  downloader method prepends WSS trackers so anacrolix's
  webtorrent.TrackerClient picks them up first.
- internal/engine/webrtc.go: BuildICEServers helper converts the TOML
  WebRTCConfig into []webrtc.ICEServer with shared TURN credentials.
- internal/cmd/daemon.go + download.go: pass WebRTC config through to
  the engine.

Tests (8 new, all green; full suite 0 lint issues, 0 vet):
- buildMagnet free function: defaults-only, with extras, trim+empty-skip
- downloader method: WebRTC disabled keeps WSS out, enabled prepends them
- BuildICEServers: nil when disabled, STUN-only path, TURN+credentials
- NewTorrentDownloader: full WebRTC-enabled construction (logs WebRTC
  peer enabled, magnet contains wss://tracker.torrentclaw.com)

End-to-end smoke (browser ↔ unarr peer transfer) is deferred to a
manual test once tracker.torrentclaw.com WSS is live.
2026-05-06 08:59:58 +02:00
Deivid Soto
6955b6144b chore(release): 0.7.0
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.7.0
- Update CHANGELOG.md
2026-04-10 19:18:38 +02:00
Deivid Soto
37fcb9fad9 feat(daemon): enhance service management with start, stop, restart, and status commands for Windows 2026-04-10 19:18:13 +02:00
Deivid Soto
debf77005f chore(release): 0.6.8
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.6.8
- Update CHANGELOG.md
2026-04-10 16:36:27 +02:00
Deivid Soto
f699b26fa6 feat(library): add server-driven file deletion with allow_delete config 2026-04-10 16:35:12 +02:00
Deivid Soto
8ad8a5ea47 chore(release): 0.6.7
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.6.7
- Update CHANGELOG.md
2026-04-10 11:47:58 +02:00
Deivid Soto
db316726fd feat(scan): always scan downloads + organize dirs, deduplicate child paths
ResolveScanPaths() collects downloads.dir, organize.movies_dir,
organize.tv_shows_dir, and library.scan_path (if set), then removes
paths that are subdirectories of a parent already in the list.

This ensures the daemon and CLI scan all configured dirs without
relying solely on scan_path being set.
2026-04-10 11:46:20 +02:00
Deivid Soto
b3f2b3e64d chore(release): 0.6.6
- Bump version to 0.6.6
- Update CHANGELOG.md
2026-04-09 18:37:56 +02:00
Deivid Soto
f1b4f2e327 fix(stream): fix black screen on remote/Tailscale streaming
Three root-cause fixes for VLC showing a black screen when opening a
stream from a different network or via Tailscale:

1. PrioritizeTail: when VLC opens an MKV/MP4 stream it immediately seeks
   to the end of the file to read the container index (seekhead/moov
   atom). For active torrents those end-pieces aren't downloaded yet, so
   the reader blocks indefinitely. PrioritizeTail() opens a background
   reader positioned at the last 5 MB, keeping those pieces at high
   priority until ctx is cancelled or they finish downloading.

2. /health endpoint: GET /health returns a lightweight JSON response
   {"status":"ok","streaming":bool,...} so connectivity can be tested
   with a simple curl from any device before involving VLC.

3. Per-request logging: every incoming /stream request now logs the
   client IP and Range header, making it trivial to confirm whether
   remote/Tailscale clients are reaching the server at all.
2026-04-09 16:15:41 +02:00
Deivid Soto
7eaf357680 chore(release): 0.6.5
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.6.5
- Update CHANGELOG.md
2026-04-09 14:16:02 +02:00
Deivid Soto
29f4886a53 chore(release): 0.6.4
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.6.4
- Update CHANGELOG.md
2026-04-09 10:54:42 +02:00
Deivid Soto
8fae119903 fix(daemon): report error status when stream path is rejected 2026-04-09 10:54:14 +02:00
Deivid Soto
d7fa0af504 chore(release): 0.6.3
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.6.3
- Update CHANGELOG.md
2026-04-09 09:26:17 +02:00
Deivid Soto
228564eb7f feat(library): resilient scan for large libraries and better ffprobe errors
- Use a dedicated 10-minute HTTP client for library-sync so libraries
  with hundreds or thousands of items no longer time out
- Show actionable ffprobe-not-found error: detects Docker and suggests
  FFPROBE_PATH env var, config.toml setting, or package install
- Include static ffprobe binary in Docker image (johnvansickle.com)
- Bump version to 0.6.2
2026-04-09 09:13:38 +02:00
Deivid Soto
3fd19f1406 feat(wake): long-poll wake listener for instant CLI sync
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
CLI now holds a GET /api/internal/agent/wake connection open.
When the server calls triggerWake(userId) — on stream request,
download queue, pause, cancel, resume, scan, etc. — the CLI
receives the signal immediately and fires a sync cycle in <100ms
instead of waiting up to 10s for the next scheduled interval.

- Add WaitForWake(ctx) to Client using a no-timeout HTTP client
- Add runWakeListener goroutine to SyncClient (auto-reconnects)
- Start wake listener from SyncClient.Run()

Closes: sub-second stream latency from the web UI
2026-04-09 00:01:24 +02:00
Deivid Soto
ef4f38d324 fix: resolve deadlock, data races and path traversal vulnerabilities
- task.go: fix deadlock in ToStatusUpdate() — calling Percent() (which
  RLocks) while already holding RLock caused deadlock when a writer was
  waiting; compute percent inline instead
- usenet.go: fix data race in Cancel() — tracker and taskDir were read
  without the mutex while Download() writes them under it; read all
  fields under the same lock
- upnp.go: fix UPnP Remove() blocking shutdown — run cleanup in goroutine
  with 10s deadline (removeNATPMP worst case is 3s dial + 5s deadline)
- daemon.go: add path traversal protection for stream requests — validate
  sr.FilePath is within configured directories before os.Stat; defends
  against compromised API server sending arbitrary paths
- client.go: add wakeClient without timeout for long-poll wake endpoint
  where context controls cancellation
- sync.go: trigger immediate sync when entering watching mode so stream
  requests are picked up without waiting for the next scheduled interval
2026-04-08 23:36:18 +02:00
Deivid Soto
78c16c295e test: add comprehensive test suite for engine, agent and cmd packages
- Refactor download.go and stream.go with downloadDeps/streamDeps structs
  for dependency injection, enabling unit testing without real I/O
- download_test.go: 15 tests — input validation, mock downloaders, method
  selection, cobra Args, deadlock detection
- stream_test.go: input validation, noOpen flag, engine error handling
- client_test.go: context cancellation, timeout, full Sync roundtrip,
  watch-progress and HTTP error unwrapping
- sync_test.go: TriggerSync on watching transition, adjustInterval
- torrent_test.go: TorrentDownloader lifecycle without network
- stream_server_test.go: HTTP server lifecycle, SetFile/ClearFile,
  concurrent requests, Shutdown releases port, content-type
- manager_integration_test.go: full pipeline — success, torrent→debrid
  fallback, all-fail, multi-concurrent, ForceStart, OnTaskDone,
  recent-finished drain, cancel mid-download, organize
- usenet_test.go: Cancel/Pause race regression test (run with -race)
- daemon_test.go: isAllowedStreamPath table tests
- CI: split coverage gate to engine+agent only (50% threshold); cmd
  coverage still reported but not gated (interactive UI commands)
- lefthook: add pre-push hook with go test -race -count=1 -timeout=120s
2026-04-08 23:36:00 +02:00
Deivid Soto
b14ab98580 chore(release): 0.6.0
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.6.0
- Update CHANGELOG.md
2026-04-08 18:57:36 +02:00
Deivid Soto
5d4a67c7a2 feat(sync): replace WS+DO transport with unified HTTP sync
Replace the WebSocket + Cloudflare Durable Object architecture with a
single POST /sync endpoint. The CLI now operates autonomously with local
state (tasks.json) and syncs bidirectionally via adaptive-interval HTTP
polling (3s watching, 60s idle).

- Remove transport_ws, transport_hybrid, transport_http (~2,600 lines)
- Add SyncClient with adaptive interval loop
- Add LocalState for CLI-side task persistence
- Add TaskStateFromUpdate() helper (DRY)
- Extract finalize() to deduplicate processTask/processTaskRetry
- Consolidate shortID() into agent.ShortID (was in 3 packages)
- Wire GetActiveCount so `unarr status` shows active tasks
- Remove poll_interval, heartbeat_interval, ws_url from config
- Simplify ProgressReporter (sync replaces direct HTTP reporting)
2026-04-08 18:50:59 +02:00
Deivid Soto
56a386f4e2 chore(release): 0.5.5
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.5.5
- Update CHANGELOG.md
2026-04-07 23:33:24 +02:00
Deivid Soto
4d7362a567 fix(daemon): cancel watch reporter on stream switch and re-notify ready
- Register WatchReporter cancel funcs in streamRegistry so they get
  cancelled when switching to a different stream (prevents goroutine leak)
- Re-notify streamReady when the server is already serving the requested
  task (handles duplicate stream requests from the web UI)
- Rewrite tests for byte-based tracking semantics, remove dead
  parseRangeStart tests
2026-04-07 23:30:53 +02:00
Deivid Soto
bfa8ec5f11 chore(release): 0.5.4
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.5.4
- Update CHANGELOG.md
2026-04-07 19:18:41 +02:00
Deivid Soto
55fb74c814 chore(release): 0.5.3
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.5.3
- Update CHANGELOG.md
2026-04-07 19:08:49 +02:00
Deivid Soto
5994a30447 feat(stream): persistent stream server with file swapping 2026-04-07 19:08:37 +02:00
Deivid Soto
080fdf4d76 chore(release): 0.5.2
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.5.2
- Update CHANGELOG.md
2026-04-07 17:06:04 +02:00
Deivid Soto
eb8f5e8b1a feat(stream): report multi-network URLs for smart resolution 2026-04-07 17:05:52 +02:00
Deivid Soto
dc1a21d8f0 chore(release): 0.5.1
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.5.1
- Update CHANGELOG.md
2026-04-07 16:19:38 +02:00
Deivid Soto
d2edc08a1e fix(stream): prevent duplicate events from killing active stream server 2026-04-07 16:19:01 +02:00
Deivid Soto
a857661b27 fix(daemon): report failed status on stream request errors 2026-04-07 12:39:22 +02:00
Deivid Soto
a9179dc758 feat(daemon): add on-demand library scan via heartbeat and WebSocket 2026-04-07 11:36:42 +02:00
Deivid Soto
4cf07c411c fix(daemon): use correct systemd user target and isolate test cache 2026-04-06 18:49:44 +02:00
Deivid Soto
8388220dae chore(release): 0.5.0
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Bump version to 0.5.0
- Update CHANGELOG.md
2026-04-06 10:16:57 +02:00
Deivid Soto
819c727bf5 feat(organize): use server metadata for file organization and subtitle handling 2026-04-05 23:36:01 +02:00
Deivid Soto
48e4fb9f7b fix(lint): remove unused newStubCmd function
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
2026-04-01 12:29:05 +02:00
Deivid Soto
4d35e197f0 feat(cli): add login command and refactor shared helpers 2026-04-01 12:20:51 +02:00
Deivid Soto
0dafeaa70d feat(stream): report watch progress to API via HTTP Range tracking
Track the highest byte offset served by the stream server to estimate
playback progress (0-100%). A WatchReporter goroutine sends progress
to POST /api/internal/agent/watch-progress every 10s during streaming.

- Add maxByteOffset + totalFileSize to StreamServer for Range tracking
- Add FileSize() to fileProvider interface (all 3 providers)
- New WatchReporter: periodic progress reporter tied to daemon context
- New WatchProgressUpdate type with optional progress/position/duration
- Wire reporter into all 3 stream paths (task stream, disk stream, active download stream)
2026-04-01 12:16:45 +02:00
Deivid Soto
932312fc56 chore(cli): remove moreseed stub command 2026-03-31 23:12:07 +02:00
Deivid Soto
ab3b393c22 chore(cli): remove redundant stub commands (monitor, open, add, compare) 2026-03-31 23:03:08 +02:00
Deivid Soto
d0dbfc3d12 fix(ci): fix lint errors and pin CI to Go 1.25
- Run gofmt on all files
- Export SetupUPnP to fix unused lint error
- Remove Go 1.26 from CI matrix (only test with 1.25)
2026-03-31 22:15:12 +02:00
Deivid Soto
3e0f3a5a64 feat(cli): upgrade command, rich status, and version cache
Some checks failed
Release / release (push) Failing after 0s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
- Replace `upgrade` stub with real command (alias for `self-update`)
- Also register `update` as alias: `unarr update` works too
- Rewrite `status` to show full config, disk usage, daemon state, and
  update availability with colored sections
- Add version check cache (1h TTL) so `status` is instant on repeat runs
- Guard against division by zero on empty filesystems
- Guard against negative durations from clock skew
- Guard against stale PID via heartbeat recency check (2 min)
- Add comprehensive test coverage across agent, engine, upgrade, usenet,
  arr, library, mediaserver, and UI packages
- Improve Makefile coverage target to exclude cmd/ glue code
- Fix stream handler resource cleanup and ffprobe error handling
2026-03-31 22:05:43 +02:00
Deivid Soto
01d62ffa13 fix(progress): always report status transitions and poll for control signals 2026-03-31 16:55:50 +02:00
Deivid Soto
aed5f0475d fix(lint): use default:none to disable errcheck, fix all gofmt and exhaustive 2026-03-31 00:29:16 +02:00
Deivid Soto
efa4562acd refactor: migrate lint config to v2, remove daemon auto-upgrade, add trust badges
Some checks failed
Release / release (push) Failing after 1s
Release / docker (push) Has been skipped
Release / virustotal (push) Failing after 0s
2026-03-30 23:24:16 +02:00
Deivid Soto
b00e7fbf0e feat(init): add 60s countdown, skip key, and cancel detection to browser auth 2026-03-30 14:07:57 +02:00