122 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 1b1977efa3 |
fix(deploy): правки ревью гейта ПТИЦЫ — таймаут сессии, crash-loop, множественный id (#3324)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m4s
CI / backend-tests (pull_request) Successful in 17m35s
command_timeout: 30m — дефолт appleboy/ssh-action 10m короче worst-case гейта (~17 мин), сессию убило бы посреди диагноза и авария читалась бы обрывом связи. Деградация «нет health-конфига» требовала лишь running дважды: crash-loop с временем жизни больше паузы проходил как стабильный (обе проверки видят running, просто это разные жизни контейнера). Теперь сверяется RestartCount до/после окна 15 с; окно учитывается в счётчике ожидания, иначе таймаут 240 с растянулся бы на ~24 мин и упёрся в command_timeout. cid(): `ps -aq` возвращает несколько id при залежавшемся exited-контейнере → docker inspect падает → пустой статус → ложный красный. tail -n1. worker healthcheck: убран `2>&1` (глушил причину, которую деплой печатает из .State.Health.Log), retries 3→5 — при interval 60s тройка промахов = 3 минуты, столько длится обычный флап Redis, а из unhealthy контейнер сам не выходит. |
|||
| 13c4420d1f |
fix(deploy): гейт ПТИЦЫ читает health worker/beat/frontend, а не только curl backend (#3324)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m4s
CI / backend-tests (pull_request) Successful in 17m40s
Деплой ПТИЦЫ проверял ровно один признак — `curl backend /health`. Worker и beat не имели healthcheck'а в compose вообще, у frontend была TCP-only проба, которую деплой не читал. Crash-loop воркера, вставший beat и фронт с 500 уезжали зелёным деплоем: признак «прод жив» отсутствовал в старом состоянии ровно так же, как в новом. compose: worker — `celery inspect ping -d celery@$(hostname)` (адресно в ЭТОТ узел, без -d ответил бы любой воркер на брокере); beat — свежесть shelve-файла расписания (на inspect ping beat не отвечает; поминутная beat-задача гарантирует обновление mtime не реже ~3 мин при sync_every=180 с, порог 10 мин = 3× запас). Фиктивной `true`-пробы нет: она повторяла бы State.Running. deploy.yml: после подъёма — ожидание healthy для backend/worker/beat/frontend через docker inspect, HTTP-статус фронта (TCP мало), сверка running-образа с локально скачанным $IMAGE_TAG (приём #2679 из deploy-tradein). Каждая проверка при провале печатает контейнер, статус, healthcheck-лог и хвост логов; итог — явный rc в логе и exit им же. Порядок «миграции до подъёма кода» не тронут, `up -d --wait` не используется намеренно (подъём разбит на несколько up). |
|||
| f31cb56081 |
прод: лендинг ходил в чужой бэкенд — имя сервиса двоится между продуктами
All checks were successful
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m12s
CI / backend-tests (pull_request) Successful in 17m35s
CI Trade-In / changes (pull_request) Successful in 8s
CI / frontend-tests (pull_request) Successful in 1m11s
Публичный лендинг отдавался БЕЗ витрины: без ленты сделок, без строк сверки
«прогноз против факта», без подписи разброса. Страница про точность — без
единого доказательства. Отдавалось 106 КБ вместо 241 КБ.
Причина. ПТИЦА и МЕРА — разные compose-проекты, но оба назвали свой сервис
backend и оба подключены к общей сети gendesign_shared. Изнутри фронта:
backend → 172.18.0.6 (МЕРА) + 172.18.0.9 (ПТИЦА)
tradein-backend → 172.18.0.6
BACKEND_URL=http://backend:8000 уводил серверный рендер в бэкенд ПТИЦЫ, тот
отвечал 401 no authenticated user, и страница рендерилась пустой.
Отказ тихий вдвойне. fetch не бросает — приходит валидный HTTP-ответ, просто
чужой. И имя двоится, поэтому часть перегенераций попадала в правильный адрес:
утром страница была с данными, к обеду без них, и это выглядело случайной
поломкой, а не ошибкой конфигурации.
DATABASE_URL болен тем же: @postgres:5432 мог уйти в базу ПТИЦЫ. Там спасало
лишь несовпадение кредов — отказ вместо тихого чтения не тех данных. Полагаться
на это нельзя: защита держится на том, что у чужой базы нет пользователя с
нашим паролем. Переведён на однозначное имя во всех трёх сервисах.
Гейт check-compose-ambiguous-hosts.py: пересечение имён сервисов обоих compose
и запрет ссылаться на них как на хост. Селфтест по конвенции соседних гейтов —
он провалился дважды на моих же фикстурах (в них не было общего имени, то есть
ловить было нечего), и это ровно то, ради чего селфтест и нужен.
Фальсификация на настоящем файле: возврат backend:8000 даёт точную строку 479,
возврат @postgres — все четыре ссылки.
|
|||
|
|
4e8cf5ab62 |
chore(ci): node 20 отслужил — рантайм фронтов и оба CI-джоба на node 24 LTS
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 13s
CI Trade-In / browser-tests (pull_request) Successful in 1m16s
CI Trade-In / frontend-checks (pull_request) Successful in 1m30s
CI / frontend-tests (pull_request) Successful in 1m23s
CI / openapi-codegen-check (pull_request) Successful in 2m23s
CI Trade-In / backend-tests (pull_request) Successful in 5m47s
CI / backend-tests (pull_request) Successful in 18m12s
Node 20 вышел из поддержки 30.04.2026: security-патчи для него больше не выпускаются, а образ node:20-alpine продолжает собираться и молча уносить это в прод. Node 24 — текущая Active LTS. Меняется ровно major рантайма, больше ничего: frontend/Dockerfile node:20-alpine → node:24-alpine (deps/builder/runner) tradein-mvp/frontend/Dockerfile то же, три стадии .forgejo/workflows/ci.yml node-version "20" → "24" (два джоба) .forgejo/workflows/ci-tradein.yml то же (один джоб) Версия в CI намеренно держится равной major'у из Dockerfile — так было и раньше, комментарии рядом обновлены вместе с числом, чтобы не разошлись. Ни `engines`, ни `.nvmrc` в проекте нет — других мест, где закреплён major, не осталось (проверено grep'ом по Dockerfile/yml/md). Совместимость: next 15.5.24 поддерживает node 20/22/24; sharp 0.35.4 — node ^18.17 || ^20.3 || >=22, prebuild linuxmusl-x64 есть. Приёмка — этот самый CI: джобы фронтов теперь выполняются на node 24, так что зелёный прогон PR и есть доказательство. Локально проверить нечем — на машине node 26, это не тот major. |
||
|
|
192fdbfbf6 |
fix(ops/metrics): тема «метрики» задаётся дефолтом, а не ручным заведением секрета
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m2s
CI / backend-tests (pull_request) Successful in 17m10s
Разделение тем из #3163 зависело от шага «завести секрет руками». Шаг отказал сразу же: 27.08 два прогона деплоя подряд отработали зелёными, напечатали строку про откат — и тема «метрики» осталась пустой, а весь инфраструктурный поток продолжил идти в тему клиентских инцидентов. Номер темы форума секретом не является: в репозитории уже лежат домены, пути на хостах, имена контейнеров и внешние адреса. Ставим 245 значением по умолчанию прямо в деплое; переменная окружения по-прежнему перекрывает — переезд темы или другой чат решается ею, без правки кода. Откат на METRICS_TELEGRAM_TOPIC_ID убран намеренно и запрещён тестом. Он возвращал ровно то состояние, ради ухода от которого всё затевалось, и сообщал об этом строкой в логе прогона, которую никто не читает. Молчаливое «почти правильно» хуже явной поломки. Прогнано в обе стороны: с переменной — тема из окружения, без неё — 245. backend/tests/ops — 86 passed. |
||
|
|
b90872b5d7 |
feat(ops/metrics): инфра-алерты уезжают в тему «метрики», клиенты остаются в «алертах»
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m57s
CI / backend-tests (pull_request) Successful in 17m26s
Форумная группа имеет три темы, но тема «метрики» была пуста: оба прямых получателя Alertmanager — telegram и telegram-heartbeat — брали топик из той же переменной METRICS_TELEGRAM_TOPIC_ID, что и сервис alert-ack. Развести их было нечем, и heartbeat вместе со всем инфраструктурным шумом падал в ленту клиентских инцидентов. Смешанные в одной теме, инфраструктура и клиентский инцидент не равны по срочности и приучают пролистывать обе. Вводится METRICS_TELEGRAM_INFRA_TOPIC_ID для прямых получателей Alertmanager. alert-ack и вебхук GlitchTip остаются на прежней переменной, тема поддержки не тронута. Пока новая переменная не задана, берётся старая — до этого момента поведение ровно прежнее, а не сломанное. Клиентская METRICS_TELEGRAM_TOPIC_LINE убрана целиком: после переезда обоих получателей на инфраструктурную строку шаблон её не содержит, а деплой продолжал бы её собирать и объявлять в envsubst. Тест, закрепляющий сборку такой строки, зеленел бы вечно и мешал бы её убрать. Проверено рендером, а не чтением: при заданной теме telegram и telegram-heartbeat дают 245, telegram-clients уходит вебхуком без темы; при незаданной — поля message_thread_id нет вовсе (пустое значение уронило бы Alertmanager целиком). Логика отката прогнана во всех трёх состояниях переменных. backend/tests/ops — 86 passed. Closes #3163 |
||
|
|
99125b8093 |
fix(ops/metrics): Prometheus не видел ни одного Alertmanager — цель file_sd осталась пустой
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Профиль alerts включили, а файл целей `alertmanager_targets.yml` остался плейсхолдером `[]`. Снаружи всё зелёное: alertmanager и alert-ack Up/healthy, деплой зелёный, в логах ни одной ошибки — при этом `activeAlertmanagers: []` и 1568 уведомлений в `prometheus_notifications_dropped_total`. Горящий с 26.08 `Watchdog` не доехал никуда, как и HostAgentDown с RemoteWriteStalled. Причина в том, что включатель профиля и цель для Prometheus лежали в разных местах: профиль поднимает deploy-metrics.yml по наличию токена и чата, а файл целей правился руками. Разъезд не ловится ничем — `[]` штатен при выключенном профиле, поэтому ни валидация, ни healthcheck, ни лог на него не реагируют. Файл становится производным (`alertmanager_targets.gen.yml`, в .gitignore) и рендерится деплоем тем же условием, что включает профиль: цель при включённых алертах, `[]` при выключенных. Рендер идёт до `up` и пишет усечением на месте, поэтому инод сохраняется и работающий Prometheus подхватывает цель сам — та же ловушка одиночного бинд-маунта, что уже описана в этом workflow у Alertmanager. Пустой список пишется явно, а не удалением файла: несуществующий путь docker подменяет каталогом, и Prometheus не стартует вовсе. Closes #3155 |
||
|
|
4ef8bc818b |
ci(tradein): гейт невалидных индексов — деплой ловил их уже на проде
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 59s
CI Trade-In / frontend-checks (pull_request) Successful in 1m5s
CI Trade-In / backend-tests (pull_request) Successful in 4m53s
Оборванный CREATE INDEX CONCURRENTLY оставляет индекс с indisvalid=false: планировщик им не пользуется, ошибки нет, а re-run миграции с IF NOT EXISTS видит его как существующий и молча пропускает. До сих пор это ловил только шаг 3b деплоя — то есть после раскатки на прод, ценой красного деплоя и ручного DROP INDEX CONCURRENTLY в окне. Тот же запрос теперь стоит в CI сразу после сборки схемы из 252 миграций. Проверка в деплое ОСТАЁТСЯ: CI собирает схему с нуля и по построению не может воспроизвести оборванный CIC на живой базе — это разные детекторы, не дубликаты. CI ловит миграцию, которая рождает невалидный индекс из чистой схемы; деплой ловит след аварии на проде. Оба пути проверены на реальной базе (prod tradein-postgres): штатный предикат → invalid=0, гейт зелёный; инвертированный → 358, ветка падения срабатывает и печатает список idx/tbl. Refs #2990 |
||
|
|
5ea05cffa6 |
fix(ops/metrics): правки конфига Alertmanager молча не доезжали до контейнера
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m0s
CI / backend-tests (pull_request) Successful in 17m28s
Пойман на проде 27.08 сразу после мержа #3136. На диске лежал новый конфиг — `webhook_configs` на сервис кнопки подтверждения, — `amtool check-config` его одобрил, деплой зелёный. А контейнер продолжал слать алерты напрямую: на диске: webhook_configs: url http://alert-ack:8080/alertmanager в контейнере: telegram_configs: Конфиг подключён бинд-маунтом ФАЙЛА, а рендер делает `rm` и создаёт файл заново — иначе не перезаписать: после chown он принадлежит 65534 с правами 600, а каталог принадлежит деплой-пользователю. `rm` + создание даёт НОВЫЙ инод, тогда как открытый дескриптор внутри работающего контейнера продолжает смотреть на прежний, уже удалённый. `up -d` контейнер не трогает: он сравнивает описание сервиса, а содержимое бинд-маунта в сравнение не входит. Отказ беззвучный — ни одного красного признака нигде. Значит и все прежние правки маршрутизации применялись лишь тогда, когда контейнер пересоздавался по совпадению. Перезагрузка по SIGHUP/API не лечит: она перечитывает тот же открытый инод. Лечит только пересоздание контейнера — его и добавляю, под флагом, который выставляется ПОСЛЕ успешной проверки конфига. Порядок важен: при обратном битый конфиг убивал бы работающий Alertmanager вместо того, чтобы оставить прежний работать. Прод уже приведён в соответствие вручную — контейнер пересоздан, маршрут клиентских инцидентов теперь идёт через кнопку. Эта правка нужна, чтобы следующая правка конфига доехала сама. Три теста: пересоздание есть, оно закрыто проверкой флага (безусловное рвало бы доставку на каждом деплое метрик), флаг выставляется после проверки. Прогон: 78 ops-тестов зелёные, ruff чист. |
||
|
|
6f120c6605 |
feat(observability): клиентский инцидент зовёт дежурного поимённо + чинит сломанное продолжение команды (#3078)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m59s
CI / backend-tests (pull_request) Successful in 17m26s
Две вещи, вторая — исправление собственной ошибки из #3127. ## Дежурного зовут по имени 27.08 продукты лежали 10 часов, и в канале «диск занят на 86 %» и «клиенты не могут открыть сайт» выглядели одинаково. Появился отдельный маршрут: severity=critical И host=apps, то есть критично на ПРОДУКТОВОЙ машине — значит людям недоступна МЕРА и Site Finder, а не «где-то в инфраструктуре тесно». У такого сообщения другой текст (🚨 КЛИЕНТЫ ЗАТРОНУТЫ), упоминание дежурного и repeat_interval 30 минут против 3 часов у прочего критичного: пока инцидент не погашен, напоминание должно быть неудобным. Сужение по host=apps существенно. Без него дежурного звали бы на каждую инфраструктурную мелочь, и тег перестал бы что-либо значить за неделю. Сам аккаунт в репозиторий не попадает — берётся из METRICS_TELEGRAM_ONCALL. Дежурный меняется, конфиг в git — нет. Пустая переменная = сообщение без тега, поведение не ломается. ## Починка: комментарий внутри продолжения команды В #3127 блок rm -f вместе с комментарием встал МЕЖДУ строками, каждая из которых заканчивалась обратным слешем. Строки склеиваются, и весь вызов envsubst уехал в комментарий — конфиг Alertmanager перестал бы рендериться вовсе, при полностью зелёном деплое. Синтаксически это корректный шелл, bash -n такое не ловит, а в диффе не видно: строки выглядят как отдельные. Поэтому проверка структурная и применяется ко ВСЕМ shell-блокам workflow, а не только к месту ожога. |
||
|
|
630f3e6e76 |
fix(observability): конфиг Alertmanager читается контейнером — гейт падал на своих же правах (#3078)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Как только канал алертов реально включился (#3126), профиль alerts впервые дошёл до проверки конфига — и деплой встал: amtool: error: failed to validate 1 file(s) Checking '/tmp/am.yml' FAILED: open /tmp/am.yml: permission denied Отрендеренный конфиг пишется с правами 600 и принадлежит деплой-пользователю, а и amtool, и сам Alertmanager в образе prom/alertmanager работают под nobody (65534). Прочитать чужой файл 600 они не могут. Проверка падала не на содержимом конфига, а на доступе к нему — и контейнер после подъёма упал бы ровно там же. Дефект не поймали раньше по понятной причине: без токена профиль alerts не включался, и эта ветка не исполнялась НИ РАЗУ с момента появления гейта в #3111. Проверка, которая никогда не запускалась, ничем не отличается от отсутствующей — это ровно тот класс тихой поломки, ради которого весь стек и заводится. Права не ослабляем: в файле лежит токен бота. Вместо chmod 644 (который внёс бы токен в список файлов, читаемых любым локальным пользователем машины) отдаём файл во владение 65534 одноразовым контейнером от root — passwordless sudo на хосте нет, а бинд-маунт правит host-инод напрямую. Доступ остаётся ровно у того, кто конфиг читает. Следствие, которое легко проглядеть: после смены владельца `>` в этот файл на следующем деплое уже не запишет, поэтому добавлен rm перед рендером — каталог принадлежит деплой-пользователю, пересоздать файл он может. |
||
| 70db090813 |
Merge pull request 'feat(ops): сторож расхождения «прод ↔ main» — недоехавший деплой перестаёт быть молчаливым (#3029)' (#3125) from chore/3029-deploy-drift-guard into main
All checks were successful
deploy-drift / drift (push) Successful in 8s
Deploy / changes (push) Successful in 8s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 35s
Deploy / build-worker (push) Successful in 37s
Deploy / deploy (push) Successful in 1m1s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 10s
|
|||
|
|
30a21abfa7 |
feat(observability): токен алертов приходит из секретов Actions, а не только с машины (#3078)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Стек наблюдаемости был готов ещё вчера, но Alertmanager не поднимался: профиль alerts включается, только когда заданы METRICS_TELEGRAM_BOT_TOKEN и METRICS_TELEGRAM_CHAT_ID, а читались они ИСКЛЮЧИТЕЛЬНО из файла окружения на инфраструктурной машине. То есть включить алерты можно было только правкой прод-файла руками по ssh — в обход репозитория, без следа в истории и без возможности сделать это из CI. Именно это и держало задачу открытой дольше нужного. Цена промедления измерена: 27.08 продукты лежали 10 часов, и ни одно звено оповещения не сработало (#3119); в тот же день сутки не доезжал деплой, и об этом тоже никто не узнал (#3029). Теперь три переменные форвардятся в ssh-шаг из секретов Actions. Порядок разрешения сохранён осознанно: инжектированные значения ставятся ДО того, как скрипт подхватит окружение машины, поэтому хост, если ключи заданы на нём, переопределяет секреты — последнее слово остаётся за машиной, а секреты работают как разумный дефолт. Канал проверен вживую: бот MERAsupport_bot, форум «МЕРА», тема «алерты» (message_thread_id=158) — пробное сообщение доставлено. |
||
|
|
de56b8ae01 |
feat(ops): сторож расхождения «прод ↔ main» — молчаливый недоехавший деплой становится видимым (#3029)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m8s
CI / backend-tests (pull_request) Successful in 17m30s
27.08 прод сутки жил на позавчерашнем коммите, и это нашлось только руками. Замена IP сервера (#3110) осиротила секрет DEPLOY_HOST: deploy.yml падал на `dial tcp ***:***: i/o timeout`, при этом CI оставался зелёным, PR продолжали мержиться, а deploy-infra.yml исправно обновлял Beget и создавал впечатление, что всё в порядке. Проверок, которые спрашивают не «прошёл ли прогон», а «доехал ли код», не было ни одной. Этот сторож — ровно такая: ежечасно читает HEAD с прод-хоста и сверяет с tip main. Три исхода вместо двух. «Хост не ответил» (2) отделён от «на хосте не тот код» (1) — это разные аварии с разной первой командой в разборе, и сегодня погорели именно на их смешении: недоступность выглядела как обычный красный прогон. Льготный период 30 минут гасит ложную тревогу на деплое, который ещё в полёте: ежечасный сторож неизбежно попадёт в окно между мержем и концом выката, а сторож, которого научились игнорировать, хуже отсутствующего. Логика вынесена в scripts/check-deploy-drift.sh и не ходит по сети — SSH живёт в workflow, где секреты. Благодаря этому тест ИСПОЛНЯЕТ настоящий скрипт, а не пересказывает его: ошибка в самом bash видна только при запуске. Read-only: один ssh и git rev-parse, ничего не деплоит. |
||
|
|
efaab2efda |
chore(ops): адрес Poincare — 188.124.37.140 вместо заменённого 188.246.224.93 (#3110)
All checks were successful
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI / openapi-codegen-check (pull_request) Successful in 1m58s
CI / backend-tests (pull_request) Successful in 17m16s
Selectel заменил адрес сервера в ночь на 27.08 по нашей заявке: старый 188.246.224.93 фильтровался российскими операторами и не открывался ни с домашнего интернета, ни с мобильного (#3110). Новый адрес фильтрации не имеет — проверено с машины владельца после переключения DNS. Замена уронила сервер на 10 часов: адрес на порте сменился, а в ОС остался прежний (разбор и восстановление — #3119). Здесь только то, что осталось в репозитории. Единственное функциональное вхождение — дефолт HOST_IP в ops/selectel-ci-access.sh: скрипт открывает раннеру доступ на прод-хост, и с прежним значением он молча настроил бы доступ на адрес, которого у нас больше нет. Остальные семь — тексты комментариев в Caddyfile-секциях, compose, bootstrap-скриптах и раннбуке крона; они не исполняются, но именно по ним сверяются при переезде, и разошедшийся адрес в них дороже, чем кажется. |
||
|
|
5b7ef161e3 |
feat(observability): алерты адресуются в топик форумной группы (#3078)
All checks were successful
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m2s
CI / backend-tests (pull_request) Successful in 17m24s
Бот, которым шлются тревоги, — тот же, что пересылает сообщения поддержки, а
его чат форумный. Без message_thread_id Alertmanager кладёт тревоги в общую
тему, вперемешку с клиентской перепиской.
Поле поддерживается: проверено amtool check-config на том же образе, что
поднимается в проде (prom/alertmanager:v0.28.0). Схема Alertmanager строгая и
неизвестные поля отвергает, так что успешная проверка означает именно
поддержку, а не молчаливое игнорирование.
Подставляется ЦЕЛАЯ СТРОКА, а не значение: envsubst не умеет условий, и при
шаблоне вида `message_thread_id: ${TOPIC_ID}` незаданный топик дал бы
`message_thread_id:` без значения. Это не деградация - Alertmanager с таким
конфигом не стартует вовсе, то есть алертинг исчезает целиком. Деплой
формирует либо всю строку с отступом, либо пустую.
Топик необязателен: без него поле отсутствует, алерты уходят в общую тему,
поведение прежнее.
Попутно добавлена проверка конфига через amtool ДО подъёма стека - по образцу
`caddy validate` ниже в этом же файле. amtool берётся из того же образа, что и
сам Alertmanager, иначе проверялась бы не та версия схемы. Битый конфиг теперь
роняет деплой громко, а не выключает алертинг тихо.
Тесты (4) рендерят шаблон обоими способами и разбирают результат как YAML -
проверяется фактический конфиг, а не наличие нужных слов в тексте. Отдельно
проверено, что переменная объявлена в списке envsubst: забыть её - значит
оставить в конфиге литерал плейсхолдера.
Фальсификация: на исходных файлах краснеют 3 из 4; проходит только тест,
фиксирующий сохранённое поведение при незаданном топике. tests/ops целиком -
35 passed.
|
||
|
|
beafe6925b |
fix(observability): агент не падает из-за переменной чужой роли (#3078)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m55s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Successful in 17m25s
Джоба agent-apps упала целиком:
error while interpolating services.postgres-exporter-infra.environment.
DATA_SOURCE_NAME: required variable INFRA_EXPORTER_DSN is missing a value
INFRA_EXPORTER_DSN нужен экспортеру с profiles: ["infra"], который на
продуктовом хосте не поднимается вовсе. Но compose интерполирует ВЕСЬ файл
до фильтрации по профилям, поэтому `${VAR:?}` роняет команду из-за чужой
переменной. Вместе с агентом не поднялись alloy, node-exporter и cadvisor,
которым никакой DSN не нужен. Симметрично упал бы и инфраструктурный агент -
на двух продуктовых переменных.
Второй дефект в той же цепочке: GENDESIGN_EXPORTER_DSN тоже отсутствовал.
setup-metrics-exporter-dsn.sh читает только runtime-файл окружения бэкенда, а
DATABASE_URL и TRADEIN_DATABASE_URL живут в основном. Значит подстановка
всегда была пустой, add_key печатал "нечем заполнить" и выходил с кодом 0 -
мягкий пропуск встречался с жёстким требованием compose.
Стало:
- compose: `:-` вместо `:?` у трёх DSN. Интерполяция больше не может упасть.
- deploy-metrics.yml: профиль экспортеров включается, только если нужные ЭТОЙ
роли DSN заполнены; иначе ::warning и агент поднимается без экспортера.
Громкость не убрана, а перенесена туда, где роль известна. Тот же приём, что
уже применён к Alertmanager в джобе server.
- setup-metrics-exporter-dsn.sh: читает оба файла окружения (базовый, затем
runtime - он перекрывает). Пишет по-прежнему только в runtime, лишних копий
пароля не заводит.
Почему `:-` не ослабление: пустой DATA_SOURCE_NAME поднял бы экспортер,
который молча не отдаёт метрик, - ровно тот тихий отказ, ради которого весь
стек и заводится. Поэтому пустой DSN теперь означает "профиль не включаем",
а не "поднимаем пустым".
Известное следствие, отмеченное в коде: INFRA_EXPORTER_DSN не собирает никто -
скрипт знает только про GENDESIGN_/TRADEIN_ и работает на продуктовом хосте.
Пока это так, инфраструктурный агент будет честно предупреждать, что метрик
Postgres инфры нет, вместо того чтобы падать целиком.
Тесты (3) структурные, проверяют оба конца инварианта: обязательности не
вернулись в compose; каждая DSN-переменная проверяется в деплое; профили не
захардкожены. Фальсификация: на исходных файлах краснеют все три.
|
||
| 1da2eee142 |
Merge pull request 'fix(observability): стек не поднимался — привилегированную роль спрашиваем у контейнера, а не угадываем (#3078)' (#3105) from fix/3078-metrics-role-discovery into main
Some checks failed
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy Metrics / server (push) Failing after 26s
Deploy Metrics / agent-apps (push) Has been skipped
Deploy Metrics / agent-infra (push) Has been skipped
Deploy / build-backend (push) Successful in 37s
Deploy / build-worker (push) Successful in 40s
Deploy / deploy (push) Successful in 1m4s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 10s
Reviewed-on: #3105 |
|||
|
|
33bc7e4acf |
fix(observability): привилегированную роль спрашиваем у контейнера, а не угадываем (#3078)
All checks were successful
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m2s
CI / backend-tests (pull_request) Successful in 17m21s
Стек наблюдаемости не поднялся ни на одном хосте после мержа #3099: джоба server упала, agent-apps и agent-infra пропустились как зависимые. Причина (задача 23657, 26.08 08:55): err: ОШИБКА: не нашёл роль с правом CREATE ROLE в gendesign-infra-postgres setup-metrics-grafana-role.sh искал привилегированную роль перебором трёх имён - glitchtip, forgejo, postgres. Ни одно не совпадает ни с одним реальным кластером проекта: infra-postgres -> infra, gendesign-postgres-1 -> gendesign, tradein-postgres -> tradein. Комментарий над перебором сам предупреждал, что "угадывать postgres неверно", и дальше шло угадывание. Замер на живом контейнере 26.08 (read-only, ничего не создавалось): POSTGRES_USER изнутри контейнера: infra glitchtip - отказ, forgejo - отказ, postgres - отказ, infra - 1 Стало: имя берём из POSTGRES_USER самого контейнера - это та переменная, которой роль и создана при initdb, то есть источник истины. Прежний список оставлен ПОСЛЕ него запасным путём для кластера не из образа postgres. Попутно - глоб в paths деплоя. Воркфлоу запускает ТРИ setup-скрипта, а в триггере стоял только setup-metrics-secrets.sh: правка двух остальных не заводила выкат, и на хосте молча оставалась старая версия. Тот же класс, что #2203 закрыл глобом ops/*.sh. Добавлен тест, который сверяет запускаемые скрипты с шаблонами paths - на исходном воркфлоу он краснеет, указывая на setup-metrics-exporter-dsn.sh. Тесты (6) исполняют РЕАЛЬНЫЙ скрипт с подставным docker и проверяют фактический выбор роли, а не наличие правильных слов в комментарии. Фальсификация: на исходном коде краснеют 3 из 5 ролевых тестов; проходят только те два, что фиксируют сохранённое поведение (запасной перебор и громкая ошибка при отсутствии привилегий). tests/ops целиком - 28 passed. |
||
|
|
46b42c80db |
ci(caddy): гард — import обязан быть покрыт volume-маунтом
All checks were successful
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m10s
CI / openapi-codegen-check (pull_request) Successful in 2m18s
CI / backend-tests (pull_request) Successful in 17m41s
Follow-up к #3103. Прод лёг на ~30 минут потому, что PR завёл `import ../metrics-*.caddy.snippet` в caddy/sites/infra.caddy, но не добавил bind-монты этих файлов в docker-compose.prod.yml. Соседний гард `caddy validate` эту дыру не ловит принципиально: он копирует каталог caddy/ целиком (`docker cp caddy ...`), а на проде смонтированы только отдельные файлы плюс два каталога. Расхождение между «что лежит в репозитории» и «что реально видит контейнер» видно только если сверять с маунтами. check-caddy-snippet-mounts.py разбирает bind-монты сервиса caddy:, резолвит каждый `import` в Caddyfile / caddy/sites/*.caddy / caddy/*.caddy.snippet относительно КОНТЕЙНЕРНОГО пути импортирующего файла и падает, если цель не покрыта ни одним маунтом. Именованные сниппеты `(name) { }` пропускаются, для glob/placeholder-импортов (`caddy/sites/{$CADDY_SITES:*}.caddy`) проверяется каталог. --selftest воспроизводит ровно баг #3102. |
||
|
|
309d273f3f |
feat(observability): стек метрик и логов — Prometheus, Loki, Grafana, агенты на обоих хостах
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Метрик в проекте не было ни одной: ни экспортеров, ни /metrics в бэкендах, единственный канал наблюдения — journald, единственный сигнал об аварии — исключение в GlitchTip. Из-за этого целый класс отказов невидим в принципе: задача рапортует done, строк ноль, исключения нет. Так протухли данные на семь месяцев (#2998), 34 дня был мёртв house_imv_backfill (#2698), 8 суток писал ноль newbuilding_enrich (#2767), 91 день копилось раздутие listings (#2992). Grafana не заменяет GlitchTip: ошибки остаются там. Grafana OSS не принимает Sentry DSN ни одним компонентом, а скрубберы в before_send — требование 152-ФЗ. Здесь появляется другой класс данных: ряды и алерты по трендам. Наблюдатель поставлен у ДРУГОГО провайдера, чем наблюдаемое: серверная сторона на Beget, рядом с GlitchTip. Если ляжет Poincare, мониторинг должен об этом сказать, а не лечь вместе с ним. Транспорт push, а не pull: агент на Poincare шлёт remote_write и логи исходящим HTTPS, поэтому там не открывается ни одного входящего порта сверх 22/80/443. При обрыве канала Alloy копит в WAL и досылает — pull-скрейп в той же ситуации терял бы точки именно в аварии, ради которой мониторинг и нужен. Два контура доступа с разными учётками. Пароль приёмника по построению лежит открытым на продуктовом хосте, значит его компрометация неизбежна вместе с хостом; будь это учётка витрины, утёк бы и доступ к дашбордам. GlitchTip читается прямым SQL, а не Sentry-плагином: у плагина на 6.1.6 stats_v2 отдаёт 500 (баг GlitchTip #381), Events/Discover — 404 (#416), а в grafana/sentry-datasource слово glitchtip не встречается ни разу. Схема сверена на живой базе: колонка времени называется timestamp, а не received, и отдельной таблицы IssueIndex не существует — агрегаты лежат на самой issue_events_issue. Алерты за профилем alerts: канал доставки — открытый вопрос #3078, и стек не должен на нём стоять. Деплой предупреждает, что уведомлять пока некому. Каждая настройка, способная отказать молча, закрыта явно: ретенция Prometheus задана и по времени и по размеру, retention_enabled у компактора Loki (без него retention_period не работает вовсе), путь к журналу и запуск Alloy от root (иначе агент читает ноль записей без ошибки), проверка Caddy до перезагрузки (на этом хосте тот же Caddy держит git, errors и obsidian). Refs #3078 |
||
| 729e9acc52 |
fix(tradein/deploy): подключить selectel-оверрайд — иначе после переезда бот умрёт молча (#3093)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Successful in 2m42s
Deploy Trade-In / build-browser (push) Successful in 3m16s
Deploy Trade-In / deploy (push) Successful in 3m15s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
Deploy Trade-In / test (push) Successful in 3m52s
Deploy Trade-In / build-backend (push) Successful in 34s
|
|||
| bffec49434 |
feat(ops): у волта Obsidian появился автоматический бэкап (#3090)
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy Infra Host / sync-infra-host (push) Successful in 4s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / deploy (push) Successful in 1m35s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 9s
|
|||
| de157b1024 |
fix(ci): стек CouchDB после переезда остаётся на своём хосте (#3087)
All checks were successful
Deploy Obsidian / deploy-obsidian (push) Successful in 1m52s
|
|||
| 17d23cfaae |
chore(ci): деплой не убивает worker посреди скрап-прогона (#3084)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-worker (push) Successful in 43s
Deploy / build-backend (push) Successful in 44s
Deploy / build-frontend (push) Successful in 42s
Deploy / deploy (push) Successful in 1m41s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 9s
|
|||
| 536137d460 |
feat(ci): деплой проверяет, к тому ли хосту подключился (#3079)
All checks were successful
Deploy Infra Host / sync-infra-host (push) Successful in 2s
Deploy / changes (push) Successful in 9s
Deploy Trade-In / changes (push) Successful in 12s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 52s
Deploy / build-worker (push) Successful in 52s
Deploy / build-frontend (push) Successful in 52s
Deploy Trade-In / build-browser (push) Successful in 41s
Deploy Obsidian / deploy-obsidian (push) Successful in 2m15s
Deploy / deploy (push) Successful in 1m53s
Deploy Trade-In / build-frontend (push) Successful in 2m44s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 12s
Deploy Trade-In / test (push) Successful in 4m8s
Deploy Trade-In / build-backend (push) Successful in 47s
Deploy Trade-In / deploy (push) Successful in 1m42s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
|
|||
| be2e07d9c3 |
feat(ops): лёгкий Postgres на Beget под forgejo и glitchtip (#3080)
All checks were successful
Deploy Infra Host / sync-infra-host (push) Successful in 2s
Deploy / changes (push) Successful in 7s
Deploy / deploy-caddy (push) Has been skipped
Deploy / perimeter-smoke (push) Successful in 10s
Deploy / build-backend (push) Successful in 50s
Deploy / build-frontend (push) Successful in 51s
Deploy / build-worker (push) Successful in 53s
Deploy / deploy (push) Successful in 1m43s
Deploy / deploy-status (push) Successful in 1s
|
|||
| 0ba52e55db |
chore(ops): развести crontab по хостам под переезд (#3072)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-frontend (push) Successful in 41s
Deploy / build-backend (push) Successful in 43s
Deploy / build-worker (push) Successful in 45s
Deploy / deploy (push) Successful in 1m32s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 9s
|
|||
| 01128e6331 |
chore(ci): синхронизировать /opt/gendesign на остающемся хосте (#3071)
All checks were successful
Deploy Infra Host / sync-infra-host (push) Successful in 2s
|
|||
|
|
4cb8f32dcc |
Merge remote-tracking branch 'forgejo/main' into fix/2990-clean-start-initdb
All checks were successful
CI / changes (pull_request) Successful in 10s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Successful in 57s
CI Trade-In / frontend-checks (pull_request) Successful in 1m40s
CI / openapi-codegen-check (pull_request) Successful in 2m45s
CI Trade-In / backend-tests (pull_request) Successful in 5m15s
CI / backend-tests (pull_request) Successful in 18m13s
|
||
|
|
a3ccbbd045 |
fix(db/ci): 077 гардится по USER MAPPING, деплой ждёт готовности БД по TCP
Deep-review BLOCK на PR #3011: обе правки чинили заявленный симптом только частично. 077: гард считал pending-строки по source='rosreestr' AND dedup_hash ~ md5-паттерн и пропускал backfill, только если таких строк 0. На чистой БД они есть — 003_seed_deals.sql сеет синтетические сделки с тем же паттерном, значит pending > 0 уже на пустом томе, и миграция всё равно падала на "user mapping not found" (воспроизведено в CI run 8257). Первичный гард теперь проверяет напрямую наличие USER MAPPING для gendesign_remote (идиома из app/core/fdw.py:57-62), счётчик pending оставлен вторым — экономит обращение к FDW, когда мигрировать уже нечего. deploy-tradein.yml: цикл ожидания готовности postgres ходил по unix-сокету (pg_isready без -h). На пустом томе временный init-сервер отвечает на сокете, пока docker-entrypoint-initdb.d ещё прогоняет цепочку миграций — проба зеленела посреди initdb. Добавлен -h 127.0.0.1 (тот же приём уже есть в ci-tradein.yml:157) — TCP открывается только после полного завершения initdb.d. Отдельно ужесточён sentinel baseline-детекции: раньше «схема уже накачена» проверялась одной таблицей listings (миграция 002, почти голова цепочки). Если бы гонка готовности когда-нибудь вернулась, listings был бы уже создан, а хвост цепочки — ещё нет, и baseline тихо пометил бы недостающие миграции применёнными без прогона. Теперь проверяются оба конца — listings (голова) и houses_geog_gist_idx, индекс из миграции 270 (хвост); при несовпадении (ровно один конец на месте) деплой падает громко с explicit ошибкой вместо угадывания. |
||
| f2945b7157 |
fix(ci): деплой падает громко, если :latest отстаёт от головы по компоненту (#2950) (#3023)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy Trade-In / changes (push) Successful in 15s
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / build-browser (push) Successful in 57s
Deploy / build-backend (push) Successful in 1m2s
Deploy / build-frontend (push) Successful in 1m8s
Deploy / build-worker (push) Successful in 1m9s
Deploy / deploy (push) Successful in 1m58s
Deploy / deploy-status (push) Successful in 3s
Deploy Trade-In / build-frontend (push) Successful in 3m14s
Deploy / perimeter-smoke (push) Successful in 14s
Deploy Trade-In / test (push) Successful in 4m12s
Deploy Trade-In / build-backend (push) Successful in 33s
Deploy Trade-In / deploy (push) Successful in 2m16s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
|
|||
| 95db3f44c8 |
fix(ops): бэкапы — +x на deploy-скриптах при деплое tradein, тихий s3 cp (#3005) (#3019)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-browser (push) Successful in 41s
Deploy / deploy (push) Successful in 1m38s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Successful in 2m38s
Deploy Trade-In / test (push) Successful in 3m47s
Deploy Trade-In / build-backend (push) Successful in 34s
Deploy Trade-In / deploy (push) Successful in 1m38s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
|
|||
|
|
efc965a257 |
fix(db/ci): чистый старт БД больше не падает на 077 и не может уехать на пустой схеме
Some checks failed
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Failing after 55s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m3s
CI Trade-In / frontend-checks (pull_request) Successful in 1m47s
CI / openapi-codegen-check (pull_request) Successful in 2m26s
CI / backend-tests (pull_request) Successful in 17m51s
Блокер переезда (#2990). Чистый старт на пустом томе падал: 077 читает foreign table gendesign_rosreestr_deals, а USER MAPPING создаёт бэкенд при старте (app/core/fdw.py), то есть ПОСЛЕ docker-entrypoint-initdb.d. Контейнер не поднимался вообще. Путь «пустой том» на реальном железе не исполнялся ни разу, а CI этот файл явно пропускал — гейт, который должен был поймать, был ослаблен. Проверено по всем 14 миграциям, упоминающим FDW-таблицы: читает ровно одна — 077. Остальные только CREATE/DROP FOREIGN TABLE и COMMENT, им ни USER MAPPING, ни связь с чужой БД не нужны. 077 не удалена, а сделана самозащитной: гард считает строки в md5-форме и выходит раньше обращения к FDW, если мигрировать нечего. На чистой БД таких строк нет по определению. Удаление файла было бы неверным — прод помнит миграции по bare-filename в _schema_migrations, и test_applied_migration_is_not_renamed_or_deleted падает на удалении. Исключение в ci-tradein.yml снято: теперь цепочка применяется целиком, то есть CI сам стал репетицией чистого старта. Отдельно закрыт тихий отказ в deploy-tradein.yml. Ветка baseline срабатывала по одному лишь отсутствию _schema_migrations, а это состояние неоднозначно: так выглядит и наполненный прод до внедрения tracking, и пустая БД нового сервера. Во втором случае baseline пометил бы все миграции применёнными, ни одной не прогнав, и деплой уехал бы зелёным на пустой схеме. Добавлен sentinel по listings: пусто → baseline пропускается, цепочка применяется с нуля. Refs #2990, #2989 |
||
|
|
2d2336cd51 |
fix(ops): деплой триггерится на любой ops/*.sh, а не только docker-prune.sh (#2203)
All checks were successful
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m6s
CI / backend-tests (pull_request) Successful in 17m11s
paths-filter в deploy.yml знал только про ops/docker-prune.sh (#2887) — правка ops/backup.sh или новый ops/restore-drill.sh из этого же PR не долетели бы до /opt/gendesign: деплой не триггерится -> git reset --hard origin/main не исполняется -> cron на VM месяцами крутит старую версию, молча. Точечный список сам по себе и есть баг: #2887 добавил только тот файл, о котором тогда шла речь, и следующий новый ops-скрипт (backup.sh) остался за бортом. Глоб ops/*.sh закрывает класс целиком — не матчит подпути (ops/db-bootstrap/**, ops/glitchtip-auth-forwarder/**), у них свои explicit триггеры уже есть, дублирования нет. |
||
| 68d041022d |
fix(ci): ожидание докер-лока оставляет след в логе (#2950) (#2958)
Some checks are pending
Deploy / changes (push) Waiting to run
Deploy / build-backend (push) Blocked by required conditions
Deploy / build-worker (push) Blocked by required conditions
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / deploy-caddy (push) Blocked by required conditions
Deploy / perimeter-smoke (push) Blocked by required conditions
Deploy / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-browser (push) Successful in 50s
Deploy Trade-In / build-frontend (push) Successful in 2m58s
Deploy Trade-In / test (push) Successful in 3m53s
Deploy Trade-In / build-backend (push) Successful in 32s
Deploy Trade-In / deploy (push) Successful in 1m53s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 8s
|
|||
| feff8214f7 |
fix(ci): докер-секции прод-деплоев исключают друг друга через host-lock (#2950) (#2955)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-browser (push) Successful in 48s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 47s
Deploy / build-frontend (push) Successful in 49s
Deploy / build-worker (push) Successful in 49s
Deploy / deploy (push) Successful in 1m26s
Deploy / deploy-status (push) Successful in 3s
Deploy / perimeter-smoke (push) Successful in 13s
Deploy Trade-In / build-frontend (push) Successful in 2m59s
Deploy Trade-In / test (push) Successful in 3m52s
Deploy Trade-In / build-backend (push) Successful in 37s
Deploy Trade-In / deploy (push) Successful in 2m22s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
|
|||
| 2a01dea103 |
fix(ci): прод-деплои в одну группу concurrency — прун одного убивал pull другого (#2950) (#2952)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy Trade-In / changes (push) Successful in 10s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-worker (push) Successful in 51s
Deploy / build-backend (push) Successful in 52s
Deploy / build-frontend (push) Successful in 51s
Deploy Trade-In / build-browser (push) Successful in 32s
Deploy / deploy (push) Successful in 1m21s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Successful in 3m30s
Deploy Trade-In / test (push) Successful in 4m15s
Deploy Trade-In / build-backend (push) Successful in 31s
Deploy Trade-In / deploy (push) Successful in 2m6s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
|
|||
| d25ff668f7 |
ci: правка конфига прокси больше не тянет полный деплой Site Finder (#2916) (#2925)
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / perimeter-smoke (push) Successful in 9s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-worker (push) Successful in 45s
Deploy / build-frontend (push) Successful in 44s
Deploy / build-backend (push) Successful in 45s
Deploy / deploy (push) Successful in 1m8s
Deploy / deploy-status (push) Successful in 1s
|
|||
| e86f0782da |
ci: смоук периметра МЕРЫ запускается сразу после деплоя (#2917) (#2922)
All checks were successful
Deploy / changes (push) Successful in 10s
perimeter-smoke-mera / smoke (push) Successful in 12s
Deploy Trade-In / changes (push) Successful in 14s
Deploy / build-frontend (push) Successful in 51s
Deploy / build-backend (push) Successful in 53s
Deploy / build-worker (push) Successful in 53s
Deploy Trade-In / build-browser (push) Successful in 40s
Deploy / deploy (push) Successful in 1m33s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Successful in 2m50s
Deploy Trade-In / test (push) Successful in 4m8s
Deploy Trade-In / build-backend (push) Successful in 35s
Deploy Trade-In / deploy (push) Successful in 1m39s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
|
|||
|
|
73c4c487ed |
fix(mera/b2c): житель Серова получал «вы вне области», а подсказки игнорировали выбранный город
All checks were successful
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m6s
CI Trade-In / backend-tests (pull_request) Successful in 4m50s
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Successful in 48s
Два дефекта, найденных прогоном сценария глазами посетителя на живом домене. ## 1. Город предлагали выбрать, но отвечать по нему не умели Дропдаун на сайте (`OBLAST_CITIES`, city-registry.ts) и списки покрытия (`COVERAGE_GREEN/YELLOW_CITIES`, trade_in.py) — одно множество, записанное в двух местах. Они разошлись в обе стороны: предлагали, но не отвечали: Серов отвечали, но не предлагали: Берёзовский, Среднеуральск, Ревда Житель Серова выбирал СВОЙ город из НАШЕГО дропдауна и получал: «Этот адрес вне области, по которой мы собираем данные. Сейчас это Свердловская область: Екатеринбург целиком и ещё несколько городов вокруг.» Про город в той же самой области. Серов при этом покрыт данными: 363 активных объявления в радиусе 15 км, все свежие (замер по проде). Поэтому добавлен в жёлтый тир, а не убран из дропдаунa; три недостающих города добавлены на фронт. Шапка city-registry.ts этот риск прямо предсказывала — «перед добавлением 7-го города сверить оба списка вручную, теста на это пока нет». Теперь тест есть: бэкендовый сьют читает TS-реестр и требует РАВЕНСТВА множеств. Плюс проверка, что у каждого города с порогом есть центроид, — иначе порог мёртвый, город по координатам не резолвится. ## 2. Подсказки не слушались выбранного города `city_hint` доезжает до геокодера, но на выдачу не влияет: его смотрит только екатеринбургский кадастровый тир (как признак «речь не про ЕКБ, тир пропускаем»), а DaData-тир ограничен регионом целиком и хинта не принимает. Замер: выбран Серов, введено «Ленина 1» → первой подсказкой «Невьянский р-н, пгт Верх-Нейвинский». Человек выбирает верхний вариант и считает чужой дом — ровно баг #2576, ради которого город и спрашивают. Публичная ручка теперь подставляет город в саму строку запроса. Проверено на проде: «Серов Ленина 1» даёт серовскую выдачу целиком. Для Екатеринбурга подстановка безвредна — три разных адреса дали тот же результат с префиксом и без, поэтому правило одно на все города, без исключения для основного трафика. Чинится в публичной ручке, а не в геокодере: там от `city_hint` зависит поведение закрытого контура (`target_city_ambiguous`). ## Фикстура теста `_FAR_AWAY_CITY` стояла в 21 км от центра Серова и работала как «далеко от всех» лишь потому, что Серов не был поддержан. Переехала в Тавду — 271 км до ближайшего центроида. ## Мутации убрать Серов из покрытия (состояние прода) → падает сверка списков не подставлять город в строку → падает проверка ручки откат → 21 passed Плюс backend 75 passed, vitest 56 passed, tsc, lint, build, isolation guard. `city-registry.ts` добавлен в paths-фильтр БЭКЕНДОВОГО лэйна: сверку списков делает бэкендовый тест, и без этой строки правка одного лишь дропдауна её бы не запускала — то есть ровно тот путь, которым списки и разошлись. |
||
|
|
eb7ac3d326 |
fix(ci): гейт Caddyfile не мог прочитать конфиг — -v $PWD из job-контейнера
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Successful in 52s
CI Trade-In / frontend-checks (pull_request) Successful in 1m32s
CI / frontend-tests (pull_request) Successful in 1m36s
CI / openapi-codegen-check (pull_request) Successful in 1m59s
CI Trade-In / backend-tests (pull_request) Successful in 5m10s
CI / backend-tests (pull_request) Successful in 16m46s
Первая версия шага смонтировала `$PWD` внутрь caddy-контейнера и упала на `open /etc/caddy/Caddyfile: no such file or directory`. Причина: job сам исполняется внутри контейнера, а `docker run` создаёт КОНТЕЙНЕР-БРАТ на том же демоне. Путь в `-v` резолвится на ХОСТЕ, тогда как `$PWD` — путь внутри job-контейнера, которого на хосте нет. Классическая ловушка docker-in-docker, и она не зависит от содержимого конфига — смонтируй так что угодно, монтирования просто не произойдёт. Заменено на `docker create -w /work` + `docker cp` + `docker start -a`: копия не зависит от того, как смонтирован workspace. Копируется и каталог `caddy/` — Caddyfile делает `import caddy/users.caddy.snippet`, без него validate падает на импорте. Проверено локально обе стороны: валидный конфиг → `Valid configuration`, exit 0; конфиг с незакрытой скобкой → `unexpected EOF`, exit 1. Без второй проверки гейт мог бы оказаться вечно-зелёным. |
||
|
|
56194c606a |
fix(mera/b2c): семь дефектов публичного периметра, найденных состязательным ревью
Some checks failed
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Failing after 8s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 46s
CI Trade-In / frontend-checks (pull_request) Successful in 1m3s
CI Trade-In / backend-tests (pull_request) Successful in 4m42s
Ревью четырьмя независимыми линзами (периметр, семантика Caddy, политика ПДн
против кода, фронт) + по два проверяющих на каждую находку. Ниже — то, что
пережило проверку и воспроизведено на живом коде, а не выведено из чтения.
## Caddy: открытый редирект и потерянные ссылки
Захват хвоста регекспом (`^/trade-in/mera-public/(.+)$` → `redir /{re…1}`) —
открытый редирект. Захват берётся из РАСКОДИРОВАННОГО пути, поэтому
`/trade-in/mera-public/%5Cevil.example/pay` даёт цель `/\evil.example/pay`, а
браузеры трактуют `/\` как `//` — Location уводит на чужой хост. Готовая
фишинговая заготовка с домена, который напечатан внутри оферты и уходит
модератору эквайера. Заменено поимённым списком путей: такой адрес просто не
матчится.
Адреса со слэшем на конце (`/oferta/`, и длинные `…/oferta/`) отдавали 404 —
ровно те ссылки, ради сохранности которых редирект и делался. Добавлена
нормализация, цепочка замкнута (проверено: 2 перехода → 200).
Query-строка терялась: размещённые ссылки с UTM приходили бы в аналитику как
прямой заход. `uri strip_prefix` + `{uri}` переносит её. Обёртка `route`
обязательна — без неё `redir` выполняется раньше `uri` и Location равен
исходному адресу (бесконечный цикл, поймано на стенде).
`/v3` — черновое превью с маркетинговыми плейсхолдерами — было открыто на
боевом домене молча. Теперь названо вслух и запинено тестом.
## Гейты, которых не было
`caddy validate` не звал НИ ОДИН workflow, а deploy применяет конфиг не через
`reload` (тот отказался бы принять битый), а через `up -d --force-recreate` —
опечатка уводит контейнер в crash-loop и роняет ВСЕ домены. Добавлен гейт в
ci.yml, тем же образом caddy:2, что и на проде.
Проверка «роут ↔ Caddy» была односторонней и пропускала обратную ошибку —
путь, открытый наружу, о котором приложение не знает. Так и уехал `/v3`.
Теперь двусторонняя, плюс проверка, что для каждой страницы есть 301.
## Бюджет внешнего геокодера
Per-IP окна ограничивают одного клиента, но не сумму: 40/мин с адреса — это
57 600 в сутки при бесплатном тире DaData в 10 000, ОБЩЕМ с закрытым контуром.
Подтверждено на проде: достаточно упомянуть не-екатеринбургский город, чтобы
локальный тир отключился и запрос гарантированно ушёл во внешний сервис. То
есть один скрипт оставлял без подсказок платящих пилотов.
Per-IP снижен до 20/мин, добавлен общий суточный потолок 2000 и потолок
одновременных подсказок (4): кадастровый тир уходит в FDW-скан чужой базы,
держит соединение около секунды, а пул общий с B2B — полтора десятка
параллельных публичных запросов клали бы закрытый контур.
## «Адрес нигде не сохраняется» — теперь правда целиком
Две утечки, обе воспроизведены:
1. ЖУРНАЛЫ. Геокодер печатает введённую строку открытым текстом на каждый
вызов, прод пишет stdout в persistent journald — адрес ложился на диск
рядом с IP того же запроса в access-логе Caddy. Закрыто фильтром логов на
время публичного запроса (contextvar, переживает await и to_thread).
Закрытый контур логи сохраняет: они нужны для разбора жалоб пилотов.
2. МОНИТОРИНГ. sentry_sdk кладёт в событие ПОЛНОЕ тело запроса — а тело
публичной ручки это ровно `{"q": "<адрес>"}`; `send_default_pii=False` тут
не гейт, он про куки. Плюс брэдкрамб httpx несёт адрес в query геокодера.
Закрыто `scrub_public_address`.
Текст п. 5.4 политики расширен до «ни в журналы веб-сервера, ни в технические
журналы, ни в мониторинг» — ровно то, что теперь обеспечено кодом.
## Фронт
- Отмена запроса подсказок откладывалась внутрь следующего debounce-такта и
не наступала вовсе, если человек переставал печатать: ответ по старой строке
долетал и ложился в список. Контроллер создаётся сразу, отменяется в cleanup.
- Список схлопывался на каждое нажатие — клик по намеченному пункту
промахивался. Старая выдача висит, пока не пришла новая.
- «Комнат» с лэндинга — свободный текст: «студия» не совпадала ни с одним
option, селект показывал пустоту, parseInt давал NaN, на сервер уходил
rooms: null → 422 с текстом «сломалось на нашей стороне». Нормализация
вынесена чистой функцией и покрыта тестами.
- У пробы покрытия не было ни таймаута, ни отмены: оборванное соединение
оставляло кнопку в «Смотрим данные…» навсегда. 15 с + понятный текст.
- Ошибка подсказок глушилась в пустой список — тупик без объяснения.
- Комбобокс: Tab проваливался в кнопки подсказок, список не закрывался по
уходу фокуса и перекрывал поля, Escape оставлял висячий aria-activedescendant.
## Проверено
Локальный стенд (реальный site-блок Caddy + заглушка): 18 маршрутов, включая
`%5C`, `//`, `%2F` — все три теперь 404. vitest 55 passed, backend 17 passed по
публичному API, tsc, lint, build, isolation guard 41 файл, caddy validate.
Мутации: снять редакцию логов → падает тест журналов; не вырезать тело запроса
→ падает тест мониторинга; убрать /estimate из Caddy → падает тест маршрутов.
|
||
|
|
b1fb7bb055 |
Merge remote-tracking branch 'forgejo/main' into feat/mera-public-api
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Successful in 46s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m7s
CI Trade-In / backend-tests (pull_request) Successful in 4m45s
# Conflicts: # tradein-mvp/backend/app/core/rbac.py |
||
|
|
7424c283d5 |
feat(mera/b2c): отдельный экран оценки на meraocenka.ru и короткие адреса
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 44s
CI Trade-In / frontend-checks (pull_request) Successful in 1m2s
CI Trade-In / backend-tests (pull_request) Successful in 4m41s
## Экран проверки — /estimate Проверка квартиры вынесена на собственный адрес: там у автокомплита есть место под список подсказок, а у результата — место рядом с полями. Форма в герое лэндинга осталась входной точкой и уводит сюда, донося набранное через sessionStorage (НЕ через query — адрес в URL попал бы в access-лог Caddy рядом с IP посетителя, а мы на той же странице обещаем ничего не хранить). Показывает живую пробу покрытия: сколько похожих квартир продаётся рядом и сколько в среднем висят их объявления. Ни одной рублёвой цифры — цену продаёт платный шаг. Тексты вердикта вынесены чистой функцией (coverage-copy.ts) и покрыты тестами: подпись под возрастом обязана говорить «объявление», а не «продаётся» (выборка цензурирована), при неизвестном возрасте плитки нет вообще, а пустая когорта объясняется как факт о рынке с подсказкой, что поменять, — директива «никогда не блокировать вывод». ## Короткие адреса Человек больше не видит /trade-in/mera-public/... — только /, /estimate, /oferta, /refund, /privacy. Длинные адреса отдают 301 на короткие: у страницы один канонический адрес, старые ссылки живы. Цена решения: ссылки работают только на meraocenka.ru (короткие пути раздаёт этот хост). Открывать лэндинг для проверки нужно там же, а не с gendsgn.ru. Ссылки эмитятся обычным <a> (PublicLink) — next/link подставляет basePath, и href="/estimate" уехал бы на несуществующий /trade-in/estimate. ## Три дефекта, найденных на живом сайте 1. Палитра v3 никуда не доезжала. b2c-tokens.ts не импортировал НИКТО, ни одна --b2c-* переменная не объявлялась, каскад молча пропускал такие декларации — лэндинг отдавал 200 бесцветным. Добавлен мост b2cVars, гейтом стал тест: каждая использованная в CSS переменная обязана быть объявлена. 2. Голый /trade-in/mera-public падал в 404 — матчер был со слэшем и звёздочкой. Ровно туда вела «Главная» в подвале. 3. «Для бизнеса» вела на «/» — то есть на сам лэндинг. Теперь абсолютный адрес B2B-контура. Пункты «Проверьте себя» и «Продажа под ключ» вели на якоря, которых нет нигде: приведены к виду «Статьи» — видны, но не кликабельны. ## Периметр и приватность Подсказки переведены на POST: access-лог публичного домена пишет URI целиком, то есть GET с ?q= сохранял бы адрес квартиры в файл. Тело в лог не попадает. Метод запинен тестом — это часть обещания, а не стиль. П. 5.4 политики ПДн переписан ВМЕСТЕ с кодом: прежний текст утверждал, что адрес не покидает браузер, и это перестало быть правдой. Новый говорит точно — передаётся, используется однократно, в базах не сохраняется. Последнее проверено по коду: suggest() работает без кэша, проба — один SELECT, аудит пишет строку только при наличии username. PUBLIC_ESTIMATE_ENABLED сузился до платного шага (бесплатная проба не хранит ничего, платный расчёт хранит). ## Проверено vitest 47 passed (9 файлов), tsc, next lint, next build, isolation guard 40 файлов, backend 75 passed, caddy validate = Valid configuration. Мутации структурных гейтов: убрать --b2c-accent-text из b2cVars → падает тест палитры убрать /estimate из @meraPages → падает тест маршрутов добавить импорт next/link → падает тест basePath откат → 14 passed Caddyfile добавлен в paths-filter фронтового лэйна: его читает тест маршрутов, и без этой строки правка одного лишь Caddyfile не запускала бы ни один гейт. Refs #2894, #2895 |
||
|
|
90133c4c2e |
Слияние main в fix/2683-manifest-drift
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Successful in 1m0s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m41s
CI / openapi-codegen-check (pull_request) Successful in 2m41s
CI Trade-In / backend-tests (pull_request) Successful in 5m31s
CI / backend-tests (pull_request) Successful in 17m12s
main принёс PR #2751 (ruff-шаг в ci-tradein.yml/backend-tests) параллельно с fetch-depth: 0 из этой ветки в том же job'е — не противоречат друг другу, слились автоматически. Единственное ручное разрешение — _manifest_applied.txt (modify/delete): main дописал файл, ветка его удаляет. Разрешение — удаление, это и есть предмет PR: гейт номеров миграций берёт эталон из git (origin/main), а не из ручного манифеста, который отставал и по построению не мог покраснеть (#2683, живой инцидент 15.08 — коллизия 264_ между двумя независимыми ветками). |
||
|
|
87258075a2 |
Merge main в chore/ci-config-cleanup-v2
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 52s
CI Trade-In / frontend-checks (pull_request) Successful in 1m19s
CI Trade-In / backend-tests (pull_request) Successful in 5m7s
Слить актуальный main (билд-раннер #2841/#2869, невалидные индексы #2752, честный health-check и deploy-status #2841) в ветку очистки CI. Один конфликт в .forgejo/workflows/deploy.yml: список triggers.paths — main добавил ops/docker-prune.sh (#2887), ветка добавила auth/** (RBAC roles config). Разрешено сохранением обоих путей, без потери ни одного триггера. |
||
|
|
8bce8cf5ae |
fix(ci): fail-safe registry verification + real cache self-heal + honest health-check (#2841 R2)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Ревью R2 нашёл, что вся безопасность предыдущего фикса держалась на недоказанной поддержке act_runner'ом steps.<id>.outcome: если раннер его не заполняет, retry-шаг молча не бежит, continue-on-error проглатывает падение сборки, job зелёный — а деплой тянет старый :latest на прод. - Добавлен engine-agnostic verify-шаг после каждого retry (6 мест, deploy.yml + deploy-tradein.yml): `docker buildx imagetools inspect <image>:<sha>` без continue-on-error. Не зависит от того, поддерживает ли раннер outcome — проверяет реальное состояние registry напрямую. Если ни build, ни retry реально не запушили образ — шаг падает и job честно FAILURE независимо от семантики outcome. - Вернул `cache-to` в retry-шаги (6 мест): без него битый buildcache-тег никогда не перезаписывался — retry всегда собирал без cache-to, значит cache-to не выполнялся НИКОГДА, и каждый следующий прогон снова падал на том же cache-from. Заявленное самолечение не работало ни разу. - Health-check в deploy.yml (main-стек) под `set -e` не мог упасть: `curl ... && break` — curl не последняя команда &&-списка, POSIX освобождает такие команды от errexit, цикл дохаживал до sleep (exit 0) даже если curl ни разу не отдал 200. Приведено к паттерну deploy-tradein.yml: явный флаг healthy + `exit 1` после цикла. Подтверждено локальным bash-репро (mock curl, всегда failure): старая версия — exit 0, новая — exit 1; позитивный сценарий не сломан. docker rm -f без -v в SSH-скриптах деплоя не тронут. |
||
|
|
9b3889bb36 |
ci(deploy): честный статус деплоя + нефатальный buildcache (#2841)
Зелёная галка прогона не отличима от пропущенного деплоя: если build падает из-за битого blob в удалённом buildcache, шаг deploy молча пропускается (if-условие даёт result=skipped), а прогон в целом не подсвечен как FAILED. - deploy-status: новая job в конце deploy.yml и deploy-tradein.yml, всегда бежит (if: always() && !cancelled()) и падает явно, если deploy.result != success — неважно, пропущен он (upstream build/test упал) или упал сам. - cache-from нефатален: каждый build-push-action-шаг получил id + continue- on-error, и ретрай без cache-from/cache-to при steps.build.outcome == 'failure'. Битый remote-кеш больше не роняет саму сборку; следующий успешный прогон с кешем перезаписывает buildcache-тег целиком (mode=max) и самолечит порчу. Реальные ошибки сборки (не кеш) по-прежнему валят job на ретрае — deploy-status их тоже поймает. Гейт против публикации services-портов на VPS (та же задача, проблема 1) уже покрыт scripts/check-workflow-ports.py + шагом в ci.yml (#2757/#2759, слит ранее) — сканирует все .forgejo/workflows/*.yml, включая эти два файла; новых правок не потребовалось. docker rm -f БЕЗ -v в SSH-скриптах деплоя не тронут — эти вызовы намеренно без -v (боевые тома), правка их не касается. |
||
|
|
c0782a8c4c |
chore(deploy): триггерить деплой на правку ops/docker-prune.sh
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Скрипт уборки docker-мусора (#2887) исполняется на прод-VM по cron из /opt/gendesign/ops/. Файлы туда попадают единственным путём — шагом `git reset --hard origin/main` внутри deploy.yml. Но paths-фильтр deploy.yml перечисляет подпути ops/ поимённо, а не ops/**. Поэтому мерж #2887 деплой НЕ запустил: скрипт остался в main, на VM его не было, а установленный cron указывал в пустоту. Правки скрипта и дальше доезжали бы только случайно — со следующим чужим коммитом в backend/. Ровно этот же баг уже ловили на ops/db-bootstrap/** — там рядом стоит комментарий с той же формулировкой. Добавляю ops/docker-prune.sh по образцу и фиксирую грабли в rules/deploy.md, чтобы следующий исполняемый файл в ops/ не наступил на них третий раз. |