diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml new file mode 100644 index 0000000..e53d63b --- /dev/null +++ b/.github/workflows/site.yml @@ -0,0 +1,58 @@ +name: Site + +# Tests every PR; every push to main deploys to tuejon and verifies the live site. +# The deploy is a restricted `git pull` of a static docroot: no build, no restart. +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: + contents: read + +jobs: + test: + # The runner lives on VD-FW next to production keys: never run fork code there. + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository + runs-on: [self-hosted, Linux, X64, tuejon-ci] + steps: + - uses: actions/checkout@v4 + - name: Contract tests + run: node --test 'tests/*.test.mjs' + - name: Legal pages are present + run: | + test -f imprint.html + test -f privacy.html + - name: Generated German page and WebMCP manifest are current + run: | + python3 build-de.py + node build-manifest.mjs + git diff --exit-code + + deploy: + needs: test + if: github.event_name == 'push' + runs-on: [self-hosted, Linux, X64, tuejon-ci] + concurrency: + group: webmcpify-at-deploy + cancel-in-progress: false + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Pull main into /opt/webmcpify on tuejon + # The key's forced command runs `git pull --ff-only` and prints the deployed HEAD. + run: | + deployed="$(ssh -i ~/.ssh/webmcpify-at-deploy -o IdentitiesOnly=yes -o BatchMode=yes tj@tuejon.at)" + echo "deployed $deployed for $GITHUB_SHA" + git fetch -q origin main + git merge-base --is-ancestor "$GITHUB_SHA" "$deployed" + - name: Verify the public site + run: | + curl -fsSI https://webmcpify.at/ | grep -i '^strict-transport-security:' + for path in / /de/ /docs/ /docs/site-tools/ /webmcp-agent-skill/ /imprint.html /privacy.html /llms.txt /sitemap.xml /.well-known/webmcp; do + code="$(curl -s -o /dev/null -w '%{http_code}' "https://webmcpify.at$path")" + echo "$code $path" + test "$code" = 200 + done diff --git a/README.md b/README.md index d0ad57c..b402a50 100644 --- a/README.md +++ b/README.md @@ -60,16 +60,17 @@ The site is itself agent-ready, in the three layers a WebMCP integration can hav ## Deploy -The site is served directly from a git clone on the host — no build, no pipeline: +Merging to `main` deploys automatically. `.github/workflows/site.yml` runs the contract +tests and the generated-file check on every PR, then on every push to `main`: -- Host: `tuejon.at`, docroot `/opt/webmcpify` (clone of `main`), nginx vhost - `/etc/nginx/sites-available/25-webmcpify.conf` (TLS via Let's Encrypt/certbot, - http→https and www→apex 301s, HSTS). -- Redeploy after merging to `main`: +1. pulls `main` into the docroot on the host through a restricted deploy key, whose + forced command only runs `git pull --ff-only` and prints the deployed `HEAD`; +2. fails unless the deployed commit contains the pushed one; +3. verifies `https://webmcpify.at/` (200 + HSTS) and every public route. - ```bash - ssh tj@tuejon.at 'cd /opt/webmcpify && git pull' - ``` +Host: `tuejon.at`, docroot `/opt/webmcpify` (clone of `main`, no build step), nginx vhost +`/etc/nginx/sites-available/25-webmcpify.conf` (TLS via Let's Encrypt/certbot, +http→https and www→apex 301s, HSTS). Jobs run on the repo's self-hosted `tuejon-ci` +runner and skip pull requests from forks. -- Verify: `curl -sI https://webmcpify.at/` (200, `strict-transport-security` present) - and spot-check changed pages. +Manual fallback (same effect as the pipeline): `ssh tj@tuejon.at 'cd /opt/webmcpify && git pull --ff-only'`.