Lampac NextGen — локальный Docker-развёртывание
Сервер: Proxmox VM 130 «lampac», Debian 13, 192.168.88.243, логин prodik. Детали — AGENT.md (раздел Размещение на сервере).
Локальное развёртывание Lampac NextGen — самохостируемого бекенда для Lampa — в Docker. Официальная документация: docs.lampac.dev.
Быстрый старт
docker compose up -d # первый запуск
docker compose logs -f # логи (строки compilation / configure module / loaded module)
docker compose restart # после правки config/init.conf или манифестов в mods/
- Веб-клиент Lampa: http://localhost:9118
- Админ-панель: http://localhost:9118/adminpanel (root-пароль из
config/passwd) - TorrServer-прокси:
http://localhost:9118/ts/*(порт TorrServer — 9085)
По умолчанию сервер слушает порт 9118; если порт занят — меняй listen.port в config/init.conf до первого запуска.
Первая установка: SQLite не может создать базу в named volume
Симптом. Свежий docker compose up -d уходит в crash-loop (Restarting (139), healthcheck unhealthy), в логах:
Unhandled exception. Microsoft.Data.Sqlite.SqliteException (0x80004005):
SQLite Error 14: 'unable to open database file'.
at Microsoft.Data.Sqlite.SqliteConnection.Open()
at Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal.SqliteDatabaseCreator.Create()
at Microsoft.EntityFrameworkCore.RelationalDatabaseCreator.EnsureCreated()
at Core.Program.Run(String[] args)
Причина. Процесс в контейнере работает от uid 1000 (пользователь образа lampac), а Docker при первом создании named volume делает его владельцем root:root. Смонтированный каталог /lampac/database поэтому недоступен на запись для lampac, sqlite3 получает EACCES и приложение падает сразу после EnsureCreated() — база не создаётся ни разу, поэтому изнутри контейнера это не лечится.
Та же первопричина у второй ошибки первого запуска:
System.UnauthorizedAccessException: Access to the path '/lampac/passwd' is denied.
config/passwd монтируется файлом и тоже должен принадлежать uid 1000.
Лечение — на хосте, от root:
# тома (первый запуск или миграция/копия томов)
chown -R 1000:1000 /var/lib/docker/volumes/lampac_database/_data \
/var/lib/docker/volumes/lampac_cache/_data
# файл пароля (владелец 1000, права 600 — читает только процесс контейнера)
chown 1000:1000 config/passwd && chmod 600 config/passwd
docker compose up -d # или docker compose restart, если контейнер уже создан
Проверка:
docker compose ps # Up (healthy)
docker logs --since 2m lampac | grep -ci unhandled # 0
ls -ln /var/lib/docker/volumes/lampac_database/_data/Sisi.sql # uid/gid 1000
После лечения в
lampac_databaseдолжны появитьсяSisi.sql,Sync.sql,TimeCode.sql,tgnotify/,tracks/,storage/— это и есть признак, что базы созданы.
Проверить uid процесса в контейнере: docker exec lampac id → uid=1000(lampac).
Правило: любой bind-mount файл/каталог, в который пишет контейнер, должен принадлежать 1000:1000 (см. .gitignore для config/passwd, dlna/, lampa-main/).
Секреты (вынесены из git)
Модули TelegramBot, TelegramAuthBot, TelegramAuth читают bot_token и mutations_api_secret из конфига. В репозитории эти поля всегда пустые — реальные значения живут в файле, который не коммитится.
| Файл | В git? | Назначение |
|---|---|---|
config/init.conf |
❌ .gitignore | Шаблон конфига, живёт только локально (не коммитится, в т.ч. в Home/lampac). Именно его редактируете для обычных настроек (listen, SkipModules, initPlugins, модули). |
config/secrets.env |
❌ .gitignore | Токены ботов и mutations_api_secret. TELEGRAM_NOTIFY_BOT_TOKEN, TELEGRAM_AUTH_BOT_TOKEN, TELEGRAM_AUTH_MUTATIONS_SECRET. |
config/init.custom.conf |
❌ .gitignore | Боевой конфиг, который монтируется в контейнер. Собирается из шаблона + secrets.env скриптом. |
scripts/config-secrets.py |
✅ | Собирает/разбирает боевой конфиг: apply, export, check, status. |
scripts/install-git-hooks.sh |
✅ | Устанавливает хуки: post-merge/post-checkout → apply, pre-commit → check. |
docker-compose.yaml |
✅ | Монтирует ./config/init.custom.conf:/lampac/init.conf (не init.conf). |
Рабочий процесс:
# первый раз / после git pull:
python3 scripts/config-secrets.py apply # init.custom.conf = шаблон + secrets.env
docker compose up -d # рекреация из-за нового mount
# правки в init.conf:
# 1) правите config/init.conf (шаблон)
# 2) python3 scripts/config-secrets.py apply -> обновляет init.custom.conf
# 3) docker compose restart lampac
# перед коммитом (init.conf не коммитится — он в .gitignore):
git add -A
git commit -m "..."
Git-хуки (рекомендуется): bash scripts/install-git-hooks.sh — после git pull/смены ветки apply запустится сам, а pre-commit не даст случайно закоммитить секреты.
docker-compose.override.yml(в .gitignore) — серверный override для Proxmox (том DLNA ->/mnt/torrents), живёт только на сервере.
Структура проекта
| Путь | Назначение |
|---|---|
docker-compose.yaml |
Основной сервис (образ ghcr.io/lampac-nextgen/lampac:1.68.1) |
docker-compose.override.yml |
Серверный override (Proxmox: том DLNA -> /mnt/torrents), в .gitignore |
config/init.conf |
Шаблон конфига (JSON, в .gitignore — только локально) |
config/init.custom.conf |
Боевой конфиг (в .gitignore, монтируется в /lampac/init.conf) |
config/secrets.env |
Токены ботов и mutations_api_secret (в .gitignore) |
config/passwd |
Пароль root для /adminpanel и /weblog (в .gitignore) |
lampa-main/ |
Веб-клиент Lampa (в git, bind-mount в /lampac/wwwroot/lampa-main) |
volume lampac_cache |
Кеш (/lampac/cache) — Docker named volume |
volume lampac_database |
SQLite-базы Sync, TimeCode, tg-notify, tracks, storage (/lampac/database) — Docker named volume |
dlna/ |
Локальные медиа для DLNA (/lampac/data/dlna) |
plugins/extensions.json |
Каталог плагинов для веб-клиента (раздел «Плагины»), отдаётся через /extensions |
mods/<Имя>/ |
Пользовательские копии встроенных модулей с enable: true |
Веб-клиент
lampa-mainхранится в git (каталогlampa-main/) и смонтирован bind-mountом в/lampac/wwwroot/lampa-main(вdocker-compose.override.ymlна сервере), поэтому переживает пересоздание контейнера. В образе клиента нет, аLampaCronавтообновление из GitHub не докачивает (на сервере нет доступа к GitHub) — поэтому клиент держим в репозитории. Обновить клиент: скачать архив с https://github.com/yumata/lampa/archive/refs/heads/main.zip, распаковать вlampa-main/(топ-каталогlampa-main-mainпереименовать вlampa-main),chown -R 1000:1000 lampa-main, закоммитить; на сервереgit pull— перезапуск не нужен (статика). См. такжеscripts/restore-lampa-main.sh.
Включённые модули из mods/
Встроенные модули с "enable": false в manifest.json внутри образа нельзя включать правкой файла в контейнере — файлы пропадут при обновлении. Механика по официальной документации (модули Lampac, пользовательские модули):
- Скопировать модуль из образа в
mods/<Имя>/на хосте:docker cp lampac:/lampac/module/<Имя> "mods/<Имя>" - В
mods/<Имя>/manifest.jsonпоставить"enable": true. - Добавить том в
docker-compose.yaml:- ./mods/<Имя>:/lampac/mods/<Имя> - При необходимости добавить секцию модуля в
config/init.conf(runtime-флаг). docker compose up -d(при добавлении тома) илиrestart(при правке манифеста сdynamic: true).
Сейчас подключены
| Модуль | Что даёт | Секция в init.conf |
|---|---|---|
Tracks |
/tracks.js, /ffprobe — субтитры и дорожки |
"Tracks": { "enable": true } |
AdminPanel |
/adminpanel — редактор init.conf, current.conf, users.json |
"AdminPanel": { "enable": true } |
Tg-notify.bot |
/tg-notify.js, /api/tg/* — уведомления о новых сериях в Telegram |
секция "TelegramBot" |
TelegramAuth |
/tg/auth/* — привязка UID устройства к учётке Telegram, HTTP API статуса |
секция "TelegramAuth" (enable: false → без accsdb-гейта) |
TelegramAuthBot |
@GatusMonBot — long-polling бот привязки и админ-команды |
секция "TelegramAuthBot" |
Клиентский плагин tg-notify.js зарегистрирован в LampaWeb.customPlugins; {localhost} в URL автоматически подставляется сервером.
Модуль
Musicудалён изmods/(коммитremove Music): в образе у негоmanifest.enable: false, поэтому/music.js→ 404, секция"Music"изinit.confубрана, плагин изcustomPluginsубран. Вернуть музыку — той же механикой: скопироватьlampac:/lampac/module/Musicвmods/Music, поставитьenable: true, вернуть секцию и плагин.
Ещё можно включить тем же способом
В образе выключены также: DatabaseEditor, ExternalBind, LogUserRequest-Lite, Transcoding, Music, WatchTogether.
Конфигурация
config/init.conf— JSON. После правки:docker compose restart. Следи за валидностью JSON (лишняя запятая ломает парсинг).BaseModule.SkipModules/BaseModule.LoadModules— фильтры загрузки модулей (имя, группа, regex).SkipModulesимеет приоритет надLoadModules.LampaWeb.initPlugins— флаги встроенных плагинов веб-клиента;LampaWeb.customPlugins— дополнительные плагины (status: 1).LampaWeb.autoupdate: true— Lampa Web-клиент автообновляется изyumata/lampa(раз в 90 минут).
Установка плагинов по ссылке через веб (AdminPanel)
Список плагинов, который сервер раздаёт всем клиентам через /lampainit.js и /on.js, собирается по LampaWeb.initPlugins + LampaWeb.customPlugins. Watcher перечитывает init.conf без перезапуска (интервал ~1 с, runtime-параметры применяются на лету).
- Открой http://localhost:9118/adminpanel, введи root-пароль (содержимое
config/passwd) — пароль сохраняется в cookieaccspasswd(штатный механизм root-авторизации). - В редакторе
init.confоткрой секциюLampaWeb→customPluginsи добавь запись:Поддерживается плейсхолдер{ "url": "https://example.com/plugin.js", "status": 1, "name": "ИмяПлагина" }{localhost}— сервер подставит свой адрес. - Сохрани — перезапуск не нужен. Проверка:
curl -s http://localhost:9118/lampainit.js | grep <имя>.
Нюансы:
status: 1— активная запись.- Family-dedup:
name.jsиname/js/{token}считаются одним плагином; если в registry клиента уже есть один вариант, второй из серверного списка не добавляется. - Внешние URL должны быть доступны из браузеров клиентов и отдавать CORS-заголовки.
- Альтернатива без сервера: вставить ссылку напрямую в веб-клиенте Lampa (настройки → «Плагины») — тогда плагин живёт только в localStorage этого браузера и грузится браузером напрямую.
API (для скриптов): GET/POST /adminpanel/api/init, POST /adminpanel/api/init/section/{key} с cookie accspasswd=<root-пароль>.
Каталог плагинов (раздел «Плагины» в веб-клиенте)
В веб-клиенте Lampa раздел «Плагины» грузит каталог (store) с маршрута /extensions (включается при LampaWeb.initPlugins.pirate_store: true). Содержимое берётся из файла LampaWeb/plugins/extensions.json модуля — у нас он переопределён файловым mount из ./plugins/extensions.json (см. docker-compose.yaml). Плейсхолдер {localhost} в ссылках сервер заменяет своим адресом.
Все плагины каталога размещены локально (mirror): .js-файлы лежат в ./plugins/, каталог целиком смонтирован в /lampac/wwwroot/plugins/ (одна строка в compose) и ссылки в каталоге ведут на {localhost}/plugins/<имя>.js. (Целиком в корень wwwroot монтировать нельзя — перекроется веб-клиент lampa-main.) Сами файлы плагинов всегда локальные; исключение — плагин-лоадеры, подгружающие основной код с внешнего хоста: «Клубничка 18+» (rc.bwa.ad) и Showy (showy.online/online.js — файл локально патчен под абсолютный URL, иначе он бы подгружал /online.js с нашего сервера). Остальные скачаны целиком (для pubtorr / torrentmanager / td скачано тело, а не лоадер).
plugins/sources.json— карта «локальный файл → исходный URL». Для обновления плагина: скачать файл по исходному URL, перезаписать локальный, перезапуск не нужен.plugins/modss.js— локальная patched-копия MODS's (заменён вызовLampa.Utils.isPWA()), раздаётся как/plugins/modss.js, но из каталога исключена (upstream мёртв: загрузчик отвечает stub-ошибкой «Используйте: lampa.mx»). Вернуть карточку можно, добавив запись сlink: "{localhost}/plugins/modss.js".
Удалены из каталога (мёртвы upstream, проверено по факту выдачи):
- «MODS's» (
lampa.stream/modss) — загрузчик отвечает stub-ошибкой; - «Отзывы и рецензии» (
lampaplugins.github.io/store/o.js) — файл заменён автором на пустой stub; - «Вторая кнопка Торренты», «Поиск локального TorrServer», «Горячие кнопки», «Золотая Тема», «Тип управления», «Старый стиль», «Мультфильмы» — публичных копий нет;
- «Телевидение by Skaz» / «ТВ by skaz» / «ТВ by skaz 2.0» — файлы заменены placeholder-заглушками;
- «Импорт/Экспорт закладок, истории» (
skaz.tv/export.js) — 404.
Важный нюанс — диск-кеш ответа: маршруты LampaWeb кешируют ответы в /lampac/cache/static/<bucket>/<id>.json (named volume lampac_cache), кеш переживает рестарт контейнера. После правки extensions.json для мгновенного применения удалить закешированную запись:
docker exec lampac rm -rf /lampac/cache/static/59 # bucket /extensions; либо весь /lampac/cache/static
Перезапуск контейнера при этом не требуется (маршрут читает файл при каждом запросе, если нет кеш-записи).
Tg-notify.bot: Telegram-бот уведомлений о новых сериях
Фоновый Telegram-бот (long polling): пользователь подписывается на сериал прямо из карточки Lampa, бот отслеживает выход новых эпизодов и появление выбранной озвучки и шлёт в Telegram уведомление с обложкой, номером серии и описанием из TMDB.
Модуль объединяет три подсистемы:
- Трекер новых серий — Trakt API (если задан
trakt_client_id), иначе TMDB. - Трекер озвучек — параллельный опрос встроенных балансиров Mirage, Collaps и VideoHub через
lampac_host(/lite/mirage,/lite/collaps,/lite/cdnvideohub); берётся максимальный эпизод из всех источников. - Клиентский плагин
tg-notify.js— вкладка «TG Подписки», перехват кнопки 🔔 в карточке, меню выбора озвучки.
Как это работает (поток)
-
Привязка аккаунта — во вкладке «TG Подписки» в Lampa выдаётся deep-link
/start link_{uid}(для UID вида email —/start link64_{base64url(uid)}). После/startбот сохраняет связкуchat_id ↔ lampac_uidвdatabase/tgnotify/users.json; подписки становятся общими для всех устройств этого аккаунта. -
Подписка — в карточке сериала кнопка 🔔 перехватывается плагином, показывается меню озвучек (
GET /api/tg/voices), выбор уходит наPOST /api/tg/subscribeи сохраняется вsubscriptions.json. -
Фоновый цикл
CheckAll— раз вcheck_interval_minutes(первый прогон — через 2 минуты после старта, вручную —/check): новые серии через Trakt→TMDB; если выбрана конкретная озвучка — параллельно Mirage + Collaps + VideoHub. -
Уведомление — сообщение вида:
🎬 Название 🎙 Озвучка 📺 S05E08 — Название эпизода Описание (до 300 символов)с обложкой кадра из TMDB (
still_path), если есть.
Конфигурация (секция TelegramBot в config/init.conf)
| Поле | Назначение |
|---|---|
bot_token |
Токен от @BotFather. Обязателен — без него long polling не стартует (в логе предупреждение, сервер работает) |
tmdb_api_key |
Ключ TMDB v3: обложки, описания эпизодов, определение актуального сезона |
trakt_client_id |
Client ID Trakt; основной источник новых серий (если пуст — только TMDB) |
lampac_host |
Базовый URL Lampac для запросов к балансерам; в Docker — http://127.0.0.1:9118 |
lampac_token |
Токен accsdb; обязателен, если включена защита (иначе балансеры вернут ошибку входа) |
kp_api_key |
Ключ kinopoiskapiunofficial.tech — резолвинг tmdb_id → kinopoisk_id по названию для VideoHub (опционально; без него покрытие ~40% через мост /externalids) |
check_interval_minutes |
Период фоновой проверки (дефолт 60) |
tmdb_lang |
Язык метаданных TMDB (дефолт ru-RU, фоллбэк en-US) |
data_dir |
Каталог users.json/subscriptions.json (дефолт database/tgnotify — у нас это том ./database/, переживает рестарты) |
Быстрый старт
- @BotFather →
/newbot→ получить токен. config/init.conf→TelegramBot.bot_token= токен.docker compose restart lampac(по желанию — заполнитьtmdb_api_keyиtrakt_client_id).- В Lampa (http://localhost:9118) вкладка «TG Подписки» → deep-link →
/startу бота. - В карточке сериала — 🔔, выбрать озвучку (или «без озвучки» — уведомление о самом факте выхода серии).
Команды бота
| Команда | Действие |
|---|---|
/start |
Приветствие; /start link_{uid} — привязка аккаунта |
/list |
Список активных подписок |
/check |
Принудительная проверка в фоне |
/help |
Справка |
/unlink |
Отвязать аккаунт |
HTTP API
| Метод | Маршрут | Назначение |
|---|---|---|
GET |
/tg-notify.js |
Раздача клиентского плагина (AllowAnonymous) |
POST |
/api/tg/subscribe |
Подписка на сериал/озвучку |
POST |
/api/tg/unsubscribe |
Отписка |
GET |
/api/tg/status?tmdb_id= |
Статус подписки текущего пользователя |
GET |
/api/tg/voices?title=&year=&season=&tmdb_id= |
Доступные озвучки (Mirage + Collaps + VideoHub) |
GET |
/api/tg/link |
Deep-link для привязки бота |
GET |
/api/tg/subscriptions |
Все подписки пользователя с метаданными |
Все маршруты, кроме /tg-notify.js и /api/tg/voices, требуют requestInfo.user_uid (вход в аккаунт).
Состояние на нашем инстансе
- Модуль загружен,
/tg-notify.js→ 200, плагин уже вLampaWeb.customPlugins, вкладка «TG Подписки» видна; аккаунт привязан (database/tgnotify/users.json). bot_tokenзаполняется только локально: в git поле пустое (правило 10), токен вставлен вconfig/init.confна сервере и при работе@KlinkerZabbixBot([TelegramBot] @KlinkerZabbixBot запущен!). После любогоgit pull/правки конфига токен нужно вернуть на место — иначе polling не стартует.- Трекинг озвучек зависит от балансиров: на текущий момент
GET /lite/mirage→ телоdisable,GET /lite/collaps→503— до тех пор, пока они не будут доступны (или не включитсяlampac_tokenпри защите), бот будет уведомлять только о выходе серий (TMDB/Trakt).
TelegramAuth + TelegramAuthBot: вход в Lampa через Telegram
Пара модулей из группы Community — замена пароля аккаунта подтверждением в Telegram (сценарий: ТВ, где вводить пароль неудобно). Не путать с Tg-notify.bot — тот только уведомляет о сериях.
TelegramAuth— серверная часть: хранение пользователей/устройств, HTTP API/tg/auth/*, синхронизация UID вaccsdb; Telegram Auth Gate — вместо экранаdeny.js(модалка пароля) показывает fullscreen-оверлей с UID, кнопкой «Открыть Telegram» и QR-кодом на крупных экранах.TelegramAuthBot— long-polling бот: пользователь открывает бота (/start link_{uid}или отправляет UID), бот сам вызываетPOST /tg/auth/bind/complete; есть админ-команды. HTTP UI нет.
Активация гейта — только при трёх условиях сразу: accsdb.enable: true + LampaWeb.telegramAuthGate.enabled: true + непустой botUsername. Иначе тихий откат на deny.js.
Конфиг (по образцу init.merge.example.json модулей):
"TelegramAuth": {
"enable": true,
"mutations_api_secret": "SECRET"
},
"TelegramAuthBot": {
"enable": true,
"bot_token": "ТОКЕН_ОТ_BOTFATHER",
"mutations_api_secret": "SECRET"
},
"LampaWeb": {
"telegramAuthGate": {
"enabled": true,
"botUsername": "имя_бота_без_@",
"serviceName": "lampa"
}
}
mutations_api_secret должен совпадать в обоих модулях и быть ненулевым — защищает bind/complete и бот-админку; вместе с bot_token не публиковать. Оба модуля выключены в base.conf (SkipModules) и в манифестах (enable: false).
Состояние у нас: модули включены (копии в mods/TelegramAuth/ и mods/TelegramAuthBot/, манифесты enable: true, SkipModules пуст), работают в API-режиме: TelegramAuth.enable: false → accsdb не поднимается, гейт неактивен, доступ не режется. Бот @GatusMonBot — Telegram API OK, long polling идёт; owner_telegram_ids: [143672] создаёт владельца ролью admin при старте, auto_provision_users: true + auto_provision_activate_immediately: false — новые привязки висят в pending до одобрения кнопкой у админа. Секрет mutations_api_secret (одинаков в обеих секциях) и bot_token лежат в config/init.conf только на сервере.
Порядок включения гейта (когда решим): поставить TelegramAuth.enable: true → в LampaWeb секцию telegramAuthGate (enabled: true, botUsername: "GatusMonBot") → подключить клиентский оверлей (deny.js / telegram_auth_gate.js) → сначала связать свои устройства через бота, иначе текущие UID получат отказ.
Обновление Lampac
docker compose pull
docker compose up -d
Перед обновлением проверить, что в docker-compose.yaml раскомментированы нужные тома. Данные сохраняются в примонтированных томах: config/, cache/, database/, dlna/, mods/, lampa-main/.
wwwroot/lampa-main смонтирован как bind-mount из ./lampa-main/ (см. раздел «Структура проекта»), поэтому переживает рекреацию контейнера. Если директория на хосте пуста — bash scripts/restore-lampa-main.sh scripts/lampa-main.zip (архив берётся с https://github.com/yumata/lampa/archive/refs/heads/main.zip).
Образ закреплён точным digest (1.68.0@sha256:752dafd8…): тег 1.68.0 на ghcr — mutable, его переиздавали, и в новой сборке копии модулей в mods/ (Roslyn компилирует их на каждом старте) перестают собираться — CS0103 по символам из старого билда и crash-loop сервера. Чтобы обновить образ: взять новый digest, пересобрать копии модулей в mods/ из нового образа (docker cp lampac:/lampac/module/<Имя> mods/<Имя> + вернуть "enable": true) и заменить digest в docker-compose.yaml. Не делать «просто» docker compose pull без фиксации digest.
Безопасность
По предупреждению официальной документации:
- Не публикуй в интернет без аутентификации, WAF и ограничения сети:
/adminpanel,/weblog,/database-editor,Tracks,Transcoding,Catalog,DLNA, proxy-модули. - Нигде не раскрывай
config/passwd, cookieaccspasswd, заголовокlcrqpasswd. - Секреты в git не попадают:
TelegramBot.bot_token,TelegramAuthBot.bot_token,mutations_api_secretхранятся вconfig/secrets.env(в .gitignore). В gitconfig/init.confэти поля всегда пустые. Реальный боевой конфиг —config/init.custom.conf(тоже gitignored), собирается скриптомscripts/config-secrets.py applyиз шаблона + секретов. Передgit add config/init.conf:python3 scripts/config-secrets.py check→ должно бытьcheck: секретов в config/init.conf нет — ок. - Для публикации за reverse proxy: deployment/reverse-proxy (HTTPS в ответах, реальный IP клиента, WebSocket, SSE, streaming).
shm_size: 4096mb— рекомендованный минимум для Playwright/Chromium.