`docker rm -f ... && reaped=$((reaped+1))` отдельным оператором: при неудаче
удаления вся конструкция возвращает ненулевой код, и под `bash -e` (режим
`run:` в Actions) это уронило бы шаг — то есть уборка мусора могла бы уронить
деплой. Переписано на if/else с явным сообщением в лог.
Логика порога проверена на РЕАЛЬНЫХ датах с прода (20 контейнеров):
убрало бы 18, оставило 2 (сегодняшние, возрастом ~1ч).
Refs #2869
Поправка к первому коммиту: там я написал, что post-step у setup-buildx-action
«не срабатывает». Замер это опроверг.
Даты создания 20 висящих билдеров: 17.05, 30.05, 31.05, 13.06, 17.06, 20.06,
28.06, 05.07 — и НИ ОДНОГО за пять недель между 05.07 и 13.08. Два последних
созданы 13.08 в 11:57:43, ровно тем прогоном, что упал с
`no space left on device`. То есть штатная уборка работает, а билдеры текут,
когда job умирает АВАРИЙНО (ENOSPC, OOM, отмена concurrency-группой) — тогда
контейнер job'а мёртв и завершающие шаги не выполняются вообще.
Из этого следует, что завершающий шаг из первого коммита сам по себе почти
бесполезен: он не отработает ровно в тех случаях, которые и создают мусор.
Поэтому добавлен шаг НА ВХОДЕ: подбирает чужие протёкшие билдеры старше
6 часов (самый долгий job — ~17 минут, живой прогон под порог не попадает)
вместе с их `_state`-томами и печатает, сколько убрал и сколько места на диске.
Завершающий шаг оставлен — он дешёвый и закрывает обычные падения.
Refs #2869
setup-buildx-action создаёт билдер `docker-container` на КАЖДЫЙ прогон, а его
post-step под Forgejo act_runner не срабатывает. К 13.08 на хосте накопилось
20 контейнеров buildx_buildkit_builder-* возрастом до двух месяцев, ~19 ГБ в
их `_state`-томах: диск ушёл на 94%, деплой упал с `no space left on device`,
и тем же дефицитом покрасились ещё две проверки на других PR.
Тома не dangling (контейнеры существуют), поэтому `docker volume prune` их не
видит; `docker system df` показывает `Build Cache 0B`, потому что кэш живёт
внутри контейнеров, а не у хостового демона — рост незаметен до отказа.
Шесть сборочных job'ов (три в deploy.yml, три в deploy-tradein.yml) получают
`id: buildx` и шаг уборки в конце. `if: always()` — чтобы убирал и после
падения сборки; ошибка уборки не роняет прогон.
Шаг ГОВОРИТ, когда не сработал: если `steps.buildx.outputs.name` пуст (а под
act это возможно), в лог уходит явная строка, а не молчаливый no-op.
Refs #2869