Deploy test site / deploy (push) Canceled after 0s
site/ builds the internal test site: public/ served as is (landing page, robots.txt, static sites) plus demo pages copied out of docs/ (the plant operation page at /demos/plant-operation/). build.sh fails on dead internal links and stamps version.txt. .gitea/workflows/deploy-site.yml rsyncs site/dist to the buildfor.life web server with the blog's two-directory rename swap, checks version.txt on the server, purges Cloudflare when a token is set, and checks the public URL. Secret names match the blog's workflow. site/Caddyfile is the virtual host to append on s1 (noindex everywhere, Cloudflare-only like the blog apex, www redirect). site/README.md has the one-time setup: DNS, Caddy, web root, secrets, runner.
133 lines
5.6 KiB
YAML
133 lines
5.6 KiB
YAML
name: Deploy test site
|
|
|
|
# Builds site/dist (site/public/ plus demo pages copied out of docs/, see
|
|
# site/build.sh) and publishes it to https://cnxvinyl.lol/ on the
|
|
# buildfor.life web server (s1), the same Caddy that serves the blog.
|
|
# One-time setup (DNS, Caddy vhost, secrets, deploy key): site/README.md.
|
|
#
|
|
# Secrets (repo or user level):
|
|
# VPS_HOST, VPS_USER, VPS_SSH_KEY required; same server as the blog
|
|
# VPS_PORT optional, default 22
|
|
# VPS_KNOWN_HOSTS optional pinned host key line(s);
|
|
# without it the key is scanned per run
|
|
# CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_TOKEN
|
|
# optional; cache purge for cnxvinyl.lol
|
|
# (token needs Zone > Cache Purge)
|
|
|
|
on:
|
|
push:
|
|
branches: [master]
|
|
paths:
|
|
- "site/**"
|
|
- "docs/**/*.html"
|
|
- ".gitea/workflows/deploy-site.yml"
|
|
workflow_dispatch:
|
|
|
|
# A newer push supersedes a running deploy. Safe to cancel mid-run: rsync
|
|
# writes the non-live directory and the rename swap happens last.
|
|
concurrency:
|
|
group: deploy-cnxvinyl-lol
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
VPS_HOST: ${{ secrets.VPS_HOST }}
|
|
VPS_USER: ${{ secrets.VPS_USER }}
|
|
VPS_PORT: ${{ secrets.VPS_PORT }}
|
|
SITE_DIR: /var/www/cnxvinyl.lol
|
|
SITE_URL: https://cnxvinyl.lol
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Build site/dist
|
|
run: bash site/build.sh
|
|
|
|
- name: Set up SSH
|
|
env:
|
|
VPS_SSH_KEY: ${{ secrets.VPS_SSH_KEY }}
|
|
VPS_KNOWN_HOSTS: ${{ secrets.VPS_KNOWN_HOSTS }}
|
|
run: |
|
|
if [ -z "$VPS_HOST" ] || [ -z "$VPS_USER" ] || [ -z "$VPS_SSH_KEY" ]; then
|
|
echo "::error::VPS_HOST, VPS_USER and VPS_SSH_KEY must be set as Actions secrets (see site/README.md)"
|
|
exit 1
|
|
fi
|
|
port=${VPS_PORT:-22}
|
|
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
|
printf '%s\n' "$VPS_SSH_KEY" > ~/.ssh/deploy_key
|
|
chmod 600 ~/.ssh/deploy_key
|
|
if [ -n "$VPS_KNOWN_HOSTS" ]; then
|
|
printf '%s\n' "$VPS_KNOWN_HOSTS" >> ~/.ssh/known_hosts
|
|
else
|
|
for i in 1 2 3; do
|
|
ssh-keyscan -T 10 -p "$port" "$VPS_HOST" > ~/.ssh/keyscan.out && [ -s ~/.ssh/keyscan.out ] && break
|
|
echo "ssh-keyscan attempt $i failed; retrying in 15 s" >&2
|
|
sleep 15
|
|
done
|
|
if [ ! -s ~/.ssh/keyscan.out ]; then
|
|
echo "::error::ssh-keyscan could not reach $VPS_HOST:$port (server down or runner IP blocked?)"
|
|
exit 1
|
|
fi
|
|
cat ~/.ssh/keyscan.out >> ~/.ssh/known_hosts
|
|
fi
|
|
{
|
|
echo "Host deploy"
|
|
echo " HostName $VPS_HOST"
|
|
echo " User $VPS_USER"
|
|
echo " Port $port"
|
|
echo " IdentityFile ~/.ssh/deploy_key"
|
|
echo " IdentitiesOnly yes"
|
|
echo " BatchMode yes"
|
|
} > ~/.ssh/config
|
|
|
|
- name: Ensure rsync is available
|
|
run: command -v rsync || (sudo apt-get update && sudo apt-get install -y rsync) || (apt-get update && apt-get install -y rsync)
|
|
|
|
- name: Deploy (rsync to the idle copy, then rename swap)
|
|
# Two persistent directories alternate as live and previous, as for
|
|
# the blog: rsync only ever writes the one that is not live, and the
|
|
# swap is three same-filesystem renames, so Caddy never serves a
|
|
# half-written tree. The previous build stays on disk for rollback.
|
|
run: |
|
|
# SC2029: the paths are meant to expand on the runner side
|
|
# shellcheck disable=SC2029
|
|
ssh deploy "mkdir -p '$SITE_DIR' '$SITE_DIR-prev' && rm -rf '$SITE_DIR-tmp'"
|
|
rsync -az --delete site/dist/ "deploy:$SITE_DIR-prev/"
|
|
# shellcheck disable=SC2029
|
|
ssh deploy "mv '$SITE_DIR' '$SITE_DIR-tmp' && mv '$SITE_DIR-prev' '$SITE_DIR' && mv '$SITE_DIR-tmp' '$SITE_DIR-prev'"
|
|
# shellcheck disable=SC2029
|
|
live=$(ssh deploy "cat '$SITE_DIR/version.txt'")
|
|
echo "live on server: $live"
|
|
[ "$live" = "$(cat site/dist/version.txt)" ] || { echo "::error::server holds $live, expected $(cat site/dist/version.txt)"; exit 1; }
|
|
|
|
- name: Purge Cloudflare cache
|
|
env:
|
|
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
run: |
|
|
if [ -z "$CLOUDFLARE_ZONE_ID" ] || [ -z "$CLOUDFLARE_API_TOKEN" ]; then
|
|
echo "::warning::CLOUDFLARE_ZONE_ID / CLOUDFLARE_API_TOKEN not set; skipping cache purge"
|
|
exit 0
|
|
fi
|
|
response=$(curl -sS -X POST \
|
|
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/purge_cache" \
|
|
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
--data '{"purge_everything":true}')
|
|
echo "$response"
|
|
echo "$response" | grep -q '"success":\s*true' || { echo "::error::Cloudflare cache purge failed"; exit 1; }
|
|
|
|
- name: Check the public URL
|
|
# Warning only: DNS, Cloudflare or a basic_auth login can make this
|
|
# fail while the files on the server are already correct.
|
|
run: |
|
|
expected=$(cat site/dist/version.txt)
|
|
got=$(curl -sS --max-time 20 "$SITE_URL/version.txt?nocache=$expected" || true)
|
|
if [ "$got" = "$expected" ]; then
|
|
echo "$SITE_URL serves $expected"
|
|
else
|
|
echo "::warning::$SITE_URL/version.txt returned '${got:0:80}', expected $expected"
|
|
fi
|