From df5224e813d2aa3ea9d59ae628cfd475a664bfdf Mon Sep 17 00:00:00 2001 From: Lacky1234union Date: Fri, 16 Oct 2026 00:18:51 +0300 Subject: [PATCH] =?UTF-8?q?feat(infra):=20CD=20=D0=B1=D0=BE=D1=82=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=20Vobot=20?= =?UTF-8?q?=E2=80=94=20Gitea=20Actions=20+=20deploy.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - infra/deploy/deploy.sh [ref]: git archive → scp → remote.sh на сервере; руками и из CI, хост — DEPLOY_HOST (любой Docker-хост); каждый шаг с ретраями - remote.sh: атомарная замена app/ (app.old для отката), compose -p tgit- up --build, ожидание healthy, REVISION, подсказка отката при неудаче; безопасно перезапускать - окружение без /srv/tgit//.env пропускается (prod — когда будет свой токен) - .gitea/workflows/deploy.yml: push в dev/main → deploy; guard «актуален ли пуш», ключ через env Refs #12 Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/deploy.yml | 45 +++++++++++++++++++++++++++++++++++++ infra/deploy/deploy.sh | 34 ++++++++++++++++++++++++++++ infra/deploy/remote.sh | 36 +++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 .gitea/workflows/deploy.yml create mode 100755 infra/deploy/deploy.sh create mode 100755 infra/deploy/remote.sh diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..d3c9f5f --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,45 @@ +name: CD + +# Как в Vobot: тесты здесь не гоняем — они прошли в CI на PR. +# dev → /srv/tgit/dev, main → /srv/tgit/prod на хосте Vobot (infra/deploy/deploy.sh). +# Окружение без /srv/tgit//.env на сервере пропускается (prod — когда будет свой токен бота). +on: + push: + branches: [dev, main] + workflow_dispatch: {} + +# Деплои одной ветки — строго по очереди, без отмены. +concurrency: + group: cd-${{ github.ref }} + cancel-in-progress: false + +jobs: + deploy: + name: deploy → ${{ github.ref_name == 'main' && 'prod' || 'dev' }} + runs-on: ubuntu-latest + env: + DEPLOY_ENV: ${{ github.ref_name == 'main' && 'prod' || 'dev' }} + steps: + - uses: actions/checkout@v4 + + # Серия мержей подряд: устаревший прогон сам сворачивается, задеплоит актуальный (опыт Vobot) + - id: guard + name: "Актуален ли ещё этот пуш?" + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "go=true" >> "$GITHUB_OUTPUT"; exit 0; fi + REMOTE_SHA=$(git ls-remote origin "refs/heads/${{ github.ref_name }}" | cut -f1) + if [ "$REMOTE_SHA" = "${{ github.sha }}" ]; then echo "go=true" >> "$GITHUB_OUTPUT" + else echo "⏭ ${{ github.sha }} уже не HEAD ${{ github.ref_name }} — пропускаю"; echo "go=false" >> "$GITHUB_OUTPUT"; fi + + - name: Deploy + if: steps.guard.outputs.go == 'true' + env: + # Через env:, а не инлайном в run: — многострочный ключ иначе может попасть в лог (опыт Vobot) + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + run: | + install -d -m 700 ~/.ssh + printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + export DEPLOY_SSH_OPTS="-F /dev/null -i $HOME/.ssh/deploy_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=20" + infra/deploy/deploy.sh "$DEPLOY_ENV" HEAD diff --git a/infra/deploy/deploy.sh b/infra/deploy/deploy.sh new file mode 100755 index 0000000..a79c021 --- /dev/null +++ b/infra/deploy/deploy.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Deploy a git ref of the bot to a server. Used by hand and by CD (.gitea/workflows/deploy.yml). +# infra/deploy/deploy.sh [git-ref] default ref: HEAD +# Env: DEPLOY_HOST (default tgit-deploy@72.56.247.166 — the vobot host; any Docker host works) +# DEPLOY_SSH_OPTS (extra ssh/scp options, e.g. -i key) +# Server layout: /srv/tgit//{.env, app/, app.old/, REVISION}; secrets live only in /srv/tgit//.env. +# The host drops some ssh connections — every remote step is retried, and each one is safe to repeat. +set -euo pipefail +ENV=${1:?usage: deploy.sh [ref]} +REF=${2:-HEAD} +case "$ENV" in dev | prod) ;; *) echo "env must be dev or prod" >&2; exit 2 ;; esac +HOST=${DEPLOY_HOST:-tgit-deploy@72.56.247.166} +OPTS=${DEPLOY_SSH_OPTS:-} +DIR=/srv/tgit/$ENV +SHA=$(git rev-parse --short "$REF") +TGZ=$(mktemp --suffix=.tgz) +trap 'rm -f "$TGZ"' EXIT +git archive --format=tar.gz -o "$TGZ" "$REF" + +retry() { local n=0; until "$@"; do n=$((n + 1)); [ $n -ge 5 ] && return 1; echo "retry $n: $1" >&2; sleep $((n * 5)); done; } + +# shellcheck disable=SC2086 +if ! retry ssh $OPTS "$HOST" "test -f $DIR/.env"; then + echo "::notice::$HOST:$DIR/.env not found — environment '$ENV' is not configured, skipping deploy" + exit 0 +fi + +echo "deploy $REF ($SHA) -> $HOST:$DIR" +# shellcheck disable=SC2086 +retry scp -q $OPTS "$TGZ" "$HOST:$DIR/release-$SHA.tgz" +# shellcheck disable=SC2086 +retry scp -q $OPTS infra/deploy/remote.sh "$HOST:$DIR/remote.sh" +# shellcheck disable=SC2086 +retry ssh $OPTS "$HOST" "bash $DIR/remote.sh $ENV $SHA" diff --git a/infra/deploy/remote.sh b/infra/deploy/remote.sh new file mode 100755 index 0000000..a715110 --- /dev/null +++ b/infra/deploy/remote.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Runs on the server: unpack release-.tgz, swap app/ atomically, compose up, wait for healthy. +# bash /srv/tgit//remote.sh (uploaded by deploy.sh; safe to re-run) +set -euo pipefail +ENV=$1 +SHA=$2 +DIR=/srv/tgit/$ENV +PROJECT=tgit-$ENV +cd "$DIR" + +if [ -f "release-$SHA.tgz" ]; then + rm -rf app.new && mkdir app.new && tar -xzf "release-$SHA.tgz" -C app.new && rm -f "release-$SHA.tgz" + cp .env app.new/.env + rm -rf app.old + [ -d app ] && mv app app.old + mv app.new app +fi + +cd app +docker compose -p "$PROJECT" up -d --build --remove-orphans /dev/null || true) + [ "$status" = healthy ] && break + sleep 3 +done +if [ "$status" != healthy ]; then + echo "bot is '$status' after 2 min — last logs:" >&2 + docker logs --tail 50 "$PROJECT-bot-1" >&2 || true + echo "rollback: cd $DIR && rm -rf app && mv app.old app && cd app && docker compose -p $PROJECT up -d --build" >&2 + exit 1 +fi +echo "$SHA" > ../REVISION +docker image prune -f >/dev/null +echo "bot healthy @ $SHA ($ENV)"