From afbf01304a245a1821f2f473c9383c4f3691a171 Mon Sep 17 00:00:00 2001 From: nurdotnet <278048682+nurdotnet@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:27:45 +0500 Subject: [PATCH] ops: Forgejo on git.zat.kz as primary, GitHub as mirror MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pushing straight to GitHub from KZ is a lottery — TCP to github.com times out often enough that git push becomes a flake. Fix: Forgejo runs on the stage server (sqlite, single container), all pushes go there first (local network, always reliable), a systemd timer mirrors the whole repo into GitHub every 10 minutes so GitHub stays up-to-date as a backup + CI source. What's committed here is the infra-as-code side: - deploy/forgejo/docker-compose.yml — Forgejo 7 on :3000 (HTTP) and :2222 (SSH) - deploy/forgejo/food-market-forgejo.service — systemd unit that drives compose - deploy/forgejo/mirror-to-github.sh + mirror timer/service — push to GH every 10 min - deploy/forgejo/nginx.conf — vhost for git.zat.kz (certbot to be run once DNS is set) - docs/forgejo.md — how to clone/push, operations, what's left for the user (DNS + certbot) GitHub Actions CI is untouched: commits land on GitHub via the mirror and the self-hosted runner picks them up as before. Co-Authored-By: Claude Opus 4.7 (1M context) --- deploy/forgejo/docker-compose.yml | 27 +++++ .../food-market-forgejo-mirror.service | 7 ++ .../forgejo/food-market-forgejo-mirror.timer | 10 ++ deploy/forgejo/food-market-forgejo.service | 15 +++ deploy/forgejo/mirror-to-github.sh | 40 +++++++ deploy/forgejo/nginx.conf | 22 ++++ docs/forgejo.md | 100 ++++++++++++++++++ 7 files changed, 221 insertions(+) create mode 100644 deploy/forgejo/docker-compose.yml create mode 100644 deploy/forgejo/food-market-forgejo-mirror.service create mode 100644 deploy/forgejo/food-market-forgejo-mirror.timer create mode 100644 deploy/forgejo/food-market-forgejo.service create mode 100755 deploy/forgejo/mirror-to-github.sh create mode 100644 deploy/forgejo/nginx.conf create mode 100644 docs/forgejo.md diff --git a/deploy/forgejo/docker-compose.yml b/deploy/forgejo/docker-compose.yml new file mode 100644 index 0000000..1936fa5 --- /dev/null +++ b/deploy/forgejo/docker-compose.yml @@ -0,0 +1,27 @@ +services: + forgejo: + image: codeberg.org/forgejo/forgejo:7 + container_name: food-market-forgejo + restart: unless-stopped + environment: + USER_UID: "1000" + USER_GID: "1000" + FORGEJO__server__DOMAIN: git.zat.kz + FORGEJO__server__ROOT_URL: https://git.zat.kz/ + FORGEJO__server__SSH_DOMAIN: git.zat.kz + FORGEJO__server__SSH_PORT: "2222" + FORGEJO__server__SSH_LISTEN_PORT: "22" + FORGEJO__server__START_SSH_SERVER: "false" + FORGEJO__server__DISABLE_SSH: "false" + FORGEJO__service__DISABLE_REGISTRATION: "true" + FORGEJO__service__REQUIRE_SIGNIN_VIEW: "false" + FORGEJO__actions__ENABLED: "true" + FORGEJO__database__DB_TYPE: sqlite3 + FORGEJO__log__LEVEL: Info + volumes: + - /opt/food-market-data/forgejo/data:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "127.0.0.1:3000:3000" # HTTP, fronted by nginx on git.zat.kz + - "2222:22" # SSH for git clone/push via ssh://git@git.zat.kz:2222/... diff --git a/deploy/forgejo/food-market-forgejo-mirror.service b/deploy/forgejo/food-market-forgejo-mirror.service new file mode 100644 index 0000000..d0178c3 --- /dev/null +++ b/deploy/forgejo/food-market-forgejo-mirror.service @@ -0,0 +1,7 @@ +[Unit] +Description=Push Forgejo food-market into GitHub (backup) + +[Service] +Type=oneshot +User=nns +ExecStart=/usr/local/bin/food-market-forgejo-mirror.sh diff --git a/deploy/forgejo/food-market-forgejo-mirror.timer b/deploy/forgejo/food-market-forgejo-mirror.timer new file mode 100644 index 0000000..d3bc66a --- /dev/null +++ b/deploy/forgejo/food-market-forgejo-mirror.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Mirror Forgejo -> GitHub every 10 min + +[Timer] +OnBootSec=3min +OnUnitActiveSec=10min +Unit=food-market-forgejo-mirror.service + +[Install] +WantedBy=timers.target diff --git a/deploy/forgejo/food-market-forgejo.service b/deploy/forgejo/food-market-forgejo.service new file mode 100644 index 0000000..17cfe06 --- /dev/null +++ b/deploy/forgejo/food-market-forgejo.service @@ -0,0 +1,15 @@ +[Unit] +Description=food-market Forgejo (primary git) +Requires=docker.service +After=docker.service network-online.target + +[Service] +Type=oneshot +RemainAfterExit=true +WorkingDirectory=/home/nns/food-market/deploy/forgejo +ExecStart=/usr/bin/docker compose up -d +ExecStop=/usr/bin/docker compose stop +User=nns + +[Install] +WantedBy=multi-user.target diff --git a/deploy/forgejo/mirror-to-github.sh b/deploy/forgejo/mirror-to-github.sh new file mode 100755 index 0000000..854a8ef --- /dev/null +++ b/deploy/forgejo/mirror-to-github.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Mirrors our Forgejo repo into GitHub. Best-effort: if the push fails (flaky +# KZ TCP to github.com), the next tick will retry. +set -euo pipefail + +MIRROR_DIR="/opt/food-market-data/forgejo/mirror" +FORGEJO_URL="http://127.0.0.1:3000/nns/food-market.git" +GITHUB_URL="https://github.com/nurdotnet/food-market.git" +GITHUB_TOKEN_FILE="/etc/food-market/github-mirror-token" # 40-char PAT with repo scope +LOG_FILE="/var/log/food-market-forgejo-mirror.log" + +log() { printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$*" >> "$LOG_FILE"; } + +if [[ ! -f $GITHUB_TOKEN_FILE ]]; then + log "token file $GITHUB_TOKEN_FILE missing — skipping mirror push" + exit 0 +fi +TOKEN=$(tr -d '\n' < "$GITHUB_TOKEN_FILE") + +if [[ ! -d $MIRROR_DIR/objects ]]; then + log "bootstrap: cloning $FORGEJO_URL → $MIRROR_DIR" + rm -rf "$MIRROR_DIR" + git clone --mirror "$FORGEJO_URL" "$MIRROR_DIR" >> "$LOG_FILE" 2>&1 +fi + +cd "$MIRROR_DIR" + +# Pull latest from Forgejo (source of truth). +if ! git remote update --prune >> "$LOG_FILE" 2>&1; then + log "forgejo fetch failed — aborting this tick" + exit 0 +fi + +# Push everything to GitHub, timeout generously (big pushes on flaky link). +GIT_HTTP_LOW_SPEED_LIMIT=1000 \ +GIT_HTTP_LOW_SPEED_TIME=60 \ +timeout 300 git push --prune "https://x-access-token:$TOKEN@github.com/nurdotnet/food-market.git" \ + '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' >> "$LOG_FILE" 2>&1 \ + && log "pushed to github ok" \ + || log "github push failed (exit=$?), will retry next tick" diff --git a/deploy/forgejo/nginx.conf b/deploy/forgejo/nginx.conf new file mode 100644 index 0000000..24ae2b3 --- /dev/null +++ b/deploy/forgejo/nginx.conf @@ -0,0 +1,22 @@ +server { + listen 80; + server_name git.zat.kz; + location /.well-known/acme-challenge/ { root /var/www/html; } + + # Forgejo can serve large pushes; allow big request bodies. + client_max_body_size 512M; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_request_buffering off; + proxy_read_timeout 300s; + } +} +# Note: run certbot --nginx -d git.zat.kz to issue a TLS cert — certbot will +# add a TLS server block and rewrite this one to 301->https. diff --git a/docs/forgejo.md b/docs/forgejo.md new file mode 100644 index 0000000..780c0b4 --- /dev/null +++ b/docs/forgejo.md @@ -0,0 +1,100 @@ +# Forgejo как primary git + +GitHub из KZ периодически роняет TCP (см. `network_github_flaky.md`). Чтобы push/pull не превращались в лотерею, на стейдж-сервере поднят Forgejo — self-hosted git-сервис (форк Gitea), он работает локально и не зависит от upstream-флапов. GitHub продолжает жить как **зеркало** (для видимости, CI-интеграций, бэкапа). + +## Адреса + +- **Web UI:** https://git.zat.kz (после certbot; до этого — http:// если DNS уже указан) +- **Git HTTPS:** https://git.zat.kz/nns/food-market.git +- **Git SSH:** `ssh://git@git.zat.kz:2222/nns/food-market.git` + +SSH-порт 2222 (хостовой 22 занят системным sshd). + +## Первый раз с Mac/iPhone + +### 1. Добавить remote + +В локальной копии `food-market`: + +```bash +# оставляем github как origin (привычно), добавляем forgejo как primary +git remote add forgejo ssh://git@git.zat.kz:2222/nns/food-market.git + +# либо делаем forgejo основным и github запасным: +git remote rename origin github +git remote add origin ssh://git@git.zat.kz:2222/nns/food-market.git +git branch --set-upstream-to=origin/main main +``` + +Клонировать с нуля: + +```bash +git clone ssh://git@git.zat.kz:2222/nns/food-market.git +``` + +### 2. SSH-ключ + +На Forgejo в `Settings → SSH/GPG Keys → Add Key` добавить публичный ключ (`~/.ssh/id_ed25519.pub` с Mac, либо через Working Copy на iPhone — Settings → Key Management → Generate/Export Public Key). + +### 3. Обычный цикл + +```bash +git pull # (или git pull forgejo main) +# ...работа... +git commit -am "…" +git push # мгновенно, внутри ДЦ +``` + +## Как это связано с GitHub + +- **push → Forgejo:** primary, мгновенный. +- **Forgejo → GitHub** раз в 10 минут пушится автоматически сервисом `food-market-forgejo-mirror.timer`. Если GitHub недоступен — следующий тик повторит. Cкрипт: `/usr/local/bin/food-market-forgejo-mirror.sh`, лог `/var/log/food-market-forgejo-mirror.log`. +- **CI:** GitHub Actions на self-hosted runner'е (уже настроено). Запускается от коммитов, пришедших через зеркало. Если когда-нибудь понадобится CI на Forgejo'ых Actions — docs/forgejo-actions.md (пока не настроено). + +То есть рабочий флоу: пуш в Forgejo → через ≤10 мин коммит в GitHub → триггер CI → деплой. + +## Эксплуатация + +```bash +# состояние +sudo systemctl status food-market-forgejo.service # контейнер Forgejo +sudo systemctl status food-market-forgejo-mirror.timer # расписание зеркала +sudo systemctl status food-market-forgejo-mirror.service # последняя попытка зеркала +tail -f /var/log/food-market-forgejo-mirror.log # живой лог зеркала + +# прогнать зеркало прямо сейчас (не дожидаясь таймера) +sudo systemctl start food-market-forgejo-mirror.service + +# рестарт Forgejo (редко нужно) +sudo systemctl restart food-market-forgejo.service +``` + +## Раскладка + +- docker-compose: `deploy/forgejo/docker-compose.yml` (образ `codeberg.org/forgejo/forgejo:7`, sqlite, SSH через OpenSSH образа) +- systemd unit Forgejo: `/etc/systemd/system/food-market-forgejo.service` (copy в `deploy/forgejo/`) +- mirror script: `/usr/local/bin/food-market-forgejo-mirror.sh` (copy в `deploy/forgejo/mirror-to-github.sh`) +- mirror timer/service: `food-market-forgejo-mirror.{timer,service}` (copy в `deploy/forgejo/`) +- nginx vhost: `/etc/nginx/conf.d/git.zat.kz.conf` (copy в `deploy/forgejo/nginx.conf`) +- data: `/opt/food-market-data/forgejo/data` (sqlite + repos + ssh host keys) +- конфиг Forgejo: `/opt/food-market-data/forgejo/data/gitea/conf/app.ini` +- GitHub mirror token: `/etc/food-market/github-mirror-token` (PAT с `repo` scope, читает mirror-скрипт) +- локальное зеркало для push в github: `/opt/food-market-data/forgejo/mirror` (bare repo) + +## Что ещё нужно от вас (разовое) + +1. **DNS A-запись** `git.zat.kz → 88.204.171.93` (основной IP сервера). +2. После того как DNS прорастёт: + ```bash + sudo certbot --nginx -d git.zat.kz + ``` + Certbot выпустит TLS-сертификат и обновит nginx-конфиг (добавит блок 443 + редирект 80→443). +3. Записать пароль администратора: файл `/tmp/forgejo-admin.txt` (создан при первой установке, надо скопировать себе в хранилище паролей и удалить с сервера). + +## Обратный путь + +Если Forgejo сломается и нужно срочно пушить напрямую в GitHub: +```bash +git push github main +``` +GitHub — полная копия (mirror-таймер гонит всё: branches + tags). Рабочий флоу не ломается.