56 KiB
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. - Пуш-ключ
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, который из-под rus не читается: под root стоит проверить sshd -T | grep -i permitrootlogin.
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
MaxAuthTries 3
AllowUsers rus tgclaude
Через socket-activation (ssh.socket).
Сеть
- Управляется 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, GW161.97.64.1. DNS195.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 (NSdns1/dns2.registrar-servers.com). fr1.ruzzy.devA → 161.97.93.252. Апексruzzy.devA → 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
backend=systemd banaction=ufw
[sshd] enabled=true
[recidive] enabled=true bantime=1w findtime=1d maxretry=5
backend=auto logpath=/var/log/fail2ban.log
Обновления
- Ubuntu Pro подключён (аккаунт ruzzyrullezz@gmail.com): livepatch, esm-infra, esm-apps — enabled.
- livepatch приходит снапом
canonical-livepatch(v11.0.2, rev 414); рядом толькоcore22иsnapd2.76.3 — больше снапов на хосте нет. Состояние:canonical-livepatch status→ ядро6.8.0-137.137-generic,patchState: nothing-to-apply, tierupdates, поддержка этого ядра до 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:
/swapfile2 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. - vhost
default— catch-allserver_name _,listen 80 default_server, отдаёт дефолтную страницу/var/www/html/index.nginx-debian.html. На:443default_serverне задан, поэтому TLS-запрос с неизвестным SNI попадает на первый по алфавиту vhost (git.ruzzy.dev) и получает его сертификат. - Сертификаты LE:
mon,git,share,www.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 (uidprometheus) и дашборды (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; jobpushgatewayс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 не трогали.- ⚠️ 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.
- ⚠️ Pushgateway хранит последнее значение вечно: если хост умер, метрики не исчезнут, а «застынут». Поэтому свежесть ловит отдельный алерт
- Подключён
ru1(node + cadvisor + openvpn-exporter, напрямую по публичному IP).de1выведен — его таргеты удалены. Планируется: экспортёры wireguard/awg и telemt.
Gitea (~/gitea, stack в репо stacks/gitea/)
- Перенесён с
de12026-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.32026-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: SSH2222(наружу), web127.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.
AmneziaWG exit (для ru1) — перенос VPN, фаза 1
- Зеркало exit-узла
de1: AmneziaWG serverwg0= 10.8.1.0/24, ListenPort 36360/udp, те же server-ключи/PSK и obfuscation-параметры (Jc/Jmin/Jmax/S1/S2/H1-4), что и наde1(server-pubkeyLumb1o…) → на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 режет туннельный трафик (наde1ufw был выключен). - Пиры:
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)
- Перенесён с
de12026-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) — копировать его нельзя. - Монтирует
~/MyVault(/vault, 186 МБ). Vault-синк в gitea работает: cron уrus*/5 * * * *→~/openclaw/sync.sh—git pull --rebase --autostash, затем при изменениях коммит «vault sync <дата>» и push вruzzy/MyVault.git. Ключ~/.ssh/openclaw_gitea, лог~/.openclaw/logs/vault-sync.log. - 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. В SQLiteimages.file_pathабсолютный; при монтировании в/dataanalyze падал с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→ 200image/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(с правом записи), remotessh://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, причём у gatewayrestart: unless-stopped, то есть он остановлен явно, а не упал. Порты gateway18789,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из cronrus(см. 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.dump1.2 ГБ),var-www-envs/(.env-ы 8 проектов), 5 pre-wipe-снимков claude-channels от 2026-07-16/18. Кандидат №1 на выселение с сервера, если понадобится место.~/downloads— пустой.~/MyVault(186 МБ) — см. 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.zst1.4 ГБ +lib2.1 ГБ),/home/tgclaude/.ollama(4.6 ГБ моделей) иollama-serve.log. Не запускался с 2026-08-25, процесса не было, юнитов и бинарей вне хомаtgclaudeне существовало. Итого 8.1 ГБ; хомtgclaude21 → 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:*/5 * * * *→~/openclaw/sync.sh— синк 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 (refresh30 мин,analyze5 мин), в системный крон не входит.
Разобрано 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+ согласованный.backupgitea.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, scopewrite: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. - ⏳ Дроп-ин
/etc/ssh/sshd_config.d/50-cloud-init.confне читается из-подrus— под root проверитьsshd -T | grep -iE "permitrootlogin|passwordauth", чтобы знать эффективные значения, а не собранные из файлов вручную. - ⚠️ Описания других серверов не сверялись:
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.