Files
infra/servers/fr1.md
T

63 KiB
Raw Blame History

fr1 — fr1.ruzzy.dev (161.97.93.252)

Снимок состояния сервера. Секреты (пароли/токены) — только на сервере, не здесь.

Провайдер: Contabo VPS · France / Lauterbourg (Grand Est) ОС: Ubuntu 24.04.4 LTS · kernel 6.8.0-137-generic (с 2026-08-07; ранее 6.8.0-134 с 2026-07-31, до того 6.8.0-106) Ресурсы: 8 vCPU / 23 GB RAM / 387 GB hostname: fr1 (FQDN fr1.ruzzy.dev через /etc/hosts)


Пользователи и доступ

  • Пользователь rus (uid 1000, группы rus, sudo, users, docker, tgclaude), sudo по паролю.
  • Пользователь tgclaude (uid 1001, группы tgclaude, users, docker) — аккаунт агента Astra, вход по SSH разрешён, sudo нет. /home/tgclaude — 21 ГБ, крупнейший потребитель диска. Подробно — в разделе «Пользователь tgclaude».
  • ⚠️ Группа docker = root-эквивалент. docker:x:988:rus,tgclaude — любой её член монтирует / в контейнер и делает что угодно от рута (этим же способом чистились root-овы файлы 2026-09-08). Поэтому «у tgclaude нет sudo» не означает ограничения прав на хосте.
  • Вход с Mac: ssh rus-161 (алиас в ~/.ssh/config) → ключ ~/.ssh/id_161_97_93_252 (ed25519).
  • root-доступ по SSH закрыт; для root изнутри — sudo -i.
  • Пользователь marusia-tunnel — удалён 2026-10-11 вместе со всей интеграцией с Марусей (отменена владельцем): пользователь и /home/marusia-tunnel, sshd_config.d/70-marusia-tunnel.conf, запись в AllowUsers, vhost astra.ruzzy.dev (sites-available/astra.ruzzy.dev.conf, ссылка zz-…, conf.d/marusia-webhook.conf), сертификат LE astra.ruzzy.dev, ~rus/marusia-audit/. Скрипт демонтажа — ~rus/marusia-removal.sh, копии удалённого — /root/marusia-removed-20261011/ (root 700). nginx/sshd перезагружались после успешных -t; проверено: git/mon открываются, SSH-вход работает.
  • Пуш-ключ metrics-push@rpi (с 2026-10-11) в ~rus/.ssh/authorized_keys с restrict,command="/home/rus/bin/pushgw-ingest rpi": этим ключом домашняя малина может только залить метрики в pushgateway — шелла, форвардинга и pty нет. Бэкап прежнего файла — ~/.ssh/authorized_keys.bak-20261011.

SSH (/etc/ssh/sshd_config.d/00-hardening.conf)

⚠️ В основном /etc/ssh/sshd_config строкой 42 стоит PermitRootLogin yes (правка шаблона Contabo). Побеждает всё равно дроп-ин: Include /etc/ssh/sshd_config.d/*.conf стоит строкой 12, а sshd берёт первое встреченное значение. То есть root-вход закрыт, но держится на порядке строк — уберёшь дроп-ин, и root-вход по ключу включится. Рядом лежит 50-cloud-init.conf (root 600) — эффективные значения проверены под root 2026-10-11, см. ниже.

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
MaxAuthTries 3
AllowUsers rus tgclaude

Через socket-activation (ssh.socket), слушает 0.0.0.0:22 и [::]:22.

  • marusia-tunnel был в AllowUsers 2026-10-11 (для туннеля Маруси), убран в тот же день вместе с интеграцией; бэкап исходного — 00-hardening.conf.bak-20261011-171345.
  • Проверено под root 2026-10-11: 50-cloud-init.conf содержит PasswordAuthentication yes, но эффективное значение — no (00-hardening читается первым); sshd -T → permitrootlogin no, passwordauthentication no. ⚠️ То же «держится на порядке файлов»: переименуй 00-hardening во что-то после 50-, и вход по паролю включится.
  • Отпечатки host-ключей: ED25519 SHA256:M5J9nH+svyA3+beD7ZllUn1tayMMO2cxw/t3Z/kj6KU, ECDSA SHA256:fMZEytu7XI3W+Mzo4WQ6WZHDn0nYiagtbKb4JyUIX5w, RSA SHA256:53olW/dhoPclFgp8X6OWh0euoOfSmwXWZb6c026OGAw.

Сеть

  • Управляется netplan → systemd-networkd, конфиг /etc/netplan/50-cloud-init.yaml (наследие cloud-init, сейчас просто статический файл).
  • Матч интерфейса по MAC 00:50:56:64:ff:15 + set-name: eth0. Ядро называет карту ens18, netplan переименовывает её в eth0 через сгенерированный /run/systemd/network/10-netplan-eth0.link.
  • IPv4 161.97.93.252/18, GW 161.97.64.1. DNS 195.179.224.53 / 209.126.15.53.
  • ⚠️ IPv6 2a02:c207:2338:7709::1/64, дефолт via fe80::1 onlink — мигает, а не «работает» или «не работает». Хронология одного дня, 2026-09-08: утром fe80::1 в ip -6 neigh был REACHABLE и ping6 наружу шёл без потерь (до этого месяцами FAILED); к 15:50 снова FAILED, ping6 — 100% потерь. Конфиг на сервере не менялся ни разу, то есть причина целиком на стороне Contabo. Практический вывод: не завязывать ничего на IPv6 этого хоста и не считать очередное «заработало» починкой. IPv4 при этом не страдает.
  • ⚠️ На имя eth0 завязано NAT-правило AmneziaWG (-o eth0 MASQUERADE в wg0.conf) и правила ufw. Если netplan не отработает, интерфейс останется ens18 и VPN-выход молча сломается.
  • Страховка net-rescue (заведена 2026-08-01): /usr/local/sbin/net-rescue.sh + net-rescue.timer (OnBootSec=150, enabled). Идемпотентно находит карту по MAC — независимо от её имени — и доставляет адрес, дефолтный маршрут и DNS, если их нет. Пишет в journal тегом net-rescue. Смысл: пережить повторение аварии 2026-07-31 без похода в VNC.
  • Третий механизм правки маршрута — /etc/cron.d/staticroute (из образа Contabo, не наш): @reboot root ip route replace $(link-scope сеть eth0) via $(дефолтный GW) dev eth0. Аргументы вычисляет на ходу из текущей таблицы, поэтому если адреса на eth0 нет вообще — тихо ничего не делает. Дублирует задачу net-rescue, но вслепую и без привязки к MAC. Знать о нём надо: при разборе «кто поправил маршрут» это третий кандидат после netplan и net-rescue.
  • cloud-init установлен, но сетевой конфиг ему запрещён: /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg (network: {config: disabled}) — иначе перезапишет netplan.

DNS

  • Зона ruzzy.dev — Namecheap (NS dns1/dns2.registrar-servers.com).
  • fr1.ruzzy.dev A → 161.97.93.252. Апекс ruzzy.dev A → 161.97.93.252 (переведён с de1).
  • CNAME → fr1.ruzzy.dev: git, mon, www, share.
  • ⚠️ cam.ruzzy.dev (CNAME → fr1) остался в Namecheap после удаления ipcam 2026-09-08 — снести руками.
  • PTR (reverse DNS): дефолтный vmi3387709.contaboserver.net (не менялся).

Firewall (ufw)

  • Default: deny incoming, allow outgoing.
  • Открыто (IPv4 + IPv6): 22/tcp (OpenSSH), 80/tcp, 443/tcp, 2222/tcp (gitea-ssh), 36360/udp (awg-de-exit).
  • 9100/tcp — только from 172.16.0.0/12 (node-exporter для Prometheus в docker).
  • Роутинг туннеля: allow FWD wg0 ↔ eth0 (в обе стороны) + allow in on wg0.
  • Плюс динамические REJECT-правила от fail2ban.
  • ⚠️ ufw НЕ управляет docker-портами. Публикацию ports: - "X:Y" docker вписывает в iptables напрямую, минуя цепочки ufw: порт открыт в мир независимо от deny incoming и в ufw status не виден. Проверено 2026-09-08 — ipcam-FTPS месяцами держал наружу 2221 и 30000-30010, правил ufw для них не было вообще. Список выше описывает только host-сервисы; чтобы контейнер не светил порт, биндить надо на 127.0.0.1: (как у grafana/prometheus/alertmanager/gitea-web). Аудит открытого наружу — ss -tlnH + docker ps, а не ufw status.

fail2ban (/etc/fail2ban/jail.local)

[DEFAULT]
bantime=1h  findtime=10m  maxretry=5
ignoreip = 127.0.0.1/8 ::1 31.153.37.106 46.199.208.45
backend=systemd  banaction=ufw

[sshd]      enabled=true
[recidive]  enabled=true  bantime=1w  findtime=1d  maxretry=5
            backend=auto  logpath=/var/log/fail2ban.log
  • 46.199.208.45 — текущий домашний IP (Cyta, за ним rpi и Mac), добавлен 2026-10-11 (было только 31.153.37.106), бэкап jail.local.bak-20261011-175800. Зачем: туннель Маруси с rpi переподключается сам, и серия ошибок входа забанила бы весь дом — вместе с ssh rus-161 — на час, а recidive на неделю.
  • ⚠️ Домашний IP динамический (PPPoE Cyta, см. servers/rpi.md). После смены адреса защита молча пропадёт, а в ignoreip останется чужой адрес — сверять с router status на rpi.
  • ⏳ Происхождение 31.153.37.106 нигде не записано (стоит с 2026-06-21) — выяснить и убрать, если не нужен.

Обновления

  • Ubuntu Pro подключён (аккаунт ruzzyrullezz@gmail.com): livepatch, esm-infra, esm-apps — enabled.
  • livepatch приходит снапом canonical-livepatch (v11.0.2, rev 414); рядом только core22 и snapd 2.76.3 — больше снапов на хосте нет. Состояние: canonical-livepatch status → ядро 6.8.0-137.137-generic, patchState: nothing-to-apply, tier updates, поддержка этого ядра до 2027-08-17 (upgradeRequiredDate — после этой даты livepatch потребует апгрейда ядра).
  • ⚠️ snapd обновляет себя и livepatch сам, 4 раза в сутки, без окна обслуживания: snap refresh --time → timer: 00:00~24:00/4, holds не выставлены. Это отдельный от apt канал изменений на хосте, и отчёт в Telegram про него не приходит (uu-telegram-report.sh висит только на apt).
  • unattended-upgrades: Update-Package-Lists "1", Unattended-Upgrade "1" (ежедневно, security). Авто-reboot выключен.

Система

  • swap: /swapfile 2 GB (в /etc/fstab), vm.swappiness=10 (/etc/sysctl.d/99-swappiness.conf).
  • AppArmor включён; время — systemd-timesyncd.

Логирование (для разборов) — пересобрано 2026-08-01

  • journald: Storage=persistent; ретенция — дроп-ин /etc/systemd/journald.conf.d/10-retention.conf: SystemMaxUse=8G, MaxRetentionSec=1year. Было SystemMaxUse=500M — журнал упирался в потолок и хранил всего ~11 дней, из-за чего при разборе аварии логи за 15 июля уже не существовали.
  • История bash (в .bashrc у rus и у root): HISTTIMEFORMAT='%F %T ', HISTSIZE=100000, HISTFILESIZE=200000, HISTCONTROL=ignoredups (было ignoreboth — команды с ведущим пробелом исчезали бесследно), PROMPT_COMMAND="history -a" — сброс после каждой команды, чтобы жёсткий ресет не съедал сессию.
  • ⚠️ Записи в ~/.bash_history с датой 1970-01-01 — это легаси до 2026-08-01, реального времени у них нет. Проставлен сентинел #1 перед каждой такой строкой, иначе bash рисовал бы им текущее время и вводил в заблуждение при разборе.
  • rsyslog дублирует auth/syslog в /var/log/*.log (ротация ~4 недели) — это спасло разбор, когда journald уже вычистил июль.
  • sysstat / sar — исторические CPU, память, диски, сеть с шагом 10 минут. Собирает sysstat-collect.timer (OnCalendar=*:00/10 → /usr/lib/sysstat/sa1), бинарные срезы в /var/log/sysstat/saNN (NN — день месяца), ежедневный текстовый отчёт sarNN от sysstat-summary.timer. Читать: sar -u -f /var/log/sysstat/sa07 (CPU), -r память, -d диски, -n DEV сеть. Ценность именно в том, что пишет системный таймер, а не контейнер: данные остаются, даже когда лежал сам docker с Prometheus.
  • Ретенция sysstat — пересобрана 2026-09-08. Было HISTORY=7: история жила 7–8 дней, файлы затирались по кругу номером дня месяца — ровно та же ловушка, что была с journald на 500M (при разборе аварии месячной давности загрузки уже нет, а sysstat держат именно за этим). Стало HISTORY=365, ENABLED="true" в /etc/default/sysstat, мёртвый /etc/cron.d/sysstat удалён.
  • Важная механика: при HISTORY > 28 скрипт /usr/lib/sysstat/sa1 сам добавляет sadc -D, и имена файлов переключаются с saDD на saYYYYMMDD — без этого поднятие HISTORY было бы бесполезным, срезы всё равно перезаписывались бы каждый месяц. Первый датированный файл появляется на следующем срабатывании таймера. Чистку и сжатие делает sa2 (find -mtime +HISTORY → удаление, +COMPRESSAFTER=10 → xz); при старом HISTORY=7 сжатие не срабатывало никогда, теперь работает.
  • Старые файлы эры 7 дней (sa31, sa01…sa08) остаются рядом ещё год и читаются как обычно: sar -u -f /var/log/sysstat/sa07.
  • Почему /etc/cron.d/sysstat удалён без сожалений: оба его правила искали debian-sa1, которого в Ubuntu 24.04 нет (бинарь называется sa1) — то есть это был no-op. Сбором всё время занимался systemd-таймер, дублей в данных не было (срезы ровно в :00/:10/:20…). Флаг ENABLED="false" там же был легаси и на таймер не влиял — выставлен в true для соответствия факту.
  • Известные дыры: команды без sudo (docker stop, mpstat, …) не фиксируются нигде; sudo su — слепая зона. snoopy/auditd и log_output в sudoers сознательно НЕ ставили.
  • ⚠️ Открытый баг: rsyslogd периодически валится в action 'action-0-builtin:omfile' suspended и в эти окна теряет строки. Наблюдался и 25.07, и перед падением 31.07. Не чинилось.

Docker

  • Docker 29.6 + Compose v5.1 (официальный репозиторий). rus в группе docker.

nginx + TLS

  • nginx 1.24 (apt) + certbot 2.9 (python3-certbot-nginx), автопродление через systemd-таймер certbot.
  • vhost /etc/nginx/sites-available/mon.ruzzy.dev.conf → проксирует на Grafana (127.0.0.1:3000), HTTPS-редирект, websocket.
  • vhost'ы share.ruzzy.dev.conf и www.ruzzy.dev.conf — статика с общим корнем /var/www/share.ruzzy.dev (autoindex off, try_files $uri =404, server_tokens off), у каждого свой сертификат. Используется для раздачи клиентских конфигов OpenVPN — сейчас там marina-de.ovpn, masha-de.ovpn; с 2026-10-12 ещё astra/index.html и astra/privacy.html — страницы OAuth-приложения Astra для Google (stacks/www-ruzzy/, см. servers/rpi.md).
  • vhost default — catch-all server_name _, listen 80 default_server, отдаёт дефолтную страницу /var/www/html/index.nginx-debian.html. На :443 default_server не задан, поэтому TLS-запрос с неизвестным SNI попадает на первый по алфавиту vhost (git.ruzzy.dev) и получает его сертификат. Так отвечают и https://ruzzy.dev, и https://fr1.ruzzy.dev — своих vhost'ов и сертификатов у них нет, браузер видит несовпадение имени (проверено 2026-10-11).
  • vhost astra.ruzzy.dev (вебхук Маруси) — удалён 2026-10-11 вместе с сертификатом. ⚠️ DNS-запись astra.ruzzy.dev (CNAME → fr1.ruzzy.dev) у регистратора — удалить вручную; пока она есть, запросы к этому имени отвечает vhost git с чужим сертификатом.
  • Сертификаты LE: mon, git, share, www, astra (с 2026-10-11, certbot certonly --nginx, продление тем же таймером). cam.ruzzy.dev удалён 2026-09-08 вместе с ipcam.

Мониторинг (~/monitoring, stack в репо stacks/monitoring/)

  • Стек: Prometheus + Grafana + node-exporter + cAdvisor + Alertmanager + Pushgateway (docker compose).
  • Алерты: Alertmanager → Telegram (бот @ultimate_log_bot, chat 607015). Правила prometheus/rules/alerts.yml: TargetDown, HostHighCPU, HostLowMemory, HostDiskLow/Critical; группа rpi (с 2026-10-11) — SMART и бэкапы малины: PushMetricsStale, SmartHealthFailed, SmartBadSectors(+Growing), SmartUdmaCrcErrors, SmartSelftestFailed, SmartTemperatureHigh, BackupFailed, BackupStale. Токен — в ~/monitoring/alertmanager/telegram_token (chown 65534, не в git).
  • Prometheus: retention 90d, слушает 127.0.0.1:9090. Job'ы: node и cadvisor (file_sd, instance'ы fr1 и ru1), openvpn (file_sd, только ru1 — 85.198.109.86:9176), gitea (static, gitea:3000 через внешнюю docker-сеть monitoring_monitoring), prometheus (self). На 2026-09-08 все таргеты up, активных алертов нет.
  • Grafana: 127.0.0.1:3000, доступ снаружи через https://mon.ruzzy.dev (nginx + Let's Encrypt). Datasource Prometheus (uid prometheus) и дашборды (Node Exporter Full, cAdvisor, Network/Traffic, Gitea, OpenVPN, RPi — диск и бэкап OneDrive (rpi-disk.json, с 2026-10-11) — папка Infra) провижатся автоматически. Пароль admin — в ~/monitoring/docker-compose.yml на сервере (в репо вынесен в ${GF_ADMIN_PASSWORD}).
  • Тома: monitoring_prometheus_data — 6.5 ГБ (при retention 90d; самый крупный том на сервере, учитывать при планировании диска), monitoring_grafana_data — 50 МБ, monitoring_alertmanager_data — пустой.
  • Multi-server: таргеты через file_sd (prometheus/targets/{node,cadvisor}/*.yml) — новый сервер = добавить файл + curl -X POST .../-/reload. См. stacks/monitoring/README.md.
  • node-exporter — network_mode: host (видит реальные интерфейсы хоста: eth0, wg0-туннель и т.д.; иначе показывал бы только сеть docker-бриджа). Слушает host:9100, доступ ограничен ufw (from 172.16.0.0/12 to port 9100), Prometheus скрейпит через host.docker.internal:9100. nodename берётся с хоста (fr1).
  • Дашборд Network / Traffic (network.json): RX/TX по интерфейсам, пакеты, ошибки/дропы, статус — с фильтром по instance/device (lo/veth/docker скрыты).
  • DNS: mon.ruzzy.dev → 161.97.93.252.
  • Pushgateway (с 2026-10-11) — для хостов за NAT, которых не скрейпнуть (домашняя rpi, см. servers/rpi.md). Слушает только 127.0.0.1:9091; job pushgateway с honor_labels: true (иначе job/instance заменились бы на pushgateway:9091). Персистентность в томе monitoring_pushgateway_data — рестарт не обнуляет метрики. Как приходят данные: хост делает ssh rus@fr1 <job> < metrics.prom, forced command ~/bin/pushgw-ingest <instance> делает PUT /metrics/job/<job>/instance/<instance>. instance зашит в authorized_keys, а не берётся от клиента — хост не может писать от чужого имени; имя job фильтруется по [a-z0-9_]. Наружу порт не открыт, nginx/ufw не трогали.
    • В ~/bin/pushgw-ingest есть режим health (с 2026-10-11): ssh … health отдаёт key=value о здоровье Prometheus/Alertmanager/pushgateway, свежести пушей хоста и счётчиках доставки в Telegram — только чтение. Им пользуется внешний сторож monwatch на rpi.
    • ⚠️ Pushgateway хранит последнее значение вечно: если хост умер, метрики не исчезнут, а «застынут». Поэтому свежесть ловит отдельный алерт PushMetricsStale по push_time_seconds.
    • ⚠️ Живой ~/monitoring/docker-compose.yml расходится с репо (пароль Grafana вписан литералом), поэтому при деплое 2026-10-11 compose не копировался, а блок pushgateway вставлен на месте. Бэкапы: docker-compose.yml.bak-20261011, prometheus/prometheus.yml.bak-20261011.
  • Подключён ru1 (node + cadvisor + openvpn-exporter, напрямую по публичному IP). de1 выведен — его таргеты удалены. Планируется: экспортёры wireguard/awg и telemt.

Gitea (~/gitea, stack в репо stacks/gitea/)

  • Перенесён с de1 2026-06-21. Gitea 1.27.3 (обновлён 2026-08-31 с 1.25.4), БД SQLite (всё в /srv/gitea, 1.9 ГБ, 20 репо).
  • Пин образа в ~/gitea/docker-compose.yml и в stacks/gitea/ приведён к 1.27.3 2026-09-08 — до этого оба файла держали 1.25.4, и любой docker compose up -d откатил бы Gitea на две минорные версии. Бэкап прежнего файла — ~/gitea/docker-compose.yml.bak-20260908.
  • Запущенный контейнер gitea создан вне compose, поэтому docker compose up -d его пересоздаст — короткий простой, но данные на bind-mount не теряются. Конфликта больше нет: мешавший gitea_old_1254 (он нёс метки того же compose-проекта и поднимался вторым, забирая порты 2222/3001) удалён 2026-09-08, --dry-run теперь показывает ровно одно Container gitea Creating.
  • Откат на 1.25.4 возможен — предмиграционный снимок есть: /home/tgclaude/backups/gitea-backup-20260831-045458.tar.gz, 4.5 ГБ (config/app.ini + data/). Проверено 2026-09-08 распаковкой data/gitea.db: версия схемы в архиве — 323, в живой БД — 343, репозиториев 20, пользователей 4. То есть архив снят до миграции 1.25→1.27 и для откатa годен. Контейнер gitea_old_1254 и образ 1.25.4 удалены 2026-09-08 (они как раз откатом не были — работали бы на уже миграцированных данных); при необходимости образ просто docker pull gitea/gitea:1.25.4.
  • ⚠️ Этот снимок лежит в хоме tgclaude и до 2026-09-08 нигде не был описан — при уборке диска его легко снести по незнанию. Правило на будущее: перед апгрейдом Gitea снимать /srv/gitea, и складывать снимок туда, где он описан.
  • Контейнер gitea: SSH 2222 (наружу), web 127.0.0.1:3001 (3000 занят Grafana → перевесил на 3001).
  • nginx vhost git.ruzzy.dev → 127.0.0.1:3001, TLS Let's Encrypt, client_max_body_size 512M.
  • DNS: git.ruzzy.dev (CNAME → fr1) → 161.97.93.252.
  • ROOT_URL/SSH_DOMAIN = git.ruzzy.dev (не менялись). Host-ключи SSH и user/deploy-ключи перенесены с данными — ssh://git@git.ruzzy.dev:2222 работает как раньше.
  • Docker-томов нет — все данные на bind-mount /srv/gitea (1.9 ГБ), это и есть точка бэкапа.
  • Это origin репо infra и vault openclaw.
  • Пользователь astra (id 5, с 2026-10-11) — агент на домашней малине (servers/rpi.md): коллаборатор с записью в ruzzy/infra и ruzzy/astra-bot, свои репо в astra/…, постоянный токен astra-rpi (хранится только на малине). Пароль случайный и нигде не сохранён — вход только ключом/токеном; сбросить — gitea admin user change-password. Deploy-ключи astra (id 10, 11) удалены.
  • Как сделать разовую операцию через API без следов: gitea admin user generate-access-token --scopes write:repository --raw (конфиг по умолчанию верный — работающая Gitea тоже читает /data/gitea/conf/app.ini, БД /data/gitea.db) → POST /api/v1/repos/<owner>/<repo>/keys → удалить токен прямо из БД: docker exec -u 1000 gitea sqlite3 /data/gitea.db "delete from access_token where name='<имя>'" (API требует basic-auth, как выяснилось с imgbot-токенами). Проверено 2026-10-11: после удаления токен получает 401.

books.ruzzy.dev — читалка с ИИ (с 2026-10-13, ~/books, код — репо ruzzy/books)

  • Деплой: из клона ruzzy/books (~/pets/books на Mac) — rsync -a --exclude data --exclude .env ./ rus-161:books/ && ssh rus-161 'cd ~/books && docker compose up -d --build' (подробно — README репо). ~/books на fr1 — копия, не git-клон.
  • ⏳ На 2026-10-13: контейнер запущен; vhost + сертификат ставит владелец (sudo sh ~/books/nginx/install.sh — у rus sudo по паролю, root-вход закрыт); бот для входа и CLAUDE_CODE_OAUTH_TOKEN в .env — тоже за ним.
  • Контейнер books (образ books:local, compose ~/books/docker-compose.yml), 127.0.0.1:3010 → nginx vhost books.ruzzy.dev (sites-available/books.ruzzy.dev.conf, client_max_body_size 210M, proxy_buffering off, таймаут 900 с — ответы ИИ потоком). Данные — ~/books/data (uid 1000 = rus): books.db, files/, models/, claude/. Лимиты: 4 ГБ RAM, 4 CPU, cap_drop ALL, no-new-privileges.
  • ИИ — Claude Code 2.1.197 внутри контейнера в фоновом режиме по подписке владельца (CLAUDE_CODE_OAUTH_TOKEN в ~/books/.env, 600), без инструментов; для проверки фактов — только WebSearch. ⚠️ Лимиты подписки общие с claude-channels и работой владельца.
  • Вход — Telegram Login Widget, только ID 607015; бот для входа — отдельный (токен в .env, домен у @BotFather /setdomain). Секреты .env: TG_BOT_TOKEN, SESSION_SECRET, CLAUDE_CODE_OAUTH_TOKEN.
  • ⚠️ Агенту книг сознательно не дан доступ к хосту (в отличие от claude-channels с ключами к fr1/gitea): книги и выделения — чужой текст, возможны внедрённые инструкции.

AmneziaWG exit (для ru1) — перенос VPN, фаза 1

  • Зеркало exit-узла de1: AmneziaWG server wg0 = 10.8.1.0/24, ListenPort 36360/udp, те же server-ключи/PSK и obfuscation-параметры (Jc/Jmin/Jmax/S1/S2/H1-4), что и на de1 (server-pubkey Lumb1o…) → на ru1 свитч = только смена Endpoint.
  • Установка: amneziawg-dkms+amneziawg-tools (PPA amnezia), host-ом через awg-quick@wg0 (enabled). Конфиг /etc/amnezia/amneziawg/wg0.conf (секрет, не в git).
  • NAT: PostUp masquerade 10.8.1.0/24 → eth0 + ip_forward=1.
  • ufw: 36360/udp открыт + ufw route allow in on wg0 out on eth0 (и обратно) + ufw allow in on wg0 — обязательно, иначе ufw режет туннельный трафик (на de1 ufw был выключен).
  • Пиры: ru1 (10.8.1.4) переключён сюда; прямые 10.8.1.1/2/3 пока на de1.
  • Проверено: зарубежный exit RU-клиентов идёт через fr1 (exit-IP 161.97.93.252).
  • ⏳ Остаётся (фаза 2): telemt MTProxy, OpenVPN, прямые AWG-клиенты. DNS ruzzy.dev/www — уже переведены на fr1.

claude-channels (Telegram-бот @iluha_rabobot)

  • Перенесён с de1 2026-06-22. ~/claude-channels/ (Dockerfile Node22+Bun+Claude Code, compose, .env с токеном). Запущен, авторизован, подключён к Telegram. Claude Code 2.1.197; в .claude.json модели sonnet-5[1m], opus-4-8[1m], opus-4-7.
  • Volume claude-channels_claude-home — при переносе положили только нужное: .claude.json + .claude/.credentials.json (авторизация) + settings + channels (allowlist). Плагины/кэш/история НЕ копировались (на de1 раздувались до 6.5 ГБ).
  • ⚠️ Но том раздулся заново: 2.8 ГБ (проверено 2026-09-08) — второй по величине после prometheus_data. Экономия от аккуратного переноса съедена работой самого бота (кэш/история/плагины нарастают внутри контейнера). Если снова упрёмся в диск — смотреть сюда.
  • ⚠️ В томе, помимо авторизации Claude, лежат SSH-ключи: /root/.ssh/id_ed25519 (в Gitea как deploy-ключ, Host git.ruzzy.dev) и /root/.ssh/astra_fr1 — ключ к самому хосту под tgclaude (см. раздел tgclaude). Том — чувствительный артефакт, обращаться как с секретом.
  • telegram-плагин переустановлен заново в контейнере (claude plugin install telegram@claude-plugins-official, v0.0.6) — копировать его нельзя.
  • Доступа к Obsidian-vault у бота нет с 2026-10-11 (решение владельца: vault — его личная база, а не рабочая папка агентов; заметки — к Astra на rpi). Убрано: монтирование /home/rus/MyVault:/vault из ~/claude-channels/docker-compose.yml (бэкап docker-compose.yml.bak-20261011), разделы «Obsidian Vault»/«Git и Obsidian Vault» в /root/CLAUDE.md бота заменены пометкой «доступа нет» (бэкап /root/CLAUDE.md.bak-20261011), синк-cron снят, ~/MyVault удалён (227 МБ; перед удалением всё было в gitea, незапушенного не было). История — ruzzy/MyVault, архив на 2026-09-23.
  • Мост ask-astra в контейнере (/root/agent-bridge/bin/ask-astra) по умолчанию работал в /vault — переключён на /root/astra-work (пустой git-репозиторий; ask-astra требует git). Бэкапы — ask-astra.bak-20261011, astra-section.md.bak-20261011.
  • Gmail MCP — без OAuth-кредов (gmail-инструменты не работают, на бот не влияет).

Telegram-уведомления

  • Бот @ultimate_log_bot. Конфиг /etc/tg-notify.conf (chmod 600, токен+chat_id — секрет на сервере).
  • /usr/local/bin/tg-notify.sh "msg" — универсальная отправка (HTML, префикс hostname).
  • /usr/local/bin/uu-telegram-report.sh — сводка автообновлений + reboot-required; повешен на apt-daily-upgrade.service через drop-in /etc/systemd/system/apt-daily-upgrade.service.d/telegram.conf (ExecStartPost). Молчит, если нечего сообщать.

Пользователь tgclaude (агенты) и его нагрузки

Аккаунт uid 1001 — общий вход для агентов на этот же хост, не персональный. До 2026-09-08 был описан только в ~rus/MyVault/TOOLS.md (шпаргалке агентов), в репо infra не значился вовсе. Его нагрузки не видны ни в docker ps (они не в контейнерах), ни при обходе ~rus, поэтому при инвентаризации их легко пропустить. rus состоит в группе tgclaude, так что большая часть этого хома читается без sudo.

  • Кто заходит: агент Astra, работающий внутри контейнера claude-channels (бот @iluha_rabobot). Ключ и конфиг лежат в самом контейнере: /root/.ssh/astra_fr1 + Host fr1-astra → HostName 161.97.93.252, User tgclaude, IdentitiesOnly yes (созданы 2026-09-07), а публичная часть — в authorized_keys у tgclaude. То есть бот ходит по SSH на свой же хост.
  • ⚠️ Цепочка привилегий, которую стоит осознавать целиком: Telegram-бот → агент в контейнере → SSH как tgclaude → группа docker → root на хосте. Ни sudo, ни пароля на этом пути не требуется. Всё, что ограничивает агента, — его собственная сдержанность.
  • Ключ astra_fr1 живёт в томе claude-channels_claude-home: пересоздание контейнера доступ не отберёт, а бэкап тома содержит рабочий ключ к хосту.

imgbot — /home/tgclaude/imgbot (209 МБ; python/uv, .venv, Playwright + chromium, есть .git). Headless-поиск и скачивание картинок по текстовому запросу: источники Bing, Yandex, Google и Reddit (по сохранённым кукам), Pexels API; дедуп SHA-256 + перцептивный dHash; SQLite-манифест; фоновая проверка релевантности vision-моделью через OpenRouter; MCP-сервер с инструментами request_photos, list_saved, export_by_query. Правится активно — README и .git от 2026-09-08.

  • Оба сервиса живут в контейнерах, стек — stacks/imgbot/ в репо, на сервере ~/imgbot-daemon/ (переведено 2026-09-08). Один образ imgbot-daemon:local (база mcr.microsoft.com/playwright/python:v1.62.0-noble), два сервиса:
    • imgbot-daemon — APScheduler: refresh каждые 30 мин (докачка картинок) и analyze каждые 5 мин (vision-проверка релевантности, 3 запроса к OpenRouter на картинку). ~15% ядра постоянно, поэтому cpus: 2.0, mem_limit: 3g.
    • imgbot-http — объединил прежние imgbot-mcp-http (8765) и imgbot-file-server (8766): MCP отдаётся как под-приложение Starlette (/mcp), рядом маршрут /photo/<id>. Один процесс, один порт 8765.
  • Ключевые решения по контейнерам (все с комментариями в compose, ломается молча, если забыть):
    • Данные монтируются в тот же путь, что на хосте — /home/tgclaude/imgbot-data:/home/tgclaude/imgbot-data. В SQLite images.file_path абсолютный; при монтировании в /data analyze падал с FileNotFoundError на старых строках, а новые стали бы нечитаемыми для чего угодно вне контейнера.
    • ⚠️ Корень хранения может быть не один. При переезде был пропущен второй — легаси-архив ~tgclaude/images, куда смотрели 1350 строк манифеста, — и /photo/<id> полтора часа отдавал 404 на 19.5% коллекции молча: в логах ничего, потому что analyze эти строки уже не трогал. Архив с тех пор удалён (см. «Выведено»), сейчас корень один. Правило: перед контейнеризацией проверять все префиксы file_path, а не только data_dir — select substr(file_path,1,30), count(*) from images group by 1;
    • Исходники монтируются read-only из рабочей копии /home/tgclaude/imgbot:/app:ro — правки применяются docker restart, без пересборки. В образе только зависимости и chromium.
    • HOME=/tmp обязателен: collector.py безусловно вызывает Path.home(), а uid 1001 не имеет записи в /etc/passwd образа.
    • Версия playwright в образе (1.62.0) должна совпадать с uv.lock и тегом базового образа — иначе разъезжается ревизия chromium.
    • /etc/localtime + /etc/timezone внутрь: иначе контейнер писал в общий daemon.log в UTC поверх строк в CEST.
    • shm_size: 1gb — chromium падает на дефолтных 64 МБ.
  • Сеть и доступ: imgbot-http подключён к imgbot_default и к внешней claude-channels_default, порт на хост не публикуется. Агент внутри claude-channels обращается по имени http://imgbot-http:8765. Проверено 2026-09-08: MCP initialize → 200 с serverInfo, /photo/1 → 200 image/jpeg, несуществующий id → 404.
  • ⚠️ Авторизации нет сознательно. Прежде токен IMGBOT_FILE_SERVER_TOKEN закрывал только /photo, а /mcp пускал кого угодно — проверено запросом без заголовков. Держать одну дверь на замке при открытой второй смысла не было, поэтому убрали и токен, и упоминания о нём в docstring и README. Обоснование: снаружи адреса нет, попасть можно лишь с самой машины и из подключённых docker-сетей. Если сервис когда-нибудь выставят наружу — авторизацию вернуть первым делом: request_photos тратит бюджет API и работает под живыми куками Google/Reddit, export_by_query копирует файлы.
  • ⚠️ У MCP своя защита от DNS-rebinding: по умолчанию принимает только localhost, на Host: imgbot-http:8765 отвечал 421 Invalid Host header. Список задаётся IMGBOT_ALLOWED_HOSTS (сейчас imgbot-http:8765,localhost:8765,127.0.0.1:8765) — при смене имени сервиса или порта его надо править, иначе получишь загадочный 421.
  • Код проекта теперь в Gitea — ruzzy/imgbot (private), создан 2026-09-08. До этого репозиторий существовал единственной копией на fr1: без remote, без бэкапа. Push идёт по deploy-ключу ~tgclaude/.ssh/imgbot_gitea (с правом записи), remote ssh://git@git.ruzzy.dev:2222/ruzzy/imgbot.git. Разовая копия перед переездом — ~rus/imgbot-repo-pre-merge-20260908-133444.tar.gz.
  • ⚠️ _job_analyze глушит исключения через log.exception и возвращается нормально, поэтому APScheduler пишет executed successfully даже после падения. Мониторить надо job failed/Traceback в логе, отсутствие ошибок ничего не доказывает.
  • README описывает те же refresh/analyze как задачи cron с flock — при живом daemon это дубль. Проверить crontab -l -u tgclaude (нужен root): если записи есть, работа делается дважды.
  • Данные: /home/tgclaude/imgbot-data (2.6 ГБ) — images/, живой manifest.db, exports/ 21 МБ, логи daemon.log/refresh.log/bulk_fill.log, состояние и куки Google/Reddit (mode 600).

Прочее у tgclaude:

  • /home/tgclaude/.local/lib — 2.3 ГБ python-пакетов, chrome-libs — 3 МБ библиотек для headless-chromium.
  • Ещё один Telegram-бот — send_galleries.py, send_random_photo.py, send_retry.py, gallery_paths.txt, токен в ~/.tg_bot_token (600). Отдельный и от @ultimate_log_bot, и от @iluha_rabobot.
  • /home/tgclaude/backups/gitea-backup-20260831-045458.tar.gz — 4.5 ГБ, предмиграционный снимок Gitea, см. раздел Gitea.
  • ⚠️ /home/tgclaude/.openclaw-auth-profile-secrets — права 777 на каталоге, предназначенном для секретов (сейчас пуст).
  • ⏳ Не проверено, нужен root: crontab пользователя tgclaude (README imgbot упоминает периодические задачи «run via cron» — значит, что-то запланировано), а также .cache, .docker, .ssh.

Прочие нагрузки и каталоги (не оформлены в стеки)

  • openclaw — две независимые копии, не путать:
    • /home/tgclaude/openclaw-deploy/openclaw (947 МБ) — именно отсюда запущены контейнеры (по метке com.docker.compose.project.config_files), состояние в /home/tgclaude/.openclaw (135 МБ, владелец при этом rus, mode 700). Контейнеры openclaw-openclaw-{gateway,cli}-1 (образ openclaw:local) — exited с 2026-08-31, причём у gateway restart: unless-stopped, то есть он остановлен явно, а не упал. Порты gateway 18789, 18790, 3978; cli делит с ним netns. Сеть openclaw_default (172.22.0.0/16) и пустой том openclaw_whisper_models живы.
    • ~rus/openclaw (212 МБ, не git-репозиторий — просто распакованное дерево) + ~rus/.openclaw (2.0 ГБ, из них agents/ почти всё) + ~rus/openclaw-work (147 МБ) + ~rus/.openclaw-auth-profile-secrets — отдельная копия, своих контейнеров не имеет. Единственная живая часть — sync.sh из cron rus (см. claude-channels), лог пишется.
    • Образ-снимок openclaw:backup-20260831 (1.55 ГБ) — путь откатa для openclaw.
  • ~/backups — 5.4 ГБ, всё с уже выведенного de1: openclaw-de1-2026-06-22.tgz (3.1 ГБ), claude-channels-de1-2026-06-22.tgz (1.1 ГБ), postgres-de1-2026-06-22/ (1.3 ГБ, 11 дампов — крупнейший pers_data.dump 1.2 ГБ), var-www-envs/ (.env-ы 8 проектов), 5 pre-wipe-снимков claude-channels от 2026-07-16/18. Кандидат №1 на выселение с сервера, если понадобится место.
  • ~/downloads — пустой. ~/MyVault удалён 2026-10-11 — см. claude-channels.

Выведено с fr1

  • ipcam / FTPS-приёмник (был ~/ipcam, fauria/vsftpd, порты 2221:21 + 30000-30010, домен cam.ruzzy.dev, данные /srv/ipcam/videos, сертификат /srv/ftps/certs + deploy-хук certbot) — удалён 2026-09-08: контейнер, образ, том ipcam_ipcam_logs, сеть, каталоги /srv/ipcam и /srv/ftps, хук и LE-сертификат cam.ruzzy.dev. Видео так и не начали складываться (в /srv/ipcam на момент удаления 0 файлов). Хвосты вне сервера: DNS-запись в Namecheap и настройка аплоада на самой камере.

  • Уборка 2026-09-08 — удалены неиспользуемые каталоги и их образы: ~/sc2-parser (+образ sc2-parser:latest), ~/booty (пустой), ~/booty-collector (+сиротский том booty_data, 812 МБ), ~/vacuum-scraper (2.0 ГБ, из них output/ от root — дочищен через контейнер, +образ 887 МБ), ~/downloads/vacuum-scraper-images.tar.gz (121 МБ). Код обоих проектов остался в Gitea (booty-collector.git, vacuum-scraper.git). Плюс вычищены docker build cache (43.4 ГБ), 6 анонимных томов, базовые образы node:22, node:24-bookworm-slim, node:22-alpine, busybox и связка gitea_old_1254 + gitea/gitea:1.25.4. Итог: занято на / 104 → 58 ГБ (27% → 15%).

  • ollama удалён 2026-09-08 — /home/tgclaude/ollama (3.5 ГБ: ollama.tar.zst 1.4 ГБ + lib 2.1 ГБ), /home/tgclaude/.ollama (4.6 ГБ моделей) и ollama-serve.log. Не запускался с 2026-08-25, процесса не было, юнитов и бинарей вне хома tgclaude не существовало. Итого 8.1 ГБ; хом tgclaude 21 → 13 ГБ, занято на / 58 → 50 ГБ (15% → 13%).

  • Легаси-архив ~tgclaude/images удалён 2026-09-08 — 1386 файлов, 1.9 ГБ на диске (0.84 ГиБ по сумме size_bytes). Это был не мусор, а второй корень хранения imgbot: migrate_legacy.py зарегистрировал файлы в манифесте на месте, поэтому вместе с каталогом удалены и 1350 строк таблицы images (запрос «жопки-архив»; 61 из них была признана релевантной и уже доставлена ботом). Удаление необратимо: у этих строк source_url пустой — imgbot не знал, откуда файлы взялись, и докачать их не сможет. Метаданные сохранены в ~rus/imgbot-legacy-archive-rows-20260908.json (808 КБ) — хеши, размеры, вердикты, но не источники. Монтирование каталога убрано из compose (иначе docker пересоздал бы его пустым от root). После удаления: 5578 строк, все файлы на месте, один корень хранения.

  • Синк vacuum-scraper → ~tgclaude/images признан мёртвым 2026-09-08, задание из крона root снято. Его роль забрал imgbot.

Расписания (cron + systemd-таймеры)

Полная инвентаризация на 2026-09-08 — чтобы «кто это сделал в 3 утра» не приходилось выяснять заново.

Наше:

  • crontab -u rus — пуст с 2026-10-11 (было единственное задание — синк MyVault в Gitea, снят; см. claude-channels).
  • /etc/cron.d/staticroute — @reboot правка маршрута, из образа Contabo (см. раздел «Сеть»).
  • certbot.timer + /etc/cron.d/certbot — продление сертификатов (крон-вариант выходит no-op на systemd).
  • sysstat-collect.timer (*:00/10) и sysstat-summary.timer (00:07) — сбор и суточный отчёт sar.
  • apt-daily.timer / apt-daily-upgrade.timer — unattended-upgrades, на второй повешен uu-telegram-report.sh.
  • ua-timer.timer — Ubuntu Pro. logrotate, man-db, dpkg-db-backup, e2scrub_all, systemd-tmpfiles-clean — штатное.
  • Внутри контейнера imgbot-daemon — свой APScheduler (refresh 30 мин, analyze 5 мин), в системный крон не входит.

Разобрано 2026-09-08 — что тут было и почему убрано:

  • crontab -u root пуст. Там было */5 * * * * rsync -a --delete … /home/rus/vacuum-scraper/output/images/ /home/tgclaude/images/ — зеркалило вывод vacuum-scraper в хом tgclaude каждые 5 минут. Пайплайн умер вместе с удалением vacuum-scraper (роль забрал imgbot), задание снято 2026-09-08. Это же объясняло, почему в ~tgclaude/images лежали те же UUID-имена, что в удалённом output/images: живое зеркало, а не старая копия. Мина, которую заодно убрали: rsync --delete мог удалять файлы, на которые ссылался манифест imgbot, — то есть в любой момент осиротить строки в БД.

  • /etc/cron.hourly пуст, оба скрипта удалены 2026-09-08. Что там было и почему убрано:

    • free — одна строка echo 1 > /proc/sys/vm/drop_caches, то есть сброс страничного кэша каждый час. Артефакт шаблона Contabo ради «красивого» вывода free. Срабатывание доказано по Prometheus: node_memory_Cached_bytes проваливался ровно в :17 каждого часа (именно в это время /etc/crontab запускает run-parts /etc/cron.hourly) — например 13:17 минус 3.89 ГиБ, 10:17 минус 2.22 ГиБ. При этом платы на диске не обнаружено: чтение после провалов 0.14–0.48 МиБ/с, рабочий набор мал и возвращается в кэш дешёво. Убрано не ради производительности, а потому что это регулярная пила на графиках памяти, не связанная ни с чем реальным, — прямое препятствие для разборов, ради которых на этом сервере задрана ретенция журналов.
    • fstrim — fstrim / каждый час при том, что штатный fstrim.timer (OnCalendar=weekly) на этой машине уже enabled. То есть trim шёл дважды; часовой убран, недельный таймер оставлен.

Проверено и оставлено как есть:

  • /etc/cron.daily/sysstat — no-op: первой строкой [ ! -d /run/systemd/system ] || exit 0, то есть на systemd сразу выходит. Дубля с sysstat-summary.timer нет, трогать не надо.
  • update-notifier-download.timer / update-notifier-motd.timer — десктопное наследие образа, оставлены.

Отключено 2026-09-08 как наследие шаблона:

  • Таймеры fwupd-refresh.timer (раз в сутки ходил в сеть за метаданными прошивок LVFS) и motd-news.timer (/etc/default/motd-news: ENABLED=1, URLS=https://motd.ubuntu.com — раз в сутки запрашивал у Canonical новостной текст для приветствия) — disabled.
  • Демоны ModemManager, fwupd, multipathd, udisks2 — masked. Все четыре висели с 0.0% CPU и ~100 МБ RSS суммарно, и обслуживать им было нечего: multipath-устройств нет (/dev/mapper — только control, диск один), модемов нет (в /sys/class/net только eth0, wg0, lo и docker-интерфейсы), прошивок в KVM-гостe нет, съёмных носителей тоже.

Бэкап (с 2026-10-11)

До этого регулярных бэкапов fr1 не было (только разовый снимок Gitea перед апгрейдом).

  • Домашняя rpi каждую ночь (02:15 MSK) забирает зашифрованную выгрузку — stacks/server-backup/. Ключ server-backup@rpi в ~rus/.ssh/authorized_keys с restrict,command="/home/rus/bin/backup-export" (бэкап прежнего файла — authorized_keys.bak-20261011b).
  • ~/bin/backup-export читает root-файлы через docker run --rm --network none -v /:/host:ro debian:stable-slim — sudo тут по паролю, а rus в группе docker. Шифрует ~/bin/age (статический v1.3.2) открытым ключом; закрытый только у владельца.
  • daily: /etc, /srv/gitea + согласованный .backup gitea.db (живой файл исключён), /usr/local, ~rus (без backups/openclaw/кэшей), /root, crontab. weekly (вс): тома claude-channels_claude-home, grafana, alertmanager, pushgateway + ~tgclaude (без backups/openclaw-deploy/.local).
  • ⚠️ Prometheus (том 7.9 ГБ) не бэкапится сознательно. ⚠️ Новые важные данные вне перечисленных путей в бэкап не попадут — добавлять в fr1-backup-export.
  • В образах добавился debian:stable-slim (нужен для выгрузки).

Открытые хвосты (на 2026-09-08)

Мелочи, оставленные сознательно, — чтобы не искать их заново.

  • ⚠️ В Gitea живут два временных токена — imgbot-setup и imgbot-setup2, scope write:repository,write:user. Созданы через docker exec -u 1000 gitea gitea admin user generate-access-token для создания репо ruzzy/imgbot и deploy-ключа. Удалить через API не удалось: Gitea отвечает 401 на управление токенами по токен-авторизации, нужна basic-auth. Отозвать вручную: Настройки → Приложения.
  • В таблице queries манифеста imgbot осталась строка жопки-архив, хотя все её 1350 картинок удалены. refresh может попытаться пополнить её, ища по этой строке в поисковиках, — получится мусор. Снести строку или оставить.
  • В ~tgclaude/imgbot/.env осталась строка IMGBOT_FILE_SERVER_TOKEN — после объединения HTTP-сервисов её никто не читает.
  • ~tgclaude/.openclaw-auth-profile-secrets — права 777 на каталоге для секретов (сейчас пуст), стоит chmod 700.
  • Не решено, нужны ли на fr1: ~rus/backups (5.4 ГБ архивов с выведенного de1) и спящий openclaw (947 МБ дерева + 2.0 ГБ состояния + образ-снимок 1.55 ГБ).
  • Страховочные файлы, оставленные после работ: ~rus/imgbot-repo-pre-merge-20260908-133444.tar.gz (369 КБ), ~rus/imgbot-legacy-archive-rows-20260908.json (808 КБ), ~rus/gitea/docker-compose.yml.bak-20260908.
  • ⏳ DNS-запись astra.ruzzy.dev (остаток отменённой интеграции с Марусей) — удалить у регистратора. Всё остальное на fr1 демонтировано 2026-10-11.
  • ⚠️ Описания других серверов не сверялись: servers/de1.md и servers/ru1.md датируются временем до вывода de1, так что могут врать.

Как это описание поддерживается

Механизм заведён 2026-09-08, после того как сверка нашла десяток расхождений.

В репозитории: CLAUDE.md — контракт «тронул сервер, обнови описание в том же прогоне» и правила оформления; .claude/skills/server-audit/ — метод сверки и накопленные грабли; scripts/snapshot.sh — инвентаризация живого состояния за один проход (только чтение, без sudo, секретов не печатает).

На самом сервере, для агентов, у которых репозитория нет:

  • ~rus/CLAUDE.md и ~tgclaude/CLAUDE.md — указатель на репо, правило и пять граблей, которые дороже всего стоили (ufw не видит docker-портов; группа docker равна root; процесс из SSH не переживёт ребут; абсолютные пути в чужих базах; мигающий IPv6). Рядом AGENTS.md — симлинк на тот же файл, для агентов, читающих эту конвенцию. Claude Code подхватывает CLAUDE.md вверх по дереву каталогов, поэтому файл в хоме виден и при работе в подкаталогах вроде ~tgclaude/imgbot.
  • ~rus/MyVault/TOOLS.md, раздел SSH — та же обязанность для агента Astra. Файл смонтирован в контейнер claude-channels как /vault, так что изменение видно ему сразу, и синкается в Gitea кроном каждые 5 минут.

⚠️ Чего этот механизм не гарантирует. MOTD и .bashrc не помогут: .bashrc выходит на строке 6 для неинтерактивных шеллов, а MOTD показывается только при интерактивном входе — то есть агент, дёргающий ssh fr1 'команда', не увидит ничего. Файлы в хомах достанут только тех, кто читает CLAUDE.md/AGENTS.md и чей рабочий каталог оказался внутри хома. Серверного хука «агент закончил работу» не существует. Детектор дрейфа (таймер, сравнивающий отпечаток состояния с прошлым и пишущий в Telegram) обсуждался и сознательно не заводился — если незадокументированные изменения снова начнут накапливаться, начинать стоит с него.

Установленные пакеты (сверх базы)

htop, mtr-tiny, tmux, vim, curl, wget, git, unzip, net-tools, dnsutils, ncdu, rsync, fail2ban, unattended-upgrades, sysstat.

  • Исправление ложного PushMetricsStale подготовлено в infra 2026-10-11: правило stacks/monitoring/prometheus/rules/alerts.yml ограничено job=smart (было smart|backup_.* с порогом 3 ч для всех). backup_servers успешно завершился на rpi в 15:14, итог отправляется раз в сутки после прогона, поэтому алерт в 18:24 был ложным. Свежесть backup_* остаётся под BackupStale/BackupNeverReported с отдельными интервалами. ⚠️ Изменение пока только в репозитории: агенту fr1 требуется проверить правила promtool, установить alerts.yml и reload Prometheus; с rpi административного доступа к fr1 нет. Не перепушивать старые результаты ради исчезновения алерта.