feat(backup): external git-mirror + issues/PR export #2

Open
opened 2026-08-06 20:55:38 +03:00 by slaid098 · 0 comments
Owner

Контекст

GitHub-аккаунт slaid098 удалён безвозвратно. Вся история проекта (код, issues, PR, память) теперь живёт только на git.slaid098.dev (Forgejo на втором сервере). Если хост умрёт / хостинг удалят / сервер сгорит — уносим всё безвозвратно, как уже случилось с GitHub-аккаунтом.

Память (opencode-memory) синхронизирована с Forgejo, clean, но в bundle-бэкапы /root/workspace/_backups/bundles/ НЕ входит. crontab/systemd на хосте недоступны — регулярных бэкапов нет. Только ручные git-bundle'ы 9 workspace-репо от 2026-08-06.

Активных репозиториев на Forgejo всего 3: opencode-config, opencode-voice-dictation, forgejo-infra. Плюс opencode-memory — отдельный git-репо (синхронизирован с Forgejo). Итого 4 источника для зеркалирования.

Forgejo-инстанс НЕ на этом хосте (linux-2), а на втором сервере — работаем только через REST API (FORGEJO_URL/FORGEJO_TOKEN в окружении).

Инструменты opencode-config (create-issue, create-pr, merge-pr, post-review, pipeline-status, project-status, spec-status) уже портированы на Forgejo через env-fallback — FORGEJO_URL задан → используют Forgejo REST API, иначе fallback на gh CLI. Issue #1 про портирование устарел и должен быть закрыт отдельно.

Задача

Реализовать disaster recovery уровня A: код + issues/PR на внешнем git-хостинге. Два слоя защиты:

Слой 1 — Git-mirror (push). Настроить push mirror для 4 репо на внешний git-хост (GitLab.com — предпочтительно, Codeberg — запасной). Использовать Forgejo's built-in push mirror (через API /api/v1/repos/{repo}/push_mirrors), не локальные git-операции — Forgejo сам пушит по schedule. Переносит код + коммиты + ветки + теги.

Слой 2 — Issues/PR-экспорт. Forgejo Actions workflow в forgejo-infra (schedule daily), который через API тянет issues/PR/comments/labels из всех репо, сериализует в JSON + человекочитаемый markdown, коммитит в отдельный репо forgejo-archive на внешнем хосте. Обратная сторона — forgejo-restore.sh скрипт, который читает archive и через API воссоздаёт issues/PR на новом инстансе.

Цель: "одной командой поднять точно такой же Forgejo со всеми настройками, pull-request'ами и issues" — на новом сервере.

Контракты

  • Forgejo push_mirrors API: POST /api/v1/repos/{repo}/push_mirrors с {target_address, target_username, target_password, interval, mirror, ...}. Документация: https://codeberg.org/forgejo/forgejo/src/branch/forgejo/routers/api/v1/repo/push_mirror.go
  • Forgejo issues API: GET /api/v1/repos/{repo}/issues?state=all&limit=50&page=N, для каждого issue GET /api/v1/repos/{repo}/issues/{n}/comments
  • Forgejo pulls API: GET /api/v1/repos/{repo}/pulls?state=all&limit=50&page=N
  • Forgejo labels API: GET /api/v1/repos/{repo}/labels
  • Forgejo milestones API: GET /api/v1/repos/{repo}/milestones
  • Внешний хост: GitLab.com (предпочтительный, приватные репо, неограниченно) или Codeberg (запасной, Gitea-based). Выбор за пользователем при имплементации.
  • Archive-формат: иерархия archive/{repo}/issues/{N}.json + archive/{repo}/issues/{N}.md + archive/{repo}/pulls/{N}.json + archive/{repo}/pulls/{N}.md + archive/{repo}/labels.json + archive/{repo}/milestones.json + archive/meta.json (timestamp, forgejo version, exporter version)
  • Восстановление: forgejo-restore.sh <archive-repo-path> <target-forgejo-url> <token> — читает archive, создаёт labels/milestones/issues/PR через API в правильном порядке (с маппингом старых ID → новых)

Инварианты

  • Зеркалируются ТОЛЬКО 4 репо: opencode-config, opencode-voice-dictation, forgejo-infra, opencode-memory. 8 legacy workspace-папок без Forgejo — вне scope
  • Push mirror — read-only на стороне GitLab, single source of truth остаётся Forgejo (зеркало не используется для активной работы)
  • Issues/PR-экспорт — снимки (snapshot) раз в сутки, НЕ real-time sync
  • БЕЗ секретов: пользователи инстанса, токены, настройки app.ini, webhooks, CI secrets — вне scope (это уровень B, forgejo dump, отдельная задача)
  • RAG-индекс (opencode-memory/.rag/, 200 МБ) НЕ бэкапим — перестраивается за 32 сек через memory-save
  • Source of truth для настроек инстанса Forgejo — сам forgejo-infra репо (reproducible setup), его git-mirror уже покрывает восстановление конфигурации

Граничные случаи

  • forgejo-infra есть на Forgejo, но НЕ имеет локального clone'а в /root/workspace/ — настройка push mirror только через API, не через локальные git-операции
  • Issues с attachments/media — attachments НЕ переносим в MVP (только текст комментариев)
  • PR review comments на конкретные строки кода (diff comments) — позиционная информация (line, side, path) может теряться при переимпорте, сохраняем как обычные комментарии с пометкой "review comment on file X line Y"
  • Удалённые/закрытые issues/PR — включать в экспорт (state=all), полный архив
  • Длинные истории комментариев — пагинация (limit=50, page=N), мерджить в один файл на issue
  • Rate limits Forgejo API — экспорт должен быть идемпотентным и устойчивым к retry (если упало на середине — продолжить с последнего issue)
  • Markdown-рендеринг Forgejo vs GitLab — отличия в расширениях (!-функции, математика, admonitions). В archive храним raw markdown (source), не рендер
  • Зеркало ломается при force-push / history rewrite — Forgejo Actions должен алертить (fail job) при broken mirror
  • Порядок восстановления: labels → milestones → issues (с label/milestone mapping) → PR (как issue с признаком is_pull) → comments. Важен порядок создания, чтобы ссылки (на другие issues, на milestone) работали
  • Аутентификация restore: restore-скрипту нужен токен с правами issue/write/admin на целевом инстансе — задавать через env, не вшивать
  • Дубликаты при повторном restore: restore должен быть идемпотентным — проверять по original_id в начале body или по label migrated-from:{original_id} и пропускать существующие

Влияние на связанные компоненты

  • forgejo-infra репо — добавится Forgejo Actions workflow backup-export.yml (schedule daily) + forgejo-restore.sh скрипт + документация в README
  • Forgejo инстанс — добавятся 4 push_mirrors настройки (через API), потребуются секреты для пуша на внешний хост (FORGEJO_TOKEN для API + GITLAB_TOKEN для push, хранить в Forgejo Actions secrets)
  • Внешний git-хост (GitLab/Codeberg) — создаются 5 приватных репо: 4 mirror-targets + forgejo-archive (вручную пользователем или через API)
  • opencode-memory — добавляется push mirror, но настройки делаются через Forgejo API (не локально), так как memory живёт вне workspace
  • AGENTS.md в opencode-config — добавить процедуру disaster recovery в раздел docs/ (короткий how-to: "если Forgejo упал — как восстановить")
  • Инструменты opencode-config (create-issue, merge-pr и т.д.) — НЕ затрагиваются, они уже портированы
  • Issue #1 (port to Forgejo) — должен быть закрыт как устаревший после завершения этой задачи (или независимо), так как портирование фактически завершено

Вне scope

  • Уровень B: полный forgejo dump (БД + app.ini + users + tokens + webhooks + LFS + attachments) с шифрованием и хранением на отдельной VPS — отдельная задача
  • 8 legacy workspace-папок без Forgejo (arena-models, slaid098, slaid098-dev, video_uniq, voice_assistant, youtube-kit, youtube-soft, salvage-slaid098) — постепенный перенос потом
  • LFS-объекты — если есть, добавим в следующей итерации
  • Attachments к issues (картинки, файлы) — MVP только текст
  • RAG-индекс — перестраивается за 32 сек
  • Автоматизация через cron/systemd на хосте — недоступно, используем Forgejo Actions
  • Реальная миграция на новый сервер — только подготовка (archive + restore-скрипт), не выполнение миграции
  • GitLab CI / Codeberg CI — не настраиваем, только хранилище
  • Двусторонняя синхронизация (зеркало не редактируется)

Критерии приемки

  • Пользователь создал аккаунт на GitLab.com (или Codeberg) и передал Personal Access Token (scope: api + write_repository)
  • Созданы 5 приватных репо на внешнем хосте: opencode-config, opencode-voice-dictation, forgejo-infra, opencode-memory, forgejo-archive
  • Через Forgejo API настроены push_mirrors для 4 репо → verified: после push в Forgejo коммиты появляются на внешнем хосте в течение interval
  • В forgejo-infra добавлен Forgejo Actions workflow .forgejo/workflows/backup-export.yml — schedule daily, экспортирует issues/PR/comments/labels/milestones из 4 репо
  • Archive-формат соответствует контракту: иерархия archive/{repo}/issues/{N}.json + .md + archive/{repo}/pulls/{N}.json + .md + archive/{repo}/labels.json + archive/{repo}/milestones.json + archive/meta.json
  • Workflow коммитит archive в forgejo-archive репо на внешнем хосте (через HTTPS + token)
  • Restore-скрипт forgejo-restore.sh в forgejo-infra: читает archive → через API воссоздаёт labels/milestones/issues/PR/comments на новом Forgejo инстансе, идемпотентный (пропускает уже мигрированные)
  • Smoke-test: выполнить restore на тестовом пустом Forgejo инстансе → убедиться что issues/PR/labels/comments воссоздаются корректно
  • Документация в forgejo-infra/README.md: процедура disaster recovery одной командой, предпосылки (внешний токен, чистый Forgejo), шаги восстановления
  • Короткая ссылка в opencode-config/docs/decisions/ (новый ADR) — фиксирует решение об уровне A, выбранном хостинге, и ссылку на эту issue
## Контекст GitHub-аккаунт `slaid098` удалён безвозвратно. Вся история проекта (код, issues, PR, память) теперь живёт только на `git.slaid098.dev` (Forgejo на втором сервере). Если хост умрёт / хостинг удалят / сервер сгорит — уносим всё безвозвратно, как уже случилось с GitHub-аккаунтом. Память (`opencode-memory`) синхронизирована с Forgejo, clean, но в bundle-бэкапы `/root/workspace/_backups/bundles/` НЕ входит. `crontab`/`systemd` на хосте недоступны — регулярных бэкапов нет. Только ручные git-bundle'ы 9 workspace-репо от 2026-08-06. Активных репозиториев на Forgejo всего 3: `opencode-config`, `opencode-voice-dictation`, `forgejo-infra`. Плюс `opencode-memory` — отдельный git-репо (синхронизирован с Forgejo). Итого 4 источника для зеркалирования. Forgejo-инстанс НЕ на этом хосте (linux-2), а на втором сервере — работаем только через REST API (`FORGEJO_URL`/`FORGEJO_TOKEN` в окружении). Инструменты opencode-config (create-issue, create-pr, merge-pr, post-review, pipeline-status, project-status, spec-status) уже портированы на Forgejo через env-fallback — `FORGEJO_URL` задан → используют Forgejo REST API, иначе fallback на `gh` CLI. Issue #1 про портирование устарел и должен быть закрыт отдельно. ## Задача Реализовать disaster recovery уровня A: код + issues/PR на внешнем git-хостинге. Два слоя защиты: **Слой 1 — Git-mirror (push).** Настроить push mirror для 4 репо на внешний git-хост (GitLab.com — предпочтительно, Codeberg — запасной). Использовать Forgejo's built-in push mirror (через API `/api/v1/repos/{repo}/push_mirrors`), не локальные git-операции — Forgejo сам пушит по schedule. Переносит код + коммиты + ветки + теги. **Слой 2 — Issues/PR-экспорт.** Forgejo Actions workflow в `forgejo-infra` (schedule daily), который через API тянет issues/PR/comments/labels из всех репо, сериализует в JSON + человекочитаемый markdown, коммитит в отдельный репо `forgejo-archive` на внешнем хосте. Обратная сторона — `forgejo-restore.sh` скрипт, который читает archive и через API воссоздаёт issues/PR на новом инстансе. Цель: "одной командой поднять точно такой же Forgejo со всеми настройками, pull-request'ами и issues" — на новом сервере. ## Контракты - Forgejo push_mirrors API: `POST /api/v1/repos/{repo}/push_mirrors` с `{target_address, target_username, target_password, interval, mirror, ...}`. Документация: https://codeberg.org/forgejo/forgejo/src/branch/forgejo/routers/api/v1/repo/push_mirror.go - Forgejo issues API: `GET /api/v1/repos/{repo}/issues?state=all&limit=50&page=N`, для каждого issue `GET /api/v1/repos/{repo}/issues/{n}/comments` - Forgejo pulls API: `GET /api/v1/repos/{repo}/pulls?state=all&limit=50&page=N` - Forgejo labels API: `GET /api/v1/repos/{repo}/labels` - Forgejo milestones API: `GET /api/v1/repos/{repo}/milestones` - Внешний хост: GitLab.com (предпочтительный, приватные репо, неограниченно) или Codeberg (запасной, Gitea-based). Выбор за пользователем при имплементации. - Archive-формат: иерархия `archive/{repo}/issues/{N}.json` + `archive/{repo}/issues/{N}.md` + `archive/{repo}/pulls/{N}.json` + `archive/{repo}/pulls/{N}.md` + `archive/{repo}/labels.json` + `archive/{repo}/milestones.json` + `archive/meta.json` (timestamp, forgejo version, exporter version) - Восстановление: `forgejo-restore.sh <archive-repo-path> <target-forgejo-url> <token>` — читает archive, создаёт labels/milestones/issues/PR через API в правильном порядке (с маппингом старых ID → новых) ## Инварианты - Зеркалируются ТОЛЬКО 4 репо: `opencode-config`, `opencode-voice-dictation`, `forgejo-infra`, `opencode-memory`. 8 legacy workspace-папок без Forgejo — вне scope - Push mirror — read-only на стороне GitLab, single source of truth остаётся Forgejo (зеркало не используется для активной работы) - Issues/PR-экспорт — снимки (snapshot) раз в сутки, НЕ real-time sync - БЕЗ секретов: пользователи инстанса, токены, настройки `app.ini`, webhooks, CI secrets — вне scope (это уровень B, forgejo dump, отдельная задача) - RAG-индекс (`opencode-memory/.rag/`, 200 МБ) НЕ бэкапим — перестраивается за 32 сек через `memory-save` - Source of truth для настроек инстанса Forgejo — сам `forgejo-infra` репо (reproducible setup), его git-mirror уже покрывает восстановление конфигурации ## Граничные случаи - `forgejo-infra` есть на Forgejo, но НЕ имеет локального clone'а в `/root/workspace/` — настройка push mirror только через API, не через локальные git-операции - Issues с attachments/media — attachments НЕ переносим в MVP (только текст комментариев) - PR review comments на конкретные строки кода (diff comments) — позиционная информация (`line`, `side`, `path`) может теряться при переимпорте, сохраняем как обычные комментарии с пометкой "review comment on file X line Y" - Удалённые/закрытые issues/PR — включать в экспорт (state=all), полный архив - Длинные истории комментариев — пагинация (limit=50, page=N), мерджить в один файл на issue - Rate limits Forgejo API — экспорт должен быть идемпотентным и устойчивым к retry (если упало на середине — продолжить с последнего issue) - Markdown-рендеринг Forgejo vs GitLab — отличия в расширениях (!-функции, математика, admonitions). В archive храним raw markdown (source), не рендер - Зеркало ломается при force-push / history rewrite — Forgejo Actions должен алертить (fail job) при broken mirror - Порядок восстановления: labels → milestones → issues (с label/milestone mapping) → PR (как issue с признаком is_pull) → comments. Важен порядок создания, чтобы ссылки (на другие issues, на milestone) работали - Аутентификация restore: restore-скрипту нужен токен с правами issue/write/admin на целевом инстансе — задавать через env, не вшивать - Дубликаты при повторном restore: restore должен быть идемпотентным — проверять по `original_id` в начале body или по label `migrated-from:{original_id}` и пропускать существующие ## Влияние на связанные компоненты - `forgejo-infra` репо — добавится Forgejo Actions workflow `backup-export.yml` (schedule daily) + `forgejo-restore.sh` скрипт + документация в README - Forgejo инстанс — добавятся 4 push_mirrors настройки (через API), потребуются секреты для пуша на внешний хост (FORGEJO_TOKEN для API + GITLAB_TOKEN для push, хранить в Forgejo Actions secrets) - Внешний git-хост (GitLab/Codeberg) — создаются 5 приватных репо: 4 mirror-targets + `forgejo-archive` (вручную пользователем или через API) - `opencode-memory` — добавляется push mirror, но настройки делаются через Forgejo API (не локально), так как memory живёт вне workspace - `AGENTS.md` в opencode-config — добавить процедуру disaster recovery в раздел docs/ (короткий how-to: "если Forgejo упал — как восстановить") - Инструменты opencode-config (create-issue, merge-pr и т.д.) — НЕ затрагиваются, они уже портированы - Issue #1 (port to Forgejo) — должен быть закрыт как устаревший после завершения этой задачи (или независимо), так как портирование фактически завершено ## Вне scope - Уровень B: полный `forgejo dump` (БД + app.ini + users + tokens + webhooks + LFS + attachments) с шифрованием и хранением на отдельной VPS — отдельная задача - 8 legacy workspace-папок без Forgejo (`arena-models`, `slaid098`, `slaid098-dev`, `video_uniq`, `voice_assistant`, `youtube-kit`, `youtube-soft`, `salvage-slaid098`) — постепенный перенос потом - LFS-объекты — если есть, добавим в следующей итерации - Attachments к issues (картинки, файлы) — MVP только текст - RAG-индекс — перестраивается за 32 сек - Автоматизация через cron/systemd на хосте — недоступно, используем Forgejo Actions - Реальная миграция на новый сервер — только подготовка (archive + restore-скрипт), не выполнение миграции - GitLab CI / Codeberg CI — не настраиваем, только хранилище - Двусторонняя синхронизация (зеркало не редактируется) ## Критерии приемки - [ ] Пользователь создал аккаунт на GitLab.com (или Codeberg) и передал Personal Access Token (scope: api + write_repository) - [ ] Созданы 5 приватных репо на внешнем хосте: `opencode-config`, `opencode-voice-dictation`, `forgejo-infra`, `opencode-memory`, `forgejo-archive` - [ ] Через Forgejo API настроены push_mirrors для 4 репо → verified: после push в Forgejo коммиты появляются на внешнем хосте в течение interval - [ ] В `forgejo-infra` добавлен Forgejo Actions workflow `.forgejo/workflows/backup-export.yml` — schedule daily, экспортирует issues/PR/comments/labels/milestones из 4 репо - [ ] Archive-формат соответствует контракту: иерархия `archive/{repo}/issues/{N}.json` + `.md` + `archive/{repo}/pulls/{N}.json` + `.md` + `archive/{repo}/labels.json` + `archive/{repo}/milestones.json` + `archive/meta.json` - [ ] Workflow коммитит archive в `forgejo-archive` репо на внешнем хосте (через HTTPS + token) - [ ] Restore-скрипт `forgejo-restore.sh` в `forgejo-infra`: читает archive → через API воссоздаёт labels/milestones/issues/PR/comments на новом Forgejo инстансе, идемпотентный (пропускает уже мигрированные) - [ ] Smoke-test: выполнить restore на тестовом пустом Forgejo инстансе → убедиться что issues/PR/labels/comments воссоздаются корректно - [ ] Документация в `forgejo-infra/README.md`: процедура disaster recovery одной командой, предпосылки (внешний токен, чистый Forgejo), шаги восстановления - [ ] Короткая ссылка в `opencode-config/docs/decisions/` (новый ADR) — фиксирует решение об уровне A, выбранном хостинге, и ссылку на эту issue
slaid098 added the
enhancement
label 2026-08-06 20:55:53 +03:00
Sign in to join this conversation.
No labels
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: slaid098/forgejo-infra#2
No description provided.