313 lines
65 KiB
Markdown
313 lines
65 KiB
Markdown
# 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`, `books` (с 2026-10-13).
|
||
- ⚠️ `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`, `books` (с 2026-10-13), `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 --exclude .git ./ rus-161:books/ && ssh rus-161 'cd ~/books && docker compose up -d --build'` (подробно — README репо). `~/books` на fr1 — копия, не git-клон.
|
||
- 2026-10-13: vhost и сертификат LE `books.ruzzy.dev` (до 2027-01-11, продление — таймер certbot) поставил владелец `sudo sh ~/books/nginx/install.sh` (у `rus` sudo по паролю, root-вход закрыт); DNS — CNAME `books` → `fr1.ruzzy.dev` в Namecheap (добавлен в тот же день; ⚠️ wildcard-записи у зоны нет — новый поддомен = новая CNAME, иначе certbot падает с NXDOMAIN). Проверено: https 200, http→https 301, `/` без входа → `/login`, `X-Robots-Tag: noindex`.
|
||
- 2026-10-13 `CLAUDE_CODE_OAUTH_TOKEN` вписан, проверено на «SRE» (592 с.): индексация 75 с, рекап главы ~8 с, перевод выделенного — первые слова ~5 с (целиком 10–17 с), вопрос с WebSearch ~10 с; у агента «нет инструментов». ⚠️ Токен владелец передал через чат с ассистентом — перевыпустить (`claude setup-token`) и вписать напрямую.
|
||
- Контейнер `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 и работой владельца.
|
||
- Вход — логин и пароль (решение владельца 2026-10-13, вместо Telegram Login): в `.env` `BOOKS_LOGIN` и scrypt-хеш `BOOKS_PASSWORD_HASH` (`scrypt:соль:хеш`; ⚠️ без `$` — compose подставляет переменные в `.env`, с `$` хеш молча портился и пароль «не подходил»). Не больше 5 неудачных попыток за 15 мин с IP (IP — последний в `X-Forwarded-For`, его ставит nginx) и 30 — всего. Секреты `.env`: `BOOKS_PASSWORD_HASH`, `SESSION_SECRET`, `CLAUDE_CODE_OAUTH_TOKEN`.
|
||
- 2026-10-13: в образе ещё Go 1.23.4 + goimports + staticcheck (проверка листингов книг; кэш модулей — `~/books/data/go`, качает с proxy.golang.org) и pymupdf4llm. Фоновая подготовка «книжных» глав — Claude по подписке, ~5–10 мин/глава, параллельно ≤2 запроса (`LLM_PARALLEL_BG`), интерактив — отдельно ≤3. Картинки глав — `~/books/data/books/<id>/img/`.
|
||
- ⚠️ Агенту книг сознательно не дан доступ к хосту (в отличие от 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 нет. Не перепушивать старые результаты ради исчезновения алерта.
|