@torrent-tv/proxy 2.64.1 → 2.64.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -0
- package/docs/logs.md +43 -0
- package/package.json +1 -1
- package/routes/api/sources/stats/get.js +5 -0
- package/services/torrent-pool.js +19 -6
package/CLAUDE.md
CHANGED
|
@@ -42,6 +42,9 @@ Linux-only host (e.g. POSIX-only signals must degrade elsewhere).
|
|
|
42
42
|
`routes/*` are now thin HTTP translators.
|
|
43
43
|
- `container-index/` — legacy readers (ebml-reader, matroska/mp4/avi keyframe and
|
|
44
44
|
subtitle tables) — used internally by `container/*`, deprecated as direct import.
|
|
45
|
+
- `docs/logs.md` — where to find logs (HA `docker logs` + `/data/proxy.log`, DO
|
|
46
|
+
`infra-server-1` with forwarded frontend `POST /api/client-logs`). Browser
|
|
47
|
+
console not needed.
|
|
45
48
|
- `playback-planner.js` — single ffmpeg probe returns audioCodec, videoCodec,
|
|
46
49
|
container, durationSeconds. `mode` is advisory; the browser decides.
|
|
47
50
|
- `hls-session-manager.js` — one ffmpeg per (source, file, settings). Serves a
|
package/docs/logs.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Logs — where to look
|
|
2
|
+
|
|
3
|
+
All logs are visible via container `docker logs`, no browser console copy-paste needed.
|
|
4
|
+
|
|
5
|
+
## HA proxy (aarch64, addon `b34a1737_torrent_tv_proxy`)
|
|
6
|
+
|
|
7
|
+
Image `b34a1737/aarch64-addon-torrent_tv_proxy:<version>` = `@torrent-tv/proxy` `<version>` (see `ha-addon/torrent_tv_proxy/config.yaml`).
|
|
8
|
+
|
|
9
|
+
- Console + file are the same: the proxy logs to both `stdout` and `/data/proxy.log` on start (`logging to /data/proxy.log as well as the console`).
|
|
10
|
+
- Via container:
|
|
11
|
+
```bash
|
|
12
|
+
ssh ha "sudo docker logs app_b34a1737_torrent_tv_proxy --tail 200"
|
|
13
|
+
ssh ha "sudo docker exec app_b34a1737_torrent_tv_proxy cat /data/proxy.log | tail -n 300"
|
|
14
|
+
```
|
|
15
|
+
- Filter by session: every transcode session logs its id, e.g. `003ed2fd-7c9b-4cd5-9d05-ff875ff2be23`, `hold segment-00068.mp4 failed`, `encode-run failed`, `EXITED_*`.
|
|
16
|
+
- Host file `/data/proxy.log` survives `docker logs` rotation; `core dumps: 1 present` line shows kept dump under `/data`.
|
|
17
|
+
|
|
18
|
+
SSH to HA requires `MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com` — server `OpenSSH_10.3` on `homeassistant.local` offers only `*-etm` (`Unable to negotiate` / `Corrupted MAC` otherwise). Already in `~/.ssh/config` `Host ha`.
|
|
19
|
+
|
|
20
|
+
## DO server (webauth.courses, `infra-server-1`)
|
|
21
|
+
|
|
22
|
+
- Server is `infra-server-1` (`ghcr.io/torrent-tv/server:latest`) on `do` (`206.189.97.152`).
|
|
23
|
+
- Frontend logs are forwarded: browser batches `{sessionId, tag, signalSessionId, lines: [{level,ts,msg}]}` and `POST`s to `https://webauth.courses/api/client-logs` (`server/routes/api/client-logs/post.js:56`), server does `console.log` with prefix `[client <tag> <id> sig=<webrtcSessionId>]`. They appear together with backend logs in the same container.
|
|
24
|
+
- Via container:
|
|
25
|
+
```bash
|
|
26
|
+
ssh do "docker logs infra-server-1 --tail 200 | cat"
|
|
27
|
+
# filter a single viewing:
|
|
28
|
+
ssh do "docker logs infra-server-1 --tail 500 | grep 003ed2fd"
|
|
29
|
+
ssh do "docker logs infra-server-1 --tail 500 | grep '\[client'"
|
|
30
|
+
```
|
|
31
|
+
- No need to open eruda or copy browser console on phone — it is already in `infra-server-1` logs.
|
|
32
|
+
|
|
33
|
+
## Quick triage
|
|
34
|
+
|
|
35
|
+
- Rewind/seek bug (hold 0ms → 500): HA `hold ... failed after 0ms → 500` + `encode-run` lines for the same `<sessionId>`, and DO `[client ...] fragLoadError / levelLoadError 500` for same `sn`.
|
|
36
|
+
- Cushion / link budget: HA `memory: rss=... anon=...` and `cushion` lines; DO `[eta]` / `[cushion]` from client.
|
|
37
|
+
- Update check: `ssh ha "sudo docker exec hassio_cli ha apps info b34a1737_torrent_tv_proxy | grep version"` and `ssh ha "sudo docker ps --filter name=app_b34a1737_torrent_tv_proxy"`.
|
|
38
|
+
|
|
39
|
+
## Related
|
|
40
|
+
|
|
41
|
+
- `ha-addon/CLAUDE.md` — addon build/update detour via `hassio_cli`, cache-bust via `config.yaml` version.
|
|
42
|
+
- `docs/container-architecture.md` — what container/track classes log and where.
|
|
43
|
+
- `server/routes/api/client-logs/post.js:1` — sanitization (control chars → space, `MAX_LINES 50`, `MAX_MSG_LEN 2000`).
|
package/package.json
CHANGED
|
@@ -34,8 +34,13 @@ export async function handleApiSourceStatsGet(req, reply, { sourceRegistry, torr
|
|
|
34
34
|
torrent = await torrentPool.getTorrent(sourceRecord.sourceType, sourceRecord.source);
|
|
35
35
|
} catch (error) {
|
|
36
36
|
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
logger.warn(`stats: getTorrent failed for ${sourceKey.slice(0, 8)}: ${message} stack=${error instanceof Error ? error.stack?.split("\n")[1]?.trim() ?? "" : ""}`);
|
|
37
38
|
return reply.code(500).send({ error: `Failed to load torrent: ${message}` });
|
|
38
39
|
}
|
|
40
|
+
if (!torrent) {
|
|
41
|
+
logger.warn(`stats: torrent missing for ${sourceKey.slice(0, 8)} (getTorrent returned null/undefined)`);
|
|
42
|
+
return reply.code(500).send({ error: "Torrent instance not found" });
|
|
43
|
+
}
|
|
39
44
|
|
|
40
45
|
const fileIndexRaw = typeof req.query.fileIndex === "string" ? req.query.fileIndex : "";
|
|
41
46
|
const fileIndex = fileIndexRaw !== "" && /^\d+$/.test(fileIndexRaw) ? Number(fileIndexRaw) : null;
|
package/services/torrent-pool.js
CHANGED
|
@@ -1191,7 +1191,7 @@ export class TorrentPool {
|
|
|
1191
1191
|
`(~${(freed / (1024 * 1024)).toFixed(0)} MB)`
|
|
1192
1192
|
);
|
|
1193
1193
|
this.#cancelIdleRemoval(torrent);
|
|
1194
|
-
this.#removeTorrent(torrent);
|
|
1194
|
+
this.#removeTorrent(torrent, "disk-cap");
|
|
1195
1195
|
used -= freed;
|
|
1196
1196
|
}
|
|
1197
1197
|
}
|
|
@@ -1582,15 +1582,20 @@ export class TorrentPool {
|
|
|
1582
1582
|
return;
|
|
1583
1583
|
}
|
|
1584
1584
|
this.#cancelIdleRemoval(torrent);
|
|
1585
|
+
const name = typeof torrent.name === "string" ? torrent.name : "(unknown)";
|
|
1586
|
+
const ih = String(torrent.infoHash ?? "?").slice(0, 8);
|
|
1587
|
+
logger.info(`torrent-pool: scheduling idle removal for "${name}" [${ih}] in ${TORRENT_IDLE_TTL_MS / 1000}s`);
|
|
1585
1588
|
const timer = setTimeout(() => {
|
|
1586
1589
|
this.#idleTimers.delete(torrent);
|
|
1587
1590
|
// Re-check: a new acquire since scheduling would have cancelled this
|
|
1588
1591
|
// timer, but guard anyway against a race.
|
|
1589
1592
|
const usage = this.fileUsageByTorrent.get(torrent);
|
|
1590
1593
|
if (usage && usage.size > 0) {
|
|
1594
|
+
logger.info(`torrent-pool: idle timer fired for "${name}" [${ih}] but refcount ${usage.size} >0 — keep`);
|
|
1591
1595
|
return;
|
|
1592
1596
|
}
|
|
1593
|
-
|
|
1597
|
+
logger.warn(`torrent-pool: idle TTL fired for "${name}" [${ih}] — removing torrent (reason=idle-ttl caller=scheduleIdleRemoval)`);
|
|
1598
|
+
this.#removeTorrent(torrent, "idle-ttl");
|
|
1594
1599
|
}, TORRENT_IDLE_TTL_MS);
|
|
1595
1600
|
timer.unref?.();
|
|
1596
1601
|
this.#idleTimers.set(torrent, timer);
|
|
@@ -1615,12 +1620,20 @@ export class TorrentPool {
|
|
|
1615
1620
|
* disk while the proxy keeps running. Best-effort.
|
|
1616
1621
|
*
|
|
1617
1622
|
* @param {import("webtorrent").Torrent} torrent
|
|
1623
|
+
* @param {string} [reason="unknown"] - Why removal was requested (idle-ttl, disk-cap, evict, api).
|
|
1618
1624
|
* @returns {void}
|
|
1619
1625
|
*/
|
|
1620
|
-
#removeTorrent(torrent) {
|
|
1626
|
+
#removeTorrent(torrent, reason = "unknown") {
|
|
1621
1627
|
if (!torrent) {
|
|
1622
1628
|
return;
|
|
1623
1629
|
}
|
|
1630
|
+
const infoHash = String(torrent.infoHash ?? "?").slice(0, 8);
|
|
1631
|
+
const name = typeof torrent.name === "string" ? torrent.name : "(unknown)";
|
|
1632
|
+
const usage = this.fileUsageByTorrent.get(torrent);
|
|
1633
|
+
const refcount = usage ? usage.size : 0;
|
|
1634
|
+
const hasData = (() => { try { return torrentDownloadedBytes(torrent); } catch { return -1; } })();
|
|
1635
|
+
// Capture caller for diagnostics — not for control flow.
|
|
1636
|
+
const caller = new Error().stack?.split("\n")[2]?.trim() ?? "";
|
|
1624
1637
|
// Drop it from the source→torrent map so a later request re-adds it.
|
|
1625
1638
|
for (const [key, value] of this.torrents) {
|
|
1626
1639
|
if (value === torrent) {
|
|
@@ -1631,14 +1644,14 @@ export class TorrentPool {
|
|
|
1631
1644
|
this.fileUsageByTorrent.delete(torrent);
|
|
1632
1645
|
this.#lastAccess.delete(torrent);
|
|
1633
1646
|
this.#readPositionByTorrent.delete(torrent);
|
|
1634
|
-
|
|
1647
|
+
logger.warn(`torrent-pool: removing torrent "${name}" [${infoHash}] reason=${reason} refcount=${refcount} downloaded=${hasData}B caller=${caller}`);
|
|
1635
1648
|
try {
|
|
1636
1649
|
torrent.destroy({ destroyStore: true }, () => {
|
|
1637
|
-
logger.info(`torrent-pool: removed
|
|
1650
|
+
logger.info(`torrent-pool: removed torrent "${name}" [${infoHash}] reason=${reason} and its store`);
|
|
1638
1651
|
});
|
|
1639
1652
|
} catch (error) {
|
|
1640
1653
|
const message = error instanceof Error ? error.message : String(error);
|
|
1641
|
-
logger.warn(`torrent-pool: failed to remove
|
|
1654
|
+
logger.warn(`torrent-pool: failed to remove torrent "${name}" [${infoHash}] reason=${reason}: ${message}`);
|
|
1642
1655
|
}
|
|
1643
1656
|
}
|
|
1644
1657
|
|