From 6fa6886acb6928151b7d4e6d1e2f78b2038c2b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20T=C3=BCchler?= Date: Sat, 19 Sep 2026 18:39:43 +0200 Subject: [PATCH 1/2] ci: test every PR and deploy main to webmcpify.at automatically Run the contract tests and the generated-file check on the repo's self-hosted runner (fork PRs are skipped), then on every push to main pull the docroot on the host through a restricted deploy key and verify the live site. --- .github/workflows/site.yml | 54 ++++++++++++++++++++++++++++++++++++++ README.md | 21 ++++++++------- 2 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/site.yml diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml new file mode 100644 index 0000000..0482e72 --- /dev/null +++ b/.github/workflows/site.yml @@ -0,0 +1,54 @@ +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: 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/ /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'`. From 19616d469c6ff1d9aaa8aad607b20ccec8c4f064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20T=C3=BCchler?= Date: Sat, 19 Sep 2026 17:14:17 +0000 Subject: [PATCH 2/2] ci: cover legal routes in deploy checks --- .github/workflows/site.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 0482e72..e53d63b 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -20,6 +20,10 @@ jobs: - 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 @@ -47,7 +51,7 @@ jobs: - 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/ /llms.txt /sitemap.xml /.well-known/webmcp; do + 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