Задача
СкопированоПочти всегда в проекте существует задача смотреть на текущие сборки из неосновной ветки репозитория. В крупных проектах есть инженеры, которые занимаются такими задачами. А что, если вы один на проекте? А что, если вы работаете в опенсорс? А что, если у вас совсем маленькая команда и нет возможности нанимать инженеров с нужным опытом?
Есть несколько готовых сервисов. Если вы работаете над статическим сайтом, можно использовать GitHub Pages, Netlify или Surge. Но это не всегда удобно по целому ряду причин. А вот если есть свой выделенный сервер, возможности резко увеличиваются. Почему бы и не использовать уже имеющийся у вас ресурс?
Готовое решение
СкопированоМоё решение основано на использовании веб-сервера Nginx, зато кроме него ничего не понадобится. С другой стороны, вы всегда сможете приспособить моё решение под свои нужды.
Итак, давайте конкретизируем ситуацию. Например, у вас есть репозиторий на GitHub _https://github.com/success-org/success-net_ с проектом вашего статического сайта success.net на движке 11ty, над которым активно работает команда разработчиков. На GitHub удобно использовать GitHub Actions для сборки сайта, а Nginx используется на сервере в качестве веб-сервера. Задача состоит в том, чтобы смотреть превью сайта с новыми материалами, фичами или исправленными багами из пулреквестов. Превью будут доступны по адресу https, где вместо 0000 будет номер пулреквеста. Развёртывание сайта будет проходить от имени пользователя deploy на сервере, приватный ключ от которого нужно предварительно добавить в список переменных репозитория под именем DEPLOY. Соответствующая страница настроек будет доступна в нашем примере по ссылке _https://github.com/success-org/success-net/settings/secrets/actions_.
Создайте в директории .github/workflows новый файл конфигурации для GitHub Actions. Назовём этот файл pr-preview.yml:
name: PR Previewon: pull_request:jobs: pr-preview: runs-on: ubuntu-latest env: DEPLOY_DOMAIN: https://${{ github.event.pull_request.number }}.dev.success.net GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} PATH_TO_CONTENT: ./content steps: - name: Загрузка проекта uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 - name: Кэширование модулей uses: actions/cache@v3 env: cache-name: cache-node-modules with: path: ~/.npm key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-build-${{ env.cache-name }}- ${{ runner.os }}-build- ${{ runner.os }}- - name: Получение идентификатора id: check run: | check_suite_url=$(curl -s -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }} | jq -r '.check_suite_url') check_run_id=$(curl -s -H "Accept: application/vnd.github.v3+json" $check_suite_url/check-runs | jq '.check_runs[] | .id') echo "check_id=$check_run_id" >> $GITHUB_OUTPUT - name: Установка модулей run: npm ci - name: Сообщение о начале публикации превью uses: hasura/comment-progress@v2.2.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} repository: ${{ github.repository }} number: ${{ github.event.pull_request.number }} id: preview-${{ github.event.pull_request.number }} message: "Идёт сборка и публикация превью... [Подробнее](https://github.com/${{ github.repository }}/runs/${{ steps.check.outputs.check_id }}?check_suite_focus=true)" recreate: true - name: Установка ключа для пользователя run: | set -eu mkdir "$HOME/.ssh" echo "${{ secrets.DEPLOY_KEY }}" > "$HOME/.ssh/success_deploy" chmod 600 "$HOME/.ssh/success_deploy" - name: Сборка и публикация сайта id: build-preview continue-on-error: true run: | cp .env.success .env npm run preview ssh -i $HOME/.ssh/success_deploy -o StrictHostKeyChecking=no deploy@dev.success.net mkdir -p /web/sites/dev.success.net/content/${{ github.event.pull_request.number }} cd dist && rsync -e "ssh -i $HOME/.ssh/success_deploy -o StrictHostKeyChecking=no" --archive --progress --compress --delete . deploy@dev.success.net:/web/sites/dev.success.net/content/${{ github.event.pull_request.number }} echo "Ссылка на превью — ${{ env.DEPLOY_DOMAIN }}" echo -e "${{ steps.links.outputs.list }}" - name: Сообщение о неудаче публикации превью uses: hasura/comment-progress@v2.2.0 if: failure() with: github-token: ${{ secrets.GITHUB_TOKEN }} repository: ${{ github.repository }} number: ${{ github.event.pull_request.number }} id: preview-${{ github.event.pull_request.number }} message: "Превью контента из ${{ github.event.after }} не опубликовано. Ошибка сборки или публикации. [Подробнее](https://github.com/${{ github.repository }}/runs/${{ steps.check.outputs.check_id }}?check_suite_focus=true)" fail: true recreate: true - name: Сообщение об успехе публикации превью uses: hasura/comment-progress@v2.2.0 if: success() with: github-token: ${{ secrets.GITHUB_TOKEN }} repository: ${{ github.repository }} number: ${{ github.event.pull_request.number }} id: preview-${{ github.event.pull_request.number }} message: '[Превью контента](${{ env.DEPLOY_DOMAIN }}) из ${{ github.event.after }} опубликовано' recreate: true
name: PR Preview
on:
pull_request:
jobs:
pr-preview:
runs-on: ubuntu-latest
env:
DEPLOY_DOMAIN: https://${{ github.event.pull_request.number }}.dev.success.net
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PATH_TO_CONTENT: ./content
steps:
- name: Загрузка проекта
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Кэширование модулей
uses: actions/cache@v3
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Получение идентификатора
id: check
run: |
check_suite_url=$(curl -s -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }} | jq -r '.check_suite_url')
check_run_id=$(curl -s -H "Accept: application/vnd.github.v3+json" $check_suite_url/check-runs | jq '.check_runs[] | .id')
echo "check_id=$check_run_id" >> $GITHUB_OUTPUT
- name: Установка модулей
run: npm ci
- name: Сообщение о начале публикации превью
uses: hasura/comment-progress@v2.2.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
number: ${{ github.event.pull_request.number }}
id: preview-${{ github.event.pull_request.number }}
message: "Идёт сборка и публикация превью... [Подробнее](https://github.com/${{ github.repository }}/runs/${{ steps.check.outputs.check_id }}?check_suite_focus=true)"
recreate: true
- name: Установка ключа для пользователя
run: |
set -eu
mkdir "$HOME/.ssh"
echo "${{ secrets.DEPLOY_KEY }}" > "$HOME/.ssh/success_deploy"
chmod 600 "$HOME/.ssh/success_deploy"
- name: Сборка и публикация сайта
id: build-preview
continue-on-error: true
run: |
cp .env.success .env
npm run preview
ssh -i $HOME/.ssh/success_deploy -o StrictHostKeyChecking=no deploy@dev.success.net mkdir -p /web/sites/dev.success.net/content/${{ github.event.pull_request.number }}
cd dist && rsync -e "ssh -i $HOME/.ssh/success_deploy -o StrictHostKeyChecking=no" --archive --progress --compress --delete . deploy@dev.success.net:/web/sites/dev.success.net/content/${{ github.event.pull_request.number }}
echo "Ссылка на превью — ${{ env.DEPLOY_DOMAIN }}"
echo -e "${{ steps.links.outputs.list }}"
- name: Сообщение о неудаче публикации превью
uses: hasura/comment-progress@v2.2.0
if: failure()
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
number: ${{ github.event.pull_request.number }}
id: preview-${{ github.event.pull_request.number }}
message: "Превью контента из ${{ github.event.after }} не опубликовано. Ошибка сборки или публикации. [Подробнее](https://github.com/${{ github.repository }}/runs/${{ steps.check.outputs.check_id }}?check_suite_focus=true)"
fail: true
recreate: true
- name: Сообщение об успехе публикации превью
uses: hasura/comment-progress@v2.2.0
if: success()
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
number: ${{ github.event.pull_request.number }}
id: preview-${{ github.event.pull_request.number }}
message: '[Превью контента](${{ env.DEPLOY_DOMAIN }}) из ${{ github.event.after }} опубликовано'
recreate: true
Готовый GitHub Action hasura/comment-progress позволяет оставлять комментарии к пулреквесту. Проверьте последнюю версию перед формированием файла конфигурации.
Примерный файл конфигурации для Nginx:
server { listen 80; server_name *.dev.success.net; error_log /web/sites/dev.success.net/logs/error.log; access_log /web/sites/dev.success.net/logs/access.log; location / { return 301 https://$http_host$request_uri; }}server { listen 443 ssl http2; server_name *.dev.success.net; brotli on; ssl_certificate /etc/letsencrypt/live/dev.success.net/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/dev.success.net/privkey.pem; if ($host ~* "^([0-9]+)\.dev.success.net$") { set $pr $1; break; } root /web/sites/dev.success.net/$pr; error_log /web/sites/dev.success.net/logs/error.log; access_log /web/sites/dev.success.net/logs/access.log; #index index.html; location / { try_files $uri $uri/ /index.html; }}
server {
listen 80;
server_name *.dev.success.net;
error_log /web/sites/dev.success.net/logs/error.log;
access_log /web/sites/dev.success.net/logs/access.log;
location / {
return 301 https://$http_host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name *.dev.success.net;
brotli on;
ssl_certificate /etc/letsencrypt/live/dev.success.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dev.success.net/privkey.pem;
if ($host ~* "^([0-9]+)\.dev.success.net$") {
set $pr $1;
break;
}
root /web/sites/dev.success.net/$pr;
error_log /web/sites/dev.success.net/logs/error.log;
access_log /web/sites/dev.success.net/logs/access.log;
#index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
Файлы со списками ошибок и удачных сессий сайта хранятся на сервере в директории /web/sites/dev.success.net/logs/. Эталонная сборка сайта из основной ветки хранится в директории /web/sites/success.net/www/. Сборки сайта из пулреквестов хранятся в директориях /web/sites/dev.success.net/0000. В файле конфигурации переменная $pr отвечает за номер пулреквеста.
Поскольку здесь используется много поддоменов, то обычные сертификаты Let’s Encrypt не подходят, надо использовать Wild Card сертификаты. Они создаются следующим способом:
- Выполняется команда
/usr. В ходе выполнения этой команды нужно зайти на DNS-сервер, вписать нужную TXT-запись и нажать клавишу Enter в терминале, что всё готово. В результате будут сгенерированы два файла: fullchain.pem (сертификат) и privkey.pem (приватный ключ)./ bin / certbot certonly - - manual - - preferred - challenges = dns - - email hi@success . net - - agree - tos - d * . success . net - Копируются файлы в нужную директорию. В нашей конфигурации эти файлы хранятся в директории по умолчанию /etc/letsencrypt/live/dev.success.net/.
Очевидно, что такой способ хранения версий сайтов далёк от идеального. Способов оптимизации довольно много, но можно использовать простой — периодически заменять файлы, которые не отличаются от хранящихся в эталонной версии, на символические ссылки. Вот такой скрипт можно хранить в директорию /web/sites/dev.success.net/ под именем symlinks.sh:
# Получаем список поддиректорий с собранными версиями сайтаls -l /web/sites/dev.success.net/pr/ > buffer1.sh# Автозаменой вывода команды `ls -l` трансформируем скрипт, чтобы можно было ходить по директориямsed -r 's/drwxr-xr-x. [0-9]+ deploy deploy [0-9]+ [A-Za-z]+ [ ]*[0-9]+ [0-9]+[:][0-9]+ /cd \/web\/sites\/dev.success.net\/pr\//g' buffer1.sh > buffer2.shrm -f buffer1.sh# Сравниваем файлы с эталонной версией сайта, формируем список кандидатов на символические ссылки, записываем в отдельные скрипты в директориях и в скрипт `pr.sh`sed -r 's/$/ \&\& diff -rqs \/web\/sites\/success.net\/www .\/ | grep "identical" > ln.sh \&\& sed -i "s\/ are identical\/\/g" ln.sh \&\& sed -i "s\/ and\/\/g" ln.sh \&\& sed -i "s\/Files \/ln -sf \/g" ln.sh \&\& sh ln.sh \&\& rm -f ln.sh/g' buffer2.sh > pr.shrm -f buffer2.sh# Выполняем и удаляем после исполнения `pr.sh`sh pr.shrm -f pr.sh
# Получаем список поддиректорий с собранными версиями сайта
ls -l /web/sites/dev.success.net/pr/ > buffer1.sh
# Автозаменой вывода команды `ls -l` трансформируем скрипт, чтобы можно было ходить по директориям
sed -r 's/drwxr-xr-x. [0-9]+ deploy deploy [0-9]+ [A-Za-z]+ [ ]*[0-9]+ [0-9]+[:][0-9]+ /cd \/web\/sites\/dev.success.net\/pr\//g' buffer1.sh > buffer2.sh
rm -f buffer1.sh
# Сравниваем файлы с эталонной версией сайта, формируем список кандидатов на символические ссылки, записываем в отдельные скрипты в директориях и в скрипт `pr.sh`
sed -r 's/$/ \&\& diff -rqs \/web\/sites\/success.net\/www .\/ | grep "identical" > ln.sh \&\& sed -i "s\/ are identical\/\/g" ln.sh \&\& sed -i "s\/ and\/\/g" ln.sh \&\& sed -i "s\/Files \/ln -sf \/g" ln.sh \&\& sh ln.sh \&\& rm -f ln.sh/g' buffer2.sh > pr.sh
rm -f buffer2.sh
# Выполняем и удаляем после исполнения `pr.sh`
sh pr.sh
rm -f pr.sh
Важно выставить необходимые права доступа. В примере предполагается, что владелец всех файлов и поддиректорий в директории /web/sites/ — deploy.
Остаётся сделать запуск скрипта symlinks.sh по расписанию. Например, сделаем так, чтобы это происходило каждое утро в 5:00. Для этого воспользуемся службой cron и создадим файл deploy в директории /etc/cron.d/:
0 5 * * * deploy sh /web/sites/dev.doka.guide/symlinks.sh
0 5 * * * deploy sh /web/sites/dev.doka.guide/symlinks.sh
Получилось несложное решение для реализации превью. Любой из предложенных шагов можно использовать и отдельно, применяйте с умом! 🙃