Files
infra/servers/rpi.md
T
gilfanovrandClaude Opus 5.5 08aa787b95 rpi: медиацентр (Jellyfin, Seerr, qBittorrent, *arr) в Docker; диск роутера и резерв DHCP
- stacks/rpi-media: docker-compose и идемпотентный provision.py
- servers/rpi.md: раздел «Медиацентр», почему NTFS и SFTP на роутере,
  закрыт хвост про резерв адреса
- AGENTS.md: роль малины

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:12:31 +03:00

44 KiB
Raw Blame History

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, запись DHCPStaticAddress.2; была без резерва).

Доступ

  • С 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, 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. Ранее постоянных правил скилла было недостаточно: запросы 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
  • Данные — /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, Kinozal) — руками, нужен аккаунт.
  • Bazarr: провайдеры без аккаунтов (embeddedsubtitles, podnapisi, yifysubtitles); для русских субтитров лучший — OpenSubtitles.com, нужен аккаунт.
  • Авторизация: везде пользователь rus, один пароль — /opt/media/.admin-password (rus 600). *arr — формы, пароль всегда (режим «без пароля из LAN» в новых *arr требует AllowedHosts). Между собой сервисы ходят по API-ключам из config/<app>/config.xml.
  • Первичная связка — stacks/rpi-media/provision.py (идемпотентный, пароль и ключи читает на месте): python3 /opt/media/provision.py [qbit jellyfin arr prowlarr bazarr seerr].
  • ⚠️ Каталог 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. Бот собирает альбом по media_group_id/чату/отправителю, быстро пересланные картинки без ID — по чату/отправителю; после 1,5 секунды тишины передаёт все картинки, подписи и источники одним запросом Codex в порядке message_id. Служебные уведомления ожидания убраны 2026-10-11 (коммит 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. Детская печать уточнена: единый PDF → «Печатай» / «Печатай на двух сторонах» → печать без системных кнопок. Доступ добавлен 2026-10-11: Telegram ID 6310034441 внесён в TG_ALLOWED_IDS (ранее только владелец 607015), профиль закреплён за ID вместо username; astra-bot 2d761d3. Настройка применяется после перезапуска бота.

  • Постоянная сессия и журнал Поли добавлены 2026-10-11, astra-bot 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. Содержание подбирается для 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) — сменить; приватные трекеры и OpenSubtitles — аккаунты пользователя; решить про автообновление образов.