ops: Forgejo on git.zat.kz as primary, GitHub as mirror
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:
parent
e9a82dd528
commit
afbf01304a
27
deploy/forgejo/docker-compose.yml
Normal file
27
deploy/forgejo/docker-compose.yml
Normal 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/...
|
||||||
7
deploy/forgejo/food-market-forgejo-mirror.service
Normal file
7
deploy/forgejo/food-market-forgejo-mirror.service
Normal 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
|
||||||
10
deploy/forgejo/food-market-forgejo-mirror.timer
Normal file
10
deploy/forgejo/food-market-forgejo-mirror.timer
Normal 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
|
||||||
15
deploy/forgejo/food-market-forgejo.service
Normal file
15
deploy/forgejo/food-market-forgejo.service
Normal 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
|
||||||
40
deploy/forgejo/mirror-to-github.sh
Executable file
40
deploy/forgejo/mirror-to-github.sh
Executable 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
22
deploy/forgejo/nginx.conf
Normal 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
100
docs/forgejo.md
Normal 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). Рабочий флоу не ломается.
|
||||||
Loading…
Reference in a new issue