#!/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:]' » сборщик релиза откажется # подписывать. Текст — на владельца, он правит его перед выпуском. 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 <