Загрузка...

Деплой WordPress + VitePress на Beget через GitHub Actions

wordpress

Как настроить production-деплой на shared-хостинг с атомарным swap, автооткатом и уведомлениями в Telegram.

Разбираю на реальном примере WordPress + WooCommerce + headless + VitePress-лендинг и документация. Приёмы работают для любого PHP-проекта на shared-хостинге с SSH-доступом.

Классика жанра

Сначала — как обычно делают на shared-хостинге:

  1. Правишь файлы локально.
  2. Открываешь FileZilla, тянешь изменения на сервер по FTP.
  3. Сайт ломается.
  4. Никто не знает, что откатывать. Бэкап — «вчерашний, наверное».

С деплоем через GitHub Actions:

  1. Push в master — триггер.
  2. На runner’е: composer install, сборка VitePress, один архив deploy-package.tar.gz.
  3. Загрузка по SSH на сервер.
  4. Бэкап → атомарный swap → health-check → маркер успеха.
  5. На любой ошибке — откат из последнего бэкапа за секунды.
  6. Telegram-сообщение с коммитом, автором и статусом.

От push до нового кода на проде — 2–3 минуты. Откат в худшем случае — секунды.


Архитектура

Поток данных

git push origin master
        ↓
GitHub Actions runner
        ↓
1. composer install --no-dev
2. npm ci --prefix landing
3. npm run build --prefix landing
4. tar czf deploy-package.tar.gz deploy-package/
5. sshpass rsync → server:$DEPLOY_PATH/.deploy-new.tar.gz
6. sshpass rsync scripts/deploy-remote.sh → server
        ↓
Сервер (Beget)
        ↓
bash .deploy-remote.sh
   1. Sanity: архив + wp-config.php на месте
   2. rsync public_html → backups/release-<ts>/
   3. tar xzf архива → public_html/.new-<ts>/
   4. Проверка критичных файлов
   5. rsync .new-<ts>/ → public_html/
   6. Симлинк /docs/ → landing/dist/docs/
   7. WP cache flush
   8. Health-check: главная, /docs/, /wp-login.php, V2-titles, статика
   9. Маркер успешного деплоя
        ↓
Telegram: «✅ Успешно» или «❌ Провал — откат выполнен»
        ↓
Cleanup: удалить .deploy-new.tar.gz и .deploy-remote.sh

Что лежит на сервере

/home/b/<user>/<site>/
├── public_html/              ← DocumentRoot
│   ├── wp/                   ← WordPress core
│   ├── wp-content/...
│   ├── vendor/               ← Composer
│   ├── landing/dist/         ← VitePress-сборка
│   ├── docs → landing/dist/docs   ← симлинк
│   ├── index.php, .htaccess, composer.json
│   └── wp-config.php         ← только на сервере, не в архиве
├── backups/                  ← 3 последних бэкапа
│   ├── release-20260619_125556/
│   ├── release-20260619_130033/
│   └── .last-successful-deploy
└── .deploy-new.tar.gz        ← служебный, удаляется после деплоя

Секреты GitHub

Имя Значение
BEGET_HOST SSH-хост (например, ocelot.beget.com)
BEGET_USERNAME SSH-пользователь
BEGET_PASSWORD SSH-пароль
BEGET_PORT SSH-порт (по умолчанию 22)
TELEGRAM_CHAT_ID ID чата для уведомлений
TELEGRAM_BOT_TOKEN Токен бота

Код

.github/workflows/deploy.yml

name: Deploy to Beget

on:
  push:
    branches: [master]
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: true

env:
  PHP_VERSION: "8.3"
  NODE_VERSION: "18"
  DEPLOY_HOST: ${{ secrets.BEGET_HOST }}
  DEPLOY_USER: ${{ secrets.BEGET_USERNAME }}
  DEPLOY_PASSWORD: ${{ secrets.BEGET_PASSWORD }}
  DEPLOY_PORT: ${{ secrets.BEGET_PORT || '22' }}
  DEPLOY_PATH: /home/b/bo3gyx78/woo2iiko.rwsite.ru

jobs:
  build-and-deploy:
    name: Build, deploy and verify
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          submodules: false
          fetch-depth: 1
          clean: true

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ env.PHP_VERSION }}
          extensions: mbstring, xml, ctype, iconv, intl, pdo_mysql, curl, zip, gd, redis
          coverage: none
          tools: composer:2

      - name: Cache Composer downloads
        uses: actions/cache@v4
        with:
          path: ~/.composer/cache
          key: composer-${{ runner.os }}-${{ hashFiles('composer.json', 'composer.lock') }}
          restore-keys: |
            composer-${{ runner.os }}-

      - name: Install Composer dependencies
        run: |
          composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: "npm"
          cache-dependency-path: landing/package-lock.json

      - name: Install Node dependencies
        run: npm ci --prefix landing

      - name: Build VitePress
        run: |
          npm run build --prefix landing
          test -f landing/dist/index.html
          test -f landing/dist/docs/index.html

      - name: Build deployment archive
        run: |
          set -e
          rm -rf deploy-package deploy-package.tar.gz
          mkdir -p deploy-package

          cp -r wp deploy-package/
          mkdir -p deploy-package/wp-content
          cp -r wp-content/mu-plugins wp-content/plugins wp-content/themes wp-content/languages deploy-package/wp-content/

          cp -r vendor deploy-package/
          mkdir -p deploy-package/landing/dist
          cp -r landing/dist/. deploy-package/landing/dist/

          for d in assets libs screenshots images docs-v1; do
            [ -d "landing/dist/$d" ] && cp -r "landing/dist/$d" "deploy-package/$d"
          done

          cp docs.htaccess deploy-package/landing/dist/docs/.htaccess
          cp index.php composer.json composer.lock .htaccess deploy-package/

          find deploy-package -type d -exec chmod 755 {} ;
          find deploy-package -type f -exec chmod 644 {} ;

          tar -czf deploy-package.tar.gz -C deploy-package .

          echo "📦 Archive contents:"
          tar -tzf deploy-package.tar.gz | awk -F/ '{print $1}' | sort -u | head -20
          echo "📦 Size: $(du -h deploy-package.tar.gz | cut -f1)"

      - name: Upload archive to server
        env:
          SSHPASS: ${{ env.DEPLOY_PASSWORD }}
        run: |
          set -e
          if ! command -v sshpass >/dev/null 2>&1; then
            sudo apt-get update -qq && sudo apt-get install -y -qq sshpass
          fi

          REMOTE_PATH="$DEPLOY_PATH/.deploy-new.tar.gz"
          REMOTE_SCRIPT="$DEPLOY_PATH/.deploy-remote.sh"

          sshpass -e rsync -avz --progress 
            -e "ssh -o StrictHostKeyChecking=no -p $DEPLOY_PORT" 
            deploy-package.tar.gz 
            "$DEPLOY_USER@$DEPLOY_HOST:$REMOTE_PATH"

          sshpass -e rsync -avz 
            -e "ssh -o StrictHostKeyChecking=no -p $DEPLOY_PORT" 
            scripts/deploy-remote.sh 
            "$DEPLOY_USER@$DEPLOY_HOST:$REMOTE_SCRIPT"

          LOCAL_SIZE=$(wc -c < deploy-package.tar.gz)
          REMOTE_SIZE=$(sshpass -e ssh -o StrictHostKeyChecking=no 
            -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" 
            "wc -c < '$REMOTE_PATH'")

          if [ "$LOCAL_SIZE" != "$REMOTE_SIZE" ]; then
            echo "❌ Size mismatch: local $LOCAL_SIZE, remote $REMOTE_SIZE"
            exit 1
          fi
          echo "✅ Archive uploaded and verified"

      - name: Deploy, health-check and auto-rollback
        uses: appleboy/ssh-action@v1.0.3
        env:
          DEPLOY_PATH: ${{ env.DEPLOY_PATH }}
          COMMIT_SHA: ${{ github.sha }}
        with:
          host: ${{ env.DEPLOY_HOST }}
          username: ${{ env.DEPLOY_USER }}
          password: ${{ env.DEPLOY_PASSWORD }}
          port: ${{ env.DEPLOY_PORT }}
          command_timeout: 20m
          envs: DEPLOY_PATH,COMMIT_SHA
          script: |
            bash "$DEPLOY_PATH/.deploy-remote.sh"

      - name: Cleanup
        if: always()
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ env.DEPLOY_HOST }}
          username: ${{ env.DEPLOY_USER }}
          password: ${{ env.DEPLOY_PASSWORD }}
          port: ${{ env.DEPLOY_PORT }}
          script: |
            rm -f "$DEPLOY_PATH/.deploy-new.tar.gz"
            rm -f "$DEPLOY_PATH/.deploy-remote.sh"
            rm -rf "$DEPLOY_PATH/public_html/.new-"* 2>/dev/null || true
            echo "🧹 Cleanup done"

  notify:
    name: Notify Telegram
    needs: build-and-deploy
    if: always()
    runs-on: ubuntu-latest

    steps:
      - name: Telegram notification
        uses: appleboy/telegram-action@v0.1.0
        with:
          to: ${{ secrets.TELEGRAM_CHAT_ID }}
          token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          message: |
            🚀 Woo2iiko Deploy

            ${{ needs.build-and-deploy.result == 'success' && '✅ Успешно' || '❌ Провал — выполнен auto-rollback' }}

            📝 Коммит: `${{ github.sha }}`
            👤 Автор: ${{ github.actor }}
            🌿 Ветка: ${{ github.ref_name }}
            🔗 https://woo2iiko.rwsite.ru/
            📚 https://woo2iiko.rwsite.ru/docs/
        continue-on-error: true

scripts/deploy-remote.sh

#!/usr/bin/env bash
# Запускается на сервере. Читает DEPLOY_PATH и COMMIT_SHA из env.

set -e

PUB="$DEPLOY_PATH/public_html"
BACKUP_DIR="$DEPLOY_PATH/backups"
ARCHIVE="$DEPLOY_PATH/.deploy-new.tar.gz"
COMMIT_SHA="${COMMIT_SHA:-unknown}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
NEW_DIR="$PUB/.new-$TIMESTAMP"
BACKUP_PATH="$BACKUP_DIR/release-$TIMESTAMP"

cleanup_and_rollback() {
  local rc=$?
  echo "🧹 Cleanup (exit $rc)"
  rm -rf "$PUB"/.new-* 2>/dev/null || true

  if [ $rc -ne 0 ]; then
    local last_backup
    last_backup=$(ls -1td "$BACKUP_DIR"/release-* 2>/dev/null | head -1 || true)
    if [ -n "$last_backup" ] && [ -d "$last_backup" ]; then
      echo "🔄 Auto-rollback to: $last_backup"
      rsync -rlD --delete 
        --no-group --no-perms --no-owner 
        --exclude='wp-config.php' 
        --exclude='wp-content/uploads/' 
        --exclude='wp-content/cache/' 
        --exclude='.new-*' 
        "$last_backup/" "$PUB/" 2>&1 || echo "rsync rollback failed"
    else
      echo "⚠️ No backup available"
    fi
  fi
  exit $rc
}
trap cleanup_and_rollback EXIT

# === Sanity checks ===
[ -f "$ARCHIVE" ] || { echo "❌ Archive not found"; exit 1; }
[ -f "$PUB/wp-config.php" ] || { echo "❌ wp-config.php missing"; exit 1; }

# === Backup ===
mkdir -p "$BACKUP_DIR"
rsync -rlD --no-group --no-perms --no-owner 
  --exclude='.new-*' 
  --exclude='wp-content/cache/' 
  --exclude='wp-content/upgrade/' 
  "$PUB/" "$BACKUP_PATH/"

# Ротация: оставляем 3 последних
cd "$BACKUP_DIR"
ls -1t | grep '^release-' | tail -n +4 | while IFS= read -r old; do
  rm -rf "$BACKUP_DIR/$old"
done
cd - >/dev/null

# === Атомарный swap ===
mkdir -p "$NEW_DIR"
tar -xzf "$ARCHIVE" -C "$NEW_DIR"

for f in index.php landing/dist/index.html landing/dist/docs/index.html; do
  [ -e "$NEW_DIR/$f" ] || { echo "❌ Missing: $f"; exit 1; }
done
for d in assets libs screenshots images; do
  [ -d "$NEW_DIR/$d" ] || { echo "❌ Missing dir: $d"; exit 1; }
done

rsync -rlD --delete 
  --no-group --no-perms --no-owner 
  --exclude='.new-*' 
  --exclude='wp-config.php' 
  --exclude='wp-content/uploads/' 
  --exclude='wp-content/cache/' 
  "$NEW_DIR/" "$PUB/"

rm -rf "$NEW_DIR"

# === Симлинк /docs/ ===
DOCS_LINK="$PUB/docs"
DOCS_TARGET="landing/dist/docs"

if [ -L "$DOCS_LINK" ]; then
  [ "$(readlink "$DOCS_LINK")" = "$DOCS_TARGET" ] || {
    rm -f "$DOCS_LINK"
    ln -s "$DOCS_TARGET" "$DOCS_LINK"
    echo "🔗 Symlink updated"
  }
elif [ -e "$DOCS_LINK" ]; then
  mv "$DOCS_LINK" "$PUB/docs.archived.$TIMESTAMP"
  ln -s "$DOCS_TARGET" "$DOCS_LINK"
  echo "🔗 Archived old docs, symlink created"
else
  ln -s "$DOCS_TARGET" "$DOCS_LINK"
  echo "🔗 Symlink created"
fi

ls -dt "$PUB"/docs.archived.* 2>/dev/null | tail -n +3 | xargs -r rm -rf

# === Cache flush ===
if command -v wp &>/dev/null; then
  wp cache flush --allow-root 2>/dev/null || true
  wp redis flush --allow-root 2>/dev/null || true
  wp rewrite flush --allow-root 2>/dev/null || true
  wp language core update --allow-root 2>/dev/null || true
  wp language plugin update --all --allow-root 2>/dev/null || true
  wp language theme update --all --allow-root 2>/dev/null || true
fi
rm -rf "$PUB/wp-content/cache/"* 2>/dev/null || true
touch "$PUB/index.php" "$PUB/wp/index.php" 2>/dev/null || true

# === Health-check ===
sleep 3
echo "🔎 Health-check..."

check_url() {
  local url="$1" name="$2"
  local code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$url" || echo "000")
  [ "$code" = "200" ] || { echo "❌ $name: HTTP $code"; return 1; }
  echo "✅ $name: HTTP 200"
}

check_url "https://woo2iiko.rwsite.ru/" "Главная"
check_url "https://woo2iiko.rwsite.ru/docs/" "Документация"
check_url "https://woo2iiko.rwsite.ru/wp/wp-login.php" "WordPress"

DOCS=$(curl -s --max-time 10 "https://woo2iiko.rwsite.ru/docs/install/purchase" || true)
echo "$DOCS" | grep -q "<title>Покупка лицензии — woo2iiko</title>" || {
  echo "❌ V2 docs: /docs/install/purchase wrong title"; exit 1;
}
echo "✅ V2 docs: /docs/install/purchase OK"

DOCS_ROOT=$(curl -s --max-time 10 "https://woo2iiko.rwsite.ru/docs/" || true)
echo "$DOCS_ROOT" | grep -q "<title>Документация Woo2iiko — woo2iiko</title>" || {
  echo "❌ V2 docs root wrong title"; exit 1;
}
echo "✅ V2 docs root OK"

CTYPE=$(curl -sI --max-time 10 "https://woo2iiko.rwsite.ru/screenshots/import.jpeg" 
  | awk -F': ' 'tolower($1) == "content-type" {print $2}' | tr -d 'r' | head -1)
[ "$CTYPE" = "image/jpeg" ] || {
  echo "❌ Screenshots: '$CTYPE' (expected image/jpeg)"; exit 1;
}
echo "✅ Screenshots: image/jpeg"

echo "$COMMIT_SHA" > "$BACKUP_DIR/.last-successful-deploy"
echo "✅ Deployed: $COMMIT_SHA"

trap - EXIT
exit 0

Грабли, на которые наступают почти все

Проблемы с которыми я столкнулся и нюансы shred хостинга.

1. chroot SFTP на Beget

Симптом: архив загружен, но скрипт не находит его по пути /tmp/deploy-new.tar.gz.

Причина: SFTP на Beget работает в chroot. target: "/tmp/..." в SFTP маппится в /home/b/<user>/tmp/..., а в обычной SSH-сессии /tmp — реальный системный /tmp. Пути не совпадают. То же самое с ~.

Решение: Не используйте appleboy/scp-action (SFTP). Используйте sshpass + rsync — они работают через SSH-протокол напрямую, без SFTP-подсистемы:

sshpass -e rsync -avz --progress 
  -e "ssh -o StrictHostKeyChecking=no -p $DEPLOY_PORT" 
  deploy-package.tar.gz 
  "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/.deploy-new.tar.gz"

Пароль передаётся через SSHPASS env, чтобы не светиться в ps.

Проверка после загрузки: wc -c локального и удалённого файла должны совпасть. Если нет — fail fast, не доходя до деплоя.

2. rsync -a падает на chgrp

Симптом:

rsync: chgrp "/home/b/.../public_html/." failed: Operation not permitted (1)
rsync error: some files/attrs were not transferred (code 23)

Деплой падает, и rollback тоже падает с той же ошибкой.

Причина: rsync -a = rsync -rlptgoD. На shared hosting нет прав на chgrpchown/chmod для чужих файлов). rsync пытается сохранить оригинальные атрибуты — fail.

Решение: rsync -rlD --no-group --no-perms --no-owner вместо -a. Это -a минус -pgo. umask при создании файлов задаст разумные права автоматически.

Применить ко всем трём rsync-вызовам: backup, swap, rollback.

Что не делать: Не вызывайте find ... -exec chmod 644 {} ; после rsync. На Beget 0600 — намеренная защита от других пользователей хостинга. Apache работает от вашего пользователя, читает 0600 нормально. chmod 644 сломает изоляцию и не решит проблему.

3. Права в архиве

tar -xzf распаковывает файлы с правами 0600 (только владелец). Apache на Beget от вашего пользователя — читает нормально. Но если Nginx проксирует с других IP — может не отдавать Content-Type (HTTP 200 с пустым телом).

Решение: Задать права до архивации:

find deploy-package -type d -exec chmod 755 {} ;
find deploy-package -type f -exec chmod 644 {} ;
tar -czf deploy-package.tar.gz -C deploy-package .

На сервере tar может перезаписать права на 0600 из-за политики безопасности. Поэтому --no-perms в rsync (см. граблю №2).

4. Проверка статики awk -F': ' с лишним двоеточием

Симптом: health-check на статику падает:

❌ Screenshots content-type: '' (expected image/jpeg)

curl с обычного компа возвращает image/jpeg нормально.

Причина: В коде было:

awk -F': ' 'tolower($1) == "content-type:" {print $2}'

-F': ' уже отрезал двоеточие с пробелом. Для строки content-type: image/jpeg:

  • $1 = "content-type" (без двоеточия)
  • $2 = "image/jpeg"

Сравнение == "content-type:" с двоеточием никогда не срабатывает. Парсер всегда возвращал пустую строку.

Решение:

awk -F': ' 'tolower($1) == "content-type" {print $2}'   # без ':'

Или надёжнее — curl сам умеет:

CTYPE=$(curl -sI --max-time 10 -o /dev/null -w '%{content_type}' "https://...")

Атомарный swap

Никогда не меняй public_html/ напрямую. Вместо этого:

  1. Создать public_html/.new-<TIMESTAMP>/ (скрытая директория).
  2. Распаковать в неё архив.
  3. Проверить критичные файлы (index.php, landing/dist/index.html, landing/dist/docs/index.html).
  4. rsync --delete из .new-<ts>/ в public_html/.
  5. Удалить .new-<ts>/.

Только теперь public_html/ отдаёт новую версию.

Почему это работает:

  • Проверка критичных файлов до замены. Если архив битый — fail до того, как сайт что-то заметит.
  • --delete убирает файлы, которых больше нет в новой версии. Без него старые JS-бандлы и CSS накапливались бы.
  • set -e + trap гарантируют: при любой ошибке — cleanup и rollback.

Время недоступности: При rsync nginx продолжает отдавать старую версию, пока новые файлы пишутся. После завершения rsync — отдаёт новую. Между ними — десятые доли секунды на смену inode-ов. Незаметно для пользователя.


Auto-rollback

trap ловит EXIT — любой выход из скрипта. Если код возврата не 0, запускается rollback из последнего backups/release-*/.

trap - EXIT в конце скрипта (после успешного health-check) убирает ловушку, чтобы при exit 0 rollback не сработал.

Что ловится:

  • tar -xzf упал (битый архив).
  • Проверка критичных файлов не прошла.
  • rsync упал.
  • Health-check: HTTP не 200.
  • Content-Type не тот.
  • Любая команда с set -e.

Что не ловится (и правильно):

  • wp cache flush — обёрнут в || true, чтобы не валить деплой на проблемах с WP-CLI.
  • Команды очистки кэша — || true.
  • rsync rollback failed|| echo без exit-кода.

Cleanup-операции не должны мешать деплою.


Health-check

Шесть проверок после деплоя. Если любая падает — exit 1trap → rollback.

  1. Главная страница — HTTP 200.
  2. Документация — HTTP 200.
  3. WordPress login — HTTP 200.
  4. V2-документация по title (<title>Покупка лицензии</title>).
  5. V2-документация root по title.
  6. Статика — Content-Type: image/jpeg.

Почему именно эти:

  • Три точки входа — главная, /docs/, /wp-login.php. Если хоть одна 200 — сайт жив.
  • V2-titles ловят случаи, когда отдаётся 200, но старая V1-сборка. На VitePress cleanUrls это бывает, если nginx проксирует запрос до rewrite.
  • Content-Type ловит случаи, когда nginx отдаёт HTML вместо изображения (например, при ошибке symlink).

sleep 3 перед health-check — Apache может 1–2 секунды кэшировать ответы на старые inode-ы.


Telegram-уведомления

- name: Telegram notification
  uses: appleboy/telegram-action@v0.1.0
  with:
    to: ${{ secrets.TELEGRAM_CHAT_ID }}
    token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
    message: |
      🚀 Woo2iiko Deploy

      ${{ needs.build-and-deploy.result == 'success' && '✅ Успешно' || '❌ Провал — выполнен auto-rollback' }}

      📝 Коммит: `${{ github.sha }}`
      👤 Автор: ${{ github.actor }}
      🌿 Ветка: ${{ github.ref_name }}
  continue-on-error: true
  • if: always() — уведомление приходит в любом исходе.
  • continue-on-error: true — если Telegram недоступен, деплой не искажается.
  • Chat ID для приватных чатов включает минус: -1001234567890.

Concurrency

concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: true

Если запушили два коммита подряд в master, второй отменит первый и пойдёт по свежему коду. Без этого — race condition: оба деплоя пишут в backups/release-*, оба делают swap, второй затирает первый.


Безопасность

  • permissions: contents: read — минимум прав.
  • SSHPASS env — пароль не светится в логах и ps.
  • continue-on-error: true на Telegram — недоступность Telegram не валит деплой.
  • -rlD --no-perms --no-owner --no-group в rsync — не трогаем чужие права на shared hosting.

Что улучшить

  • Артефакты через actions/upload-artifact — ручной откат на любой SHA без пересборки.
  • E2E smoke-тесты (Playwright): пройти по главной, нажать «Купить», проверить checkout.
  • Параллельные health-check — сейчас последовательно, можно в фоне.
  • Shellcheck для deploy-remote.sh в CI.
  • Уведомление о начале деплоя — приходит ещё до окончания.

Чеклист перед первым деплоем

  • Секреты в GitHub: BEGET_HOST, BEGET_USERNAME, BEGET_PASSWORD, BEGET_PORT, TELEGRAM_CHAT_ID, TELEGRAM_BOT_TOKEN.
  • DEPLOY_PATH в workflow совпадает с реальным путём на сервере.
  • wp-config.php уже лежит на сервере в public_html/.
  • SSH-доступ работает: ssh $BEGET_USERNAME@$BEGET_HOST заходит по паролю.
  • tar, rsync, curl есть на сервере (на Beget — есть).
  • Тестовый деплой через workflow_dispatch прошёл успешно.
  • Telegram-сообщение пришло.
  • После деплоя ls -la backups/ содержит release-<ts>/.
  • Сайт открывается, /docs/ отдаёт V2, статика с правильным Content-Type.

Полезные команды на сервере

# Список бэкапов
ls -la /home/b/<user>/<site>/backups/

# Маркер последнего успешного деплоя
cat /home/b/<user>/<site>/backups/.last-successful-deploy

# Симлинк /docs/
readlink /home/b/<user>/<site>/public_html/docs

# Тест curl с сервера
curl -sI https://example.com/screenshots/import.jpeg

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *