# AGENT.md — инструкции для ИИ-агентов Работа с проектом: локальный Docker-развёртывание [Lampac NextGen](https://github.com/lampac-nextgen/lampac). Человек читает ответы **только на русском**. ## Источники правды (порядок приоритета) 1. **Официальная документация** — [docs.lampac.dev](https://docs.lampac.dev) (полный текст страниц: `https://docs.lampac.dev/llms-full.txt`, индекс: `llms.txt`). Не гадать по памяти — проверять в доках. 2. **Живой контейнер** — состояние модулей, манифестов и конфигов брать из контейнера: ```bash docker exec lampac sh -c 'cat /lampac/module/<Имя>/manifest.json' docker exec lampac cat /lampac/current.conf # merged-конфиг: base + init ``` 3. **Логи контейнера** — `docker logs lampac`. 4. Локальные файлы проекта: `docker-compose.yaml`, `config/init.conf`, `mods/`. ## Ключевые правила ### 1. Никогда не правь файлы внутри контейнера/образа Файлы в `/lampac/module/` (и любые другие в образе) **сгорают при обновлении** (`docker compose pull && up -d`). Включение встроенного модуля делается только пользовательской копией: ```bash docker cp lampac:/lampac/module/<Имя> "mods/<Имя>" # в mods/<Имя>/manifest.json -> "enable": true # в docker-compose.yaml -> "- ./mods/<Имя>:/lampac/mods/<Имя>" # docker compose up -d ``` См. также официальные шаги: [Включить модуль](https://docs.lampac.dev/modules/overview), [Пользовательские модули](https://docs.lampac.dev/maintenance/custom-modules). Сейчас в `mods/`: `AdminPanel`, `Tracks`, `Tg-notify.bot`, `TelegramAuth`, `TelegramAuthBot`, `WatchTogether`. **`mods/Music` удалён** (remote-коммит `remove Music`): в образе у Music `manifest.enable: false`, поэтому модуль не грузится, `/music.js` → 404, секция `"Music"` и плагин убраны из `init.conf`. ### 2. Механика загрузки модуля (4 независимых фильтра) Модуль грузится, только если выполнены **все**: | Фильтр | Где | |---|---| | `"enable": true` | `manifest.json` модуля (копия в `mods/` для встроенных) | | Имя **не** в `BaseModule.SkipModules` | `base.conf` (в образе) + `config/init.conf` | | Имя попадает в `BaseModule.LoadModules` | если allowlist задан (у нас не задан — грузится всё остальное) | | Runtime-флаг | секция модуля в `config/init.conf`, например `"gst": { "enable": true }` | `SkipModules` имеет приоритет над `LoadModules` — модуль из обоих списков **не** грузится. Паттерны: точное имя, группа (`OnlineUKR`), regex (`LME.*`), `.*`. ### 3. `config/init.conf` — валидный JSON JSON **без** trailing comma. Перед применением правок проверять: ```bash python3 -m json.tool config/init.conf > /dev/null && echo OK ``` После правки конфига: `docker compose restart` (том смонтирован, `up -d` не нужен). После правки `manifest.json` в `mods/` с `dynamic: true` — тоже `restart` (или ждать hot-rebuild). После изменения томов в compose — `docker compose up -d`. ### 4. Клиентские плагины Lampa - Встроенные плагины: флаги `LampaWeb.initPlugins` (`tracks`, `dlna`, `catalog`, `sync`, `backup`, ...). - Дополнительные: `LampaWeb.customPlugins` — записи со `status: 1` добавляются как есть; поддерживается плейсхолдер `{localhost}` (подставляется реальный адрес сервера). - Если плагин «не виден» в веб-клиенте — сначала проверить HTTP-статус его URL на сервере (`curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9118/<плагин>.js`), а не лезть в клиент. ### 4.1 Каталог плагинов (store) `/extensions` - Раздел «Плагины» веб-клиента грузит каталог с `/extensions` (при `LampaWeb.initPlugins.pirate_store: true`). - Источник: `/lampac/module/LampaWeb/plugins/extensions.json` — переопределён mount из `./plugins/extensions.json`. Править хост-файл, в контейнер не залезать. - `{localhost}` в ссылках store сервер заменяет своим адресом при выдаче. - Статика: `/lampac/plugins/` **не раздаётся** (проверено: файл → 404), корень `wwwroot` — раздаётся. Все плагины каталога — **локальный mirror** в `./plugins/`, смонтирован **одним directory-mount** в `/lampac/wwwroot/plugins/` → ссылки store `{localhost}/plugins/<имя>.js`. Не монтировать каталог целиком в корень `wwwroot` — скроется веб-клиент `lampa-main`. - Обновление плагина: исходный URL — в `plugins/sources.json`; скачать, перезаписать файл, перезапуск не нужен (статика). - При проверке скачанного плагина отличать: (а) реальный код, (б) лоадер `putScriptAsync` (абсолютные URL — безопасен локально; host от currentScript — **требуется патч**, см. showy.js), (в) пустой stub/placeholder (мёртвый плагин, из каталога удалять). - **Кеш-ловушка**: ответ `/extensions` (и других LampaWeb-маршрутов) кешируется на диск в `/lampac/cache/static//.json` (named volume `lampac_cache`); кеш переживает `docker compose restart`. После правки store: `docker exec lampac rm -rf /lampac/cache/static/59` (bucket `/extensions`), перезапуск не нужен. Заголовок `X-StatiCache-Status: HIT/MISS` показывает состояние. ### 5. AdminPanel API из скриптов (root-авторизация) - Root-пароль = содержимое `config/passwd`. Механизм: cookie `accspasswd=<пароль>` (таков штатный сценарий: страница входа `auth.html` сама кладёт пароль в эту cookie). Заголовок `lcrqpasswd` — для внутренних вызовов, на `/adminpanel/api/*` с ним уходит 302 на `/adminpanel/auth`. - Не передавать root-пароль в query string (предупреждение доков). - Сервер может отвечать 404 вместо 401/403, чтобы не раскрывать админский endpoint. - Основные маршруты: `GET /adminpanel/api/init` (чтение), `POST /adminpanel/api/init` (полная запись), `POST /adminpanel/api/init/section/{key}` (секция), `GET /adminpanel/api/current` (merged-конфиг), `GET /adminpanel/api/users-json`. - Правка секций через API пишется в `init.conf` — watcher подхватит без рестарта (runtime-параметры). `customPlugins` — runtime-параметр: плагин появляется в `/lampainit.js` через ~1 с. ### 6. Диагностика «модуль не найден» Чек-лист (в этом порядке): 1. `curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:9118/<маршрут>` — 404 подтверждает, что модуль не загружен. 2. `docker logs lampac | grep -E "compilation|loaded module" | grep <Имя>` — скомпилирован ли и загружен ли. 3. Манифест: `enable: true`? (в `mods/` для встроенных) 4. Фильтры `SkipModules`/`LoadModules` в `config/init.conf`. 5. Runtime-секция модуля в `config/init.conf`. 6. В логе после старта ожидаются строки `compilation <Имя>`, `configure module: <Имя>` (если есть конфиг-секция), `loaded module: <Имя>`. ### 7. Команды эксплуатации ```bash docker compose up -d # запуск / пересоздание (при изменении compose) docker compose restart # после правки init.conf / mods docker compose logs -f # логи docker compose pull && docker compose up -d # обновление (сначала поменять тег в compose) docker exec lampac sh -c '...' # осмотр состояния в контейнере ``` `container_name: lampac` — контейнер один; одновременный запуск нескольких compose-файлов с одним именем невозможен. `docker-compose.override.yml` (gitignore) живёт только на сервере Proxmox и не коммитится. ### 8. Чего не трогать - `config/passwd`, `dlna/` — в .gitignore, не коммитить. `cache`/`database` — **named Docker volumes** (`lampac_cache`, `lampac_database`), в git не попадают. Ловушка `.gitignore`: паттерны без якоря (`cache/`) матчат каталоги на любой глубине и без учёта регистра — из-за этого `mods/Music/Services/Cache/` не попадал в git и ломала Roslyn-компицию на сервере (CS0103, crash-loop). Новые данные/каталоги проверять через `git status`/`git check-ignore -v`. - В named volume данные принадлежат тому, кто их туда положил: после миграции/копии `chown -R 1000:1000` (процесс в контейнере — `lampac:1000`), иначе SQLite «attempt to write a readonly database». Исключение: `cache/static/` (в volume `lampac_cache`) — одноразовый диск-кеш ответов LampaWeb; очистка (`docker exec lampac rm -rf /lampac/cache/static/`) — штатное действие после правки store/статических маршрутов (см. 4.1). - Тег образа в `docker-compose.yaml` закреплён **точным digest** (`1.68.0@sha256:752dafd8…`): тег 1.68.0 на ghcr mutable, его переиздавали, и новая сборка ломает Roslyn-компиляцию наших копий `mods/` (пример: `Music` → `CS0103 MusicMetadataCacheService`, crash-loop). Обновление образа = новый digest + пересборка копий `mods/` из нового образа. Не делать «просто» `docker compose pull` без фиксации digest. - `LampaWeb.autoupdate` — веб-клиент Lampa сам обновляется из git (`yumata/lampa`), `wwwroot/lampa-main` в контейнере перезаписывается — туда не править. ### 9. После изменений в проекте Обновлять `README.md` и `AGENT.md` в той же ревизии: структура, включённые модули, секции конфига, новые правила. ### 10. Безопасность (не публиковать) `config/passwd`, cookie `accspasswd`, заголовок `lcrqpasswd`, `bot_token` в `TelegramBot`, `bot_token` в `TelegramAuthBot`, `mutations_api_secret`. Маршруты `/adminpanel`, `/weblog`, `Tracks`, `Catalog`, `DLNA`, proxy-модули — только для доверенной сети без WAF не открывать. **Секреты в git не попадают:** в закоммиченном `config/init.conf` токены и секрет должны быть **пустыми** (так делает и remote — коммит `remove`), реальные значения живут только в рабочем файле на сервере (копия до мержа — `/root/init.conf.local`). Перед `git add config/init.conf`: `git diff config/init.conf | grep -E ":[0-9]{8,}:AA|\"mutations_api_secret\": \"[a-f0-9]{16}"` → должно быть пусто. ### 11. Tg-notify.bot (Telegram-бот) - Механика: long polling по `TelegramBot.bot_token`; если токен пуст — polling не стартует (в логе `[TelegramBot] Установите bot_token…`, сервер работает). После правки секции: `docker compose restart lampac`. - **Токен только локально:** в git поле пустое (правило 10). На сервере в `init.conf` вставлен `@KlinkerZabbixBot` — после `git pull`/переписывания конфига проверять, что polling снова стартовал (`[TelegramBot] @KlinkerZabbixBot запущен!`). Второй экземпляр на том же токене даёт `Conflict: terminated by other getUpdates request`. - Три подсистемы: трекер серий (Trakt→TMDB), трекер озвучек (параллельно Mirage/Collaps/VideoHub через `lampac_host`: `/lite/mirage`, `/lite/collaps`, `/lite/cdnvideohub`), клиентский плагин `tg-notify.js` (вкладка «TG Подписки», перехват 🔔). Полный разбор — в README.md. - Данные подписок: `database/tgnotify/{users.json,subscriptions.json}` (том `./database/`, в .gitignore). Не «чинить» вручную — это живые данные бота. - Диагностика: `GET /tg-notify.js` (200 без авторизации), `GET /api/tg/voices` (200), `/api/tg/*` остальной — требует `user_uid`. Озвучки могут не работать, если балансеры недоступны (проверить `GET /lite/mirage` — тело `disable`/ошибка, `/lite/collaps` — 503); тогда уведомления приходят только о выходе серий. - `kp_api_key` опционален: без него VideoHub резолвит `kinopoisk_id` только через мост `/externalids` (покрытие ~40%), иначе — молча пропускается, Mirage/Collaps работают. ### 12. TelegramAuth / TelegramAuthBot (вход через Telegram) - Не путать с Tg-notify.bot (уведомления): эта пара — **аутентификация** (вход в Lampa через Telegram вместо пароля), группа Community. Копии в `mods/TelegramAuth/` и `mods/TelegramAuthBot/`, манифесты `enable: true`, из `SkipModules` убраны (у нас `SkipModules: []`). - **Сейчас: API-режим.** `TelegramAuth.enable: false` → `accsdb` не поднимается, гейт неактивен, доступ не режется; работают привязки, `/tg/auth/*`, админ-API. `TelegramAuthBot` — бот **`@GatusMonBot`**, long polling идёт (`Telegram API OK`). - Гейт (`LampaWeb.telegramAuthGate`) активен только при `accsdb.enable: true` + `telegramAuthGate.enabled: true` + непустом `botUsername`, иначе тихий откат на `deny.js`. Включать **только вместе с `accsdb`**, и сначала связать свои устройства через бота — иначе текущие UID получат отказ. - `mutations_api_secret` одинаковый в обоих модулях, ненулевой; `bot_token` — в `init.conf`, **не коммитить** (правило 10): в git оба поля пустые. - `owner_telegram_ids: [143672]` → владелец создаётся как `admin` при старте. `auto_provision_users: true` + `auto_provision_activate_immediately: false` → новые привязки ждут одобрения (`/users` в боте, кнопки Принять/Отклонить). - Данные: `database/tgauth/{users.json,admins.json,user_langs.json}` (том `lampac_database`, владелец `1000:1000`). Живые данные — не «чинить» руками. - Проверка: `GET /tg/auth/status?uid=x` → 200; `GET /tg/auth/admin/users` → 401 без заголовка, 200 с `X-TelegramAuth-Mutations-Secret`. ### 13. WatchTogether (комнаты синхронного просмотра) - Копия в `mods/WatchTogether/` (`enable: true`), секция `WatchTogether` в `init.conf` (`enable`, `allow_anonymous: true`, `relay_max_channels: 500`, `relay_max_clients_per_channel: 32`, `gc_empty_timeout_minutes: 30`, `gc_max_lifetime_hours: 12`). - Клиентский плагин зарегистрирован в `LampaWeb.customPlugins`: `{localhost}/watchtogether.js`. - Роуты: `GET /watchtogether.js` → 200, `GET /watch_together/` → 200, `WS /wt/c/{channel}` → 400 на обычном GET (нужен WebSocket upgrade — это штатно, handshake даёт 101). - Lampac пассивно хранит состояние комнаты и становится **хостом-«тенью»** (`ShadowHost.cs`), если создатель отключился; роль возвращается ему при переподключении. Лаг ≤ 2 с. - При появлении reverse proxy: на `/wt/c/` обязателен WebSocket upgrade, отключённый buffering и таймаут больше длительности просмотра.