Секреты вынесены в config/init.custom.conf + config/secrets.env
- 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
This commit is contained in:
+11
@@ -1,5 +1,7 @@
|
|||||||
# Секреты
|
# Секреты
|
||||||
config/passwd
|
config/passwd
|
||||||
|
config/secrets.env # токены ботов и mutations_api_secret
|
||||||
|
config/init.custom.conf # боевой конфиг со secrets (собирается config-secrets.py)
|
||||||
|
|
||||||
# Локальные данные и кеш (только корневые каталоги; якорь «/» обязателен —
|
# Локальные данные и кеш (только корневые каталоги; якорь «/» обязателен —
|
||||||
# иначе «cache/» игнорирует и mods/Music/Services/Cache/ и т.п.)
|
# иначе «cache/» игнорирует и mods/Music/Services/Cache/ и т.п.)
|
||||||
@@ -14,3 +16,12 @@ config/passwd
|
|||||||
|
|
||||||
# Серверный override (Proxmox: том DLNA -> /mnt/torrents), живёт только на сервере
|
# Серверный override (Proxmox: том DLNA -> /mnt/torrents), живёт только на сервере
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
|
|
||||||
|
# Секреты (не коммитить — см. README, раздел «Секреты»)
|
||||||
|
config/passwd
|
||||||
|
config/secrets.env
|
||||||
|
config/init.custom.conf
|
||||||
|
|
||||||
|
# Веб-клиент Lampa (качается LampaCron с github.com, нет в образе,
|
||||||
|
# пропадает при пересоздании контейнера; см. scripts/restore-lampa-main.sh)
|
||||||
|
wwwroot/lampa-main/
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ docker cp lampac:/lampac/module/<Имя> "mods/<Имя>"
|
|||||||
|
|
||||||
Сейчас в `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).
|
Сейчас в `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 независимых фильтра)
|
### 2. Механика загрузки модуля (4 независимых фильтра)
|
||||||
|
|
||||||
Модуль грузится, только если выполнены **все**:
|
Модуль грузится, только если выполнены **все**:
|
||||||
@@ -116,12 +118,12 @@ docker exec lampac sh -c '...' # осмотр состояния в конте
|
|||||||
|
|
||||||
`config/passwd`, cookie `accspasswd`, заголовок `lcrqpasswd`, `bot_token` в `TelegramBot`, `bot_token` в `TelegramAuthBot`, `mutations_api_secret`. Маршруты `/adminpanel`, `/weblog`, `Tracks`, `Catalog`, `DLNA`, proxy-модули — только для доверенной сети без WAF не открывать.
|
`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}"` → должно быть пусто.
|
**Секреты:** в 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-бот)
|
### 11. Tg-notify.bot (Telegram-бот)
|
||||||
|
|
||||||
- Механика: long polling по `TelegramBot.bot_token`; если токен пуст — polling не стартует (в логе `[TelegramBot] Установите bot_token…`, сервер работает). После правки секции: `docker compose restart lampac`.
|
- Механика: 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`.
|
- **Токен только локально:** в 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.
|
- Три подсистемы: трекер серий (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). Не «чинить» вручную — это живые данные бота.
|
- Данные подписок: `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); тогда уведомления приходят только о выходе серий.
|
- Диагностика: `GET /tg-notify.js` (200 без авторизации), `GET /api/tg/voices` (200), `/api/tg/*` остальной — требует `user_uid`. Озвучки могут не работать, если балансеры недоступны (проверить `GET /lite/mirage` — тело `disable`/ошибка, `/lite/collaps` — 503); тогда уведомления приходят только о выходе серий.
|
||||||
|
|||||||
@@ -64,22 +64,61 @@ ls -ln /var/lib/docker/volumes/lampac_database/_data/Sisi.sql # uid/gid 1000
|
|||||||
|
|
||||||
Проверить uid процесса в контейнере: `docker exec lampac id` → `uid=1000(lampac)`.
|
Проверить uid процесса в контейнере: `docker exec lampac id` → `uid=1000(lampac)`.
|
||||||
|
|
||||||
Правило: **любой bind-mount файл/каталог, в который пишет контейнер, должен принадлежать `1000:1000`** (см. `.gitignore` для `config/passwd`, `dlna/`).
|
Правило: **любой bind-mount файл/каталог, в который пишет контейнер, должен принадлежать `1000:1000`** (см. `.gitignore` для `config/passwd`, `dlna/`, `lampa-main/`).
|
||||||
|
|
||||||
|
## Секреты (вынесены из git)
|
||||||
|
|
||||||
|
Модули `TelegramBot`, `TelegramAuthBot`, `TelegramAuth` читают `bot_token` и `mutations_api_secret` из конфига. В репозитории эти поля **всегда пустые** — реальные значения живут в файле, который не коммитится.
|
||||||
|
|
||||||
|
| Файл | В git? | Назначение |
|
||||||
|
|---|---|---|
|
||||||
|
| `config/init.conf` | ✅ | Шаблон конфига, секреты **пустые**. Именно его редактируете для обычных настроек (`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`). |
|
||||||
|
|
||||||
|
**Рабочий процесс:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# первый раз / после 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
|
||||||
|
|
||||||
|
# перед коммитом:
|
||||||
|
python3 scripts/config-secrets.py export # секреты -> пустые в шаблоне
|
||||||
|
git add config/init.conf
|
||||||
|
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.0`) |
|
| `docker-compose.yaml` | Основной сервис (образ `ghcr.io/lampac-nextgen/lampac:1.68.1`) |
|
||||||
| `config/init.conf` | Конфигурация сервера (JSON, монтируется в `/lampac/init.conf`) |
|
| `docker-compose.override.yml` | Серверный override (Proxmox: том DLNA -> `/mnt/torrents`), в .gitignore |
|
||||||
| `config/passwd` | Пароль root для `/adminpanel` и `/weblog` (не в git) |
|
| `config/init.conf` | Шаблон конфига (JSON, в git, секреты **пустые**) |
|
||||||
| volume `lampac_cache` | Кеш (`/lampac/cache`) — Docker named volume, не файловый том |
|
| `config/init.custom.conf` | Боевой конфиг (в .gitignore, монтируется в `/lampac/init.conf`) |
|
||||||
|
| `config/secrets.env` | Токены ботов и `mutations_api_secret` (в .gitignore) |
|
||||||
|
| `config/passwd` | Пароль root для `/adminpanel` и `/weblog` (в .gitignore) |
|
||||||
|
| `lampa-main/` | Веб-клиент Lampa (bind-mount в `/lampac/wwwroot/lampa-main`, в .gitignore, качается LampaCron) |
|
||||||
|
| volume `lampac_cache` | Кеш (`/lampac/cache`) — Docker named volume |
|
||||||
| volume `lampac_database` | SQLite-базы Sync, TimeCode, tg-notify, tracks, storage (`/lampac/database`) — Docker named volume |
|
| volume `lampac_database` | SQLite-базы Sync, TimeCode, tg-notify, tracks, storage (`/lampac/database`) — Docker named volume |
|
||||||
| `dlna/` | Локальные медиа для DLNA (`/lampac/data/dlna`) |
|
| `dlna/` | Локальные медиа для DLNA (`/lampac/data/dlna`) |
|
||||||
| `plugins/extensions.json` | Каталог плагинов для веб-клиента (раздел «Плагины»), отдаётся через `/extensions` |
|
| `plugins/extensions.json` | Каталог плагинов для веб-клиента (раздел «Плагины»), отдаётся через `/extensions` |
|
||||||
| `mods/<Имя>/` | Пользовательские копии встроенных модулей с `enable: true` |
|
| `mods/<Имя>/` | Пользовательские копии встроенных модулей с `enable: true` |
|
||||||
|
|
||||||
> `docker-compose.override.yml` (в .gitignore) — серверное override для Proxmox (том DLNA -> `/mnt/torrents`), живёт только на сервере.
|
> Веб-клиент `lampa-main` **нет в образе**: `LampaCron` качает его с GitHub при старте, и он живёт только в writable-слое контейнера. При пересоздании контейнера (`docker compose up -d`) клиент исчезает, если не смонтирован как bind-mount. См. раздел «Секреты» и `scripts/restore-lampa-main.sh`.
|
||||||
|
|
||||||
## Включённые модули из `mods/`
|
## Включённые модули из `mods/`
|
||||||
|
|
||||||
@@ -287,7 +326,9 @@ docker compose pull
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
Перед обновлением проверить, что в `docker-compose.yaml` раскомментированы нужные тома. Данные сохраняются в примонтированных томах: `config/`, `cache/`, `database/`, `dlna/`, `mods/`.
|
Перед обновлением проверить, что в `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.
|
**Образ закреплён точным 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.
|
||||||
|
|
||||||
@@ -297,6 +338,6 @@ docker compose up -d
|
|||||||
|
|
||||||
- Не публикуй в интернет без аутентификации, WAF и ограничения сети: `/adminpanel`, `/weblog`, `/database-editor`, `Tracks`, `Transcoding`, `Catalog`, `DLNA`, proxy-модули.
|
- Не публикуй в интернет без аутентификации, WAF и ограничения сети: `/adminpanel`, `/weblog`, `/database-editor`, `Tracks`, `Transcoding`, `Catalog`, `DLNA`, proxy-модули.
|
||||||
- Нигде не раскрывай `config/passwd`, cookie `accspasswd`, заголовок `lcrqpasswd`.
|
- Нигде не раскрывай `config/passwd`, cookie `accspasswd`, заголовок `lcrqpasswd`.
|
||||||
- **Секреты в `config/init.conf` не коммитятся**: `TelegramBot.bot_token`, `TelegramAuthBot.bot_token`, `mutations_api_secret`. В git они должны быть пустыми — реальные значения живут только в файле на сервере (`/root/init.conf.local` — резервная копия перед мержем). Перед `git add config/init.conf` проверять `git diff` на токены (`git diff config/init.conf | grep -E ":[0-9]{8,}:AA"` → пусто).
|
- **Секреты в git не попадают**: `TelegramBot.bot_token`, `TelegramAuthBot.bot_token`, `mutations_api_secret` хранятся в `config/secrets.env` (в .gitignore). В git `config/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://docs.lampac.dev/deployment/reverse-proxy) (HTTPS в ответах, реальный IP клиента, WebSocket, SSE, streaming).
|
- Для публикации за reverse proxy: [deployment/reverse-proxy](https://docs.lampac.dev/deployment/reverse-proxy) (HTTPS в ответах, реальный IP клиента, WebSocket, SSE, streaming).
|
||||||
- `shm_size: 4096mb` — рекомендованный минимум для Playwright/Chromium.
|
- `shm_size: 4096mb` — рекомендованный минимум для Playwright/Chromium.
|
||||||
|
|||||||
+2
-1
@@ -11,13 +11,14 @@ services:
|
|||||||
shm_size: 4096mb
|
shm_size: 4096mb
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/passwd:/lampac/passwd
|
- ./config/passwd:/lampac/passwd
|
||||||
- ./config/init.conf:/lampac/init.conf
|
- ./config/init.custom.conf:/lampac/init.conf
|
||||||
- ./plugins/extensions.json:/lampac/module/LampaWeb/plugins/extensions.json
|
- ./plugins/extensions.json:/lampac/module/LampaWeb/plugins/extensions.json
|
||||||
- ./plugins:/lampac/wwwroot/plugins
|
- ./plugins:/lampac/wwwroot/plugins
|
||||||
- ./mods:/lampac/mods
|
- ./mods:/lampac/mods
|
||||||
- cache:/lampac/cache
|
- cache:/lampac/cache
|
||||||
- database:/lampac/database
|
- database:/lampac/database
|
||||||
- ./dlna:/lampac/data/dlna
|
- ./dlna:/lampac/data/dlna
|
||||||
|
- ./lampa-main:/lampac/wwwroot/lampa-main
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
cache:
|
cache:
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Вынос секретов Lampac из git: шаблон config/init.conf <-> боевой config/init.custom.conf.
|
||||||
|
|
||||||
|
В git (config/init.conf) секреты всегда ПУСТЫЕ — это делает рабочее дерево чистым,
|
||||||
|
поэтому git pull/merge никогда не конфликтует и секреты не уезжают в remote.
|
||||||
|
Для сервера собирается config/init.custom.conf (шаблон + config/secrets.env),
|
||||||
|
он монтируется в контейнер вместо init.conf (см. docker-compose.yaml) и
|
||||||
|
в .gitignore.
|
||||||
|
|
||||||
|
Команды:
|
||||||
|
apply config/init.conf + config/secrets.env -> config/init.custom.conf (после git pull)
|
||||||
|
export config/init.custom.conf -> config/init.conf, секреты обнуляются (перед git add)
|
||||||
|
check проверяет, что в config/init.conf (или в staged-версии) нет секретов
|
||||||
|
status показывает, что где лежит
|
||||||
|
|
||||||
|
Обычно руками ничего не нужно: scripts/install-git-hooks.sh вешает
|
||||||
|
post-merge/post-checkout на apply и pre-commit на check.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
TEMPLATE = os.path.join(ROOT, "config", "init.conf")
|
||||||
|
LIVE = os.path.join(ROOT, "config", "init.custom.conf")
|
||||||
|
SECRETS = os.path.join(ROOT, "config", "secrets.env")
|
||||||
|
|
||||||
|
# (секция, поле) в init.conf -> ключ в config/secrets.env
|
||||||
|
MAP = [
|
||||||
|
(("TelegramBot", "bot_token"), "TELEGRAM_NOTIFY_BOT_TOKEN"),
|
||||||
|
(("TelegramAuthBot", "bot_token"), "TELEGRAM_AUTH_BOT_TOKEN"),
|
||||||
|
(("TelegramAuth", "mutations_api_secret"), "TELEGRAM_AUTH_MUTATIONS_SECRET"),
|
||||||
|
(("TelegramAuthBot", "mutations_api_secret"), "TELEGRAM_AUTH_MUTATIONS_SECRET"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def load_secrets() -> dict[str, str] | None:
|
||||||
|
"""None, если secrets.env отсутствует (тогда apply просто предупреждает)."""
|
||||||
|
if not os.path.exists(SECRETS):
|
||||||
|
return None
|
||||||
|
out: dict[str, str] = {}
|
||||||
|
for line in open(SECRETS, encoding="utf-8"):
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
k, v = line.split("=", 1)
|
||||||
|
out[k.strip()] = v.strip()
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def load_json(path: str) -> dict:
|
||||||
|
with open(path, encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def save_json(path: str, data: dict) -> None:
|
||||||
|
tmp = path + ".tmp"
|
||||||
|
with open(tmp, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
f.write("\n")
|
||||||
|
os.replace(tmp, path)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_apply() -> None:
|
||||||
|
"""Шаблон + секреты -> боевой файл, который монтируется в контейнер."""
|
||||||
|
tpl = load_json(TEMPLATE)
|
||||||
|
sec = load_secrets()
|
||||||
|
if sec is None:
|
||||||
|
print(f"apply: нет {os.path.relpath(SECRETS, ROOT)} — "
|
||||||
|
f"{os.path.relpath(LIVE, ROOT)} не создан (секреты останутся пустыми)")
|
||||||
|
return
|
||||||
|
missing = []
|
||||||
|
for (section, field), key in MAP:
|
||||||
|
value = sec.get(key, "")
|
||||||
|
if not value:
|
||||||
|
missing.append(key)
|
||||||
|
if section in tpl:
|
||||||
|
tpl[section][field] = value
|
||||||
|
save_json(LIVE, tpl)
|
||||||
|
# контейнер пишет в боевой конфиг через AdminPanel (uid 1000)
|
||||||
|
try:
|
||||||
|
os.chown(LIVE, 1000, 1000)
|
||||||
|
except PermissionError:
|
||||||
|
pass
|
||||||
|
os.chmod(LIVE, 0o644)
|
||||||
|
print(f"apply: {os.path.relpath(LIVE, ROOT)} собран из шаблона + secrets.env")
|
||||||
|
if missing:
|
||||||
|
print(f" внимание: нет значения для {', '.join(sorted(set(missing)))} — поле останется пустым")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_export() -> None:
|
||||||
|
"""Боевой файл -> шаблон, секреты обнуляются (для git add)."""
|
||||||
|
if not os.path.exists(LIVE):
|
||||||
|
print(f"export: {os.path.relpath(LIVE, ROOT)} нет — нечего экспортировать")
|
||||||
|
return
|
||||||
|
live = load_json(LIVE)
|
||||||
|
changed = 0
|
||||||
|
for (section, field), _ in MAP:
|
||||||
|
if section in live and live.get(section, {}).get(field):
|
||||||
|
live[section][field] = ""
|
||||||
|
changed += 1
|
||||||
|
save_json(TEMPLATE, live)
|
||||||
|
print(f"export: шаблон обновлён из боевого конфига, секретов обнулено: {changed}")
|
||||||
|
|
||||||
|
|
||||||
|
def read_template_for_check(staged: bool) -> str:
|
||||||
|
if staged:
|
||||||
|
try:
|
||||||
|
return subprocess.check_output(
|
||||||
|
["git", "show", ":config/init.conf"], cwd=ROOT, text=True
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
sys.exit("check: config/init.conf не в индексе")
|
||||||
|
return open(TEMPLATE, encoding="utf-8").read()
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_check(staged: bool = False) -> None:
|
||||||
|
"""Падает (exit 1), если в шаблоне есть непустой секрет."""
|
||||||
|
data = json.loads(read_template_for_check(staged))
|
||||||
|
found = []
|
||||||
|
for (section, field), key in MAP:
|
||||||
|
value = (data.get(section) or {}).get(field)
|
||||||
|
if value:
|
||||||
|
found.append(f"{section}.{field} ({key}) = {value[:6]}…")
|
||||||
|
if found:
|
||||||
|
sys.exit(
|
||||||
|
"check: ОТКАЗ — в config/init.conf есть секреты, коммитить нельзя:\n "
|
||||||
|
+ "\n ".join(found)
|
||||||
|
+ "\nВыполните: python3 scripts/config-secrets.py export"
|
||||||
|
)
|
||||||
|
print("check: секретов в config/init.conf нет — ок")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_status() -> None:
|
||||||
|
tpl = load_json(TEMPLATE)
|
||||||
|
live = load_json(LIVE) if os.path.exists(LIVE) else None
|
||||||
|
print(f"{'путь':28} {'есть?' :6} секреты")
|
||||||
|
for (section, field), key in MAP:
|
||||||
|
t = (tpl.get(section) or {}).get(field)
|
||||||
|
l = (live.get(section) or {}).get(field) if live else None
|
||||||
|
print(
|
||||||
|
f"{section}.{field:24} шаблон={'заполнен' if t else 'пусто':8} "
|
||||||
|
f"боевой={'заполнен' if l else ('нет файла' if live is None else 'пусто')}"
|
||||||
|
)
|
||||||
|
print(f"secrets.env: {'есть' if os.path.exists(SECRETS) else 'НЕТ'}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
cmd = args[0] if args else "status"
|
||||||
|
if cmd == "apply":
|
||||||
|
cmd_apply()
|
||||||
|
elif cmd == "export":
|
||||||
|
cmd_export()
|
||||||
|
elif cmd == "check":
|
||||||
|
cmd_check(staged="--staged" in args)
|
||||||
|
elif cmd == "status":
|
||||||
|
cmd_status()
|
||||||
|
else:
|
||||||
|
sys.exit(__doc__)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Вешает git-хуки, которые держат секреты вне git.
|
||||||
|
# post-merge / post-checkout -> apply (собирает config/init.custom.conf)
|
||||||
|
# pre-commit -> check (отменяет коммит с секретами)
|
||||||
|
set -e
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
mkdir -p .git/hooks
|
||||||
|
|
||||||
|
cat > .git/hooks/post-merge <<'H'
|
||||||
|
#!/bin/sh
|
||||||
|
exec python3 scripts/config-secrets.py apply
|
||||||
|
H
|
||||||
|
|
||||||
|
cat > .git/hooks/post-checkout <<'H'
|
||||||
|
#!/bin/sh
|
||||||
|
exec python3 scripts/config-secrets.py apply
|
||||||
|
H
|
||||||
|
|
||||||
|
cat > .git/hooks/pre-commit <<'H'
|
||||||
|
#!/bin/sh
|
||||||
|
exec python3 scripts/config-secrets.py check --staged
|
||||||
|
H
|
||||||
|
|
||||||
|
chmod +x .git/hooks/post-merge .git/hooks/post-checkout .git/hooks/pre-commit
|
||||||
|
echo "хуки установлены:"
|
||||||
|
ls -l .git/hooks/post-merge .git/hooks/post-checkout .git/hooks/pre-commit
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Восстанавливает веб-клиент Lampa (wwwroot/lampa-main), который LampaCron
|
||||||
|
# берёт с github.com и который пропадает при пересоздании контейнера
|
||||||
|
# (нет в образе, живёт только в writable-слое).
|
||||||
|
#
|
||||||
|
# bash scripts/restore-lampa-main.sh scripts/lampa-main.zip
|
||||||
|
set -e
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
ZIP="${1:-scripts/lampa-main.zip}"
|
||||||
|
if [ ! -f "$ZIP" ]; then
|
||||||
|
echo "нет $ZIP — берите архив с https://github.com/yumata/lampa/archive/refs/heads/main.zip"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker exec lampac sh -c "rm -rf /lampac/wwwroot/lampa-main /lampac/wwwroot/lampa.zip"
|
||||||
|
docker cp "$ZIP" lampac:/lampac/wwwroot/lampa.zip
|
||||||
|
|
||||||
|
docker exec lampac sh -c '
|
||||||
|
cd /lampac/wwwroot
|
||||||
|
unzip -q -o lampa-main.zip
|
||||||
|
mv lampa-main-main lampa-main 2>/dev/null || mv lampa-main lampa-main 2>/dev/null || true
|
||||||
|
chown -R 1000:1000 lampa-main
|
||||||
|
rm -f lampa.zip
|
||||||
|
# инъекция lampainit.js (делает LampaCron при следующем старте)
|
||||||
|
python3 - <<PY
|
||||||
|
import os
|
||||||
|
p="/lampac/wwwroot/lampa-main/index.html"
|
||||||
|
html=open(p,encoding="utf-8").read()
|
||||||
|
if "src=\"/lampainit.js\"" not in html:
|
||||||
|
html=html.replace("</body>", "<script src=\"/lampainit.js\"></script></body>")
|
||||||
|
open(p,"w",encoding="utf-8").write(html)
|
||||||
|
print("index.html: lampainit.js внедрён")
|
||||||
|
else:
|
||||||
|
print("index.html: уже был")
|
||||||
|
PY
|
||||||
|
'
|
||||||
|
|
||||||
|
echo "lampa-main восстановлен ($(docker exec lampac sh -c 'find /lampac/wwwroot/lampa-main -type f | wc -l') файлов)"
|
||||||
Reference in New Issue
Block a user