Робот ежесуточной синхронизации с автором + workflow Gitea Actions (расписание 03:30)
Синхронизация с автором / sync (push) Successful in 2s

This commit is contained in:
2026-10-12 21:10:32 +03:00
parent 13c86723be
commit 6b497b047d
2 changed files with 176 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env bash
# Робот ежесуточной синхронизации: проверяет обновления у автора проекта и, если они есть,
# переносит их в нашу ветку dev — но только когда всё сошлось.
#
# Где живёт: запускается из .gitea/workflows/fork-sync.yml на раннере (CT 114 «runner»).
# Что НЕ делает: не выпускает релиз и не подписывает его — закрытый ключ подписи остаётся
# только на рабочей станции владельца, поэтому версия автора публикуется вручную.
#
# Переменные окружения (все имеют значения по умолчанию, кроме TOKEN):
# TOKEN токен Gitea с правом записи в репозиторий (берётся из секретов workflow)
# GITEA_URL внутренний адрес Gitea (снаружи контейнер её не видит: имя резолвится в публичный IP)
# REPO владелец/репозиторий
# UPSTREAM адрес авторского репозитория
# WORK рабочая копия на раннере
# BRANCH наша ветка для синхронизации
# LOG файл отчёта последнего запуска (его читают из контейнера руками)
set -Eeuo pipefail
GITEA_URL=${GITEA_URL:-http://10.10.10.54:3000}
REPO=${REPO:-hacker07/3X-UI_autoVPS}
UPSTREAM=${UPSTREAM:-https://github.com/itsnotkubrick/3X-UI_KIT}
WORK=${WORK:-/opt/kit-fork-sync/repo}
BRANCH=${BRANCH:-dev}
LOG=${LOG:-/opt/kit-fork-sync/last-run.log}
TOKEN=${TOKEN:-}
say() { printf '\n==> %s\n' "$*"; }
ok() { printf ' ✓ %s\n' "$*"; }
die() { printf '✗ %s\n' "$*" >&2; exit 1; }
mkdir -p "$(dirname "$LOG")"
exec > >(tee -a "$LOG") 2>&1
printf '\n===== запуск %s =====\n' "$(date '+%F %T %Z')"
[[ -n $TOKEN ]] || die "нет TOKEN: workflow не передал секрет GITEA_TOKEN"
command -v git >/dev/null || die "нет git"
PY=${PY:-$(command -v python3 || command -v python || true)}
[[ -n $PY ]] || die "нет python3 — без него не применить швы"
auth_url="${GITEA_URL}/${REPO}.git"
auth_url="${auth_url/http:\/\//http://x-access-token:${TOKEN}@}"
# ---------------------------------------------------------------- рабочая копия
if [[ ! -d $WORK/.git ]]; then
say "Первая синхронизация: клонирую репозиторий"
git clone -q "$auth_url" "$WORK" || die "не смог склонировать $REPO"
git -C "$WORK" remote set-url origin "$auth_url"
git -C "$WORK" remote add upstream "$UPSTREAM" 2>/dev/null || git -C "$WORK" remote set-url upstream "$UPSTREAM"
else
git -C "$WORK" remote set-url origin "$auth_url"
git -C "$WORK" remote set-url upstream "$UPSTREAM" 2>/dev/null || git -C "$WORK" remote add upstream "$UPSTREAM"
fi
cd "$WORK"
say "Забираю ветки: наша $BRANCH и авторскую main"
git fetch -q origin --prune
git fetch -q upstream 'refs/heads/main:refs/heads/upstream' --prune
git checkout -q "$BRANCH"
git reset -q --hard "origin/$BRANCH"
author_version=$(git show upstream:VERSION 2>/dev/null | tr -d '[:space:]' || true)
[[ -n $author_version ]] || die "у автора не читается VERSION — проверьте ветку upstream"
ours=$(tr -d '[:space:]' <VERSION)
echo " у автора: $author_version · у нас: $ours"
new_commits=$(git rev-list --count "$BRANCH..upstream")
if [[ $new_commits == 0 ]]; then
say "У автора нет новых коммитов — публиковать нечего"
ok "проверка выполнена, ветка $BRANCH не изменялась"
exit 0
fi
say "У автора $new_commits новых коммитов — переношу наши правки на свежий код"
git --no-pager log --oneline "$BRANCH..upstream" | sed 's/^/ /'
# ---------------------------------------------------------------- перенос и проверки
# Скрипт форка делает всё сам: rebase на ветку автора, проверка швов, синтаксис, LF,
# тесты, подъём номера версии (--apply). На конфликте он останавливается и ничего не публикует.
if ! bash tools/fork-sync.sh --apply; then
die "перенос не прошёл (конфликт в местах наших правок). Ветка $BRANCH не изменена. Разберите вручную на рабочей станции"
fi
new_version=$(tr -d '[:space:]' <VERSION)
[[ -n $new_version ]] || die "после переноса не читается VERSION"
# Заглушка записи в нашем журнале версий: без раздела «## v<версия>» сборщик релиза откажется
# подписывать. Текст — на владельца, он правит его перед выпуском.
if ! grep -q "^## v$new_version" FORK-CHANGELOG.md; then
say "Добавляю запись в FORK-CHANGELOG для версии $new_version"
"$PY" - "$new_version" "$author_version" <<'PY'
import sys, pathlib
new, author = sys.argv[1], sys.argv[2]
p = pathlib.Path("FORK-CHANGELOG.md")
text = p.read_text(encoding="utf-8")
block = (f"## v{new}: обновление от автора {author}\n\n"
f"* Автоматическая синхронизация подтянула код автора {author} в нашу сборку: наши правки (швы)\n"
f" перенесены на свежий код, проверки прошли. Описать, что изменилось у автора, до выпуска релиза.\n\n")
marker = "## v"
i = text.find(marker)
p.write_text(text[:i] + block + text[i:], encoding="utf-8", newline="\n")
print(f" ✓ раздел «## v{new}» добавлен")
PY
fi
# Финальный контроль: швы на месте, синтаксис и LF — как того требует сборщик релиза.
"$PY" tools/fork_apply.py --check >/dev/null || die "после переноса швы не проверяются — ничего не публикую"
for f in scripts/*.sh tools/*.sh; do bash -n "$f" || die "$f: синтаксис"; done
if grep -qU $'\r' scripts/*.sh tools/*.sh VERSION 2>/dev/null; then
die "в файлах CRLF — сервер сверяет SHA256, сумма не сойдётся. Ничего не публикую"
fi
ok "швы на месте, синтаксис и переводы строк в порядке"
# ---------------------------------------------------------------- публикация
if git diff --quiet origin/"$BRANCH" -- VERSION scripts tools .gitea 2>/dev/null && [[ $new_version == "$ours" ]]; then
say "Содержимое не изменилось — публиковать нечего"
exit 0
fi
say "Публикую в $BRANCH"
git add -A
git -c user.name="fork-sync-bot" -c user.email="fork-sync@rukavishnikoff.ru" \
commit -q -m "Синхронизация с автором ${author_version}: швы и версия ${new_version}" \
-m "Автоматика CT 114. Проверки: швы на месте, bash -n, LF. Релиз и подпись — вручную с рабочей станции." \
|| die "не смог закоммитить"
curl -fsS -o /dev/null -H "Authorization: token ${TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO}/branches/${BRANCH}" || die "Gitea недоступна перед отправкой"
# Перенос идёт rebase'ом: наша ветка переписывается на свежий код автора, поэтому отправляем
# с --force-with-lease — если за это время в ветку что-то попало с рабочей станции, отправка
# откажется, а не затрёт чужой коммит.
git push -q --force-with-lease=refs/heads/"$BRANCH" origin "$BRANCH" \
|| die "не смог отправить ветку $BRANCH (возможно, в ней есть ваши свежие коммиты — тогда синхронизируйте вручную)"
ok "ветка $BRANCH обновлена: $(git --no-pager log --oneline -1)"
cat <<EOF
Готово. Дальше вручную, с рабочей станции:
1) git fetch origin && git checkout $BRANCH && git reset --hard origin/$BRANCH
2) описать изменения в FORK-CHANGELOG.md (раздел уже добавлен)
3) bash tools/release.sh ~/.ssh/3x-ui-kit-fork/release_key — подпись и релиз
4) git tag v$new_version && git push origin $BRANCH v$new_version
EOF