Files
infra/servers/rpi.md
T

159 lines
46 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# rpi — домашняя Raspberry Pi 4 (`rpi.local`, 192.168.10.25)
Поднята 2026-10-11. Домашний сервер за NAT: первая задача — бэкап личного
OneDrive на внешний HDD; с того же дня ещё и медиацентр (Jellyfin + *arr, Docker). Мониторится с fr1 через pushgateway (скрейпить
её снаружи нельзя — она за домашним роутером).
## Железо и ОС
- Raspberry Pi 4, SD 64 ГБ (`mmcblk0`), питание штатное — `vcgencmd get_throttled` = `0x0`.
- Raspberry Pi OS Lite 64-bit **Trixie** (Debian 13), образ `2026-10-06-raspios-trixie-arm64-lite`.
- Headless-настройка — **cloud-init**, а не старые `ssh`/`userconf.txt`: в Trixie Pi Imager пишет `user-data`/`network-config` на `bootfs`. Наш `user-data` создал пользователя, ключ, hostname, таймзону MSK.
- Сеть: Ethernet 1 Гбит/с, DHCP. MAC `dc:a6:32:db:b0:f9`, роутер `192.168.10.254` (Huawei HG8245X6-10, Cyta). Адрес `192.168.10.25` закреплён за MAC статической DHCP-привязкой на роутере (2026-10-11; была без резерва).
## Доступ
- С Mac: `ssh rpi` (алиас → `rpi.local`, user `rus`, ключ `~/.ssh/id_ed25519`). Резолвится через mDNS (avahi в образе по умолчанию).
- Только по ключу: `passwordauthentication no`, у `rus` пароль заблокирован, **sudo NOPASSWD** (иначе sudo был бы недоступен вовсе).
## Диски
| Устройство | Модель | Роль |
|---|---|---|
| `sda` | WD My Passport 4 ТБ (WD40NDZW, s/n WD-WX82D6228YCD) | ext4 `backup` → `/mnt/backup` |
| — | WD Elements 1 ТБ (WD10SDRW) | **в USB роутера**, не малины: NTFS/MBR, метка `ROUTER` → `router:` / `/mnt/router` (см. «Вторая копия») |
- fstab по UUID с `nofail,x-systemd.device-timeout=30s` (`stacks/rpi-backup/fstab.line`): без диска малина грузится, бэкап просто пропускается (`mountpoint -q`).
- ⚠️ **Два 2.5" HDD от USB Pi 4 не тянут.** Проверено 2026-10-11: при раскрутке обоих — `over-current` на всех USB3-портах; под нагрузкой 4-ТБ диск отваливался (`USB disconnect`) и залипал в `Unit Not Ready`. Один диск стабилен (20 ГБ чтения 103 МБ/с, mkfs ~60 ГБ записи без сбросов). Второй диск — только через USB3-хаб с собственным питанием.
- Залипший после отвала диск лечится power-cycle всех USB (Pi 4 переключает питание портов только разом): `sudo uhubctl -l 2 -a off; sudo uhubctl -l 1-1 -a off; sleep 8; …-a on`.
- SMART на момент ввода: 9 897 ч, 0 переназначенных/ожидающих, Start_Stop 21 946, Load_Cycle 42 530.
## Бэкап OneDrive (код — `stacks/rpi-backup/`)
- rclone (официальный .deb, v1.75.2 — в Debian 1.60, старый), remote `onedrive:` = личный OneDrive (drive_type personal). Токен — в `~rus/.config/rclone/rclone.conf` (600), только на малине. Получен через `rclone authorize onedrive` на Mac.
- `onedrive-backup` (таймер каждые 4 ч, `00/4:30`): `rclone sync` в `/mnt/backup/onedrive/current`, всё удалённое/перезаписанное в облаке — в `archive/<ts>/` **навсегда** (пользователь хочет иметь всё, что когда-либо было в OneDrive). Хэши архива — `manifests/archive-<ts>.qxh`, лог каждого прогона — `logs/sync-<ts>.log` (INFO: поштучно что скачано/убрано в архив).
- Целостность: rclone сверяет QuickXorHash каждого скачанного файла с OneDrive; `onedrive-verify` (вс 05:00) перечитывает весь HDD: `rclone check` current↔облако + `rclone checksum` archive↔манифесты. Отчёт — `logs/verify-<ts>*`.
- Оба юнита под общим `flock /run/lock/onedrive-backup.lock` — не идут параллельно и не убивают друг друга.
- ⚠️ Файл, загруженный и удалённый в облаке между прогонами (<4 ч), в бэкап не попадёт.
- ⚠️ `Personal Vault` исключён — rclone через API туда не ходит.
- ⚠️ Архив без ротации: растёт вечно. Скрипт выходит с кодом 2 при заполнении >90 % (→ алерт BackupFailed), плюс HostDisk* если когда-то поставим node-exporter.
- Первая полная выгрузка (368 ГБ) запущена 2026-10-11 ~13:13, ~27 МБ/с (упор в домашний канал).
## Вторая копия — 1 ТБ диск в роутере (с 2026-10-11)
- Диск воткнут в роутер Huawei (`192.168.10.254`), а не в малину — **не ест питание USB малины** (два HDD от неё не тянут, см. «Диски»). Доступ — SFTP роутера на порту **8022**, пользователь `root`, remote `router:` в `~rus/.config/rclone/rclone.conf` (пароль только там), host key — `~rus/.ssh/known_hosts_router`.
- ⚠️ Роутер не считает хэши (`shell_type = none`, `md5sum_command = none`) — rclone сравнивает по размеру+mtime, целостность проверяется перечиткой.
- `mnt-router.service` — `rclone mount router: /mnt/router` (user rus, `--vfs-cache-mode writes`, кэш в `/mnt/backup/.rclone-cache`). Для просмотра/ручной работы; бэкап пишет **напрямую в remote**, не через FUSE (коды ошибок, без кэша).
- Скорость (замер 2026-10-11, параллельно с выгрузкой OneDrive): запись ~8 МБ/с, чтение ~11 МБ/с — первичная копия ~370 ГБ ≈ 13 ч. Диск 932 ГБ.
- `router-backup` (`stacks/rpi-backup/`, таймер 06:00, `flock`): в `router:rpi-backup/` — `onedrive/{archive,manifests,logs}` только `copy` (там ничего не удаляется), затем зеркало `onedrive/current` с `--max-delete 1000`, затем зеркало зашифрованных `servers/` с `--max-delete 50`. Порядок важен: удалённое из current к этому моменту уже скопировано в archive. Логи — `/mnt/backup/router-logs/`.
- ⚠️ `--max-delete` — предохранитель: если локально «пропадёт» много файлов (сбой диска/монтирования), копия на роутере не зачистится, а прогон упадёт с ошибкой → алерт `BackupFailed`.
- По воскресеньям `verify`: сверка размеров деревьев + перечитать с роутера последний daily каждого сервера и сравнить sha256.
- **Ежемесячно (1-го, 08:00) `router-sample`**: случайные файлы OneDrive из `current/` и `archive/` старше суток, ~2 ГБ / до 300 шт., перечитываются с роутера и сверяются sha256 с HDD — ловит тихую порчу, которую сверка размеров не видит. Отсутствующий на роутере файл = ошибка. Итог — `backup_*{job="backup_router_sample"}` → `BackupFailed`, `BackupStale` (>35 дней). Проверено 2026-10-11: 20 из 20 скопированных файлов совпали.
- Мониторинг: `router_mount_up` и заполнение `/mnt/router` (в `smart-metrics`, `df` под `timeout 20`), итог прогона — `backup_*{job="backup_router"}`; алерты `RouterDiskMountDown` (30 мин), `BackupDiskFull` (>90 %, любой диск бэкапа), `BackupStale` (>30 ч); ряд «Вторая копия» на дашборде rpi-disk.
- ⚠️ 1 ТБ меньше основного диска: сейчас влезает (~370 ГБ OneDrive + ~100 ГБ серверов), но архив версий растёт вечно — следить за `BackupDiskFull`.
- Почему NTFS (2026-10-11): прошивка Cyta видит FAT32 и NTFS, **exFAT — нет** (диск в списке USB не появлялся); FAT32 отпал из-за лимита 4 ГБ на файл. Файл 4,4 ГБ через SFTP записан и прочитан. На Mac NTFS штатно не создать — делали `mkntfs` из ntfs-3g, собранного без FUSE (`--disable-ntfs-3g`), на блочное `/dev/diskNs1` (на `rdisk` mkntfs отказывается).
- ⚠️ FTP роутера (порт 21) включён в его конфиге, но из LAN **filtered**: доступ к FTP со стороны LAN открывается только на странице ONT Access Control, а у пользователя `cytauser` её нет (нужен суперадмин Cyta). Поэтому SFTP: включён 2026-10-11 через веб-API роутера — сервер `8022`, LAN=1, **WAN=0**, аккаунт `root` с корнем `/mnt/usb/usb-1058-363434_1`. Пароли SFTP и веб-интерфейса — только у пользователя и в `rclone.conf`.
- ⚠️ Потолок ~10 МБ/с — CPU роутера (dropbear): `aes128/256-ctr` вдвое медленнее `chacha20` (нет аппаратного AES), параллельные потоки почти не помогают. Быстрее только диск на малине через хаб с питанием (~100 МБ/с).
## Бэкап серверов fr1/ru1 (с 2026-10-11, код — `stacks/server-backup/`)
- Малина — внешняя площадка для VPS: `servers-backup.timer` (02:15) забирает по SSH выгрузки, которые **сервер сам шифрует открытым ключом age** (`tar | zstd | age`). Закрытого ключа на малине нет — ни она, ни Astra, ни взломщик архивы не прочитают. Ключ у владельца: Mac `~/.config/age/servers-backup.key`.
- Хранилище `/mnt/backup/servers/<host>/{daily,weekly,monthly}` (root 700): 14 / 8 / 12, monthly — хардлинк первого daily месяца. fr1 daily ~2.1 ГБ (5.5 мин), ru1 ~17 МБ, fr1 weekly (вс) — тома ботов и imgbot.
- Ключ `/etc/server-backup/id_ed25519` (root 600): на серверах ограничен forced command — только запустить выгрузку. Host keys закреплены в `/etc/server-backup/known_hosts`.
- Целостность: sha256 шифротекста считает сервер, малина сверяет при приёме и по воскресеньям (`servers-backup verify`). Итог — метрики `backup_*{job="backup_servers"}`, алерт `BackupStale` >26 ч, панель на дашборде rpi-disk.
- Пробное восстановление 2026-10-11 на Mac: оба архива расшифрованы, на месте CA OpenVPN (`easy-rsa/pki/private/ca.key`), ключи AWG, конфиги прокси, репо gitea, снимок `gitea.db`.
## Мониторинг → fr1
- `smartd` (`stacks/rpi-backup/smartd.conf`): `DEVICESCAN -d removable`, `-n standby,q` (не будить диск), короткий самотест сб 03:00, длинный 1-го числа 05:00. ⚠️ `-d sat` с DEVICESCAN не работает («Not a SAT device») — тип моста пусть определяет сам.
- `smart-metrics` (таймер 15 мин): smartctl `-n standby` → метрики `smart_*` → job `smart`. Спящий диск не будится: отдаются значения из кэша `/var/lib/smart-metrics/` + `smart_disk_standby=1`.
- `backup-metrics <job>` — `ExecStopPost=+` у юнитов бэкапа: `backup_last_exit_code`, `backup_last_run_timestamp`, `backup_last_success_timestamp` (время успеха хранится локально в `/var/lib/backup-metrics/`, т.к. PUT заменяет группу целиком).
- Доставка: `metrics-push <job>` = ssh с ключом `/etc/metrics-push/id_ed25519` (root, 600) на `rus@161.97.93.252`; host key fr1 закреплён в `/etc/metrics-push/known_hosts`. На fr1 ключ ограничен forced command (`servers/fr1.md` → Мониторинг).
- `smart-metrics` заодно отдаёт `backup_disk_size_bytes`/`backup_disk_avail_bytes` для `/mnt/backup` (statfs, диск не будит).
- Дашборд: https://mon.ruzzy.dev/d/rpi-disk (SMART, износ за сутки, свежесть бэкапов, заполнение).
- ⚠️ `BackupStale` видит только **существующие** серии — задача, ни разу не отчитавшаяся, им не ловится. Для этого `BackupNeverReported` на `absent()` (с 2026-10-11): OneDrive 12 ч, серверы 26 ч, роутер 30 ч, проверка OneDrive 8 дней; и `PushMetricsStale` на отсутствие SMART-метрик 1 ч. Доставка в Telegram проверена тестовым алертом 2026-10-11.
- **monwatch — сторож самого мониторинга** (с 2026-10-11, `stacks/rpi-backup/bin/monwatch`, таймер раз в час): весь мониторинг живёт на fr1, и если упадёт fr1 или цепочка доставки — алертов не будет вообще. Малина спрашивает fr1 `health` (тот же ключ метрик, режим только чтение): живы ли Prometheus/Alertmanager/pushgateway, видит ли Prometheus пуши малины моложе часа, не растут ли `alertmanager_notifications_failed_total{integration="telegram"}`. При проблеме (после повторной проверки через 2 мин) пишет в Telegram **напрямую, минуя fr1** — ботом @rasp_contol_bot (токен из `/etc/astra-bot.env`) в чат 607015; повтор раз в 6 ч, ✅ при восстановлении. Состояние — `/var/lib/monwatch/`. Путь тревоги проверен имитацией 2026-10-11 (`MONWATCH_MAX_PUSH_AGE=1`).
- ⚠️ Сообщения сторожа приходят в чат с ботом Astra — это бот сам пишет, Codex их не видит и не обрабатывает.
- Алерты — группа `rpi` в `stacks/monitoring/prometheus/rules/alerts.yml`.
## Домашняя печать (с 2026-10-11)
- Brother DCP-L2620DW обнаружен через mDNS: `192.168.10.24`, `BRW2C9C58C1EEA7.local`, IPP порт 631 `/ipp/print`; чёрно-белый, A4, двусторонняя печать. Ранее службы печати на rpi не было.
- Установлены `cups`, `cups-client`, `cups-filters`, `cups-ipp-utils` (CUPS 2.4.10); включён `cups.service`. Очередь по умолчанию `home-brother`, драйвер `everywhere`, URI `ipp://BRW2C9C58C1EEA7.local:631/ipp/print`. По умолчанию A4, одна сторона; `printer-is-shared=false`, CUPS слушает только `127.0.0.1:631` и `[::1]:631`. Доступ из Telegram — команда агента, без отдельного открытого веб-интерфейса.
- Код настройки и команда — `stacks/rpi-print/`; `home-print` установлен в `/usr/local/bin/` (root, 755). `home-print status` — состояние/очередь; `home-print [--duplex] <PDF/PNG/JPEG>` — одна копия, A4 с подгонкой. Печать только по прямой просьбе пользователя; приём задания в очередь не означает, что бумага уже вышла.
- **Навык `home-print` упрощён для ребёнка 2026-10-11**; источник — [astra-bot `11d0a85`](https://git.ruzzy.dev/ruzzy/astra-bot/commit/11d0a85), `codex/skills/home-print/`, установлен в `~astra/.codex/skills/home-print/`. Вместо PNG каждой страницы и повторного подтверждения отправляет **один PDF**, затем по прямой просьбе «распечатай» печатает одну копию A4 с одной стороны без дополнительных вопросов. Текст извлекается из скриншотов, картинки сохраняются в оттенках серого, все страницы проверяются локально. Если PDF не доставлен, печать не запускается; повторная отправка задания при ошибке не автоматическая. Для этого сценария установлены узкие постоянные разрешения `~astra/.codex/rules/home-print.rules` (источник `codex/rules/` в astra-bot): только `/usr/local/bin/home-print` и PDF-режим штатного отправщика. Общая политика подтверждений сохранена. Проверка: 31 тест, валидация скилла и правил; произвольный Python правилами не разрешается. Инструкции установлены в `~/.codex/AGENTS.md`; новые разговоры получают их автоматически.
- **Кнопки и промежуточные ответы печати убраны в самом боте 2026-10-11**, [astra-bot `6b1317d`](https://git.ruzzy.dev/ruzzy/astra-bot/commit/6b1317d). Ранее постоянных правил скилла было недостаточно: запросы approval и все agentMessage уходили в Telegram. Бот теперь автоматически отвечает accept только на штатную PDF-отправку разрешённому чату и home-print для print.pdf в каталогах /tmp/astra-print-*; shell-операторы и произвольные команды исключены. В задачах печати сохраняет последний ответ и отправляет его после завершения, PDF доставляется отдельным вложением. Проверено 34 тестами, установлен bot.py; прежний код — /opt/astra-bot/bot.py.bak-20261011-child-print. Перезапуск через restart-check.py после завершения текущего ответа.
- ⚠️ DOCX напрямую не печатается — сначала преобразовать в PDF. Hostname выбран вместо DHCP-IP, чтобы пережить смену адреса; требуется mDNS и включённый принтер. `printer-error-policy=abort-job` исключает неожиданную автоматическую печать старых заданий после ошибки; ошибочный прогон нужно отправить заново.
- Проверено: запрос атрибутов IPP (`ipptool`) PASS, принтер idle/enabled, A4/Gray/duplex доступны. Портрет успешно преобразован фильтрами CUPS PNG → PDF → PWG Raster (A4, 300 dpi), без ошибок. Тестовая страница A4 отправлена 2026-10-11 в 15:20 MSK: `home-brother-1`, CUPS сообщил `job-completed-successfully`, принтер вернулся в idle, очередь пуста; качество на бумаге проверяет пользователь.
- Для перевёрстки извлечённого текста в PDF установлен `python3-reportlab` 4.3.1 (2026-10-11; ранее средств создания PDF с кириллицей не было), с зависимостями Pillow/Cairo. Шрифт DejaVu Sans внедряется в PDF; PNG-предпросмотр строится из итогового серого PDF. Проверено на комплекте Лермонтова: 3 страницы A4, текст со скриншотов и исходный портрет.
## Медиацентр (с 2026-10-11, код — `stacks/rpi-media/`)
Docker CE 29.9 + Compose v5.6 из репо download.docker.com (в Debian старее). `/etc/docker/daemon.json`: json-file `10m×3` — чтобы логи контейнеров не точили SD. `rus` в группе `docker` (= root-эквивалент; у него и так sudo NOPASSWD). Стек — `/opt/media/docker-compose.yml`, конфиги — `/opt/media/config/<сервис>` (SD, ~20 МБ), образы ~7.8 ГБ на SD.
| Сервис | Порт | Зачем | Версия на 2026-10-11 |
|---|---|---|---|
| Jellyfin | 8096 (host-сеть) | медиасервер; клиенты LG webOS, Infuse/Swiftfin на iOS | 12.2 |
| Seerr | 5055 | каталог «хочу посмотреть» → Radarr/Sonarr | v3.5.0 |
| qBittorrent | 8080, торренты 6881 tcp/udp | загрузка | 5.2.4 |
| Radarr / Sonarr | 7878 / 8989 | фильмы / сериалы: поиск, загрузка, импорт | 6.4.4 / 4.0.20 |
| Prowlarr | 9696 | индексаторы, раздаёт их в Radarr/Sonarr | 2.6.5 |
| FlareSolverr | 8191 | обход Cloudflare для индексаторов с тегом `flaresolverr` | v3.5.2 |
| Bazarr | 6767 | субтитры, профиль `ru+en` | v1.6.2 |
| Homepage | 3000 | **стартовая страница**: все сервисы с описанием по-русски, живой статус, CPU/температура/диск | v2.5.0 |
- **Данные — `/mnt/backup/media`** (4-ТБ диск, решение пользователя): `torrents/{movies,tv,other}`, `library/{movies,tv}`, `.cache/jellyfin` (кэш и транскоды Jellyfin — не на SD). qBittorrent и *arr монтируют **один и тот же** корень как `/data` — иначе импорт из `torrents/` в `library/` превращается из хардлинка в копию и раздача занимает место дважды. `copyUsingHardlinks` включён. В бэкапы (`router-backup`, `onedrive-*`) `media/` не входит.
- ⚠️ **Медиа делят диск с бэкапами.** `onedrive-backup` падает с кодом 2 при заполнении >90 % (~370 ГБ свободно) → `BackupFailed`. В Radarr/Sonarr стоит `minimumFreeSpaceWhenImporting = 500 ГБ`: ниже этого импорт останавливается. **qBittorrent своего порога не имеет** — ручная заливка торрентов мимо *arr может забить диск.
- Jellyfin в `network_mode: host` — иначе LG/iOS не находят сервер автообнаружением (UDP 7359 и DLNA-мультикаст не проходят через бридж). Аппаратного транскодинга на Pi 4 нет (V4L2 убран в Jellyfin 10.9), `EncodingThreadCount=3`: расчёт на Direct Play. ⚠️ Транскод 4K/HEVC Pi не тянет — клиенту нужен нативный кодек (Infuse играет почти всё сам).
- Метаданные и интерфейс — ru, предпочтительные аудио/субтитры у пользователя — `rus`, режим субтитров Smart. Библиотеки «Фильмы» (`/data/library/movies`) и «Сериалы» (`/data/library/tv`), в Jellyfin только чтение.
- Индексаторы (публичные, без аккаунтов): NoNaMe Club, The Pirate Bay, YTS — синхронизированы в Radarr/Sonarr; Rutor и EZTV есть в Prowlarr, но в *arr не доехали (Rutor не проходит тест по категориям, EZTV на момент настройки лежал); 1337x не добавился (таймаут даже через FlareSolverr). **RuTracker** (добавлен 2026-10-11, аккаунт пользователя; логин/пароль — только в БД Prowlarr): через FlareSolverr, опции «убрать кириллицу», «добавить RUS», «первые теги в конец» — чтобы *arr сопоставляли английские названия и видели русскую дорожку; приоритет 10 (выше публичных, 25). Синхронизирован в Radarr и Sonarr. Kinozal и прочие приватные — руками.
- ⚠️ RuTracker закрывает `login.php`/`tracker.php` челленджем Cloudflare (по IPv4; IPv6 у малины нет маршрута). FlareSolverr на Pi решает его **80–180 с** — у прокси в Prowlarr `requestTimeout=180` (было 60, с ним добавление падало по таймауту). После решения Prowlarr держит cookie и поиск идёт ~4 с; когда cookie протухнет, первый поиск снова будет минутами, и *arr могут на время пометить индексатор недоступным.
- Bazarr: провайдеры без аккаунтов (`embeddedsubtitles`, `podnapisi`, `yifysubtitles`); для русских субтитров лучший — OpenSubtitles.com, нужен аккаунт.
- **Авторизация:** везде пользователь `rus`, один пароль — `/opt/media/.admin-password` (rus 600). *arr — формы, пароль **всегда** (режим «без пароля из LAN» в новых *arr требует `AllowedHosts`). Между собой сервисы ходят по API-ключам из `config/<app>/config.xml`.
- **Точка входа для пользователя — http://192.168.10.25:3000** (Homepage, конфиги `stacks/rpi-media/homepage/` → `/opt/media/homepage/`). Ключи виджетов — `/opt/media/homepage.env` (rus 600, `HOMEPAGE_VAR_*`, генерит `provision.py homepage`). ⚠️ Там же пароль qBittorrent: после смены общего пароля перегенерировать env и `docker compose up -d homepage`, иначе виджет qBittorrent отвалится. `HOMEPAGE_ALLOWED_HOSTS` обязателен — без него 400 на любой Host, кроме localhost.
- ⚠️ Встроенный виджет `jellyfin` Homepage 2.5 с Jellyfin 12 не работает: ходит на `/emby/…?api_key=` — пути нет (404), ключ в query отключён (`EnableLegacyAuthorization=false`, включать не стали). Вместо него `customapi` на `/Items/Counts` с заголовком `Authorization: MediaBrowser Token=…`.
- Radarr/Sonarr → Jellyfin (Connect «Jellyfin», тип MediaBrowser, ключ Jellyfin `homepage`; `provision.py connect` на чистой установке заводит ключ `arr-connect`): библиотека обновляется сразу после импорта. Без этого новый фильм висел невидимым до скана по расписанию — realtime-монитор хардлинк не заметил (проверено 2026-10-11 на первом фильме).
- Первичная связка — `stacks/rpi-media/provision.py` (идемпотентный, пароль и ключи читает на месте): `python3 /opt/media/provision.py [qbit jellyfin arr prowlarr bazarr seerr connect homepage]`.
- ⚠️ Каталог `config/<сервис>`, которого нет до `up`, docker создаёт **от root** — Jellyfin/Seerr (uid 1000) падают с `Permission denied`. Новые каталоги создавать заранее или `chown -R rus:rus /opt/media/config`.
- ⚠️ `fallenbagel/jellyseerr` заброшен (последний образ 2025-08) и не логинится в Jellyfin 12 — заменён на `ghcr.io/seerr-team/seerr`.
- Торренты идут **напрямую** с домашнего IP (решение пользователя 2026-10-11; вариант через AWG fr1 отложен). Порт 6881 на роутере **не проброшен** — входящих соединений нет, раздача и скорость хуже.
- Образы на `:latest`, автообновления нет: `cd /opt/media && docker compose pull && docker compose up -d`.
## Агент Astra (с 2026-10-11)
Агент на базе **Codex** (OpenAI), работает на самой малине, управление — из Telegram-бота **@rasp_contol_bot**. Код бота — отдельный репо **`ruzzy/astra-bot`** (там же схема работы).
- Пользователь `astra` (группы `sudo`, `adm`, `systemd-journal`, `users`), пароль заблокирован, **sudo NOPASSWD** (`/etc/sudoers.d/90-astra`) — по решению пользователя. SSH-вход под ним — ключом `~/.ssh/id_ed25519` с Mac (алиас `ssh rpi-astra`).
- ⚠️ sudo = агент видит всё: токен OneDrive (`~rus/.config/rclone/rclone.conf`), пуш-ключ метрик (`/etc/metrics-push/`), deploy-ключ с записью в `infra`, токен бота. Утечка контекста агента = утечка этих секретов.
- **Codex CLI 0.162.1** — полный пакет в `/opt/codex`, симлинк `/usr/local/bin/codex`. ⚠️ Голый бинарник из релиза (`codex-aarch64-…tar.gz`) не годится: `remote-control`/app-server требуют пакет (`codex-package-aarch64-unknown-linux-musl.tar.gz`), иначе «this CLI has no complete local package». Логин — подпиской ChatGPT (`codex login --device-auth`, аккаунт ruzzyrullezz@gmail.com, Plus), токен `~astra/.codex/auth.json` (600). Ключ API не используется.
- **Агенту разрешено пушить в `main` `ruzzy/infra` напрямую** (решение пользователя 2026-10-11, перекрывает правило из `CLAUDE.md` только для него): любые изменения сервера он обязан внести в `servers/rpi.md`/`stacks/` и запушить в том же прогоне, конфликты — `pull --rebase` без `--force`, неразрешимые — отдать пользователю. Источник инструкций — `codex/AGENTS.md` в `ruzzy/astra-bot`.
- Конфиг `~astra/.codex/config.toml`: `approval_policy=on-request`, `sandbox_mode=workspace-write`; инструкции агенту — `~astra/.codex/AGENTS.md` (паспорт, правила infra, что не трогать).
- **astra-bot** (`astra-bot.service`, user `astra`): `/opt/astra-bot/bot.py` ⇄ `codex app-server` (JSON-RPC stdio) ⇄ Telegram long polling. Подтверждения команд/правок — кнопками в чате. Конфиг `/etc/astra-bot.env` (root 600): `TG_TOKEN`, `TG_ALLOWED_IDS=607015,6310034441`. Тред Codex на чат — `~astra/.local/state/astra-bot.json`. Бот лежит в `/opt` под root, а не в `~/infra`, чтобы агент не правил свой код без `sudo`.
- **Приём вложений исправлен 2026-10-11** (раньше бот читал только `text`, фото и подписи молча пропадали): код — `ruzzy/astra-bot`. Фото и JPEG/PNG/WebP-документы до 20 МиБ скачиваются через Telegram `getFile` в `~astra/.local/share/astra-bot/incoming/` (файлы 600, UUID), передаются Codex как `localImage`; подпись и источник пересылки — текстом. Пересланные `/stop` и другие команды читаются как цитата. Во время работы сообщения ставятся в очередь процесса, реальная доставка картинки с подписью и источником подтверждена пользователем 2026-10-11.
- **Пачки изображений добавлены 2026-10-11** (раньше каждый элемент альбома запускал отдельную задачу и отдельное уведомление очереди): установлен [коммит `6276bf8`](https://git.ruzzy.dev/ruzzy/astra-bot/commit/6276bf8). Бот собирает альбом по `media_group_id`/чату/отправителю, быстро пересланные картинки без ID — по чату/отправителю; после 1,5 секунды тишины передаёт все картинки, подписи и источники одним запросом Codex в порядке `message_id`. Служебные уведомления ожидания убраны 2026-10-11 ([коммит `c4e3cd1`](https://git.ruzzy.dev/ruzzy/astra-bot/commit/c4e3cd1)): прежняя проверка пустой очереди повторяла их между пачками; очередь сохранена и работает молча. Проверка включает повторное заполнение опустевшей очереди. Предыдущий код — `/opt/astra-bot/bot.py.bak-20261011-silent-queue`. 23 теста прошли; изменение загружается при перезапуске через `restart-check.py`. ⚠️ Пауза больше 1,5 секунды разделяет серию на пачки; буфер и очередь не переживают перезапуск. Предыдущий код — `/opt/astra-bot/bot.py.bak-20261011-batches`.
- Код бота с доработкой Astra перенесён в `ruzzy/astra-bot` (`f9f6c39`; текущая установленная версия — `c4e3cd1`); временная копия `stacks/astra-bot/` удалена 2026-10-11. Клон — `~astra/astra-bot`. Резервная копия прежнего кода — `/opt/astra-bot/bot.py.bak-20261011-incoming`.
- ⚠️ **Почему не штатный Codex Remote из ChatGPT:** телефон к безголовому Linux не сопрягается — официально хостом может быть только macOS/Windows; `codex remote-control pair` выдаёт код, но и QR, и ручной ввод дают «Pair Failed», до демона попытка даже не доходит (проверено с debug-логом 2026-10-11). Рабочая альтернатива — ChatGPT Desktop на Mac → SSH-хост `rpi-astra`, но тогда Mac должен быть включён.
- `/boot/firmware/cmdline.txt`: добавлен `cfg80211.ieee80211_regdom=RU` (бэкап `.bak-20261011`) — только чтобы `/etc/profile.d/wifi-check.sh` не печатал в stdout login-shell предупреждение про rfkill: оно ломало бы stdio-протоколы (Codex app-server по SSH). Wi-Fi не используется.
- **Доступ к Gitea — пользователь `astra`** (с 2026-10-11, вместо двух deploy-ключей): один SSH-ключ `~astra/.ssh/gitea`, коллаборатор с записью в `ruzzy/infra` (`~astra/infra`) и `ruzzy/astra-bot` (`~astra/astra-bot`), может создавать свои репо в `astra/…` (API-токен `~astra/.config/gitea/token`, 600, scopes `write:repository,write:user`, в Gitea — `astra-rpi`). Почему пользователь, а не deploy-ключи: deploy-ключом нельзя создавать репо, а права коллаборатора видны и меняются в настройках репо. Создавать репо от имени `ruzzy` нельзя — он пользователь, не организация. git identity `Astra (rpi) <astra@rpi.ruzzy.dev>`, host key Gitea (RSA) закреплён. Автопулла нет.
- Профиль Поли (@Polli4ka303), 10 лет, сохранён в инструкциях 2026-10-11: простые короткие ответы без технических терминов; источник [astra-bot `88b88a5`](https://git.ruzzy.dev/ruzzy/astra-bot/commit/88b88a5). Детская печать уточнена: единый PDF → «Печатай» / «Печатай на двух сторонах» → печать без системных кнопок. Доступ добавлен 2026-10-11: Telegram ID `6310034441` внесён в TG_ALLOWED_IDS (ранее только владелец `607015`), профиль закреплён за ID вместо username; [astra-bot `2d761d3`](https://git.ruzzy.dev/ruzzy/astra-bot/commit/2d761d3). Настройка применяется после перезапуска бота.
- Постоянная сессия и журнал Поли добавлены 2026-10-11, [astra-bot `8b3400a`](https://git.ruzzy.dev/ruzzy/astra-bot/commit/8b3400a). После авторизации её чат сохраняется в state.child_chats, тред возобновляется после перезапуска; служебные slash-команды не сбрасывают разговор и не показывают справку администратора. Бот передаёт возраст и текущий chat_id в контекст, отправляет только итоговый ответ. Входящие сообщения, метаданные/пути вложений и текстовые ответы пишутся в ~/.local/share/astra-bot/conversations/<chat_id>.jsonl (файл 600, каталог 700); PDF, отправленный отдельным скриптом, в этот журнал не записывается. Имя пользователя выбирает профиль только после проверки TG_ALLOWED_IDS и не предоставляет доступ. Проверка: 37 тестов; резервный код /opt/astra-bot/bot.py.bak-20261011-child-session. ⚠️ При невозможности resume бот создаёт новый тред; локальный журнал остаётся, автоматического импорта журнала в контекст нет.
## Роутер Huawei HG8245X6 — скилл `router` (с 2026-10-11)
- Роутер — **OptiXstar HG8245X6-10**, оптический терминал Cyta (GPON), ПО V5R022C10S110, `192.168.10.254`. Интернет — PPPoE `1_INTERNET_R_VID_42` (публичный IP динамический, на 2026-10-11 — 46.199.208.45); ещё WAN TR069, VOIP, IPTV. За LAN3 — mesh-точки M15/E15 (Wi-Fi-клиенты за ними видны как `lan3`), на LAN4 — малина.
- Управление — Codex-скилл Astra `router`, код в `ruzzy/astra-bot` (`codex/skills/router/`), команда `/usr/local/bin/router`. Креды веб-интерфейса — `/etc/router.env` (root:astra 640), в git нет. Умеет: статус/WAN, клиенты (IPv4 из аренд DHCP, сигнал Wi-Fi), трафик по интерфейсам, DHCP static add/del, проброс портов add/del (через WAN INTERNET), Wi-Fi — только чтение. Изменения — только `--yes` и кнопкой (`codex/rules/router.rules` разрешает лишь чтение).
- ⚠️ **3 неудачных входа подряд блокируют вход в роутер** (`errloginlockNum = '3'`). `login.cgi` разбирает поля позиционно — порядок как у браузера (UserName, PassWord, Language, x.X_HW_Token), иначе вход «неудачный». login не ретраится. Веб-сервер однопоточный и рвёт соединения при частых запросах — чтение ретраится с паузой; выход (`logout.cgi`) всегда отвечает обрывом, это норма. После каждой операции — выход, чтобы не забивать лимит сессий и не выбивать человека из веб-интерфейса.
- ⚠️ Трафик **по устройствам прошивка не считает** (поля `TrafficRecvRate/SendRate` всегда 0) — только по интерфейсам: `wan` (сумма GEM Ethernet), `iptv`, `lan1..4`, `wifi-2.4`, `wifi-5`.
- ⚠️ Запись Wi-Fi не сделана сознательно: SSID 2.4/5 ГГц связаны (WifiCover/band steering), запрос — десятки полей; ошибка отключит весь дом. Делать отдельно, в согласованное окно.
- `router-watch.timer` (5 мин, root): снимок `/var/lib/router-watch/state.json` (читает astra без сети), `seen.json` (все MAC с первым появлением; база — 22 устройства на 2026-10-11 15:57), `known.json` (имена от пользователя), `events.log`. Новый MAC → Telegram ботом @rasp_contol_bot с флагами (случайный MAC, ночь, без имени, ≥3 новых за час). Метрики `router_*` → pushgateway (job `router`) → алерты группы `home-network`: `RouterWatchStale` (>20 мин), `RouterDownloadSpike`/`RouterUploadSpike` (WAN: 15-мин скорость >4× средней за неделю и >5 МБ/с ↓ / >1 МБ/с ↑, 30 мин). Дашборд https://mon.ruzzy.dev/d/home-net.
- Проверено на живом роутере 2026-10-11: чтение всех разделов; `dhcp add/del` (тестовая привязка 02:00:00:00:00:01 → .250 создана и удалена), `portmap add/del` (TCP 59999 → rpi создан и удалён).
- Статическая привязка DHCP `192.168.10.25` ↔ `dc:a6:32:db:b0:f9` (малина) на роутере уже есть — единственная. Привязка `192.168.10.101` ↔ `8e:a9:3a:4e:69:ba` удалена 2026-10-11: устройства с таким MAC нет ни в сети, ни в истории роутера (вероятно, старый телефон со сменившимся случайным MAC).
- Свободная беседа, интернет-поиск, объяснение школьного материала, поиск и генерация картинок для Поли разрешены 2026-10-11; правила установлены в ~/.codex/AGENTS.md, источник [astra-bot `95a2867`](https://git.ruzzy.dev/ruzzy/astra-bot/commit/95a2867). Содержание подбирается для 10 лет, материалы 16+/18+ исключаются; выбранные картинки проверяются перед отправкой. Это инструкции агента, не отдельный сетевой фильтр. Разрешение не распространяется на управление сервером и доступами.
## Открытые хвосты (на 2026-10-11)
- USB3-хаб с питанием — теперь только ради скорости: WD Elements 1 ТБ работает второй копией в роутере (~10 МБ/с); на малине через хаб было бы ~100 МБ/с (обновлено 2026-10-11, было «копия одна»).
- Нет node-exporter (CPU/RAM/диск самой малины в Prometheus не видны) — пуш через pushgateway для node-метрик неудобен; решить, нужен ли.
- Первый прогон бэкапа стартовал до появления `ExecStopPost` — его итог может не дойти в метрики; `backup_last_success_timestamp` появится с первого прогона по таймеру. До этого `BackupStale` молчит (серии нет).
- ⏳ `codex-remote.service` (демон `codex remote-control` с `RUST_LOG=debug`) всё ещё включён и работает, хотя сопряжение не взлетело — убрать или оставить ждать поддержки Linux от OpenAI (решение за пользователем).
- Токен бота передавался через чат с ассистентом — при желании перевыпустить в @BotFather (`/revoke`) и вписать в `/etc/astra-bot.env`.
- ⚠️ **Закрытый ключ age бэкапа серверов — одна копия, на Mac.** Положить вторую вне малины и вне Mac (менеджер паролей/распечатка), иначе потеря Mac = бэкапы fr1/ru1 бесполезны.
- Разово скопировать на малину `~rus/backups` (5.4 ГБ) и `~tgclaude/backups` (4.2 ГБ) с fr1 — тогда их можно убрать с fr1.
- Пробное восстановление бэкапа серверов — раз в квартал (следующее ~январь 2027), см. `stacks/server-backup/README.md`.
- Через неделю снять SMART и посчитать реальный прирост Start_Stop/Load_Cycle — прогноз ресурса пока оценочный.
- ⏳ Определить хранение входящих фото: сейчас автоматически не удаляются ради ссылок в старых тредах; очередь сообщений только в памяти и теряется при перезапуске.
- Медиацентр: проброс 6881 tcp/udp на роутере (раздача/скорость); пароль SFTP роутера слабый (только LAN) — сменить; Kinozal и OpenSubtitles — аккаунты пользователя; решить про автообновление образов.