57 lines
11 KiB
Markdown
57 lines
11 KiB
Markdown
# is-a.dev (GitHub PR subdomain registry) — масс-фарм доменов через API
|
||
|
||
Класс: subdomain-реестры, где домен выдаётся через PR в публичный репозиторий (is-a-dev/register). Цель — получить домен, ЧИСТЫЙ в disposable/free-domain blocklist'ах, для таргетов с work-email валидацией (Daytona и подобные).
|
||
|
||
## Почему is-a.dev
|
||
|
||
- `is-a.dev` отсутствует в основных blocklist-базах (disposable-email-domains, ivolo) — в отличие от `*.loc.cc`, `nl8.eu`, t-online (режутся финальной валидацией) и disposable-доменов (режутся на signup).
|
||
- Регистрация бесплатная и автоматизируется полностью через GitHub REST API (PAT), без браузера.
|
||
- Узкое место — merge мейнтейнером (ревью волнами); закладывать часы/дни до живого домена.
|
||
|
||
## Механика одного домена (API-only, на живой PAT)
|
||
|
||
1. `GET /user` — проверить токен И получить РЕАЛЬНЫЙ login (пул-файлы могут содержать устаревшие login'ы).
|
||
2. Имя домена = нормализованный login (`[^a-z0-9-]` → `-`, ≤25 символов, начинать с буквы).
|
||
3. `GET repos/is-a-dev/register/contents/domains/<name>.json` — 200 = занято, пробовать `<name>-dev`.
|
||
4. Создать сайт-репо `<name>-site` (auto_init) + `PUT contents/index.html` (любой валидный HTML про "personal developer blog") + `POST /pages` — PR-бот проверяет наличие website.
|
||
5. `POST repos/is-a-dev/register/forks` → поллить `GET repos/<login>/register` до 200 (fork создаётся асинхронно, до 8×3с).
|
||
6. Ветка от upstream main: `GET repos/is-a-dev/register/branches/main` → `POST git/refs` на форке.
|
||
7. Commit `domains/<name>.json` — записи под **forwardemail.net** (DNS-only forward, безлимитный на free-плане; ImprovMX free = 1 домен на аккаунт и масс-фарм не масштабирует):
|
||
```json
|
||
{"owner": {"username": "<login>", "email": "<login>@users.noreply.github.com"},
|
||
"records": {"MX": ["mx1.forwardemail.net", "mx2.forwardemail.net"],
|
||
"TXT": ["forward-email=<catchall-target>@gmail.com",
|
||
"v=spf1 include:spf.forwardemail.net ~all"]}}
|
||
```
|
||
Поле — ОБЯЗАТЕЛЬНО `records` (множественное): репозиторий сменил схему, PR со старым `record` CI молча закрывает без мержа (выглядит как человеческий реджект — причину видно только в job-логе `GET repos/is-a-dev/register/actions/jobs/<id>/logs`). Перед любым фармом сверь текущую схему по свежему merged-примеру: `raw.githubusercontent.com/is-a-dev/register/main/domains/<name>.json`. Старые PR с ImprovMX-записями чинятся пост-фактум: `GET /pulls/<n>` → head repo+branch → `GET contents/domains/<name>.json?ref=<branch>` (нужен `sha`) → PUT с новым record.
|
||
8. Тело PR ОБЯЗАНО пройти Template-check: скачать `.github/PULL_REQUEST_TEMPLATE.md`, заменить все `- [ ]` → `- [x]`, вставить website-URL между `WEBSITE_PREVIEW_START/END` и purpose между `WEBSITE_PURPOSE_START/END` (regex — в шаблоне CRLF). Без этого PR висит непроверенным.
|
||
9. `POST /pulls` (head `<login>:<branch>`, base main). 422 имеет два разных смысла — читать message: «A pull request already exists» = дубль от предыдущего прогона (найти существующий через `GET /search/issues?q=repo:is-a-dev/register is:pr is:open author:<login>` и считать успехом, иначе retry-батч сжигает токены впустую); «sha wasn't supplied»/validation = ветка/коммит не подхватились, проверить шаги 6-7; **404 «Not Found» на валидном токене и существующей ветке = account-level GitHub spam-блок** (см. питфолы), а не баг кода.
|
||
|
||
## Масс-режим
|
||
|
||
- Рабочий пул токенов: СВЕЖИЙ audit-файл (проверка ниже). Один токен = один домен.
|
||
- ThreadPoolExecutor(3), done-set персистится после каждого результата (резюм), output — JSONL со stage-меткой. Конкурентность >3 упаривается в 429 на fork/repo-эндпоинтах (второй бёрст подряд даёт волну fork_fail:429 и yield падает с ~97% до ~70%).
|
||
- Каждый пишащий шаг (git/refs создание ветки, contents PUT) оборачивать в retry-цикл 3-4× с sleep 8-20с: 429/409/«sha wasn't supplied» — транзиентные рейсы, а не перманентные отказы.
|
||
- Yield на живых токенах: ~97% PR. На password+TOTP → браузерная генерация PAT yield низкий (~25%) — не масштабировать браузерный путь, если есть живые токены.
|
||
- Мерж НЕ автоматический: мониторить `GET repos/is-a-dev/register/pulls?state=open&head=<login>:<branch>`; один мейнтейнер-бот может смержить PR сразу после Template-check, но рассчитывать на дни.
|
||
- После мержа: домен живёт на DNS is-a.dev; при forwardemail-записях почта уже работает (forward — чисто DNS, настраивать нечего) — прогнать тест `probe@<домен>` → IMAP Gmail, затем ОДИН полный авторег-цикл до дашборда таргета, прежде чем включать bulk.
|
||
- Прямой GitHub OAuth у таргетов с WorkOS (Daytona) — отдельная от домена проблема: `idp_access_denied` даже на GitHub-акках с НЕ-gmail почтой (outlook/hotmail/t-online проверены). Домен-путь (email+код на своём домене) — единственный рабочий; GH-пул тратить на PR-фарм, а не на OAuth-попытки.
|
||
|
||
## Питфолы
|
||
|
||
- **Свежесть пула решает всё.** Старые token-файлы (`all_tokens_LIVE.json` 1465 шт, `pw_alive.json`, PAT'ы внутри `gh_union_accounts.json`) дали 0 живых из 40-80 пробы; свежий audit `gh_alive_FIXED_0810.json` — 40/40 из 1135. Перед масс-фармом: прогнать 40-токенную пробу `GET /user` по КАЖДОМУ файлу-кандидату и брать самый свежий по mtime/имени.
|
||
- **Заголовок Authorization.** Терминальный вывод и write_file маскируют литерал схемы авторизации → битый заголовок даёт 401 на ВАЛИДНОМ токене (pitfall 20d). Собирать конкатенацией.
|
||
- **409/422 на PR** чаще = fork/branch/commit не успели создаться, а не дубль PR; сначала перечитать состояние форка.
|
||
- **commit_fail «sha wasn't supplied»** = POST git/refs не прошёл (429/рейс) и contents PUT ушёл в несуществующую ветку — ретраить создание refs, а не commit. Надёжный фолбэк вместо contents PUT: git data API цепочкой `POST git/blobs` → `POST git/trees` (base_tree=upstream sha) → `POST git/commits` → `PATCH git/refs/heads/<branch>` (force) — не зависит от того, успела ли ветка создаться.
|
||
- **done-set чинить только по PR-успехам**: при рескрипте state-файла сохранять в done ТОЛЬКО login'ы со stage=PR; записанные туда fail'ы исключают аккаунт из ретрая навсегда, и батч молча теряет десятки токенов.
|
||
- **done-set и tokmap сравнивать case-insensitively** (`login.lower()`): GitHub-логины регистрозависимы в API, но пул-файлы и done-сеты из разных прогонов расходятся регистром — без нормализации живые токены повторно уходят в dup-PR 422.
|
||
- **Не батчить несколько доменов в один PR** «чтобы быстрее» — Template-check проходит, но один проблемный файл топит весь PR; per-domain PR изолирует отказы.
|
||
- **Масс-фарм провоцирует account-level GitHub spam-блок: POST /pulls отдаёт 404 «Not Found» (не 403) на ВСЕ акки пула сразу.** Диагностика: тот же токен успешно создаёт issue/PR в СВОЁМ репо, а в is-a-dev — 404; мержи чужих PR у мейнтейнеров при этом продолжаются (блок на акках-авторах, не на репо). ~200+ однотипных PR за день — достаточный триггер; лок держится дни. Закрытые PR не reopen-ятся и force-push в их ветки запрещён. Новый PR требует УНИКАЛЬНОГО имени ветки (diverged старая ветка тоже даёт 404): всегда `DELETE git/refs/heads/<branch>` → создать заново от свежего upstream main sha → commit → PR. Темп фарма держать низким/разносить по дням, иначе пул токенов сгорает впустую.
|
||
|
||
## Скрипты
|
||
|
||
- `tmp/_isadev_farm_api.py` — рабочий API-only масс-фарм (token-файл → repo/pages → fork → branch → commit → PR; done-set + JSONL).
|
||
- `tmp/_gh_patgen.py` — браузерная генерация PAT из password+TOTP (низкий yield, фолбэк).
|
||
- `tmp/isadev_gh_tokens.json` — живой пул токенов (login+token).
|
||
- **Публичная репа всего пайплайна (scrubbed)**: `github.com/ExaAutocratHunt65/daytona-isadev-autoreg` — farm + fixold + daytona_reg2 + daytona_gh_reg + README с цепочкой и стенами. Копия локальных скриптов на случай чистки tmp; секретов не содержит.
|