75 KiB
title, created, updated, type, namespace, tags, related
| title | created | updated | type | namespace | tags | related | |||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Lampac — развёртывание и эксплуатация (Кра́кен) | 2026-09-22 | 2026-09-22 | tech | personal |
|
|
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 + cards/)
└── 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.
🔴 Права на файлы в wwwroot/ — владелец 1000:1000 (= пользователь lampac
в контейнере), каталоги 755, файлы 644. Не kraken:kraken — контейнер ходит
не под хост-пользователем. Если файл окажется root:root 600, dotnet ответит
Permission denied, а веб-сервер молча отдаст 404 — отсутствие прав выглядит
снаружи как «файла нет».
Раздача статики из wwwroot/
- Всё, что положено в
/lampac/wwwroot/, доступно по корню:wwwroot/watchlist-films.json→GET /watchlist-films.json(200). UseStaticFiles— вCore.dll(серверное ядро), настраивать не нужно.- 🔴
/lampac/wwwroot≠/lampac/current.conf: статика ядра, а не модуль. Проверять наличие файла надоdocker exec lampac ls ..., а доступность — обязательно черезcurl, потому что это разные вещи (см. права выше). - ⚠️ Веб-корень отдаёт только
wwwroot/. Пути вида/cards/...работают, но/card— это не статика (см. ниже).
⚠️ Всё пишущее — на data-диск (1.8T). НЕ на SD (/, 14G, 8.6G свободно).
docker-compose.yml (актуальный, 2026-09-22)
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:
{
"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:
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 отбрасывает сайт:
if (init == null || !init.enable || init.menu == null || init.hide) continue;
В логах — тишина. Сайт молча исчезает из /catalog. Обе формы (список и словарь) ломают.
main Catalog генерирует сам из menu — в JSON-ответе /catalog присутствуют оба
ключа с одинаковым содержимым. Писать main в YAML не нужно и нельзя.
⚠️ menu обязателен — он регистрирует канал, а не подменяет разделы.
🔴 Питфолл №4: элемент без href выбрасывается
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:
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 «здесь пусто».
Сигнатура роута (критично!):
[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):
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 НЕ пишутся!):
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из YAMLfull()/list()/search()— карточка, категория, поискLampa.Listener.follow('menu', ...)+e.abort()— перехват кликов по родным разделам (movie/tv/cartoon/anime/relise). Срабатывает только когда выбран источник «Мой список», поэтому родной CUB не трогается при других источниках
🔴 Питфолл №6: источник не виден в UI (enumerable: false)
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.
Управление
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 шагов с бэкапами
и самопроверкой. Запуск:
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 без ключа = сеть есть).
Проверка работоспособности
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 из Кра́кена:
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 падает
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), и первая же команда отваливается.
Каждую команду вызывать отдельно:
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 не виден внутри.
Проверка:
ssh kraken "sudo docker inspect lampac --format '{{json .Mounts}}'" | jq -r '.[].Destination'
Фикс:
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, откат после провала)
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 ломают одинаково — и список, и словарь:
main: # вариант 1 — список: НЕ работает
- categories:
Фильмы: films
main: # вариант 2 — словарь: НЕ работает
Фильмы: http://...
⚠️ Ошибка не логируется. В логах Lampac — тишина (docker logs lampac | grep -i yaml
пусто). Сайт просто не появляется в /catalog. Диагностика только перебором YAML.
Кто на самом деле подменяет родные разделы — plugin.js, а не YAML:
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:
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()), два вхождения:
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:
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 их не содержит.
# 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 каталогов).
{
"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()— ужасы по TMDBgenre_id=27(нужно для сортировки 🧟♀️)- кэш
.resolve_cache.json; различаетok/ambiguous(score < 0.75) /not_found
🔴 watchlist_sync/arr.py — ИСТОЧНИК АВТОСКАЧКИ (в коде!)
# 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 мин — пусто.
Правильные эндпоинты (проверено):
# отмена загрузки — файлы на диске НЕ трогаются
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, ...остальное}
Откат:
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-хендлер:
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 мин после остановки |
# конфиг 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 прописывает:
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 сам:
# ⚠️ на КОРНЕ, БЕЗ префикса /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:
# 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:
"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.
Порядок установки:
# 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, и прочитать временный файл:
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 / «Мой список» / …) переключается из меню каталогов на главной:
- Открыть Lampa на главной
- Встать на любой ряд каталога → нажать влево на пульте
- Слева выедет панель со списком источников
- Выбрать нужный → 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 |
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(), два вхождения):
// БЫЛО (баг)
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:
# docker-compose.yml
- ./catalog/plugin.js:/lampac/module/Catalog/plugin.js:ro
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 этого не дают.
# цель отладки + 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:
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 ноль = 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/.
Выключение модуля — двумя механизмами:
SkipModulesвinit.conf/current.confmanifest.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
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 .
{
"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
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:
ssh kraken 'docker exec lampac cat /lampac/data/ts/settings.json' | jq .BitTorr
{ "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.
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:
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:
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 — замер
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
Что осталось
- Лимит кэша TorrServer — сейчас дефолт (
CacheSize96 МБ,UseDisk: false). Диск 396 ГБ свободно, на HDD TorrServer не пишет вовсе — вопрос неактуален. - Шаг 7 плана — watchlist в UI Lampa — ✅ серверная часть сделана 2026-09-22, 🔴 архитектура признана неверной, переделывается: см. personal/plans/lampac-watchlist-plugin, разделы «ПЕРЕДЕЛКА 2» и «✅ ПРОВЕРКА "как это реализуемо"».
- Проверка полосы на торрент-источнике (remux 60–80 Мбит) — не сделана.
- Постеры/карточка в каталоге — не сделаны,
imageпустой. Решение — брать из Radarr (images[].remoteUrl) илиMediaCover/, см. раздел выше. - Автоскачка Radarr + Sonarr — 45 monitored / очередь 6 у Radarr, 13 monitored / очередь 19 у Sonarr. План отключения на апруве (personal/plans/lampac-watchlist-plugin, «ПЕРЕДЕЛКА 2»).
Карточка фильма в модуле Catalog — рабочая схема (проверено 2026-09-22)
Модуль Catalog (в образе /lampac/module/Catalog/). Каталог watchlist:
sites/watchlist.yaml + JSON в wwwroot/.
Списки
menu:
- categories:
Новинки: new # ПЕРВОЙ — она игнорирует format, берёт list.uri
Фильмы: films
Мультфильмы: cartoons
Аниме: anime
format:
"-": |
return $"http://127.0.0.1:9118/watchlist-{cat}.json";
list:
uri: watchlist-new.json # ← сюда смотрит первая категория
content:
nodes: "$.items[*]"
href: { attribute: href } # href = TMDB URL → id = "movie/NNN"
Карточка
CardController.Index(plugin, uri, type) — cat не принимает.
url = $"{init.host}/{uri}"— дефолт, даётhttp://127.0.0.1:9118/movie/424781→ 500.card_parse.initUrlисполняется как C# (CSharpEval, объектCatalogInitUrlCard(host, args, uri, query, type)) → можно собрать динамический URL.card_parse.node— статичный JSONPath (json.SelectToken(node)), подстановок нет;/= вложенность.
Рабочая конфигурация:
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-movie-424781.json:
{ "card": { "name": "...", "title": "...", "original": "...", "original_title": "...",
"year": "...", "rating": "...", "poster": "...", "img": "...",
"overview": "...", "genres": "...", "cast": "...", "runtime": 116 } }
Ключи дублируются (name+title, poster+img): CardController отдаёт
title/original_title, а card_parse ищет name/original.
Эндпоинты
| Запрос | Назначение |
|---|---|
GET /catalog |
список каналов; main = категории |
GET /catalog/list?plugin=watchlist&cat=films&page=1 |
элементы секции |
GET /catalog/card?plugin=watchlist&uri=movie/424781 |
карточка |
🔴 Не по /card — там 404 и ложный вывод «карточка сломана».
Файлы и права
- Статика раздаётся из
/lampac/wwwroot/(coreUseStaticFiles). - Владделец —
1000:1000(пользовательlampacв контейнере), каталоги755, файлы644. ИначеPermission denied→ 404. - macOS
tar→ AppleDouble._*, добавлять--no-mac-metadata.
Деплой
python3 gen_watchlist_v2.py movies-watchlist.md /tmp/wl-out/watchlist.json
python3 deploy_lampac.py /tmp/wl-out --skip-plugin
Скрипты: /Users/admin/lampac-tmp/. Логика заливки — tar+scp+sudo,
ssh -t для sudo (без tty sudo требует пароль), каждая команда отдельным
вызовом (&&-цепочка рвёт tty).
Связанные
🔴 ПОЧЕМУ КАРТОЧКА = «ЗДЕСЬ ПУСТО» (найдено 2026-09-22, вечер)
Симптом: данные в карточку приходят (title, img, overview), но в UI «здесь пусто», нет кнопки воспроизведения.
Причина: plugin.js при клике:
if(json.tmdb_id){ status.need++; Lampa.Api.sources.cub.reactionsGet(
{id: json.tmdb_id, method: json.original_name ? 'tv' : 'movie'}, ...); }
Без tmdb_id (числа, не строки!) Lampa считает карточку неполной → пустой экран.
Два бага в цепочке, оба надо было починить
-
ModInit.cs:285—nodeValueвозвращает ЛЮБОЕ значение как строку.string value = valueToken switch { JValue jValue => jValue.Value?.ToString(), // ← число → "424781" ... };Поэтому
tmdb_idиз JSON приходил строкой, иif(json.tmdb_id)давала не число. -
Кэш в памяти процесса переживает
docker restart.hybridCacheключcatalog:card:{plugin}:{uri}живёт в памяти. При правке данных старые ответы продолжают отдаваться. Обязательноdocker restart lampacи проверять на НЕзакэшированном uri.
Фикс — format в card_args
format исполняется как C# (CSharpEval, объект CatalogNodeValue(value, host))
и может вернуть настоящий тип, а не строку:
card_args:
- name_arg: tmdb_id
attribute: tmdb_id
format: "long.TryParse(value, out long v) ? v : 0"
- name_arg: source
attribute: media_type
- name_arg: runtime
attribute: runtime
format: "long.TryParse(value, out long v) ? v : 0"
- name_arg: vote_average
attribute: vote_average
format: "double.TryParse(value, out double v) ? v : 0"
- name_arg: rating
attribute: rating # строкой — Lampa показывает в бейдже
Рабочий ответ карточки (эталон)
{ "id": "movie/424781", "title": "Простите за беспокойство",
"original_title": "Простите за беспокойство", "tmdb_id": 424781,
"source": "movie", "runtime": 111, "rating": "6.8", "vote_average": 68,
"genres": [{"name":"фэнтези"},{"name":"фантастика"},{"name":"комедия"}],
"overview": "...", "img": "http://192.168.1.14:9118/proxyimg:210:0/..." }
Результат: 352 из 352 карточек отдают числовой tmdb_id.
Про CUB — почему «только фильмы из списка»
CUB и «Мой список» — два независимых источника Lampa, одновременно показать нельзя. Переключение: Настройки → Остальное → Основной источник. Пункта «Источник» в настройках НЕ существует.
Питфолл проверки на ТВ
Lampa на ТВ кэширует манифест и ответы. После правок надо полностью выгрузить приложение (не свернуть), иначе клиент держит старый кэш.