- в 0.16 расширился набор правил по умолчанию: 29 срабатываний там, где 0.15
давал "All checks passed"
- subprocess.run без явного check (PLW1510) — проставлен check=False там, где
код и так разбирает returncode сам
- слепые except Exception в оркестраторе бэкапов и скриптах помечены
noqa: BLE001 с объяснением, почему перехват намеренно широкий
- EXE001 и TRY004 отключены в pyproject: бит исполняемости в репозитории
ничего не решает, а ValueError при разборе config.toml — про содержимое
конфига, а не про тип аргумента
- ruff format в 0.16 форматирует python внутри markdown, из-за чего
переформатирован пример в docs/drafts/gitea-runner-on-demand.md
- ansible 13.2.0 -> 14.3.1 (ansible-core 2.20.1 -> 2.21.3), ansible-lint
25.12.2 -> 26.8.0, invoke 2.2.1 -> 3.0.3, requests, croniter, yamllint,
типы и транзитивные пакеты
- ruff оставлен на 0.15: в 0.16 расширился набор правил по умолчанию, из-за
чего в скриптах и tasks.py появляется 29 срабатываний; обновление вынесено
в отдельный заход вместе с правкой кода
- ansible-lint, pyrefly и yamllint на новых версиях проходят без замечаний
- geerlingguy.security 3.0.0 -> 3.0.2: фикс пропущенной точки с запятой в
50unattended-upgrades, из-за которой origin=Ubuntu,archive=*-security не
попадал в allowed origins и автообновления безопасности ничего не ставили
- geerlingguy.docker 7.9.0 -> 8.0.0: удалён сломанный nightly-репозиторий,
изменение затрагивает только RedHat
- community.docker 5.2.2 и community.general 13.3.0 зафиксированы явно, чтобы
версии не разъезжались между окружениями
- json-file с max-size 10m и max-file 3 через docker_daemon_options
- до этого логи росли неограниченно, /var/lib/docker/containers добрался до 1.8 GB
- лимит применяется только к новым контейнерам, старые подхватят его при пересоздании
- security_ssh_permit_root_login: "no" — ansible ходит под major с become,
root по SSH не нужен
- security_autoupdate_enabled: true — unattended-upgrades ставит только
патчи из security-репозиториев, автоперезагрузка выключена
- сплошная простановка no_log сделана, инвентарь рендеров заменён на историю
- осталась проверка в lefthook/CI и перенос miniflux_postgres_user и
miniflux_postgres_database из vault в открытые group_vars
- возврат читаемого диффа отдан серии secrets-file-*, приоритет понижен
до среднего
- канон ролей лежит в ansible-roles, синхронизация идёт тасками
roles-status / roles-pull / roles-push, подписка — eget и app_image
- драфт переведён в статус «реализовано»: отмечены расхождения с дизайном
(имя канона, состав подписки) и незакрытые пункты плана
- алерты на заполнение диска стоят в health.d/system_resources.conf
(warn >75%, crit >90% по каждой смонтированной ФС)
- размеры приложений и свободное место приходят в уведомлении backup-all.py
- первый вызов после простоя занял 65,9 с и был оборван минутным умолчанием,
следующие уложились в 6–10 с
- по журналу запросов шлюза попытка была одна: 65,5 с ждал провайдер,
накладные шлюза 0,46 с
- playbook-diet.yml и files/diet: образ через app_image, uid=gid=1111,
каталоги config/data/cache, backup-targets со строкой data/
- блок сайта в Caddy разделён: /api/* идёт мимо Authelia под собственную
аутентификацию, клиентские Remote-* срезаются на уровне сайта
- правило Authelia one_factor без subject: пользователи шире группы admins
- bothub заведён custom-провайдером (base_provider_type openai, адрес без /v1):
из него ходит diet, нативному DeepSeek снимки еды не по силам
- client.log_retention_days = 30: тело запроса пишется в logs.db целиком,
снимок в 2500 px даёт запись около 911 КБ
- gobackup.template.yml и backup.template.sh не применялись: плейбук сам
удалял backup.sh, а бэкап давно идёт через backup-targets
- шаблон gobackup остался копипастой от gramps, с его путями в комментариях
- задача удаления легаси-файлов оставлена, чтобы снять их с сервера
- netdata слал в бандл server, удалённый при переименовании в backups,
и все health-алерты уходили в 404
- бандлы теперь рендерятся циклом: backups для отчётов backup-all.py,
alerts для netdata
- 20 приложений её не получают: контейнерами рулит ansible от root, а
членство в группе docker равносильно root на хосте
- у gitea, miniflux и outline группа осталась — их backup.sh дампит базу
через docker compose exec из-под самого приложения
- задача service-users-docker-group закрыта, остаток вынесен в новую
backup-dump-without-docker-group
- валидация аргументов через assert вместо пары fail с одинаковым именем
- with_items/with_dict заменены на loop, имена задач без точки на конце
- добавлены meta/main.yml и README с таблицей переменных
- задача ansible-owner-role-cleanup удалена из беклога
- сниппет access_log переименован в common, туда добавлен заголовок
Strict-Transport-Security на год; без includeSubDomains — под
vakhrushev.me есть имена не с этого сервера
- у edge-контейнера убрана capability NET_ADMIN, reverse-proxy её не требует
- убран leftover-листенер :29999 у status.vakhrushev.me
- задача caddy-hardening-nits удалена из беклога
- вместо рестарта всего прокси, через который идёт весь трафик; конфиг
указан явно через --config, иначе caddy молча перезагружает не тот файл
- задача handlers-remaining-apps удалена из беклога
- правка bind-моунтнутого конфига раньше не подхватывалась до пересоздания
контейнера; теперь template/secrets шлют notify на рестарт
- miniflux: убран recreate: always, вместо него точечный рестарт miniflux_app
по изменению файлов секретов, postgres больше не дёргается
- goaccess: рестарт не нужен, build: always пересоздаёт контейнер по новому
id образа — зафиксировано комментарием
- Порт 10050 (агент Timeweb) открыт только опросчикам хостера из Server= его
конфига, а не всему интернету.
- playbook-ufw.yml добавлен в playbook-all-setup.yml перед docker: раньше он
запускался только руками и после переезда на Timeweb не был прогнан вовсе —
на сервере ufw до сих пор с ENABLED=no и пустым набором правил.
- В шапке плейбука зафиксировано, что ufw не закрывает порты, опубликованные
докером: они идут мимо цепочки INPUT.
- `admin :2019` слушал всю web_proxy_network: любой контейнер мог POST-ом в
/config/ переписать маршрутизацию и снять forward_auth. Теперь
`admin localhost:2019`.
- Метрики для netdata отдаёт отдельный site-блок `http://:2020 { metrics
/metrics }`, netdata скрейпит новый порт.
- Порт для метрик именно 2020, а не 2019, как предлагала задача: сайт на :2019
перехватывает IPv6-петлю (admin висит только на IPv4), из-за чего
`caddy reload` внутри контейнера возвращает 0 и молча ничего не применяет —
проверено на caddy:2.11.4.
- debug-логи содержат детали аутентификации, имена пользователей и внутренности
OIDC-потоков — в обычной эксплуатации не нужны, а через docker logs/dozzle это
лишняя поверхность утечки.
- Задача убрана из беклога.
- METRICS_ALLOWED_NETWORKS не помогает: за прокси Miniflux видит IP Caddy,
а netdata скрейпит по той же web_proxy_network — по IP их не различить
- задача убрана из беклога
Мажорное обновление homepage. Единственное ломающее изменение 2.0.0 —
встроенная аутентификация, она включается только через HOMEPAGE_AUTH_ENABLED,
так что дашборд остаётся за Authelia как прежде.
- dashboard и wanderer брали имя образа и версию из переменных плейбука;
теперь, как у остальных сервисов, они видны прямо в compose
- переменные homepage_image и wanderer_version удалены за ненадобностью
- имя отражает единственный источник уведомлений — скрипт бэкапа;
плейбук удаляет с сервера прежний server.cfg, иначе он остался бы
рабочим конфигом со старыми секретами
- канал tgram:// удалён, уведомления идут на почту и в matrix
- в бандл server добавлен канал matrix; в homeserver ходим по внутренней
docker-сети, для этого у tuwunel заведён алиас без подчёркивания
- плейбук проверяет формат id комнаты: значение без '!' или '#' apprise
считает алиасом и молча не доставляет уведомление
- список приложений теперь со значком статуса (забекаплено, упал дамп,
бекапить нечего) и занятым местом; размеры считает dust одним вызовом
- в конце уведомления — свободное место на дисках сервера, по одной строке
на файловую систему
- typing.Dict/List заменены на dict/list, Optional[X] — на X | None;
эти же места подсвечивал basedpyright в редакторе
- из smtp-convert-secret-key-to-password.py убрана проверка на Python 2
- набор правил ruff расширен по канону rp-local-env (ANN, PTH, C90, G и др.),
в lefthook добавлен job ruff check --fix
- код приведён под новые правила: логирование через %s вместо f-строк,
os.path заменён на pathlib, run_backup_process и initialize разбиты
на функции по порогу цикломатической сложности
- список строится по backup-targets, а не только по успешным скриптам дампа,
поэтому приложения с бекапом одних данных (calibre, goatcounter, tududi,
tuwunel) больше не теряются
- отсутствие backup.sh при наличии backup-targets — штатный случай, теперь
это info, а warning остался только когда бекапить нечего вообще
- один инстанс на два сайта, gc-main и gc-photo: нужный выбирается по Host
- домены ведутся в goatcounter_sites (group_vars), оттуда же рендерятся имена в Caddyfile
- сайты и первый пользователь заводятся идемпотентно, креды из vault
- выключены ML (52 MB и тренировка k-means по 13.7k измерений каждые 3
часа), network-viewer, debugfs и ioping; storage tiers 3 → 2, то есть
максимум истории 3 месяца вместо 2 лет; apps.plugin обходит /proc раз в
30s вместо 10s, метрики групп процессов при этом сохранены
- из /proc/net/dev отфильтрованы veth*, br-* и docker0 — 462 графика на 50
интерфейсов, из которых 43 безымянные veth докера. Побочный эффект:
проверка имени в proc_net_dev.c идёт до netdev_rename_this_device(),
поэтому теряется единственный смапленный cgroup_caddyproxy.net_eth0
- postgres перестал собирать метрики индексов (205 индексов × 2 графика);
max_db_indexes задан внутри джоб, потому что на верхнем уровне файла
go.d разбирает только confgroup.Default и молча игнорирует остальное
Замер до: 13.7k метрик, 479 MiB, ~18% ядра. Ожидаемо после: ~11.8k
метрик и ~310 MiB.
- в 2.11 появился netflow-plugin, его стоковый netflow.yaml идёт с
enabled: true и слушателями на UDP 2055/6343; при дефолтном
`enable running new plugins = yes` он поднялся бы сам, поэтому отключён
явно — экспортировать потоки нам некому, а память не бесконечная
- ломающих изменений для нас нет: убранный из prometheus-коллектора
label_prefix и удалённый go-коллектор sensors мы не используем
- константа AUTHELIA_DOCKER была прибита к 4.39.4, а сервис уже на 4.39.20 — конфиг валидировался чужим бинарником
- authelia-validate-config сам создаёт temp/: директория в .gitignore, на свежем клоне задача падала
- session cookies: inactivity 1 week, expiration 1 month, remember_me 3 months вместо дефолтных 5 минут / 1 часа / 1 месяца
- клиенту outline-app выданы scope offline_access и grant refresh_token с кастомным lifespan (refresh token 3 месяца): без них ValidateSSOAccessTask не мог продлить access token и ротацией jwtSecret убивал все сессии Outline
- consent_mode переведён в pre-configured, чтобы при повторном входе не показывался экран согласия
- rclone 1.75.0, restic 0.19.1, resticprofile 0.33.1, btop 1.4.7,
gobackup 3.1.1, task 3.52.0, zellij 0.44.3; dust уже актуален
- gobackup 3.x переименовал ключи passphrase и MSSQL, но наши конфиги
используют только local-хранилище и sqlite
- с tududi 1.3.1 модель берётся из LLM_MODEL, а не захардкожена — шлём
deepseek/deepseek-v4-pro, Bifrost резолвит провайдера по префиксу
- ключ и base URL переведены на пару LLM_*, приоритетную над OPENAI_*
- routing_rules оставлен пустым массивом, а не удалён: при
source_of_truth=config.json отсутствующая секция не трогает строки в config.db
- `true` доверял всей цепочке X-Forwarded-For, что позволяло подделать IP
клиента; у нас ровно один хоп — Caddy
- tududi с 1.3.x сам приводит значение к 1 и пишет предупреждение на каждый старт
- тег образа соответствует релизу transports/vX.Y.Z, а теги 2.1.x рядом на
Docker Hub — это helm-chart
- конфиг после апдейта валидировать по config.schema.json: неизвестные ключи
Bifrost игнорирует молча, в логах ошибки не будет
- ключ лежал внутри config_store, где Bifrost его молча игнорировал — шлюз
работал в дефолтном режиме split вместо заявленного config.json
- поднят на верхний уровень, теперь providers и governance из config.json
авторитетно перезаписывают строки в config.db на старте
- все три приложения (homepage, transcriber, remembos) собирают образы
локально ролью app_image, реестр и yc_*-переменные выведены из оборота
- ссылавшиеся задачи обновлены: narrow-secret-scopes и
no-secrets-in-playbook-output больше не ждут эту работу
yc_oauth_token, yc_container_registry и yc_container_registry_repository
больше не используются: homepage, transcriber и remembos собирают образы
локально ролью app_image.
- invoke оставлял на внешнем терминале ixon, и Ctrl+S/Ctrl+Q съедал драйвер
терминала как XOFF/XON — до micro они не доходили
- код возврата ansible-vault теперь пробрасывается наружу
- AGENTS.md: homepage/transcriber собираются ролью app_image, registry-плейбуков
и vars/transcriber.images.yml больше нет
- local-image-build: закрыты шаги по homepage и transcriber, остался вынос
yc_*-переменных из vault; добавлены замеры архивов
- container-hardening-nits: снят пункт про плавающий `:latest` у homepage
- плейбук зовёт роль app_image, docker_login убран
- compose тянет `transcriber:{{ app_image_tag }}` с pull_policy: never
- удалены playbook-transcriber-registry.yml и vars/transcriber.images.yml
с зашитым тегом образа
- плейбук зовёт роль app_image, docker_login и `pull: always` убраны
- compose тянет `homepage-nginx:{{ app_image_tag }}` с pull_policy: never
- удалён playbook-homepage-registry.yml и registry-переменные из vars
- плейбук зовёт роль app_image, docker_login в cr.yandex удалён
- compose тянет `{{ app_name }}:{{ app_image_tag }}` с pull_policy: never
вместо зашитого тега из реестра
- подписка в SYNCED_ROLES + копия из канона ansible-roles (inv roles-pull)
- роль собирает образ приложения на control-хосте по контракту `task image`
и везёт его на сервер через docker save/load, без реестра
- docs/conventions/ с индексом: чем конвенция отличается от ADR и drafts,
статусы (рекомендуемая / обязательная), когда заводить
- app-directories.md: конфигурация / данные / кеш по принципу создания и
ценности; из категорий выводится backup-targets
- раздел «Конвенции» в AGENTS.md со ссылкой на склад
- Первый шаг задачи закрыт коммитом b7b1f49; остались вынос секретов
в файлы и проверка от дрейфа.
- Записано исключение: compose miniflux оставлен без no_log, из vault он
тянет только имя пользователя и имя базы.
- Пятнадцать задач, рендерящих файлы с секретами из vault, и три
`docker_login` получили `no_log: true`: раньше `--diff` печатал значения
в терминал целой строкой при каждом изменении файла.
- Цена — дифф этих файлов больше не виден. Снимать по мере переезда
секретов в отдельные файлы; у tududi и wanderer снять не выйдет.
- Шесть задач `secrets-file-*` вместо одной: outline, wakapi, authelia,
gramps (средний), gitea, tududi (низкий, выигрыш частичный). Wanderer
отпал — файловых секретов не умеет ни meilisearch, ни pocketbase.
- Матрица механизмов со ссылками на код уехала в
docs/drafts/secrets-file-support.md, родитель — на кладбище.
- Проверено по исходникам закреплённых версий: outline, wakapi, gitea,
gramps и authelia умеют брать секреты из файла, tududi и wanderer — нет.
- Отмечено, что `secrets-env-to-file` переросла в эпик: разбивать по
приложениям отдельным заходом.
- `inv pl` идёт с `--diff`, и 14 задач в 12 плейбуках печатают отрендеренные
секреты в терминал; `no_log` есть только в `roles/secrets`. Инвентарь,
рычаги (`no_log`, `diff: false`, вынос в отдельный файл) и порядок — в задаче.
- Актуализирована `secrets-env-to-file`: список приложений сверен с шаблонами
(tuwunel чистый, у gitea только SMTP), добавлен мотив с диффом.
- Уход от Yandex Container Registry на роль `app_image`: homepage,
transcriber, remembos собираются локально и едут через docker save/load.
- Сужение прав секретов: `yc_oauth_token` открывает весь аккаунт ради
одного `docker login` — вторая задача зависит от первой.
- Пароль лежит открытым текстом рядом с репозиторием, а `ansible.cfg`
подставляет его в любую ansible-команду — отсюда дамп секретов
через `ansible-inventory --list`.
- Заведена идеей, а не задачей: компромисс «удобство против доступности
автоматизации агенту» не разрешён, варианты с KeePassXC записаны с ценой.