- config/init.conf — шаблон (секреты всегда пустые), в git - config/init.custom.conf — боевой конфиг, монтируется в /lampac/init.conf, собирается scripts/config-secrets.py apply из шаблона + secrets.env - docker-compose.yaml: монтирует ./config/init.custom.conf:/lampac/init.conf - scripts/config-secrets.py: apply/export/check/status - scripts/install-git-hooks.sh: post-merge/post-checkout -> apply, pre-commit -> check --staged - scripts/restore-lampa-main.sh: восстановление веб-клиента из архива - .gitignore: init.custom.conf, secrets.env, lampa-main/, restore.sh zip - lampa-main смонтирован как bind-mount ./lampa-main:/lampac/wwwroot/lampa-main, переживает docker compose up -d (раньше исчезал из writable-слоя) - README: раздел «Секреты», обновлена «Структура проекта» и «Обновление», «Безопасность» переписана под новую схему - AGENT: правило 10 дополнено init.custom.conf/secrets.env/хуками, §11 Tg-notify ссылается на apply, §1 упоминает lampa-main bind-mount
141 lines
17 KiB
Markdown
141 lines
17 KiB
Markdown
# 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`. **`mods/Music` удалён** (remote-коммит `remove Music`): в образе у Music `manifest.enable: false`, поэтому модуль не грузится, `/music.js` → 404, секция `"Music"` и плагин убраны из `init.conf`. `mods/WatchTogether` тоже удалён (вернуть — той же механикой, он в списке «ещё можно включить» в README).
|
||
|
||
**Веб-клиент `lampa-main` — bind-mount из `./lampa-main/`** (в .gitignore). В образе его нет, `LampaCron` качает с GitHub; при пересоздании контейнера без mount клиент исчезает. См. `scripts/restore-lampa-main.sh`.
|
||
|
||
### 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/<bucket>/<id>.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/<bucket>`) — штатное действие после правки 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` — секреты **пустые**. Реальные значения — в `config/secrets.env` (gitignored), боевой конфиг — `config/init.custom.conf` (gitignored). Собирается скриптом: `python3 scripts/config-secrets.py apply`. Перед коммитом: `python3 scripts/config-secrets.py check` (должен выдать `секретов в config/init.conf нет — ok`). После `git pull`/смены ветки `apply` запускается автоматически через git-хуки (`scripts/install-git-hooks.sh`).
|
||
|
||
### 11. Tg-notify.bot (Telegram-бот)
|
||
|
||
- Механика: long polling по `TelegramBot.bot_token`; если токен пуст — polling не стартует (в логе `[TelegramBot] Установите bot_token…`, сервер работает). После правки секции: `docker compose restart lampac`.
|
||
- **Токен только локально:** в git `config/init.conf` поле пустое (правило 10). Реальный токен живёт в `config/secrets.env`, а `docker-compose.yaml` монтирует `config/init.custom.conf` (собрать: `python3 scripts/config-secrets.py apply`). После `git pull` хук `post-merge` делает `apply` сам; если нет хуков — выполнить вручную. Второй экземпляр на том же токене даёт `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`.
|