Механизм поддержания описаний в актуальном состоянии
Сверка 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:
co-authored by
Claude Opus 5
parent
4308c5c7e1
commit
b56c137b5c
@@ -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`?
|
||||
Reference in New Issue
Block a user