Files
obsidian-vault/personal/tech/lampac-deployment.md
T

1270 lines
67 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Lampac — развёртывание и эксплуатация (Кра́кен)
created: '2026-09-22'
updated: '2026-09-22'
type: tech
namespace: personal
tags:
- lampac
- torrserver
- docker
- kraken
- streaming
- webos
related:
- '[[personal/plans/lampac-kraken]]'
- '[[personal/plans/lampac-watchlist-plugin]]'
- '[[personal/tech/multistream-single-flow-limit]]'
- '[[family/how-to/kraken-network]]'
- '[[family/how-to/tv-luna-send]]'
---
# Lampac — развёртывание и эксплуатация (Кра́кен)
> Технический справочник. Развёрнуто 2026-09-22. План и статус шагов — [[personal/plans/lampac-kraken]].
## Что это
`lampac-nextgen/lampac` (MIT, .NET 10) — серверный бэкенд для Lampa. Один контейнер, порт **9118**.
**Внутри (всё в одном образе):**
- **TorrServer** — subprocess, кэш раздач
- **JacRed** — Jackett-совместимый индексатор
- **LampaWeb** — веб-морда
- **TmdbProxy**, **DLNA**, **Sync**
🔴 **TorrServer встроенный. Отдельный контейнер не нужен.** Проверено 2026-09-22:
в логах первого старта видно `compilation TorrServer`. Внешний TS — лишняя сущность.
## Расположение на Кра́кене
```
/srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/lampac/
├── docker-compose.yml (бэкапы .bak-*, .bak2-*)
├── config/ → /lampac/config
├── cache/ → /lampac/cache (кэш TorrServer — только сюда)
├── data/ → /lampac/data (БД JacRed)
├── config-module/init.conf → /lampac/init.conf:ro (наш конфиг)
├── catalog/sites/ → /lampac/module/Catalog/sites
├── catalog/override/ → /lampac/module/Catalog/override
├── catalog/plugin.js → /lampac/module/Catalog/plugin.js:ro (ПАТЧ, см. ниже)
├── wwwroot/ → /lampac/wwwroot (JSON-каталог watchlist)
└── scripts/gen_watchlist.py (генератор .md → JSON)
```
Локальные копии рабочих артефактов (на Mac, `/Users/admin/lampac-tmp/`):
`gen_watchlist_v2.py`, `patch_plugin.py`, `plugin-patched.js`, `watchlist.yaml`,
`docker-compose.yml`, `deploy_lampac.py`.
Владелец `kraken:kraken`, права `755`.
⚠️ Всё пишущее — на data-диск (1.8T). **НЕ на SD** (`/`, 14G, 8.6G свободно).
## docker-compose.yml (актуальный, 2026-09-22)
```yaml
services:
lampac:
image: ghcr.io/lampac-nextgen/lampac:latest
container_name: lampac
platform: linux/arm64
restart: unless-stopped
ports:
- "9118:9118"
environment:
- TZ=Asia/Bishkek
- LANG=ru_RU.UTF-8
volumes:
- ./config:/lampac/config
- ./cache:/lampac/cache
- ./data:/lampac/data
# Наш конфиг: перекрывает base.conf из образа. Включает модуль Catalog.
# Без монтирования правки теряются при пересоздании контейнера.
- ./config-module/init.conf:/lampac/init.conf:ro
# Каталоги Catalog
- ./catalog/sites:/lampac/module/Catalog/sites
- ./catalog/override:/lampac/module/Catalog/override
# Патч plugin.js. Файл живёт В ОБРАЗЕ — без монтирования патч теряется
# при обновлении образа. Нужен для видимости источника в UI (см. ниже).
- ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro
# Статика: JSON-каталог watchlist (Lampac отдаёт wwwroot по HTTP сам)
- ./wwwroot:/lampac/wwwroot
```
⚠️ В контейнере рабочий каталог `/lampac`, часть файлов читается **из корня**, не из `config/`.
### Минимальный `config-module/init.conf`
Перекрывает `base.conf`, поэтому список `SkipModules` нужно **повторить целиком**, без `Catalog`:
```json
{
"BaseModule": {
"SkipModules": [
"Tracks", "Transcoding", "WebLog", "CacheMedia", "ForkPlayerXML",
"MsxNative", "Potok", "TelegramAuth", "TelegramAuthBot"
]
},
"LampaWeb": { "initPlugins": { "catalog": true } }
}
```
После любой правки — `docker compose up -d` (пересоздаст контейнер) или `docker restart lampac`.
## Модуль Catalog — внутренняя механика (разобрано по коду 2026-09-22)
Исходники внутри образа: `/lampac/module/Catalog/` —
`Controllers/{ApiController,ListController,CardController}.cs`, `ModInit.cs`, `plugin.js`, `sites/*.yaml`.
### Маршруты
| Роут | Назначение |
|---|---|
| `/catalog.js` | плагин для Lampa (клиентский JS) |
| `/catalog` | JSON: список каналов с их `menu` и `main` |
| `/catalog/list?plugin=<p>&cat=<c>&page=N` | список элементов категории |
| `/catalog/card?plugin=<p>&uri=<id>&type=movie` | карточка фильма |
WAF на префиксе `/catalog/` — **10 запросов/сек**.
### 🔴 Питфолл №1: первая категория игнорирует `format`
`ListController.cs:50` строит базовый URL **до** обработки `format`:
```csharp
string url = $"{init.host}/{(page==1 && init.list?.firstpage != null
? init.list?.firstpage : init.list?.uri)}"; // строка 50
else if (!string.IsNullOrEmpty(cat)) {
var menu = init.menu.FirstOrDefault(i => i.categories.Values.Contains(cat));
string eval = (cat != null && sort != null) ? getFormat("sort") : getFormat("-");
if (!string.IsNullOrEmpty(eval)) { url = CSharpEval(...); } // строки 78–85
if (!url.StartsWith("http")) url = $"{init.host}/{url}"; // строка 87
}
```
**Категория, стоящая ПЕРВОЙ в `menu.categories`, берёт базовый `url` из `list.firstpage`
и `format` для неё не применяется.** Симптом: первая категория отдаёт данные чужого файла
(например все 360 фильмов из общего `watchlist.json`), у элементов `img: null`,
`id` = ссылка на поиск Кинопоиска → в UI «здесь пусто».
**Правило:** `list.uri`/`firstpage` и первая категория в `menu.categories`
должны указывать на **один файл**. Переставляешь категории — правь `list.uri`.
### 🔴 Питфолл №2: `host` + `uri` склеиваются
Строка 50 склеивает `init.host` + `/` + `uri`. Значит `uri`/`firstpage` обязаны быть
**относительными** (`watchlist-films.json`), иначе двойной префикс:
`http://127.0.0.1:9118/http://127.0.0.1:9118/watchlist-films.json` → пусто.
### 🔴 Питфолл №3: `main` в YAML запрещён
`CatalogSettings` не имеет поля `main` для входного YAML → YamlDotNet давится
неизвестным полем → `ModInit.goInit()` → `null` → `ApiController.cs:70` отбрасывает сайт:
```csharp
if (init == null || !init.enable || init.menu == null || init.hide) continue;
```
**В логах — тишина.** Сайт молча исчезает из `/catalog`. Обе формы (список и словарь) ломают.
`main` Catalog **генерирует сам** из `menu` — в JSON-ответе `/catalog` присутствуют оба
ключа с одинаковым содержимым. Писать `main` в YAML не нужно и нельзя.
⚠️ `menu` **обязателен** — он регистрирует канал, а не подменяет разделы.
### 🔴 Питфолл №4: элемент без `href` выбрасывается
```csharp
if (!string.IsNullOrWhiteSpace(name) && !IsNullOrWhiteSpace(href)) // ListController.cs
```
Элемент включается **только если есть и `name`, и `href`**. Симптом: из 404 позиций
отдалось 52 — ровно те, у кого есть ссылка. **Фикс:** генератор подставляет fallback
`https://www.kinopoisk.ru/index.php?kp_query=<название>`.
### Как `href` превращается в `id` (карточка фильма)
`ListController.cs:314–322`:
```csharp
else if (href.StartsWith("http"))
href = Regex.Replace(href, "https?://[^/]+/", ""); // вырезает домен
...
id = href,
```
Поэтому **полезно отдавать TMDB-URL**: `https://www.themoviedb.org/movie/687163` →
`id = "movie/687163"` → `CardController` идёт в TMDB и отдаёт полноценную карточку
(постер, жанры, описание, рекомендации). Работает для `movie/N` и `tv/N`.
`CardController` использует **встроенный TMDB-прокси Lampac**:
`http://{localhost}:{port}/tmdb/api/3/{cat}/{id}?api_key={CoreInit.conf.cub.api_key}`
— ключ уже внутри образа, отдельный TMDB-ключ для карточек не нужен.
### 🔴 Питфолл №6: карточка без `card_parse` даёт HTTP 500
Без блока `card_parse` Catalog строит URL карточки как `$"{init.host}/{uri}"`
(`CardController.cs:33`) → `http://127.0.0.1:9118/movie/424781` — такого пути нет,
`html == null` → `BadRequest("html")` → **HTTP 500**, в UI «здесь пусто».
**Сигнатура роута (критично!):**
```csharp
[Route("catalog/card")]
public async Task<ActionResult> Index(string plugin, string uri, string type)
```
🔴 **Параметра `cat` НЕТ.** Карточка знает только `uri` и `type`. Поэтому
`initUrl: $"…/cards-{cat}.json"` даёт `cards-.json` → 404 → HTTP 500.
**Что можно и что нельзя подставлять:**
| Поле | Механика | Подстановка |
|---|---|---|
| `initUrl` | `CSharpEval.Execute<string>` с `CatalogInitUrlCard(host, args, uri, query, type)` | ✅ `uri`, `type`, `host` |
| `node` | `json.SelectToken(parse.node)` — чистый JSONPath | ❌ **статичен** |
| `name`/`image`/`description` | `ModInit.nodeValue(...)` → `attribute` | — |
**Питфолл JSONPath:** `SelectToken("movie/424781")` вернёт `null` — `/` в JSONPath это
**вложенность** (`movie` → `424781`). Симптом: **HTTP 400, тело `parse.node`**.
Слэш в ключе JSON недопустим для `SelectToken`.
**Рабочая схема (когда карточку отдаёт наш JSON, а не TMDB):**
```yaml
card_parse:
initUrl: |
return $"{host}/cards/card-{uri.Replace("/", "-")}.json";
node: card # фиксированный путь в файле
name: { attribute: name }
original_name: { attribute: original }
year: { attribute: year }
image: { attribute: poster }
description: { attribute: overview }
```
Файлы `wwwroot/cards/card-<media>-<id>.json` (по одному на фильм) с обёрткой
`{"card": {...}}`. По одному файлу — потому что `node` статичен и один общий файл
по `uri` не отфильтровать.
**Диагностика (ошибки карточки в логи Lampac НЕ пишутся!):**
```bash
curl -s -o /tmp/c.json -w "HTTP %{http_code}\n" \
"http://192.168.1.14:9118/catalog/card?plugin=watchlist&uri=movie%2F424781&type=movie"
# тела-маркеры: "html" (URL не найден) | "parse.node" (node не нашёл узел) | "json" (не JSON)
```
### 🔴 Питфолл №5: кэш Catalog
Ключ: `catalog:{plugin}:{search}:{sort}:{cat}:{page}` — **не зависит от содержимого файлов**.
`hybridCache` живёт **в памяти процесса** (не в `cache/fdb/`, не сбрасывается рестартом
контейнера — проверено). После перегенерации JSON выдача может не меняться, пока
ключ тот же. Приём `?nocache={Guid.NewGuid()}` в `format` **не помогает**.
Практический вывод: при отладке менять сам **URL/ключ** (другой `cat` или `sort`),
а не надеяться на инвалидацию кэша.
### `plugin.js` — клиентская часть
Лежит **в образе** (`/lampac/module/Catalog/plugin.js`), не на HDD. Что делает:
- `main()` — итерирует `catalog.main`, собирает секции **СВЕРХУ** родного каталога
- `menu()` — итерирует `catalog.menu` из YAML
- `full()`/`list()`/`search()` — карточка, категория, поиск
- `Lampa.Listener.follow('menu', ...)` + `e.abort()` — **перехват кликов** по родным
разделам (`movie`/`tv`/`cartoon`/`anime`/`relise`). Срабатывает **только когда
выбран источник «Мой список»**, поэтому родной CUB не трогается при других источниках
### 🔴 Питфолл №6: источник не виден в UI (`enumerable: false`)
```js
Object.defineProperty(Lampa.Api.sources, key, { get: function(){ return Api; } });
```
Свойство создаётся **не-enumerable** → `Object.keys(Lampa.Api.sources)` его не видит →
Lampa не рисует источник в панели выбора. При этом `Api.sources['Мой список']`
доступен напрямую и каталог отдаёт. Замер через DevTools ТВ
(`http://192.168.1.70:9998/json`) → `{"enumerable":false,"configurable":false}`.
**Фикс:** добавить `enumerable:true,configurable:true` в **оба** вхождения. Правка
применяется скриптом `patch_plugin.py` (не sed/grep), проверка синтаксиса —
`node --check` перед заливкой. Монтируется через compose (см. выше).
### Сканирование `sites/`
`Path.Combine(AppContext.BaseDirectory, ModInit.modpath, "sites")` — папка сканируется
на старте контейнера. Кэш-фикс: `docker restart lampac` (старый подход) **не всегда
достаточен** — см. питфолл №5.
## Управление
```bash
ssh kraken
cd /srv/dev-disk-by-uuid-6194539b-03e0-4f33-986b-4da09834e82f/lampac
docker compose up -d
docker compose logs -f
docker compose restart
docker compose down
```
Правка `docker-compose.yml` — без `sudo` (каталог `kraken:kraken`).
Создание новых каталогов в корне диска — **через `sudo`** (корень `root:root`). У `kraken` sudo без пароля.
### Развёртывание каталога watchlist (`deploy_lampac.py`)
Локальный скрипт `/Users/admin/lampac-tmp/deploy_lampac.py` — 7 шагов с бэкапами
и самопроверкой. Запуск:
```bash
python3 /Users/admin/lampac-tmp/deploy_lampac.py # полный цикл
python3 /Users/admin/lampac-tmp/deploy_lampac.py --skip-plugin # только JSON+YAML
```
Порядок: бэкап JSON в `wwwroot/.bak/` → заливка 5 JSON → `watchlist.yaml` →
патченный `plugin.js` → volume в compose (если нет) → `docker compose up -d` →
проверка (`/catalog`, 4 категории с подсчётом постеров и TMDB-ID, факт патча).
### Генератор каталога (`gen_watchlist_v2.py`)
`/Users/admin/lampac-tmp/gen_watchlist_v2.py <watchlist.md> <out_dir>`
Читает `.md`, резолвит каждый фильм: **сначала Radarr** (мгновенно, там уже `tmdbId`
и постер), **затем TMDB** по названию (2 языка × movie/tv), **фолбэк** — ссылка Кинопоиска.
Кэш `~/.lampac_tmdb_cache.json`, ключ `title|year`.
Первый прогон: **330 постеров из 403** (Radarr 7, TMDB 327, фолбэк 69).
Занимает **8–10 минут** (403 фильма × TMDB, `sleep(0.02)`).
🔴 **Запускать в background** — foreground упрётся в лимит 600 с.
Требует `TMDB_KEY` из `/Users/admin/Developer/watchlist-sync/.env`.
⚠️ TMDB-хук `api.themoviedb.org` с Кра́кена доступен (проверено: 401 без ключа = сеть есть).
## Проверка работоспособности
```bash
curl -sS --max-time 20 "http://localhost:9118/version?type=hash"
# → 46b29e1fe8fa6021cd83e75c41abffb7 (для текущего :latest)
```
По сети: `http://192.168.1.14:9118/version?type=hash` — то же значение.
## ⚠️ Питфоллы
### 1. Первый старт долгий (~4 минуты)
Контейнер компилирует и грузит ~40 native-модулей:
`compilation LampaWeb`, `DLNA`, `GStreamer`, `Sync`, `TorrServer`, ... затем `loaded module: <имя>`.
Пока идёт загрузка, HTTP **рвёт соединение**:
```
curl: (56) Recv failure: Connection reset by peer
```
🔴 **`health: healthy` не означает готовность HTTP.** Статус встаёт раньше.
Признак готовности — лог **`Configure complete`** (идёт после последнего `loaded module:`).
**Не перезапускать контейнер, не считать это поломкой.** Просто ждать.
### 2. `docker manifest inspect` с Mac отдаёт `denied`
GHCR требует авторизацию при обращении с Mac. Проверять наличие `linux/arm64` **из Кра́кена**:
```bash
ssh kraken 'docker manifest inspect ghcr.io/lampac-nextgen/lampac:latest' | jq -r '.manifests[].platform | .os + "/" + .architecture'
# linux/amd64
# linux/arm64 ← есть, сборка из исходников не нужна
# unknown/unknown
# unknown/unknown
```
### 3. Шаблон логов первого старта
```
compilation Alloha
compilation LampaWeb
...
loaded module: Vibix
loaded module: TorrServer
...
Configure complete ← HTTP начинает отвечать
```
### 4. 🔴 `sudo` через `ssh` без tty падает
```bash
ssh kraken "sudo docker exec lampac ls /lampac"
# sudo: a terminal is required to read the password;
# either use the -S option ... or configure an askpass helper
```
**Фикс: флаг `-t`.** Но ⚠️ при `-t` **нельзя передавать `a && b`** — цепочка команд
рвёт tty-сессию (`Connection to kraken closed`), и первая же команда отваливается.
**Каждую команду вызывать отдельно:**
```python
def sh(cmd):
return subprocess.run(f"ssh -t kraken {cmd}", shell=True, ...)
sh("sudo cp /tmp/x.json /srv/.../wwwroot/x.json") # ✅ одна команда
sh("sudo chown kraken:kraken /srv/.../wwwroot/x.json")
sh("sudo chmod 644 /srv/.../wwwroot/x.json")
# ❌ sh("sudo cp … && sudo chown … && sudo chmod …") — рвёт tty
```
### 5. 🔴 `docker compose up -d` не пересоздаёт контейнер при смене volumes
Изменение **только списка `volumes`** в `docker-compose.yml` не приводит к пересозданию:
`compose up -d` считает конфигурацию контейнера неизменной. Симптом: новый bind-mount
не появился, патч `plugin.js` не виден внутри.
**Проверка:**
```bash
ssh kraken "sudo docker inspect lampac --format '{{json .Mounts}}'" | jq -r '.[].Destination'
```
**Фикс:**
```bash
ssh kraken "cd /srv/dev-disk-by-uuid-6194539b-.../lampac && sudo docker compose up -d --force-recreate"
```
## Сеть
| Хост | IP |
|---|---|
| Кра́кен (eth0) | `192.168.1.14` (второй адрес `192.168.1.15`) |
| ТВ LG | `192.168.1.70` |
| Mac Alex | `192.168.1.57` |
Один сегмент `192.168.1.x` — туннели и проброс портов не нужны.
Подробнее: [[family/how-to/kraken-network]].
## Модуль Catalog — каталоги из YAML
Модуль **`Catalog`** (в образе, по умолчанию **выключен**) отдаёт свои каталоги как плагин Lampa.
Разобрано по коду и **включено в работу 2026-09-22** — см. [[personal/plans/lampac-watchlist-plugin]].
### Наш каталог «Мой список»
Отдаёт `movies-watchlist.md` как каталог в Lampa. Файлы:
| Путь на HDD (`.../lampac/`) | Роль |
|---|---|
| `config-module/init.conf` | включает Catalog (монтируется в `/lampac/init.conf:ro`) |
| `catalog/sites/watchlist.yaml` | описание каталога для Catalog |
| `catalog/override/` | переопределения (пусто) |
| `wwwroot/watchlist.json` + `watchlist-<code>.json` | JSON-данные (Lampac отдаёт по HTTP) |
| `scripts/gen_watchlist.py` | генератор: .md → JSON |
| `~/scripts/watchlist-catalog.sh` | обёртка + перезапуск (сброс кэша), cron 02:30 |
Плагин на ТВ: `http://192.168.1.14:9118/catalog.js` → источник «Мой список».
⏳ **Переделывается (2026-09-22).** Текущая реализация признана неверной: заполнен
блок `menu` (подменяет родной каталог CUB вместо добавления секций сверху), а данные
берутся парсингом `.md` вместо **Radarr API** (в `.md` нет TMDB ID и постеров).
Разбор ошибки — [[personal/plans/lampac-watchlist-plugin]], секция «🔴 АРХИТЕКТУРНАЯ ОШИБКА».
### 🔴 Рабочая форма `sites/watchlist.yaml` (проверена 2026-09-22, откат после провала)
```yaml
enable: true
jsonPath: true
displayindex: 1
displayname: Мой список
host: http://127.0.0.1:9118
menu: # ← ОБЯЗАТЕЛЕН (регистрирует канал), единственный блок категорий
- categories:
Фильмы: films
Новинки: new
Мультфильмы: cartoons
Аниме: anime
format:
"-": |
return $"http://127.0.0.1:9118/watchlist-{cat}.json";
search:
uri: watchlist.json
list:
uri: watchlist-films.json
firstpage: watchlist-films.json
total_pages: 1
content:
nodes: "$.items[*]"
name: { attribute: name }
original_name: { attribute: original }
year: { attribute: year }
href: { attribute: href }
image: { attribute: image }
args:
- { name_arg: rating, attribute: rating }
```
### 🔴 `main` в YAML ЗАПРЕЩЁН — ломает канал (опровергнуто 2026-09-22)
**Раздел ниже (гипотеза «`menu` подменяет / `main` добавляет») — НЕВЕРЕН.** Проверено
на живом сервере: попытка добавить блок `main` в YAML → **«Мой список» исчез из `/catalog`**,
`catalog/list?plugin=watchlist` отдавал пустоту. **Три раунда правок, три рестарта.**
| Гипотеза (неверна) | Факт (проверено) |
|---|---|
| `menu` подменяет родные разделы | ❌ `menu` **регистрирует канал**. `ApiController.cs:70`: `if (init == null \|\| !init.enable \|\| init.menu == null \|\| init.hide) continue;` — без `menu` сайт **отбрасывается** |
| `main` добавляет секции сверху | ❌ блока `main` **не существует** во входном YAML. YamlDotNet давится неизвестным полем → `ModInit.goInit(site)` → **null** → сайт молча пропадает |
| Заполнять нужно только `main` | ❌ `main` Catalog **генерирует САМ** из `menu`. В JSON-ответе `/catalog` есть **оба** ключа с одинаковым содержимым |
⚠️ **Обе формы `main` ломают одинаково** — и список, и словарь:
```yaml
main: # вариант 1 — список: НЕ работает
- categories:
Фильмы: films
```
```yaml
main: # вариант 2 — словарь: НЕ работает
Фильмы: http://...
```
⚠️ **Ошибка не логируется.** В логах Lampac — тишина (`docker logs lampac | grep -i yaml`
пусто). Сайт просто не появляется в `/catalog`. Диагностика только перебором YAML.
**Кто на самом деле подменяет родные разделы — `plugin.js`, а не YAML:**
```js
Lampa.Listener.follow('menu', function(e){
if (e.type=='action' && (e.action=='tv'||'cartoon'||'anime'||'movie'||'relise')) {
var catalog = Api.getCatalog();
if (!catalog) return;
...
Lampa.Router.call('category', {...});
e.abort(); // ← перехват клика по родному разделу
}
});
```
**Перехват срабатывает ТОЛЬКО когда выбран источник «Мой список».** Родной CUB/tmdb
не трогается, пока источник не переключён на наш.
### 🔴 `host` + `uri` СКЛЕИВАЮТСЯ — `uri` обязан быть относительным
`ListController.cs:53`:
```csharp
string url = $"{init.host}/{(page==1 && init.list?.firstpage != null
? init.list?.firstpage : init.list?.uri)}";
```
Полный URL в `uri`/`firstpage` даёт **двойной префикс** → пусто:
```
http://127.0.0.1:9118/http://127.0.0.1:9118/watchlist-films.json
```
Правильно: только `watchlist-films.json`, `host` приклеится сам. Так же устроены
родные YAML (`vokino.yaml` → `uri: search?name={search}`).
### ⚠️ Питфолл: явный `format` в `menu` перезагружает страницу
С `format`-блоком в `menu` Lampa **перезагружает прогрузку страницы при каждом выборе
раздела** (наблюдение Alex). Если перезагрузка не нужна — убрать `format` из `menu`,
оставить только `categories`.
### 🎯 Как добиться «CUB + мой список сверху» — варианты (решение Alex, не принято)
Родной CUB и «Мой список» — это **два источника Lampa**, переключаются в
«остальное → основной источник». Одновременно показать нельзя без вмешательства:
| Путь | Реализуемость |
|---|---|
| Блок `main` в YAML | ❌ **невозможно** — ломает парсинг |
| Оставить `menu` как есть | ⚠️ источник работает, но разделы подменяются, пока он выбран |
| Патч `plugin.js` | ⚠️ убрать `Listener.follow('menu')` → перехвата нет, но и секции сверху **не появятся** |
| Написать свой плагин | ✅ единственный путь для «сверху», объём работы |
### ⚠️ Баг Lampac: источник не виден в списке выбора
`plugin.js`, строка 59 (`loadCatalogs()`), **два вхождения**:
```js
Object.defineProperty(Lampa.Api.sources, key, { get: function(){ return Api; } });
```
`defineProperty` по умолчанию `enumerable: false` → `Object.keys(Lampa.Api.sources)`
не перечисляет источник → Lampa не рисует его в панели выбора. При этом
`Api.sources['Мой список']` работает напрямую и каталог отдаёт.
**Фикс:** `{ get: ..., enumerable: true, configurable: true }`.
`plugin.js` живёт **в образе**, не на HDD (`docker inspect` — монтируются только
`catalog/sites` и `catalog/override`). Правка через `docker exec` теряется при
пересоздании. Постоянный путь — volume:
```yaml
volumes:
- ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro
```
Проверка синтаксиса ПЕРЕД заливкой: `node --check /tmp/catalog-plugin.js`.
Статус: **не применено**, ждёт апрува Alex.
### Radarr как источник данных (постеры, TMDB ID) — ✅ ПРОВЕРЕНО 2026-09-22
Фильмы уже резолвлены в Radarr — там есть TMDB ID и постеры. Это **правильный
источник** для каталога; `.md` их не содержит.
```bash
# API-ключ Radarr
grep -oE "<ApiKey>[^<]+</ApiKey>" \
/srv/dev-disk-by-uuid-6194539b-.../docker/radarr/config/config.xml
# → cbcb8ec320104359920c0f611d503c7b
# ⚠️ -L обязателен, иначе 307 и пустое тело
curl -sL "http://localhost:7878/api/v3/movie?apikey=$KEY" | jq length # → 50
# Sonarr, порт 8989, ключ 15fbec32f1cb4b05b1c2b229ce27359b
curl -sL "http://localhost:8989/api/v3/series?apikey=$SKEY" | jq length # → 13
```
**Поля ответа Radarr (для каталога нужны все):**
`tmdbId`, `imdbId`, `title`, `originalTitle`, `year`, `titleSlug`, `hasFile`,
`genres`, `ratings.tmdb.value`, `images[] | select(.coverType=="poster") | .remoteUrl`.
Кэш постеров на диске: Radarr `/config/MediaCover/` (46 каталогов).
```json
{
"tmdbId": 1292695, "imdbId": "tt31728330",
"title": "They Will Kill You", "year": 2026,
"hasFile": true, "genres": ["Horror"],
"ratings": {"tmdb": {"value": 6.945}},
"images": [{"coverType": "poster",
"remoteUrl": "https://image.tmdb.org/t/p/original/6oI4oQKTWMVUlr8Ivqydp28Ruu6.jpg"}]
}
```
⚠️ **Названия в Radarr латиницей** (`They Will Kill You`), в `.md` — по-русски.
Перевод делает **TMDB Proxy** (`proxy_tmdb = true` в `lampainit.js`), уже включён.
⚠️ **Связки kp ↔ tmdb в данных нет.** В `.md` — 68 ссылок `kinopoisk.ru/film/<id>/`,
в Radarr — `tmdbId`. Прямого соответствия нет ни в одном файле. **Мост — модуль
`watchlist_sync/resolver.py`** (см. ниже).
### `watchlist_sync/resolver.py` — резолвер постеров (уже написан!)
Путь: `/srv/dev-disk-by-uuid-49e8f586-3839-4c5d-a1e1-58bfc3579ade/watchlist-sync/watchlist_sync/`
Уже решает задачу постеров/ID для каталога:
- поиск в **TMDB** (`api.themoviedb.org/3`, языки `ru-RU` + `en-US`)
- поиск в **Кинопоиске** (`api.poiskkino.dev/v1.4`)
- возвращает `Candidate`: `tmdb_id`, `kp_id`, `title`, `year`, `score`, `rating`, `genres`
- `is_horror()` — ужасы по TMDB `genre_id=27` (нужно для сортировки 🧟‍♀️)
- кэш `.resolve_cache.json`; различает `ok` / `ambiguous` (score < 0.75) / `not_found`
### 🔴 `watchlist_sync/arr.py` — ИСТОЧНИК АВТОСКАЧКИ (в коде!)
```python
# radarr_add():
"monitored": True,
"addOptions": {"searchForMovie": True}, # ← качает СРАЗУ после добавления
# sonarr_add():
"monitored": True,
"addOptions": {"searchForMissingEpisodes": True}, # ← то же для сериалов
```
⚠️ **Даже если снять `monitored` вручную, следующий запуск добавления из списка
включит автоскачку снова** — флаг зашит в код. Фикс: `searchForMovie` → `False`,
`searchForMissingEpisodes` → `False`. **Статус: не применён, на апруве.**
### Как выключить автоскачку на HDD — ✅ ВЫПОЛНЕНО 2026-09-22
| Сервис | Очередь | `monitored` | `rssSyncInterval` |
|---|---|---|---|
| **Radarr** | 6 → **0** | 45 → **0** | 30 → **0** |
| **Sonarr** | 19 → **0** | 13 → **0** | 15 → **0** |
Метаданные **целы**: 50 фильмов, 46 с постерами, 50 с `tmdbId` и рейтингом,
24 файла на диске **не тронуты**. Бэкапы — метка `20260922-123404`.
**Проверка пассивности:** Transmission за 45 с — NET `290GB/26.7GB`, BLOCK `214GB/393kB`
не сдвинулись. Логи Radarr (`search|download|grab`) за 2 мин — пусто.
**Правильные эндпоинты (проверено):**
```bash
# отмена загрузки — файлы на диске НЕ трогаются
DELETE /api/v3/queue/<id>?removeFromClient=true&blocklist=false
# снятие monitored — КЛЮЧ ПОЛЯ РАЗНЫЙ!
PUT /api/v3/movie/editor {"movieIds": [...], "monitored": false} # Radarr
PUT /api/v3/series/editor {"seriesIds": [...], "monitored": false} # Sonarr, НЕ "ids"
# RSS off — отправлять ВЕСЬ объект конфига с id, иначе молча вернёт старое
PUT /api/v3/config/indexer {"rssSyncInterval": 0, "id": 1, ...остальное}
```
**Откат:**
```bash
curl -sL -X PUT ".../api/v3/movie/editor" -d '{"movieIds":[...],"monitored":true}'
curl -sL -X PUT ".../api/v3/series/editor" -d '{"seriesIds":[...],"monitored":true}'
curl -sL -X PUT ".../api/v3/config/indexer" -d '{"rssSyncInterval":30,"id":1,...}'
cp radarr.db.bak-20260922-123404 radarr.db && docker restart radarr
```
### 🔴 Питфолл: `urllib` не идёт по 307, `curl -sL` идёт
Симптом: **все** DELETE/PUT из Python-скриптов вернули
`HTTP Error 307: Temporary Redirect`, **ничего не изменилось**.
Штатный `urllib` на 307/308 превращает DELETE в GET и теряет тело.
**Фикс — свой redirect-хендлер:**
```python
class RedirectKeepMethod(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
if code in (307, 308):
new = urllib.request.Request(newurl, data=req.data,
method=req.get_method())
for k, v in req.headers.items():
new.add_header(k, v)
return new
return super().redirect_request(req, fp, code, msg, headers, newurl)
_OPENER = urllib.request.build_opener(RedirectKeepMethod)
# далее _OPENER.open(req) вместо urllib.request.urlopen(req)
```
Прочие грабли сессии:
- **Sonarr группирует эпизоды сезона под одним `id`** — после удаления первой записи
остальные дают `404`, это **не сбой**, они ушли с родительской.
- **Кириллица внутри inline-`jq` в `ssh '...'`** ломает команду
(`unexpected INVALID_CHARACTER`). Ключи объекта — латиницей: `total`, `monitored`, `queue`.
## TorrServer внутри Lampac — на HDD НЕ пишет (проверено 2026-09-22)
| Параметр | Значение | Смысл |
|---|---|---|
| `UseDisk` | **`false`** | 🔴 **на диск не пишет вообще** |
| `CacheSize` | 100663296 (96 МБ) | кэш в RAM |
| `TorrentsSavePath` | `""` | пути сохранения нет |
| `TorrentDisconnectTimeout` | 180 | раздача живёт 3 мин после остановки |
```bash
# конфиг TorrServer
docker exec lampac cat /lampac/data/ts/settings.json | jq '.BitTorr | {UseDisk, CacheSize, TorrentsSavePath}'
# жив ли
curl -s http://192.168.1.14:9118/ts/echo # → MatriX.135
curl -s -X POST http://192.168.1.14:9118/ts/torrents \
-H "Content-Type: application/json" -d '{"action":"list"}' # → []
```
## 🔴 Поиск торрентов: отдельного Jackett НЕТ, его роль играет Lampac
`lampainit.js` прописывает:
```js
Lampa.Storage.set('parser_torrent_type', 'jackett');
Lampa.Storage.set('jackett_url', '192.168.1.14:9118'); // ← сам Lampac!
Lampa.Storage.set('jackett_key', '1');
```
Контейнера **Jackett в системе нет**. Lampac отдаёт Jackett-совместимый API сам:
```bash
# ⚠️ на КОРНЕ, БЕЗ префикса /jackett (с ним → 404)
curl "http://192.168.1.14:9118/api/v2.0/indexers/all/results?apikey=1&Query=matrix"
# → {"Results":[{"Tracker":"rutor","Title":"...","MagnetUri":"magnet:?xt=...","Seeders":0}]}
```
✅ Проверено: вернул реальные результаты с магнит-ссылками.
**Отдельный Jackett ставить НЕ нужно** — так задумано производителем.
## Занятые порты на Кра́кене (2026-09-22)
`7878` Radarr · `8096` Jellyfin · `8191` FlareSolverr · `8989` Sonarr · `9000` Portainer ·
`9091` Transmission · `9696` Prowlarr · `51413` Transmission (UDP) · **`9118` Lampac**
## Отладка UI Lampa на ТВ (webOS)
Правки по SSH не сделать, но **состояние читается** через Chrome DevTools Protocol:
```bash
# webOS отдаёт DevTools на порту 9998 (WebAppMgr --remote-debugging-port=9998)
curl -s http://192.168.1.70:9998/json | jq -r '.[] | "\(.type): \(.url)"'
# → file:///.../com.lampac.tv/index.html?card=...&source=cub
```
Дальше `websocket` + `Runtime.evaluate` читает живые переменные:
| Выражение | Что даёт |
|---|---|
| `Lampa.Storage.field('source')` | выбранный источник |
| `Object.keys(Lampa.Api.sources).join('\|')` | перечисляемые источники |
| `Object.getOwnPropertyDescriptor(Lampa.Api.sources,'Мой список')` | `enumerable`/`configurable` |
| `Lampa.Api.sources['Мой список'].getCatalog()` | ключи каталога |
Скрипты-пробы: `/tmp/lampa_probe.py`, `/tmp/lampa_probe2.py`, `/tmp/lampa_probe3.py`.
⚠️ `tcpdump` на Кра́кене **нет**. `luna://com.webos.service.capture/takeScreenShot`
на этом ТВ **не существует**. `luna-send` пишет в tty → нужен `script -q -c`.
Подробности: [[family/how-to/tv-lampac-plugin]].
### Общее про модуль
| Что | Как |
|---|---|
| Данные | `sites/<site>.yaml` — `host`, `menu`, `search`, `list`, `content` |
| Переопределения | `override/<site>.yaml`, `override/_.yaml` |
| Роуты | `/catalog.js`, `/catalog`, `/catalog/list`, `/catalog/card` |
| WAF | `^/catalog/` → 10 req/sec |
| Примеры | 10 файлов: `vokino`, `rezka`, `kinopub`, `aniliberty`, `eneyida`, `filmix`, `filmixapp`, `getstv`, `kinobase`, `kinoukr` |
**Обязателен HTTP:** `if (!url.StartsWith("http")) url = $"{init.host}/{url}"` — локальные файлы не читаются.
**Два режима:** `jsonPath: true` → `JToken.Parse` (JSON), иначе HtmlAgilityPack (HTML).
🔴 **Питфолл: элемент с пустым `href` ВЫБРАСЫВАЕТСЯ** —
`if (!IsNullOrWhiteSpace(name) && !IsNullOrWhiteSpace(href))` (ListController.cs).
Нужен непустой `href` у каждого элемента.
🔴 **Питфолл: `{cat}` НЕ подставляется в `nodes`** — JSONPath статичен.
Разные категории = разные JSON-файлы.
🔴 **Питфолл: кэш без версии данных** — ключ `catalog:{plugin}:{search}:{sort}:{cat}:{page}`.
После правки данных нужен `docker restart lampac`.
**Выключен в двух местах** `base.conf`/`current.conf`:
```jsonc
"BaseModule": { "SkipModules": [ "Catalog", ... ] }
"LampaWeb": { "initPlugins": { "catalog": false } }
```
Синапс модулей (всего 33): `AdminPanel, Adult, Catalog, Community, DLNA, DatabaseEditor, ExternalBind,
ForkPlayerXML, GStreamer, JacRed, Kit, LampaWeb, LampacApk, LogUserRequest-Lite, MsxNative, Music,
NextHUB, Online, OnlineAnime, OnlineENG, OnlineGEO, OnlinePaid, OnlineRUS, OnlineUKR, PidTor, Potok,
Proxy, SISI, Sync, Telemetry, Tracks, Transcoding, WebLog`.
Полный пример YAML (`vokino.yaml`): `docker exec lampac cat /lampac/module/Catalog/sites/vokino.yaml`.
Выбор порта — `docker compose config` + `sudo ss -tlnp` перед деплоем.
## Интеграция с Lampa (ТВ) — ✅ СДЕЛАНО 2026-09-22
- Плагин: `http://192.168.1.14:9118/online.js`
- TorrServer в Lampa: Настройки → TorrServer → «Использовать основную ссылку» → `http://192.168.1.14:9118`
- Буфер плеера: 50–100 МБ
- `/lg.ipk` отдаёт сам Lampac (проверено: HTTP **200**)
### Установка клиента на LG webOS — по SSH, без флешки
🔴 **`Media Station X` и `.ipk` с флешки — НЕ нужны.** ТВ имеет SSH-доступ (root), установка делается с Mac.
**ТВ:** `root@192.168.1.70`, ключ `~/.ssh/konka_webos`, webOS **5.4.2** (ядро 4.4.84, image `starfish-dvb-secured`, build 2602, `webos_build_datetime 20230405092501`).
Порты открыты: `22` (SSH), `3000`/`3001` (Luna). Из кастомного уже стоит: `org.webosbrew.hbchannel` 0.7.2, `org.webosbrew.inputhook`, `me.wouterdek.magic4pc`.
**Порядок установки:**
```bash
# 1. Скачать .ipk от Lampac прямо на ТВ
ssh root@192.168.1.70 'curl -sS --max-time 60 -o /tmp/lampa.ipk "http://192.168.1.14:9118/lg.ipk"'
# → 6 896 874 байт, формат ar (!<arch>), НЕ tar/gzip
# 2. Установить (обязательна обёртка script — см. питфолл ниже)
ssh root@192.168.1.70 'rm -f /tmp/inst.txt; script -q -c \
"luna-send -n 1 -f luna://com.webos.appInstallService/dev/install \
{\"id\":\"org.lampa.tv\",\"ipkUrl\":\"/tmp/lampa.ipk\",\"subscribe\":true}" \
/tmp/inst.txt >/dev/null 2>&1; cat /tmp/inst.txt'
# → {"subscribed":true,"returnValue":true}
# 3. Проверить, что установилось
ssh root@192.168.1.70 'ls /media/developer/apps/usr/palm/applications/'
# → com.lampac.tv ← имя приложения Lampa, НЕ org.lampa.tv
# 4. Запустить
ssh root@192.168.1.70 'rm -f /tmp/l.txt; script -q -c \
"luna-send -n 1 -f luna://com.webos.applicationManager/launch {\"id\":\"com.lampac.tv\"}" /tmp/l.txt'
# 5. Убедиться, что открылось
ssh root@192.168.1.70 'rm -f /tmp/f.txt; script -q -c \
"luna-send -n 1 -f luna://com.webos.applicationManager/getForegroundAppInfo {}" /tmp/f.txt; grep appId /tmp/f.txt'
# → "appId": "com.lampac.tv"
```
**Фактический ID приложения — `com.lampac.tv`** (vendor Lampac, version 1.0.0, type web).
⚠️ В `luna-send ... dev/install` поле `"id"` — **произвольная метка**; реальный appId берётся из `appinfo.json` внутри ipk. Ошибиться здесь не страшно, но искать приложение надо по фактическому ID.
### 🔴 Питфолл: `luna-send` по SSH молчит
**Симптом:** любой вызов `luna-send` через `ssh root@TV "luna-send ..."` печатает **хелп-экран**, а не результат. С `-t` (PTY) — тоже. С `-a '{"json"}'` — тоже.
**Причина:** `luna-send` детектирует TTY и отказывается писать в pipe.
**Решение:** обернуть в `script(1)`, который аллоцирует псевдо-TTY, и прочитать временный файл:
```bash
ssh root@TV 'script -q -c "luna-send -n 1 -f luna://service/method {json}" /tmp/o.txt; cat /tmp/o.txt'
```
Готовый враппер: `~/Automation/tv-luna-send.sh` (`TV_HOST` переопределяет таргет).
Доки: [[family/how-to/tv-luna-send]], [[family/how-to/htpc-webos-luna]].
⚠️ **Установка идёт через `com.webos.appInstallService/dev/install`**, а НЕ через
`org.webosbrew.hbchannel.service/install` (последний в образе есть, но `luna-send` его не дёрнул чисто).
⚠️ `luna-send -i` (subscribe, бесконечный) через враппер **не работает** — нужен polling.
### Первый просмотр — проверено 2026-09-22
Фильм запущен, **не дёргается**. Alex: «Вроде стабильно, не делается».
| Факт | Значение |
|---|---|
| Соединения ТВ → Lampac | 3 × ESTABLISHED `192.168.1.70 → 192.168.1.14:9118` |
| Нагрузка контейнера | `lampac` CPU 57%, NET 168 МБ ↓ / 132 МБ ↑ |
| Битрейт на eth0 | **~3–4 Мбит/с** |
⚠️ **Почему всего 3–4 Мбит, а не 15:** выбран источник **«Смотреть онлайн (lampac 1.18…)»** — встроенный
плагин `online.js`, HLS-поток. Lampac отдаёт **ссылку**, ТВ тянет видео напрямую из CDN (в `/proc/net/tcp`
ТВ видны внешние адреса на `:443`/`:8443`). Тяжёлый remux с торрента не участвует — поэтому и не дёргается.
🔴 **Следствие:** этот просмотр **не проверяет**, держит ли сеть remux 60–80 Мбит.
Тест функциональности, не полосы. Полосу проверять на торрент-источнике.
**Как убрать выбор источника:** Lampa → Настройки → Плагины → выключить `online.js`. Останутся только торренты.
## 🔴 Выбор источника каталога в Lampa (проверено 2026-09-22)
**В Lampa НЕТ пункта «Настройки → Источник».** Я его выдумал — Alex дважды указал на ошибку.
Не повторять.
Источник (Rezka / Kinobase / «Мой список» / …) переключается **из меню каталогов на главной**:
1. Открыть Lampa на главной
2. Встать на любой ряд каталога → нажать **влево** на пульте
3. Слева выедет **панель со списком источников**
4. Выбрать нужный → OK; каталог перестроится, в заголовке появится имя источника
в верхнем регистре («Фильмы — МОЙ СПИСОК»)
**Плагин `catalog.js` не добавляет пункт меню — он подменяет существующие.**
Механика: при старте приложения `Lampa.Utils.account(api_host + '/catalog', …)` получает список
каталогов, регистрирует каждый как `Lampa.Params.values.source[key]` и подменяет
`Lampa.Api.sources[key]`. Затем хук `Lampa.Listener.follow('menu', …)` перехватывает клики по
`movie`/`tv`/`cartoon`/`anime`/`relise` и подставляет URL из `catalogs[key].menu`.
⚠️ **Следствие — каталоги регистрируются ТОЛЬКО при старте приложения.**
Добавил плагин и не перезапустил Lampa → источника не будет, `/catalog` не запрашивается,
соединений с `:9118` ноль. Нужен **полный перезапуск** (не сворачивание).
### ⚠️ Клиент на ТВ — это не «отдельная Lampa»
`com.lampac.tv` (webOS) грузит UI **с сервера**: `loader.js` читает `lampac_url` из localStorage
(дефолт `http://192.168.1.14:9118`) и подключает `app.min.js` + `lampainit.js`.
Значит логика каталога на ТВ = **серверная**, и править её надо на Lampac, а не на ТВ.
`/lampainit.js` содержит `var plugins_add = [...]` со строкой
`{"url":".../catalog.js","name":"Альтернативные источники каталога","status":1}` —
это **серверный список плагинов, которые Lampac сам подсовывает** в UI.
Каталог watchlist работает через **модуль `Catalog`**, не через эту строку.
Не искать причину поломки в `plugins_add`.
### 🔴 БАГ Lampac: источник не виден в панели (`enumerable: false`)
**Найдено 2026-09-22** диагностикой через Chrome DevTools ТВ. Симптом Alex:
«Ни на главной ни в Фильмах нет! Там только Сейчас смотрят, рекомендуем итд».
**Факты с живого ТВ:**
| Замер | Значение |
|---|---|
| `Lampa.Storage.field('source')` | `'Мой список'` — источник **уже выбран** |
| `Object.keys(Lampa.Api.sources)` | `'tmdb|cub'` — ❌ источника нет в перечислении |
| `Lampa.Api.sources['Мой список'].getCatalog()` | `search, movie, cartoons, anime, menu, main` — ✅ работает |
| `Object.getOwnPropertyDescriptor(Lampa.Api.sources,'Мой список')` | `{"enumerable":false,"configurable":false}` ← корень |
**Корень — в `/lampac/module/Catalog/plugin.js`** (строка 59, `loadCatalogs()`, **два вхождения**):
```js
// БЫЛО (баг)
Object.defineProperty(Lampa.Api.sources, key, { get: function(){ return Api; } });
// НАДО
Object.defineProperty(Lampa.Api.sources, key,
{ get: function(){ return Api; }, enumerable: true, configurable: true });
```
Не-enumerable свойство не попадает в `Object.keys()` → Lampa не рисует источник в панели
выбора. **Баг Lampac, не нашей настройки.**
🔴 **`plugin.js` живёт в ОБРАЗЕ, не на HDD.** `docker inspect` показывает монтирования
только на `catalog/sites` и `catalog/override`. Правка через `docker exec` **исчезнет
при обновлении образа** — нужен bind-mount:
```yaml
# docker-compose.yml
- ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro
```
```bash
ssh kraken 'docker exec lampac cat /lampac/module/Catalog/plugin.js' > /tmp/catalog-plugin.js
cp /tmp/catalog-plugin.js /tmp/catalog-plugin.js.bak-$(date +%Y%m%d-%H%M%S)
# правка двух вхождений defineProperty (только через patch, не sed)
node --check /tmp/catalog-plugin.js # проверить синтаксис ДО заливки
scp /tmp/catalog-plugin.js kraken:/srv/.../lampac/catalog/plugin.js
ssh kraken 'cd /srv/.../lampac && docker compose up -d'
```
**Статус: фикс НЕ применён**, ждёт апрува Alex (2026-09-22).
Полный разбор — [[personal/plans/lampac-watchlist-plugin]], раздел «ГЛАВНЫЙ ПИТФОЛЛ».
### 🛠 Диагностика ТВ через Chrome DevTools — порт 9998
**Главный инструмент для отладки UI Lampa.** webOS отдаёт Chrome DevTools Protocol:
у процессов `WebAppMgr` есть `--remote-debugging-port=9998`. Даёт **чтение состояния
живой Lampa** — ни логи, ни tcpdump этого не дают.
```bash
# цель отладки + URL приложения
curl -s --max-time 8 "http://192.168.1.70:9998/json" | /usr/bin/jq -r '.[] | "\(.type): \(.url)"'
# → page: file:///media/developer/apps/usr/palm/applications/com.lampac.tv/index.html?...
```
Чтение переменных — `websocket-client` + `Runtime.evaluate`:
```python
pages = json.load(urllib.request.urlopen("http://192.168.1.70:9998/json", timeout=8))
target = next(p for p in pages if "com.lampac.tv" in p.get("url",""))
ws = websocket.create_connection(target["webSocketDebuggerUrl"], timeout=10)
# Runtime.evaluate: Lampa.Storage.field('source')
# Object.keys(Lampa.Api.sources)
# Object.getOwnPropertyDescriptor(Lampa.Api.sources,'Мой список')
```
Готовые скрипты: `/tmp/lampa_probe.py` (состояние источника),
`/tmp/lampa_probe3.py` (асинхронный вызов `menu()` каталога).
**Ловушки:**
⚠️ **Замер соединений — на ТВ, не на сервере.** `sudo ss -tn | grep :9118` на Кра́кене
сразу после старта Lampa даёт **0** (ложный вывод «не ходит»). Через ~30 с:
`ssh root@192.168.1.70 'netstat -tn | grep 9118'` → **1 ESTABLISHED + 6 TIME_WAIT**.
⚠️ **`launch` через luna требует экранированного JSON в двойных кавычках**, иначе
`errorCode: 2, invalid parameters`. `getForegroundAppInfo {}` работает без кавычек.
⚠️ **`tcpdump` на Кра́кене нет** — не ставили, DevTools дал больше.
⚠️ **`luna://com.webos.service.capture/takeScreenShot` не существует** на этом ТВ
(`Unknown method`) — скриншот так не снять.
### Диагностика «каталога нет в UI»
| Проверка | Команда | Норма |
|---|---|---|
| Плагин отдаётся | `curl -sI http://192.168.1.14:9118/catalog.js` | 200, ~32 КБ |
| Каталоги зарегистрированы | `curl -s http://192.168.1.14:9118/catalog \| jq 'keys'` | 7 ключей, «Мой список» есть |
| Данные отдаются | `curl -s "…/catalog/list?plugin=watchlist&cat=films" \| jq .total_pages` | 1 |
| ТВ достаёт сервер | `ssh root@192.168.1.70 'curl -s -o /dev/null -w "%{http_code}" http://192.168.1.14:9118/catalog'` | 200 |
| ТВ реально ходит | `ssh root@192.168.1.70 'netstat -tn \| grep 9118'` | ESTABLISHED <br>ноль = Lampa не стартовала |
| Источник в списке | DevTools: `Object.keys(Lampa.Api.sources)` | **все** каталоги, вкл. «Мой список» |
Порядок разбора: сначала сеть с ТВ → потом `netstat` на ТВ → потом `/catalog` на сервере →
потом DevTools (состояние JS). **Если источник есть в `Api.sources` напрямую, но нет в
`Object.keys()` — это баг `enumerable` в `plugin.js`, а не проблема настроек.**
⚠️ `docker logs lampac` **HTTP-запросы не пишет** — по логам факт обращения ТВ не проверить.
## Внутреннее устройство Lampac (разведка 2026-09-22)
**33 модуля** в образе (`/lampac/module/`):
`AdminPanel`, `Adult`, **`Catalog`**, `Community`, **`DLNA`**, `DatabaseEditor`, `ExternalBind`,
`ForkPlayerXML`, `GStreamer`, `JacRed`, `Kit`, `LampaWeb`, `LampacApk`, `LogUserRequest-Lite`,
`MsxNative`, `Music`, `NextHUB`, **`Online`**, `OnlineAnime`, `OnlineENG`, `OnlineGEO`, `OnlinePaid`,
`OnlineRUS`, `OnlineUKR`, `PidTor`, `Potok`, `Proxy`, `SISI`, `Sync`, `Telemetry` (+ остальные).
**Конфиг — `current.conf`** внутри образа (`/lampac/current.conf`, 157 КБ, генерируется из `base.conf` +
`init.conf`). Рабочий каталог `/lampac`, файлы читаются **из корня**, не из `config/`.
Выключение модуля — двумя механизмами:
1. **`SkipModules`** в `init.conf` / `current.conf`
2. **`manifest.json`** в каталоге модуля (`"enable": true|false`)
⚠️ **`init.conf` НЕ в образе по умолчанию** — его читают из `/lampac/init.conf`, файл перекрывает
`base.conf`. Правка через `docker exec` **теряется при пересоздании контейнера**, поэтому
смонтирован с HDD (`./config-module/init.conf:/lampac/init.conf:ro`). См. раздел docker-compose выше.
⚠️ `current.conf` в образе (157 КБ) — **сгенерированный** результат (`guid`, `listen`, полный конфиг).
Это **не** входной файл, править его бессмысленно.
### Полезные роуты (проверены curl'ом)
| Роут | Код | Что |
|---|---|---|
| `/` | 200 | LampaWeb (UI, `<base href="/lampa-main/">`) |
| `/online.js` | 200 | плагин VOD — 70+ онлайн-источников |
| `/lg.ipk` | 200 | установщик клиента для LG (6.9 МБ) |
| `/sisi` | 200 | JSON-список платформ 18+ |
| `/dlna` | 200 | DLNA-модуль |
| `/ts/echo` | 200 | TorrServer (отдаёт версию, напр. `MatriX.135`) |
| `/catalog.js` | **200** | плагин каталогов (включён 2026-09-22) — см. раздел Catalog |
| `/catalog` | **200** | JSON каналов каталогов |
| `/watchlist.json`, `/watchlist-<code>.json` | **200** | наш JSON-каталог watchlist |
| `/cub`, `/api`, `/lampac.js` | 404/500 | — |
⚠️ `wwwroot` отдаётся статикой: **любой** файл, положенный туда, доступен по HTTP
от корня (`http://<host>:9118/<файл>`). Проверено записью пробного файла.
## Radarr/Sonarr как источник метаданных для каталога (2026-09-22)
**Зачем:** каталог watchlist нуждается в **TMDB ID и постерах** для элементов.
Список `movies-watchlist.md` — просто текст, там этого нет. Но фильмы добавлялись
в **Radarr**, который уже держит метаданные (тянет из TMDB при добавлении).
### Что отдаёт Radarr API
```bash
KEY=$(ssh kraken 'grep -oE "<ApiKey>[^<]+</ApiKey>" \
/srv/...6194539b.../docker/radarr/config/config.xml' | sed 's/<[^>]*>//g')
curl -sL "http://localhost:7878/api/v3/movie?apikey=$KEY" | jq .
```
```json
{
"tmdbId": 1292695,
"title": "They Will Kill You",
"year": 2026,
"hasFile": true,
"monitored": true,
"images": [{"coverType": "poster",
"remoteUrl": "https://image.tmdb.org/t/p/original/6oI4oQKTWMVUlr8Ivqydp28R"}]
}
```
Маппинг в формат каталога: `tools.tmdbId` → `id`/`href`, `images[].remoteUrl` → `img`,
`title`+`year` → `title`, `hasFile` → признак «уже скачан».
### 🔴 Питфолл: `curl -sL`, без `-L` Radarr отдаёт 307
```bash
curl -s "http://localhost:7878/api/v3/movie?apikey=$KEY" | jq length # ❌ 0 байт
curl -sL "http://localhost:7878/api/v3/movie?apikey=$KEY" | jq length # ✅ 50
```
### Окружение arr-стека на Кра́кене
| Сервис | Порт | Конфиг на HDD |
|---|---|---|
| radarr | 7878 | `/srv/...6194539b.../docker/radarr/config/` |
| sonarr | 8989 | аналогично |
| prowlarr | 9696 | аналогично |
| transmission | — | отдельный контейнер |
Постеры складываются в `/config/MediaCover/` (46 каталогов на 2026-09-22) — можно
раздавать их статикой через `wwwroot`, не дёргая CDN TMDB.
⚠️ `sqlite3` в контейнерах arr **нет** — БД (`radarr.db`) читать либо через `strings`,
либо вытащить файл на хост (там `/usr/bin/sqlite3` есть).
### Управление автоскачкой (два рычага, оба нужны)
| Рычаг | Команда | Эффект |
|---|---|---|
| `monitored` | `PUT /api/v3/movie/editor` | фильм не ищется/не качается, метаданные остаются |
| `rssSyncInterval` | `PUT /api/v3/config/indexer` | `0` = Radarr не ходит в RSS (сейчас 30 мин) |
⚠️ В Radarr **нет** состояния «источник, но не грузить» — это ровно `monitored: false`.
### Контур воспроизведения — не путать (✅ проверено 2026-09-22)
| Слой | Что делает | Пишет на HDD |
|---|---|---|
| Radarr + Sonarr + Transmission | автоскачка релизов в библиотеку | **да** |
| Lampac **TorrServer** (встроенный) | стриминг из UI Lampa по требованию | ❌ **нет — доказано** |
| Catalog «Мой список» | показ секций с постерами | ❌ ничего не качает |
Просмотр через Lampa **не пополняет библиотеку**: TorrServer отдаёт поток и выбрасывает.
`transmission` к Lampa отношения не имеет.
**Доказательство, что TorrServer не пишет на диск** — `/lampac/data/ts/settings.json`:
```bash
ssh kraken 'docker exec lampac cat /lampac/data/ts/settings.json' | jq .BitTorr
```
```json
{ "CacheSize": 100663296, // 96 МБ — RAM-кэш
"UseDisk": false, // 🔴 НА ДИСК НЕ ПИШЕТ
"TorrentsSavePath": "", // путь сохранения пуст
"TorrentDisconnectTimeout": 180, // раздача живёт 3 мин после остановки
"PreloadCache": 0, "ReaderReadAHead": 83 }
```
`UseDisk: false` — дефолт Lampac, настраивать не надо. Кэш Lampac целиком:
`/lampac/cache/` = 31 МБ (SQLite, Playwright, gstreamer-registry — **не фильмы**),
`/lampac/data/` = 44 МБ. Список торрентов пуст:
`curl -s -X POST .../ts/torrents -d '{"action":"list"}'` → `[]`.
Живость TorrServer: `curl -s "http://192.168.1.14:9118/ts/echo"` → `MatriX.135`.
### 🔴 Поиск торрентов — Jackett внутри Lampac (отдельный НЕ нужен)
**Ловушка:** в `lampainit.js` прописано `parser_torrent_type = 'jackett'`,
но **контейнера Jackett нет** в `docker ps`. Это не поломка — Lampac **сам отдаёт
Jackett-совместимый API**.
```bash
curl -s "http://192.168.1.14:9118/lampainit.js" | grep -oE "Lampa.Storage.set\('[^']+', ?'[^']*'\)"
# jackett_url = '192.168.1.14:9118' ← сам Lampac, не сторонний сервис
# jackett_key = '1'
# parser_torrent_type= 'jackett'
# parser_use = 'true'
# source = 'cub'
# torrents_use = true (lampa_settings)
```
⚠️ **API живёт на КОРНЕ, без префикса `/jackett`:**
```bash
curl -s "http://192.168.1.14:9118/api/v2.0/indexers/all/results?apikey=1&Query=matrix"
# ✅ 200 — реальные результаты: Tracker:rutor, MagnetUri, Size, Seeders, ffprobe
curl -s "http://192.168.1.14:9118/jackett/api/v2.0/indexers/all/results" # ❌ 404
```
**Вывод:** ничего поднимать не надо, поиск торрентов работает штатно.
### ⚠️ Sonarr — второй источник автоскачки (не только Radarr)
Alex упомянул его не зря. Замер 2026-09-22:
```bash
SKEY=$(ssh kraken 'grep -oE "<ApiKey>[^<]+</ApiKey>" \
/srv/...6194539b.../docker/sonarr/config/config.xml' | sed 's/<[^>]*>//g')
# → 15fbec32f1cb4b05b1c2b229ce27359b
curl -sL "http://localhost:8989/api/v3/series?apikey=$SKEY" | jq length # → 13 сериалов
curl -sL "http://localhost:8989/api/v3/series?apikey=$SKEY" \
| jq '[.[]|select(.monitored)]|length' # → 13 monitored
curl -sL "http://localhost:8989/api/v3/queue?apikey=$SKEY" | jq .totalRecords # → 19 в очереди
```
| Сервис | Объектов | monitored | Очередь |
|---|---|---|---|
| Radarr | 50 фильмов | **45** | **6** |
| Sonarr | 13 сериалов | **13** | **19** |
**Отключать надо оба** — иначе Sonarr продолжит качать сериалы.
### Transmission — замер
```bash
ssh kraken 'docker exec transmission cat /config/settings.json' > /tmp/trans-settings.json
# download-dir /downloads · rpc-port 9091 · speed-limit-down-enabled false
# /config/torrents/*.torrent = 257 файлов (накопленный список РАЗДАЧ, не активная качка)
# CPU 0.76% — простаивает. NetIO 290G↑/26.7G↓ (суммарно за жизнь контейнера)
```
Полный состав контейнеров Кра́кена:
`lampac · radarr · jellyfin · transmission · portainer · flaresolverr ·
prowlarr · sonarr · watchtower · rclone · xray-reverse-bridge · hermes-kraken · cloudflared`
## Что осталось
1. **Лимит кэша TorrServer** — сейчас дефолт (`CacheSize` 96 МБ, `UseDisk: false`).
Диск 396 ГБ свободно, на HDD TorrServer не пишет вовсе — вопрос неактуален.
2. **Шаг 7 плана — watchlist в UI Lampa** — ✅ **серверная часть сделана** 2026-09-22,
🔴 **архитектура признана неверной**, переделывается: см.
[[personal/plans/lampac-watchlist-plugin]], разделы «ПЕРЕДЕЛКА 2» и
«✅ ПРОВЕРКА "как это реализуемо"».
3. **Проверка полосы на торрент-источнике** (remux 60–80 Мбит) — не сделана.
4. **Постеры/карточка в каталоге** — не сделаны, `image` пустой. Решение — брать
из Radarr (`images[].remoteUrl`) или `MediaCover/`, см. раздел выше.
5. **Автоскачка Radarr + Sonarr** — 45 monitored / очередь 6 у Radarr,
13 monitored / очередь 19 у Sonarr. План отключения на апруве
([[personal/plans/lampac-watchlist-plugin]], «ПЕРЕДЕЛКА 2»).