Механизм поддержания описаний в актуальном состоянии

Сверка fr1 показала, что дока расходится с фактом по десятку пунктов —
значит нужен не разовый проход, а контракт и инструмент.

  CLAUDE.md — правило «тронул сервер, обнови servers/<хост>.md в том же
  прогоне» плюс стиль: факт + причина + что сломается молча. Лежит именно
  в CLAUDE.md, потому что AGENTS.md автоматически агенту не подгружается.

  .claude/skills/server-audit/ — метод сверки и накопленные грабли: где
  дока врёт чаще всего, почему ufw не отвечает на вопрос об открытых
  портах, почему нужен up -d --dry-run, почему «executed successfully»
  не доказывает работу задания.

  scripts/snapshot.sh — инвентаризация живого состояния за один проход:
  только чтение, без sudo, секретов не печатает, недоступное под root
  помечает и подсказывает команду.

Первый же прогон скрипта нашёл две вещи, обе описаны:

  - IPv6 не «починился», а мигает: за один день FAILED -> REACHABLE ->
    FAILED без единой правки конфига. Описан как мигающий, с выводом
    ничего на него не завязывать.
  - В основном sshd_config стоит PermitRootLogin yes (правка шаблона
    Contabo). Root-вход закрыт только потому, что Include дроп-инов
    стоит выше, а sshd берёт первое значение. Уберёшь дроп-ин —
    включится.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
gilfanovr
2026-09-08 16:52:22 +03:00
co-authored by Claude Opus 5
parent 4308c5c7e1
commit b56c137b5c
5 changed files with 252 additions and 1 deletions
+74
View File
@@ -0,0 +1,74 @@
---
name: server-audit
description: Сверить фактическое состояние сервера с его описанием в servers/*.md и обновить описание. Использовать после любых работ на сервере (поднял/погасил сервис, поменял конфиг, удалил каталог, завёл ключ), а также когда просят проверить, соответствует ли дока реальности, актуализировать описание хоста или разобраться, что на сервере вообще происходит.
---
# Сверка описания сервера с фактом
Цель — чтобы `servers/<хост>.md` описывал сервер таким, какой он есть, и
чтобы по нему можно было принимать решения. Правила оформления — в
`CLAUDE.md` репозитория, здесь метод и грабли.
## Порядок
1. **Собери факт до чтения доки.** Сначала `./scripts/snapshot.sh <хост>`,
потом читай описание. Наоборот — начнёшь подгонять наблюдения под текст.
2. **Сверяй по пунктам, не по впечатлению.** Прогоняй утверждения доки
через факт по одному: версии, размеры, пути, порты, «включено/выключено».
При сверке fr1 2026-09-08 неверными оказались десять утверждений, включая
«IPv6 не работает» про работавший IPv6 и версию Gitea на два минора назад.
3. **Добери то, что требует root** — snapshot это помечает. Неинтерактивный
sudo не проходит: нужен `ssh -t <хост> 'sudo ...'`, то есть попроси
пользователя выполнить, а не пытайся обойти.
4. **Правь описание в том же прогоне.** Не откладывай на «потом опишу».
5. **Незакрытое — в «Открытые хвосты»** файла хоста. Токен, который не
удалось отозвать; права, которые не поправил; решение, которое за
пользователя не принимаешь.
## Где дока врёт чаще всего
- **Версии и размеры** — меняются молча при обновлении контейнера. Смотри
фактический тег у запущенного контейнера, а не запись в compose: они
расходятся, и тогда `docker compose up -d` откатывает сервис.
- **«Не работает»** — могло починиться на стороне провайдера. И наоборот:
IPv6 на fr1 за один день был `FAILED`, потом `REACHABLE`, потом снова
`FAILED` — такое надо описывать как мигающее, а не как состояние.
- **Открытые порты.** `ufw status` не отвечает на вопрос «что торчит
наружу»: docker публикует порты в iptables напрямую, минуя ufw. Только
`ss -tlnH` + `docker ps`. Непубликуемый порт контейнера всё равно
доступен с хоста по адресу контейнера — «внутренняя сеть» не периметр.
- **Процессы вне контейнеров.** `docker ps` их не покажет, а они не
переживут перезагрузку. Ищи `ps` с `PPID=1` и чужими пользователями.
- **Кроны других пользователей** — в `docker ps` и в `crontab -l` их нет.
Осиротевшее задание может годами падать молча: MTA не установлен, вывод
крона теряется.
- **Планы, записанные как факт.** «Планируется перенести DNS» при уже
перенесённом DNS. Проверяй раздел «планируется» отдельно.
## Грабли, которые стоили времени (не повторяй)
- **Пути в БД абсолютные.** Прежде чем контейнеризовать сервис, проверь
**все** префиксы путей в его базе, а не только основной каталог данных:
`select substr(file_path,1,30), count(*) from images group by 1;`
Монтируй в тот же путь, что на хосте, иначе часть данных пропадёт молча.
- **`up -d --dry-run` перед любым compose-действием.** Он показывает, что
compose собирается сделать, — например поднять забытый контейнер того же
проекта и словить конфликт портов.
- **Задание может глушить свои ошибки.** У imgbot `analyze` логирует
исключение и возвращается нормально, поэтому планировщик пишет
`executed successfully` после падения. Отсутствие ошибок не доказывает
работу — проверяй результат, а не статус.
- **Ретенция.** journald был на 500M (11 дней), sysstat на `HISTORY=7`.
Сервер, где разбирают аварии, обязан хранить историю дольше, чем живёт
проблема. У sysstat при `HISTORY>28` имена переключаются на
`saYYYYMMDD` — иначе поднятие числа бессмысленно.
- **Группа `docker` = root.** Пользователь без sudo, но в группе docker,
монтирует `/` в контейнер и делает что угодно. Учитывай при описании
доступа.
## Проверка результата
Перед отчётом прогони по получившемуся файлу: каждое ⚠️ подтверждено
наблюдением? у каждого изменения указана дата и прежнее значение? не
попали ли в текст секреты? если роль хоста изменилась — поправлена ли
строка в `AGENTS.md`?