Merge pull request #5 from kristianwind/docs-update-smoke
CI / Static checks (push) Successful in 12s
CI / WordPress latest + WooCommerce latest on PHP 8.3 (push) Successful in 52s

Document the update smoke test and why the download address is rebuilt
This commit is contained in:
github-actions[bot]
2026-09-16 08:54:23 +00:00
committed by GitHub
2 changed files with 58 additions and 0 deletions
+9
View File
@@ -72,6 +72,8 @@ it cannot.
that run is green.
- `release.yml` — tag, test, build, publish, then read the feed back with no
credentials to prove a shop can download it.
- `ci/update-smoke.sh` — install the previous version into a throwaway shop and
let WordPress update it by itself. The only check that proves the whole chain.
- `ci/workflows-in-sync.sh` — fails when the two workflow roots drift, which is
the only way to notice that a workflow stopped running on Gitea.
@@ -88,6 +90,13 @@ and a release with no zip attached both produce a page that looks perfect to
anyone logged in, and an update that no shop is ever offered. A check that
sends a token is the false positive that hides exactly that.
**A forge's own download URL is not the address your users can reach.**
`browser_download_url` is built from the forge's configured root URL;
`gitea.nolimit.dk` reports `http://57.129.89.117:25001/...` there. The updater
rebuilds the address from the `Update URI` header and keeps only the path. This
one hides particularly well: a CI job on the same network reaches that raw IP,
so following the reported URL goes green and only a real client outside fails.
## Candidates already in the fleet
`spool` and `subscriptions` are both WooCommerce plugins with some of this
+49
View File
@@ -16,6 +16,8 @@ Three questions, answered by three things in this repository.
ci/static.sh # syntax, hygiene, versions, headers — seconds
ci/site-test.sh # a real WordPress + WooCommerce — a few minutes
ci/build-zip.sh # what a shop actually installs
FROM_VERSION=1.4.0 \
ci/update-smoke.sh # let WordPress update itself from the live feed
```
`ci/site-test.sh` takes three knobs:
@@ -110,6 +112,31 @@ Two things in there are guards against bugs that are invisible without them:
---
## Proving an update actually arrives
`ci/update-smoke.sh` installs the **previous** version into a throwaway shop
and then gets out of the way: WordPress's own update check finds the new one,
downloads it and installs it.
```sh
FROM_VERSION=1.4.0 ci/update-smoke.sh
```
Nothing else can stand in for it. Every piece of the chain can be individually
correct and the update still not arrive:
- a package URL on an address the shop cannot reach,
- a zip whose top-level directory is not the plugin slug — which installs a
*second* copy beside the first and leaves the old one active,
- a version WordPress does not consider newer than what is installed.
It checks all three, plus that the plugin is still active afterwards and that
WordPress stops offering the update once it is applied.
Run it after a release.
---
## How the automatic updates work
The plugin header carries:
@@ -149,6 +176,28 @@ If you would rather keep the source private, create a separate public repo that
holds only releases and point `Update URI` at that instead — the updater
derives everything else from the header, so that is a one-line change.
### The download address is rebuilt, not taken
The updater ignores the `browser_download_url` the API hands back and builds
the address from the `Update URI` header instead.
That field is filled from the forge's own configured root URL, which is not
necessarily the address the outside world reaches it on. `gitea.nolimit.dk`
returns `http://57.129.89.117:25001/...` there — plain HTTP, a raw IP, a
non-standard port, and around the Cloudflare tunnel in front of it. A shop
handed that would be downloading plugin code over an unencrypted connection to
an address nothing guarantees will keep working.
The paths Gitea reports are correct; only the host is wrong. So the host comes
from the header — the one place that already had to be right for any of this
to work — and the path is kept.
This is worth knowing because of *how* it hides: a CI job on the same network
reaches that raw IP perfectly well, so a verification step that follows the
reported URL goes green. Only a real client outside fails. The release check
therefore downloads the address **the plugin builds**, and opens the zip to
confirm it is the plugin rather than an error page with a 200 on it.
### It reports nothing
The update check sends a plain unauthenticated GET for a file every visitor to