All checks were successful
Deploy Trade-In / changes (push) Successful in 18s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m28s
Deploy Trade-In / test (push) Successful in 2m53s
Deploy Trade-In / build-backend (push) Successful in 28s
Deploy Trade-In / deploy (push) Successful in 1m21s
DELETE 4 мёртвых mobileproxy-узлов (id 2/3/4/5) из scrape_proxies. Подписка закрыта, узлы мертвы с 4-9 июля; у трёх в rotate_url лежал чужой API-ключ открытым текстом (источник блокера PR #2611). scrape_proxy_rotations пуста, единственный FK не мешает. Условие по домену (url LIKE mobileproxy.space), не по id.
83 lines
6.5 KiB
PL/PgSQL
83 lines
6.5 KiB
PL/PgSQL
-- 201_purge_dead_mobileproxy_proxies.sql
|
||
-- Issue #2613 — выпилить мёртвые узлы mobileproxy из пула scrape_proxies
|
||
-- вместе с чужим API-ключом, который лежал у них в rotate_url.
|
||
--
|
||
-- WHY:
|
||
-- Владелец подтвердил: подписка mobileproxy закрыта, продлевать не будут.
|
||
-- Прямая проба каждого узла из контейнера tradein-scraper (2026-08-01)
|
||
-- подтверждает смерть: id 2 — connection refused, id 3/4/5 — 407 Proxy
|
||
-- Authentication Required. Последняя успешная проверка (last_check_at) у
|
||
-- всех четырёх — 4-9 июля, все четыре enabled=false, consecutive_fails=5.
|
||
--
|
||
-- Две причины удалить, вторая важнее:
|
||
-- 1. Мёртвые узлы засоряют пул и его health-метрики.
|
||
-- 2. rotate_url у трёх из четырёх строк (id 3, 4, 5) хранит открытым
|
||
-- текстом чужой ключ провайдера в query-параметре ссылки ротации
|
||
-- (https://changeip.mobileproxy.space/?proxy_key=...). Именно из-за
|
||
-- неоднородности этой колонки (вперемешку с ASocks-строками, где
|
||
-- rotate_url — наш собственный API-эндпоинт БЕЗ секрета в URL,
|
||
-- авторизация Bearer-заголовком) глубокое ревью PR #2611 нашло
|
||
-- блокер: вызов ротации для такой строки отправил бы НАШ токен
|
||
-- ASocks на changeip.mobileproxy.space. Пин хоста в #2611 уже
|
||
-- закрывает саму уязвимость, но чужой секрет в базе держать незачем.
|
||
--
|
||
-- ПОЧЕМУ DELETE, А НЕ UPDATE (очистка полей + enabled=false):
|
||
-- Единственный FK, ссылающийся на scrape_proxies — scrape_proxy_rotations
|
||
-- .proxy_id (заведён 198_scrape_proxy_rotations.sql), delete_rule NO ACTION.
|
||
-- На момент миграции (замер ниже) в scrape_proxy_rotations нет НИ ОДНОЙ
|
||
-- строки вообще — таблица введена в этом же цикле работ (#2600 п.5) и
|
||
-- ручная ротация ни разу не запускалась. DELETE четырёх строк scrape_proxies
|
||
-- ничего не упирает. Если бы к строкам 2-5 успела прилипнуть история ротаций
|
||
-- к моменту применения — DELETE упадёт по FK-violation ВНУТРИ этой же
|
||
-- транзакции (BEGIN/COMMIT ниже), миграция целиком откатится, deploy
|
||
-- завершится ошибкой (auto-apply strict, exit 1) без частичного эффекта и
|
||
-- без порчи данных; отдельного ON DELETE-обработчика не требуется — узлы
|
||
-- disabled=false уже сейчас, acquire() их не выдаёт (idx_scrape_proxies_pick
|
||
-- фильтрует по enabled), новых ротаций на них взяться неоткуда до deploy.
|
||
-- Строки — исторический мусор без ссылок, полное удаление честнее частичной
|
||
-- очистки (не оставляет призрачную запись мёртвого узла в пуле) и убирает
|
||
-- секрет из базы целиком, а не только из одной колонки.
|
||
--
|
||
-- Matching (по домену url, НЕ по id):
|
||
-- id в scrape_proxies разъезжается между средами (bulk-загрузка независима
|
||
-- per-среда, тот же класс проблемы решён в 199 через host:port-matching).
|
||
-- Условие — WHERE url LIKE '%mobileproxy.space%' — ловит все четыре узла
|
||
-- независимо от порта/поддомена (ha./gi./auv./aup.mobileproxy.space) и не
|
||
-- заденет ASocks-строки (212.8.249.134 / 190.2.145.131 / 175.110.115.153 /
|
||
-- 109.236.82.42 — IP-адреса, без mobileproxy.space в url вовсе).
|
||
--
|
||
-- ЗАМЕРЫ (SELECT, read-only, прод, перед миграцией, 2026-08-01):
|
||
-- Строк под условие (url LIKE '%mobileproxy.space%'): 4 (id 2, 3, 4, 5)
|
||
-- Остаток пула после удаления (url NOT LIKE '%mobileproxy.space%'):
|
||
-- 4 (id 1, 9, 10, 11) — все ASocks
|
||
-- Строк в scrape_proxy_rotations на id 2/3/4/5: 0
|
||
-- Строк в scrape_proxy_rotations всего (таблица пуста): 0
|
||
-- Секрет-паттерн (token|bearer|secret|key=|password, regex
|
||
-- case-insensitive) в rotate_url ОСТАЮЩИХСЯ 4 строк: 0 совпадений
|
||
-- (rotate_url остающихся — https://api.asocks.com/unlimited-proxy/
|
||
-- <portId>/refresh-ip, без query-параметров вообще, авторизация Bearer
|
||
-- заголовком вне URL, см. 199_scrape_proxies_asocks_rotate_url.sql)
|
||
-- FK, ссылающиеся на scrape_proxies: ровно один —
|
||
-- scrape_proxy_rotations.proxy_id -> scrape_proxies.id, delete_rule NO ACTION.
|
||
--
|
||
-- Idempotency:
|
||
-- Обычный DELETE ... WHERE — повторный прогон находит 0 строк (уже
|
||
-- удалены), no-op. Весь файл в BEGIN/COMMIT.
|
||
--
|
||
-- ГРАНИЦЫ: НЕ трогает ASocks-строки (id 1, 9, 10, 11) и их rotate_url. НЕ
|
||
-- трогает переменные окружения (*_PROXY_URL, BROWSER_PROXY_*,
|
||
-- *_PROXY_ROTATE_URL) — их снятие отдельная задача и НЕ раньше неё, иначе
|
||
-- при пустом прокси curl_proxy_url отдаёт None = скрапер идёт напрямую с IP
|
||
-- сервера. НЕ трогает app/services/proxy_pool.py, proxy_rotation.py,
|
||
-- скраперы. Никакого DDL.
|
||
--
|
||
-- Dependencies:
|
||
-- 157_scrape_proxies.sql (scrape_proxies.url/rotate_url/enabled).
|
||
-- 198_scrape_proxy_rotations.sql (FK proxy_id -> scrape_proxies.id, NO ACTION).
|
||
|
||
BEGIN;
|
||
|
||
DELETE FROM scrape_proxies
|
||
WHERE url LIKE '%mobileproxy.space%';
|
||
|
||
COMMIT;
|