Files

266 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 'macOS — Voom, Eagle и автоматическая очистка'
type: how-to
status: active
created: '2026-10-21'
updated: '2026-10-21'
tags:
- macos
- maintenance
- voom
- docker
- eagle-dashboard
aliases:
- Автоматическая очистка Mac
- Voom и Eagle
related:
- '[[personal/tech/docker-mac-disk-issues]]'
- '[[personal/tech/eagle-dashboard-launchd]]'
- '[[personal/projects/virfield]]'
---
# macOS — Voom, Eagle и автоматическая очистка
Состояние проверено **21 октября 2026** по установленным скриптам, конфигурациям и включённым заданиям Eagle. Это описание действующих правил; разовые ручные удаления могли быть шире автоматической политики.
## Сетап и расписание
Управление — [Eagle Dashboard → Crons](http://127.0.0.1:8880/?tab=crons). Eagle управляет пользовательскими LaunchAgent macOS; отдельных дублирующих расписаний в Codex нет. Все четыре задания включены. `RunAtLoad=false`: загрузка задания сама по себе не запускает очистку.
| Задание в Eagle | Label | Расписание |
|---|---|---|
| Очистка артефактов и симуляторов — каждые 2 часа | `local.voom.cleanup` | Каждые 7200 секунд |
| Docker — кэш и старые образы | `com.voom.docker-policy` | Каждые 7200 секунд |
| Бэкапы Balda и Virfield — хранить сутки | `com.voom.backup-retention` | Каждые 7200 секунд |
| Штатная очистка кэшей — воскресенье, 12:00 | `local.voom.weekly-caches` | Воскресенье, 12:00 по локальному времени Mac |
Период проверки и возраст удаления — разные параметры: например, бэкапы проверяются раз в два часа, а удаляются после суток.
### Установленные файлы
Базовый каталог: `/Users/admin/.local/share/voom-cleanup/` (`~/.local/share/voom-cleanup`). Python: `/opt/homebrew/bin/python3`. Voom: `~/.local/bin/voom`.
| Файл | Назначение |
|---|---|
| `cleanup.py` | Основной проход: временные файлы, Xcode/SwiftPM, симуляторы, merged worktree |
| `tmp_cleanup.py` | Помощник для `/private/tmp`, вызывается основным проходом |
| `docker_cleanup.py`, `docker_images.py` | BuildKit, наблюдение за ссылками образов и их ограниченная очистка |
| `docker-policy.json` | Параметры Docker |
| `docker-image-observations.json` | История наблюдаемых ссылок контейнеров; нужна для отсчёта 30 дней |
| `backup_cleanup.py`, `backup-policy.json` | Суточное хранение явно перечисленных бэкапов |
| `weekly-caches.py` | Штатные команды uv, npm и Homebrew |
Скрипты используют блокировки, чтобы повторный запуск того же задания не шёл параллельно предыдущему.
### Что сейчас действительно включено
На момент проверки существуют все три файла разрешения:
- `enable-deletion` — основной проход с `--apply`.
- `enable-docker-deletion` — Docker с `--apply`.
- `enable-tmp-deletion` — отдельное разрешение удаления из `/private/tmp`.
**Очистка `/private/tmp` включена.** Основной скрипт вызывает `run_cleanup(apply and not args.scan_only)`. Прежний вызов `run_cleanup(False)` заменён. Одного изменения описания Eagle для включения недостаточно — проверены и код, и файл разрешения.
Бэкапы запускаются отдельным заданием с `--apply`, без дополнительного файла разрешения. Немедленный разовый запуск при установке этого задания не выполнялся; расписание включено. Успех конкретного прохода проверяется по отчёту, отдельно от факта включения задания.
## Worktree и артефакты Xcode/SwiftPM
### Worktree
Репозиторий: `~/DuckDuckGo/apple-browsers.git`.
- Voom удаляет вмерженные worktree без локальных изменений; merge через squash/rebase проверяется с GitHub.
- `main`, ветки Git, заблокированные worktree и worktree с защищаемыми локальными изменениями сохраняются.
- Используются `--remove-merged-worktrees --github-merged-worktrees` с явным исключением `main`.
### Стандартный DerivedData
Каталог: `~/Library/Developer/Xcode/DerivedData`.
Проектная папка подходит для удаления, если:
1. Её `WorkspacePath` указывает на уже отсутствующий проект в домашнем каталоге; либо
2. Во всём содержимом нет изменений **48 часов**, и `LastAccessedDate` не указывает на недавнее обращение.
Требуются метаданные проекта или узнаваемые признаки Xcode output. Общие кэши без таких признаков сохраняются. Для возрастного правила учитывается максимум `mtime` и `ctime` всех элементов, включая каталоги; перед удалением условие перепроверяется.
### Внутрипроектные папки
Поиск **рекурсивный** внутри:
- `~/Developer`
- `~/DuckDuckGo`
- `~/.codex/worktrees`
- `~/Documents/Codex`
- `~/Documents/code`
| Имена каталогов | Условие |
|---|---|
| `.build` | 48 часов без изменений во всём дереве и признаки SwiftPM: `workspace-state.json`, `build.db`, `debug.yaml` или `release.yaml` |
| `dd`, `deriveddata`, `DerivedData`, `.build-dd` | 48 часов без изменений во всём дереве и признаки Xcode output |
Имена Xcode-папок сравниваются без учёта регистра. Признаки Xcode: например, `Build/Intermediates.noindex/XCBuildData/build.db`, либо `info.plist` с `WorkspacePath` вместе со стандартными каталогами результатов сборки. Одного имени папки недостаточно.
При обходе пропускаются ссылки, `.git`, `node_modules`, `.venv`, `venv`, `target`, `.Trash`, `SourcePackages` и сами корни артефактов. Внутри найденного корня возраст проверяется по всему содержимому.
Основной проход откладывает удаление при обнаружении процессов сборки `xcodebuild`, `swift-build`, `swiftc`, `swift-frontend`, `clang`, `ld` или при невозможности проверить процессы.
### Исключения VS Code/DDG — 2026-10-23
Подкаталоги `.build/xcode-editor/` и `.build/vscode-local/` сохраняются целиком, включая профиль `config.json` и manifest интеграции. Если `.build` содержит эти подкаталоги и признаки SwiftPM, очистка выбирает только остальные дочерние элементы старше 48 часов; свежесть защищённых настроек не удерживает старые соседние артефакты. Перед удалением повторно проверяется, что защищённые подкаталоги не появились в выбранной целиком `.build`.
То же исключение применяется в `/private/tmp`: эти два подкаталога не обходятся и защищены повторной проверкой перед unlink. Корневой `buildServer.json` вне `.build` проектное правило не затрагивает. Правила DerivedData и удаления worktree этим изменением не менялись.
## Симуляторы Xcode
- Используется **непустой `lastUsedAt`** из `device.plist`.
- Давность последнего использования — **от 7 дней**.
- Последнее использованное устройство сохраняется; при одинаковой максимальной дате сохраняются все совпадения.
- Запущенные устройства сохраняются. Непосредственно перед удалением повторно проверяются даты и состояние через `simctl`; удаляется только `Shutdown`.
- Устройства без даты использования не удаляются. Возраст создания и `lastBootedAt` не подставляются вместо `lastUsedAt`.
- Принудительной остановки и `simctl delete all` нет.
## `/private/tmp`: 4 часа
Удаление включено в основном проходе раз в два часа.
- Рассматриваются обычные файлы любых расширений, включая исходники, патчи, логи и результаты сборок.
- Условие: **не менее 4 часов с `mtime` каждого файла**. `ctime` и возраст родительской папки не используются.
- Старый файл может быть удалён рядом со свежим; свежий файл внутри старой папки сохраняется.
- Открытые файлы сохраняются. Открытый каталог или рабочий каталог процесса защищает всё своё поддерево. Проверка — через `lsof`.
- Ссылки, сокеты, специальные файлы, чужие владельцы и другие файловые системы пропускаются. Непустые каталоги целиком не удаляются; пустые каталоги скрипт также отдельно не убирает.
- Перед удалением перепроверяются inode, размер, `mtime`, принадлежность, открытые пути; проход по каталогам выполняется без следования ссылкам.
- При недоступности `lsof` удаление запрещено. При активной сборке удаление откладывается.
## Бэкапы Balda и Virfield: сутки
Порог — **24 часа**. Для снимка берётся самая новая дата создания (`birthtime`) или `mtime` во всём дереве. Это защищает свежие APFS-клоны, у которых сохранились старые даты содержимого. Недавнее изменение любого элемента откладывает удаление всего снимка.
### Balda
Полностью проверяются отдельные снимки внутри:
- `~/.local/share/balda-workspace/backups/`
- `~/Docker/balda-mm-mac/state/setup-backups/`
- `~/Docker/balda-mm-eagle/state/setup-backups/`
Только в корнях `~/Docker/balda-mm-mac/` и `~/Docker/balda-mm-eagle/` дополнительно выбираются:
- Каталоги/файлы `backup-*`, `state-backup-*`, `state-failed-*`.
- Файлы с шаблонами `*.before-*`, `*.backup`, `*.backup-*`, `*.bak`, `*.bak-*`.
Рабочие `state`, текущие Compose/config-файлы и рабочая БД под эти шаблоны не попадают.
### Virfield
- `backups/` и `migration-backups/` внутри `~/.virfield-tart/` и `~/.virfield-v2/`.
- `~/.virfield-v2/upgrades/`.
- `~/.virfield-v2/retained-maintenance/`.
- В корнях `~/.virfield-tart/` и `~/.virfield-v2/` — только резервные файлы `*.before-*`, `*.backup`, `*.backup-*`, `*.bak`, `*.bak-*`.
Рабочие `state.db`, `images`, `leases`, `nodes`, текущие конфиги и обычные диски VM целями не являются.
### Защита бэкапов
- Используемый снимок или его содержимое сохраняются по проверке `lsof`; использование выделенного каталога бэкапов также защищает его содержимое.
- Возраст, inode и открытые пути проверяются повторно перед удалением.
- Корни со ссылками в пути, чужие владельцы, другие файловые системы, сокеты и специальные файлы пропускаются с записью в отчёт.
- Ссылки внутри снимка не обходятся. Для удаления read-only каталогов снимка добавляются права только владельцу каталогов; файлы и внешние цели ссылок не изменяются.
- Последний бэкап не закреплён навсегда: по этой политике все подходящие снимки старше суток могут быть удалены.
## Docker
Цель жёстко закреплена: context **`colima`**, builder **`colima`**, socket `/Users/admin/.colima/default/docker.sock`. Перед работой проверяется соответствие контекста и работающего builder. Другие и неактивные builders не запускаются.
### BuildKit cache
| Тип | Минимальное неиспользование |
|---|---:|
| Обычный поддерживаемый build cache | 14 дней от `LastUsedAt` |
| Cache mounts и распознанные слои установки зависимостей | 30 дней от `LastUsedAt` |
Зависимости распознаются по типу `exec.cachemount` и описаниям с apt/apk/dnf/yum, pip/uv, npm/pnpm/yarn, cargo, gem/bundler/composer, `go mod`.
Сохраняются `InUse`, `Shared`, internal/frontend, неизвестные типы и записи без корректной даты. Удаление ограничено точным списком cache-ID и фильтром возраста; BuildKit повторно проверяет использование. Broad `--all` не используется.
### Образы: 30 дней без наблюдаемых ссылок
Область автоматической очистки:
- `execution-broker` и `execution-broker-*`.
- `execution-prism-adapter`.
- `balda` и `balda-*`, включая старые обычные версии.
- Образы без тегов и без digest-reference.
Условия и исключения:
- Нет ссылок **любых контейнеров**, включая остановленные, не менее 30 дней по журналу наблюдений.
- При появлении ссылки таймер сбрасывается; после исчезновения начинается новый отсчёт.
- Сохраняются образы, найденные в доступных файлах конфигураций и переменных окружения работающих контейнеров.
- Для обычных версий сохраняется одна самая новая сборка **для каждого repository** по Docker `Created`; одинаковые максимальные даты сохраняются все. Это дата сборки, а не сравнение номеров версий.
- Явно тестовые/временные/резервные Balda-теги (`test`, `temp`, `tmp`, `scratch`, `fixture`, `acceptance`, `prNN`, `backup`, `bak`, `rollback`) могут истечь и без сохранения «последнего тестового» образа.
- Для Broker дополнительно защищены теги/имена с `rollback`, `base`, `accepted`, `approved`, `stable`, `latest`, `current`, `production`, `prod`. Эти закрепления могут сохранять больше одной версии.
- Для Balda постоянная защита всех исторических версий отключена; действуют ссылки, конфигурации, последняя обычная версия и срок 30 дней.
- Перед каждым удалением повторно проверяются Docker inventory, конфигурации, ссылки, возраст наблюдений и теги. Удаление — по точному ID без `--force`, с `--no-prune`.
`docker-image-observations.json` отражает периодические наблюдения за ссылками контейнеров. Он **не даёт полного last used**: использование в `FROM` и короткоживущие контейнеры между проверками могут не попасть в историю. Нельзя подменять эти 30 дней возрастом создания образа. История начата 21 октября 2026; потеря журнала запускает отсчёт заново.
### Известные ограничения Docker
- Именованные образы вне списка выше сохраняются независимо от отсутствия контейнеров. Например: **Mattermost**, `budget-*`, `workspace-smb`, `workspace-nfs-client`, `ssh-manager-isolation-fixture`, `go-cli*`, базовые Go/Node/Python и прочие внешние образы.
- Для Mattermost отсутствие ссылок контейнеров/конфигураций проверено; причина сохранения — ограничение списка разрешённых имён.
- При нескольких тегах удаление по ID может закончиться конфликтом; скрипт сохраняет образ и пишет `kept_conflict_or_error`.
- Полной очистки графа скрытых промежуточных образов нет: используется `/system/df` и `--no-prune`. Проявившийся безымянный родитель должен отдельно попасть под возрастное правило; ручная дочистка всей цепочки была шире автоматической политики.
- **Контейнеры, volumes и networks автоматически не удаляются.** Удаление Broker/restore-контейнеров 21 октября было отдельным ручным действием.
## Еженедельное обслуживание кэшей
По воскресеньям в 12:00 выполняются штатные команды:
```sh
/opt/homebrew/bin/uv cache prune
/opt/homebrew/bin/npm cache verify
/opt/homebrew/bin/brew cleanup
```
Возраст и выбор данных определяют сами инструменты. `npm cache verify` проверяет/обслуживает кэш; полного удаления всего npm-кэша правило не обещает. Автообновление Homebrew на время этого задания отключено.
## Что ещё не входит в автоматические правила
- `WebKitBuild` из Downloads и архив прошивки iPhone `.ipsw`: 21 октября удалены вручную, общих правил для них нет.
- Android SDK исключён из согласованной очистки; отдельно его не трогать.
- Claude VM bundles/Cowork VM не очищаются этим сетапом.
- Все произвольные кэши приложений, все Docker images или все файлы Downloads не охватываются широким правилом.
## Проверка, отчёты и управление
JSON-отчёты в `~/.local/share/voom-cleanup/`:
| Отчёт | Что смотреть |
|---|---|
| `last-report.json` | Xcode/SPM, симуляторы, worktree, сводка tmp |
| `tmp-report.json` | Кандидаты tmp, `deleted_files`, `deletion_deferred`, ошибки |
| `docker-report.json` | BuildKit, образы, причины сохранения, результаты удаления |
| `backup-report.json` | Бэкапы, возраст, занятые пути, результаты и ошибки |
| `weekly-caches-report.json` | Exit code и вывод каждой штатной команды |
Основные логи: `launchd.log`, `launchd.stderr.log`; для еженедельного задания — `weekly-caches.log`, `weekly-caches.stderr.log`.
Включённое задание и поле `apply` сами по себе не подтверждают освобождение места: проверять время отчёта, `results`, ошибки и причины отложенного удаления. Основной скрипт при активной сборке может завершиться до обновления `last-report.json`; смотреть также лог и отдельный `tmp-report.json`.
Предпросмотр без удаления:
```sh
/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/cleanup.py --scan-only
/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/tmp_cleanup.py
/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/docker_cleanup.py
/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/backup_cleanup.py
/opt/homebrew/bin/python3 ~/.local/share/voom-cleanup/weekly-caches.py --check
```
`--scan-only` у основного скрипта показывает кандидатов артефактов/симуляторов/tmp, но пропускает Voom-проход worktree. Эти проверки могут обновлять отчёты; Docker также обновляет журнал наблюдений.
Для паузы задания использовать Eagle → Crons. Чтобы отключить только tmp, можно переименовать `enable-tmp-deletion`; остальные задачи основного прохода сохранят свои настройки. Для бэкапов отдельного файла разрешения нет — управлять их заданием в Eagle. Не создавать второе расписание поверх существующих labels.
Связанные заметки: [[personal/tech/eagle-dashboard-launchd|Eagle и launchd]], [[personal/tech/docker-mac-disk-issues|Docker на Mac: диск и история очисток]], [[personal/tech/docker_colima_setup|Colima]], [[personal/projects/virfield|Virfield]].