Актуализировано 2026-09-30. Команды выполняются из /srv/jobhunter-prod
через production wrapper (на текущем host — с sudo). Основной web ingress —
системный Caddy. App services: api, worker, matching-worker, proxy-worker,
control-worker, beat, call-agent; data services — postgres, redis.
Развёртывание описывает gate, image verification и откат.
- При неопределённости сначала включить глобальную паузу отправки.
- Не повторять автоматически Application со статусом
delivery_unknown. - Не закрывать вакансии массово при подозрении на поломку адаптера.
- Не исправлять состояние прямым SQL без отдельного плана и backup.
- Не публиковать в тикетах токены, тело резюме, полный текст письма или PII.
- Ошибка одного источника не является основанием остановить здоровые источники.
- Любое ручное изменение политики или повтор отправки должно попадать в аудит.
Добор минимума выкачен на PROD коммитом 4f04db0.
Дополнительно проверяйте подтверждённые отправки, актуальный резерв,
остаток цели и разные компании через job-agent daily-minimum-audit.
Завершённый отчёт считается за вчера; текущий день остаётся живым снимком.
При нехватке компаний изучите исключающие основные причины, а не сумму
пересекающихся отказов. Не закрывайте старые отношения автоматически ради цели.
Проверьте:
/healthи/ready;- состояние контейнеров, worker и единственного Beat;
- свежесть последнего incremental scan каждого включённого источника;
- source health, parsing/network error rate, HTTP 403/429 и CAPTCHA/login redirects;
- длину очередей и возраст самого старого задания;
- Applications в
sending,delivery_unknown,failed,pending_review; - дневной счётчик отправок и срабатывание лимита;
- свободное место БД/хранилища резюме/backup;
- успешность последнего backup и последней тестовой реставрации;
- alerts и необработанные события безопасности.
Базовые команды:
./deploy/prod-compose.sh ps
./deploy/prod-compose.sh logs --since=24h --tail=500 api worker matching-worker proxy-worker control-worker beat call-agent
./deploy/prod-compose.sh exec -T worker celery -A app.scheduler.celery_app:celery_app inspect ping
./deploy/prod-compose.sh exec -T worker celery -A app.scheduler.celery_app:celery_app inspect activeЕсли путь Celery application в проекте иной, используйте фактический аргумент запуска worker. Не выводите полное окружение контейнера в лог диагностики.
| PROD процесс | Очереди |
|---|---|
worker |
crawling,celery |
matching-worker |
matching,applications |
proxy-worker |
proxy-maintenance |
control-worker |
email,reports,maintenance,phone |
Celery inspect ping должен показать четыре worker hostname: crawler, matching,
proxy, control. Здоровый API не подтверждает их работу. Beat запущен в одном
экземпляре; call-agent является отдельным singleton process.
В общей панели шапка показывает актуальные задачи и дневной лимит выбранного профиля, sidebar — sources/Gmail/scans. Источники проверяются из «Настроек»; системные предупреждения и AuditEvent — «История → Уведомления/Журнал системы»; телефон — «Звонки». Отдельной «Диагностики» нет.
Задача исчезает после успешного выполнения. Source alert считается устранённым только после доказанного успешного scan; queue/partial/failed не закрывают его. Отметка «Прочитано» убирает warning из непрочитанных, но не подтверждает ремонт. Неизвестный старый warning не истекает автоматически через 24 часа. Подробнее — panel-notifications.md.
start_full_scan и start_incremental_scan должны быстро вернуть scan_id, а
длительная работа выполняется worker-ом. Состояние проверяется через панель, REST
или MCP get_scan_status. Для запуска всех источников используйте отдельную batch
операцию; отказ одного child scan не должен скрывать результаты остальных.
Оператору важны:
- тип и источник;
- checkpoint и момент его последнего обновления;
- количество entrypoints/pages/jobs;
- new/updated/unchanged;
- parsing/network errors;
- started/finished timestamp;
- причина pause/degraded/failed;
- correlation/scan id для поиска логов.
Не удаляйте checkpoint после временной ошибки. Полный scan должен продолжиться с него, если конфигурация источника совместима. Если конфигурация или семантика checkpoint изменилась, создайте новый run осознанно и сохраните прежний для диагностики.
Признаки:
- внезапный нулевой результат;
- резкое падение количества вакансий;
- высокий parsing error rate;
- исчезновение обязательного элемента/ID;
- изменение структуры canonical URL;
- CAPTCHA, login redirect, 403 или длительный 429;
- изменение условий/access policy.
Порядок действий:
- Отключите источник или его автоматические действия через панель/MCP.
- Оставьте глобальную отправку включённой только если другие источники доказанно изолированы; при сомнении поставьте глобальную паузу.
- Убедитесь, что recheck не переводит вакансии этого источника в
closed. - Сохраните безопасную диагностику: HTTP status, URL без секретных query, selector/field name, агрегаты и correlation id. Не сохраняйте лишний HTML с PII.
- Проверьте публичные условия вручную.
- Обновите fixtures и адаптер, прогоните unit/integration тесты.
- Выполните малый validate/smoke run с rate limit.
- Возобновите incremental scan; full scan запускайте лишь после проверки и, для Rabota.md, без массового live-обхода без разрешения.
Одна сетевая ошибка не является подтверждением закрытия вакансии. Штатный переход
— active → possibly_closed → closed после настроенного числа подтверждённых
отсутствий или явного публичного сообщения о закрытии. Массовая аномалия адаптера
останавливает переходы.
Перед перезапуском worker выясните, является ли задача идемпотентной и где хранится её логический lock. Для scan безопасное повторное выполнение опирается на checkpoint/upsert; для Application — на уникальный idempotency key и блокировку в БД/Redis.
Если Application долго находится в sending:
- не отправляйте её вручную;
- проверьте EmailDelivery и Gmail provider message id;
- найдите correlation/application id в логах;
- если неизвестно, принял ли Gmail запрос, установите/сохраните
delivery_unknown; - решение о повторе принимает оператор после проверки Gmail, не retry job.
Celery revoke не гарантирует остановку уже выполняющейся задачи. Не используйте очистку всей очереди как стандартное средство устранения одной ошибки.
Аварийная последовательность:
pause_auto_sendчерез панель или авторизованный MCP;- выключить server-side emergency/real-send switch в secret/config platform;
- перезапустить только нужные процессы, если настройка читается при старте;
- проверить, что новые решения становятся
pending_review/blocked, а не sent; - проверить активные email-задачи и Gmail Sent вручную;
- зафиксировать AuditEvent и incident timeline.
Пауза не должна отменять уже отправленное Gmail сообщение. Она обязана блокировать новые отправки при финальной policy-проверке, в том числе задачи, поставленные в очередь до паузы.
Классификация ошибок:
- 429/5xx/сетевой сбой до подтверждения — ограниченный retry с backoff и jitter;
- однозначный 4xx из-за recipient/payload/auth —
failedлибо требуется переподключение OAuth; - timeout/обрыв после возможного принятия сообщения —
delivery_unknown, без автоматического retry; - revoked refresh token — остановить реальную отправку и переподключить аккаунт.
После каждого цикла сверяйте агрегаты:
- проверено источников и страниц;
- найдено вакансий, новых и обновлённых;
- перепроверено старых;
- объединено дублей;
- подходящих;
- автоматически отправлено;
- ожидают проверки;
- пропущено/заблокировано;
- ошибки по типам.
Для каждого sent должны быть связаны Application, EmailDelivery, canonical/source job, resume, verified contact, policy decision, score, timestamp и Gmail message id. Отсутствие связи — инцидент целостности, а не повод создать вторую отправку.
Логи должны быть JSON и включать timestamp, level, event, correlation id, а для соответствующих событий — scan id/source id/application id. Запрещено логировать:
- OAuth access/refresh token и client secret;
- cookie/session/token авторизации;
- полный текст резюме или письма;
- сырой MIME;
- непроверенный HTML целиком;
- лишние телефоны, email и другие персональные данные.
Минимальные alerts:
- API not ready;
- worker/beat heartbeat отсутствует;
- очередь растёт или задача слишком стара;
- scan пропустил ожидаемое расписание;
- source health degraded/paused;
- parsing error rate превысил установленный порог;
- аномально нулевой/низкий результат;
- всплеск 403/429/5xx;
- email
delivery_unknownили рост failed; - дневной лимит неожиданно достигнут;
- backup не создан/слишком стар;
- мало диска, рост БД, истечение TLS.
Пороговые значения определяются baseline конкретного источника; не задавайте один абсолютный порог для сайтов разного размера.
Текущая поставка только экспонирует process-local /metrics API внутри
backend-сети; Caddy намеренно отвечает 404 на публичный /metrics. Endpoint
показывает registry именно опрошенного API-процесса. Метрики, увеличенные Celery
worker-ом, не агрегируются в него автоматически, потому что multiprocess collector
или отдельный worker exporter не настроен. В Compose нет Prometheus, Alertmanager,
экспортёра очереди или off-host log sink. Внутренние domain alerts и отдельная
phone Telegram task не заменяют внешний infrastructure alert pipeline. Счётчики
сбрасываются при рестарте; оператор должен добавить внешний сбор/агрегацию и
отдельную наблюдаемость worker/Beat/очереди. Перечень выше — требования к внешним
alert rules, а не уже настроенные уведомления.
HTTP rate limiter приложения также является process-local baseline с общей корзиной, а не распределённым лимитом по ключу/роли или отдельными лимитами для read/write/scan. При нескольких API-процессах и для публичного endpoint настройте ограничения в Caddy/API gateway/WAF; внутренний limiter не является защитой от распределённой нагрузки.
Канонический production backup выполняет Compose-сервис профиля ops; он получает
MIGRATOR_DATABASE_URL из root-only /etc/jobhunter/migrator.env и не передаёт
секрет в лог или аргументы shell. В DEV при отсутствии этого значения сохраняется
fallback через POSTGRES_HOST/POSTGRES_PORT/POSTGRES_DB/POSTGRES_USER и
POSTGRES_PASSWORD:
./deploy/prod-compose.sh \
--profile ops run --rm backupПеред rollout backup должен пройти изолированную restore-проверку в отдельном одноразовом PostgreSQL container и data volume; временную базу внутри PROD кластера для этой проверки не создавайте. Разрушительная процедура восстановления в существующий PROD описана отдельно ниже и требует отдельного решения оператора.
Сервис создаёт custom-format dump атомарно через временный файл, проверяет его
pg_restore --list, сохраняет в volume backup_data с mode 0600 и создаёт
соседний .sha256. Имя итогового файла выводится после успеха.
backup_data — named volume на том же Docker host, поэтому сам по себе не
защищает от потери/компрометации host. В поставке нет расписания, retention,
шифрования или автоматического off-host upload. Настройте внешний scheduler и
экспортируйте .dump вместе с .sha256 в отдельное зашифрованное object/off-host
хранилище, затем регулярно проверяйте доступность копии. Отдельно резервируйте
хранилище резюме с сохранением SHA-256/метаданных и ключ шифрования OAuth в secret
manager. Не храните ключ рядом с зашифрованными токенами в том же архиве.
Проверяйте backup только в изолированном disposable PostgreSQL container с
отдельным data volume и без Docker network. Подключите backup_data в
него read-only, создайте внутри этого контейнера тестовую БД и восстановите dump
через pg_restore. Используйте отдельные throwaway credentials; не подключайте
контейнер к PROD PostgreSQL и не создавайте временную БД в PROD-кластере:
RESTORE_NAME=jobhunter-restore-check
RESTORE_ENV_FILE=$(mktemp)
chmod 0600 "$RESTORE_ENV_FILE"
printf 'POSTGRES_DB=restore_check\nPOSTGRES_USER=restore_check\nPOSTGRES_PASSWORD=%s\n' \
"$(openssl rand -hex 32)" > "$RESTORE_ENV_FILE"
docker volume create "$RESTORE_NAME-data"
docker run -d --rm --name "$RESTORE_NAME" \
--network none \
--env-file "$RESTORE_ENV_FILE" \
-v "$RESTORE_NAME-data:/var/lib/postgresql/data" \
-v jobhunter_backup_data:/backups:ro \
postgres:16-alpine
# Wait for readiness, then run pg_restore inside the disposable container.
docker exec "$RESTORE_NAME" pg_isready -U restore_check -d restore_check
docker exec "$RESTORE_NAME" sh -ec \
'pg_restore --list /backups/job-agent-job_agent-YYYYMMDDTHHMMSSZ.dump >/dev/null && \
pg_restore --clean --if-exists --exit-on-error --no-owner --no-acl \
--username=restore_check --dbname=restore_check \
/backups/job-agent-job_agent-YYYYMMDDTHHMMSSZ.dump'
docker rm -f "$RESTORE_NAME"
docker volume rm "$RESTORE_NAME-data"
rm -f "$RESTORE_ENV_FILE"Замените имя файла на фактическое значение команды backup. Имя volume на
текущем host — jobhunter_backup_data; на другой установке проверьте фактический
Compose project/volume перед запуском drill. Одноразовый пароль
передаётся только через файл mode 0600, не через argv. Если выполняется
checksum-проверка, проверяйте соседний .sha256 до запуска pg_restore.
Восстановление использует --clean, --if-exists, --exit-on-error,
--no-owner и --no-acl; служебные PROD БД для этого drill не используются.
Затем:
- проверьте миграционную ревизию;
- выполните read-only проверки количества ключевых сущностей и связей;
- проверьте чтение нескольких файлов резюме по сохранённому SHA-256;
- не запускайте worker/beat и реальную отправку против restored БД;
- удалите тестовые данные согласно политике retention.
Для восстановления рабочей БД сначала включите глобальную паузу и server-side
real-send kill switch, остановите api, все четыре worker, beat и call-agent, сохраните текущую БД
отдельным backup и только затем запускайте production restore с явно указанной
целевой БД. Не выполняйте destructive restore поверх работающего приложения.
- Session secret: реализация использует один активный ключ без key ring; его замена инвалидирует существующие admin/user-сессии. Для одного user account suspension/version change отзывает его browser session; cookie logout сам по себе не является server-side revoke скопированной admin session.
- MCP/API bearer: конфигурация принимает список SHA-256 hash. Для перехода можно временно добавить hash нового значения, перезапустить API, заменить клиентский secret, затем удалить старый hash и снова перезапустить. В БД нет владельцев, ролей или отдельного revoke-записа для этих ключей.
- OAuth client secret: обновите secret store и переподключите при необходимости.
- Token encryption key: текущая реализация использует один
TOKEN_ENCRYPTION_KEY, без key ID, key ring и команды перешифрования. Для плановой бесшовной ротации сначала нужно реализовать версионированное хранилище и миграцию; документация не должна изображать такую возможность существующей. - PostgreSQL/Redis credentials: обновите сервер и consumers согласованно.
При компрометации token encryption key поставьте глобальную паузу, отключите real-send, отзовите Google token, сохраните необходимый audit/backup, замените ключ и заново пройдите Gmail OAuth. Простая замена делает существующий ciphertext нечитаемым; штатной online re-encryption процедуры сейчас нет.
- глобальная пауза и server-side real-send off;
- revoke token в аккаунте Google;
- ротация client secret/ключей при необходимости;
- поиск обращений без раскрытия самих токенов;
- оценка отправленных сообщений через Gmail и EmailDelivery;
- документирование и безопасное уведомление владельца.
- немедленная пауза;
- не удалять Application/EmailDelivery/audit;
- сравнить idempotency keys и Gmail message ids;
- проверить locks, retry и ручные действия;
- исправить причину и добавить regression test до возобновления.
- заблокировать вакансию/Application;
- сохранить только необходимое безопасное evidence;
- убедиться, что recipient/resume/policy не были взяты из текста вакансии;
- проверить audit и отсутствие MCP/email side effects;
- добавить обезличенный fixture и regression test.
- отозвать credential и сессии;
- глобальная пауза;
- проверить AuditEvent: profile, preferences, source, approvals, sends;
- восстановить политики из проверенного состояния, не удаляя аудит;
- усилить MFA/сетевые ограничения внешнего identity provider, если используется.
- ежедневно: health/alerts/scan/email/backup;
- еженедельно: full scan по разрешённому расписанию, review degraded sources, pending reviews и storage growth;
- ежемесячно: restore drill, dependency/security updates, access review, audit sampling, source policy review;
- ежеквартально: key rotation exercise, disaster recovery drill, threat-model review, проверка retention и прав операторов.
Передайте следующему оператору: текущую глобальную паузу и real-send switch,
активные/зависшие runs, delivery_unknown, degraded sources, незавершённые
инциденты, последнюю успешную резервную копию/restore drill и запланированное
обслуживание. Не передавайте секреты через текст отчёта.