# Дизайн: перенос git-хостинга на self-hosted Gitea в Yandex Cloud (ред. 3)

## Цель

GitHub-аккаунт проекта недоступен — `git push`/`git fetch` невозможны, и большой объём
локальных коммитов существует только на одной машине без резервной копии. Нужна замена
git-хостинга на собственный **Gitea в Yandex Cloud** как основной remote: (1) немедленная
off-machine резервная копия локальных коммитов; (2) off-box зеркало на сторонний российский
хостинг как второй уровень бэкапа. Решение также закладывает фундамент для последующего
восстановления канала автоматизации к боевому серверу — но это отдельный этап вне данной спеки.

Если репозиторий использует Git LFS для больших файлов, миграция обязана перенести и
LFS-объекты — обычный `git push` их на неподготовленный сервер не доставит (см. D3).

Эта спека описывает **комплект переноса** (набор файлов-инструкций и конфигов), который готовит
всё, что можно подготовить без облачных доступов владельца. Физический запуск (создание
виртуальной машины, ключи, секреты) выполняет владелец по комплекту.

## Контракт комплекта {#D1}

Комплект переноса обязан содержать четыре артефакта с разделёнными зонами ответственности:

1. **Runbook** (пошаговая инструкция) — провижининг VM → установка Gitea → HTTPS → создание
   пользователя/организации → добавление remote → заливка всех веток и тегов (+ LFS-объектов,
   если используются) → проверка → настройка push-зеркала. Каждый шаг помечен исполнителем:
   «владелец» (облако/ключи) или «по комплекту» (копипаст готовой команды).
2. **docker-compose** — определение сервисов Gitea + PostgreSQL с именованными томами для
   персистентности данных репозиториев и базы.
3. **Конфиг зеркала** — инструкция настройки push-mirror из Gitea на сторонний хостинг
   (GitVerse / GitFlic) как второй бэкап off-box. Инструкция ОБЯЗАНА включать шаг проверки, что
   первая синхронизация зеркала реально прошла (репозиторий появился на стороне зеркала с теми же
   ветками/коммитами), а не только сохранение настройки.
4. **Чек-лист владельца** — явный список действий, которые НИКТО кроме владельца выполнить не
   может: создание VM, DNS-запись, перенос секретов, заведение аккаунта зеркала.

Контракт: ни один артефакт не содержит реальных секретов/ключей (они — зона владельца, см. D3).

## Архитектура и размещение раннера {#D2}

Канал автоматизации к боевому серверу `111.88.246.137` исторически работал через внешний раннер
(см. `LIDERRA_HOST: 111.88.246.137` в `.github/workflows/deploy.yml` — «Universal artisan-runner»
в `.github/workflows/artisan-run.yml`), потому что внешний IP не попадал под фильтр, блокирующий
конкретный dev-IP. Из этого следует размещение:

- VM с Gitea ставится в **той же сети Yandex Cloud**, что и боевой сервер, с правилами
  безопасности, разрешающими доступ к боевому по **внутренней** сети.
- Раннер автоматизации (когда дойдём до этапа канала) ходит к боевому по внутреннему адресу —
  фильтр dev-IP его не касается. Это надёжный дефолт, не требующий снятия внешнего фильтра.
- В рамках ЭТОЙ спеки раннер не настраивается: цель — бэкап. Размещение зафиксировано здесь, чтобы
  будущий этап канала не пересматривал топологию VM.

**ПРОВЕРИТЬ (непроверенное допущение):** что боевой сервер `111.88.246.137` действительно
размещён в Yandex Cloud и что VM в той же сети может достучаться до него по внутреннему адресу.
Если боевой — у другого провайдера или внутренний доступ закрыт, топологию раннера на будущем
этапе придётся пересмотреть (например, раннер на отдельном хосте с не-забаненным внешним IP).
На этап бэкапа (цель этой спеки) это допущение НЕ влияет.

## Крайние случаи {#D3}

- **Git LFS.** Перед заливкой проверить, использует ли репозиторий LFS (`.gitattributes` с
  `filter=lfs` / наличие `git lfs`). Если да — на Gitea включить поддержку LFS и в runbook
  добавить шаг `git lfs push --all`; обычный `git push` LFS-объекты не перенесёт → риск
  «ссылки есть, файлов нет». Если LFS не используется — шаг помечается как неприменимый.
- **Коммиты на старом origin недоступны.** Пока GitHub-аккаунт заблокирован, коммиты, которых нет
  локально, забрать нельзя. Комплект бэкапит ТОЛЬКО локальные ветки/теги — это и есть спасаемая
  ценность сейчас; сведение с origin — отдельная задача после восстановления доступа.
- **Потеря VM.** Если виртуалка умрёт — спасает off-box зеркало (D1 п.3); поэтому зеркало —
  обязательная часть комплекта, не опция.
- **Секреты вне репозитория.** Приватные ключи/токены доступа к боевому и аккаунту зеркала в
  комплект не попадают — только плейсхолдеры и инструкция, куда владелец их вставляет.
- **Идемпотентность повторной заливки.** Команда заливки должна быть безопасна при повторе
  (push существующих веток/тегов не разрушает данные).
- **HTTPS.** Gitea публикуется по HTTPS (сертификат) — git-операции без шифрования не допускаются.

## Размер VM и контроль стоимости {#D6}

Чтобы владелец не взял неподходящую по цене/мощности машину, комплект обязан содержать ориентир
по размеру и стратегию контроля расходов:

- **Базовый размер (ориентир).** Gitea — лёгкий сервис: для одного-нескольких пользователей
  достаточно **2 vCPU / 2–4 ГБ RAM**; диск — **SSD от 30–50 ГБ** (данные репозиториев + база
  PostgreSQL + запас под бэкапы и LFS-объекты). Если используется LFS с крупными файлами — диск
  подбирается по их суммарному объёму с запасом.
- **Старт минимально.** Начинать с нижней границы (2 vCPU / 2 ГБ), расширять по факту нагрузки.
- **Обратимость расхода.** VM в Yandex Cloud **можно ресайзить** (поменять vCPU/RAM позже) и
  **останавливать** (у остановленной берётся плата в основном за диск, не за vCPU) — то есть
  ошибка размера НЕ необратима: пере-конфигурируется без пересоздания. Диск тоже расширяется.
- **ПРОВЕРИТЬ:** актуальные цены Yandex Cloud (стоимость vCPU/RAM/SSD в `ru-central1`, прерываемые
  vs обычные ВМ) и итоговую месячную смету — на момент написания не верифицированы вживую, перед
  созданием VM сверить в калькуляторе/консоли облака.

## Конвенция именования {#D4}

Артефакты комплекта складываются под единым префиксом каталога инфраструктуры репозитория,
имена файлов — латиницей, без пробелов, в нижнем регистре с дефисами; runbook — `.md`,
docker-compose — `.yml`, чек-лист — `.md`. Дата в имени runbook — в формате `ГГГГ-ММ-ДД`,
совпадающая с датой спеки.

## Критерий готовности {#D5}

Комплект считается готовым, когда выполнены ВСЕ условия:

- runbook покрывает полный путь провижининг → установка → заливка (включая LFS-ветку D3) →
  проверка → зеркало, и каждый шаг помечен исполнителем;
- runbook/чек-лист содержат ориентир по размеру VM и контролю стоимости (D6);
- docker-compose синтаксически валиден и поднимает Gitea + PostgreSQL с персистентными томами;
- чек-лист владельца перечисляет все действия, требующие облачных доступов/секретов;
- runbook содержит шаг проверки успешной синхронизации push-зеркала (репозиторий с ветками
  реально появился на стороне зеркала), а не только сохранения настройки;
- пункты, которые НЕ удалось подтвердить вживую (актуальные версии образов, цены/размер VM,
  текущее состояние стороннего хостинга, принадлежность боевого IP к Yandex Cloud из D2), явно
  помечены как «проверить» — не выданы за факт.

```verified-context-json
[
  {"id":"D2-host","kind":"EXTRACTED","ref":".github/workflows/deploy.yml","anchor":"LIDERRA_HOST: 111.88.246.137"},
  {"id":"D2-runner","kind":"EXTRACTED","ref":".github/workflows/artisan-run.yml","anchor":"Universal artisan-runner"}
]
```
