ops: Forgejo on git.zat.kz as primary, GitHub as mirror
Some checks failed
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker Images / API image (push) Failing after 15s
Docker Images / Web image (push) Failing after 2s

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) <noreply@anthropic.com>
This commit is contained in:
nurdotnet 2026-04-23 12:27:45 +05:00
parent e9a82dd528
commit afbf01304a
7 changed files with 221 additions and 0 deletions

View file

@ -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/...

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"

22
deploy/forgejo/nginx.conf Normal file
View file

@ -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.

100
docs/forgejo.md Normal file
View file

@ -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). Рабочий флоу не ломается.