GitHub

The website

firmwareliberator.com is the repository’s README and docs as web pages, built with Astro and served by a Cloudflare Worker that holds nothing but the built files. The pages are written once, as the repository’s Markdown; this folder only turns them into a site.

What is here

src/site.mjs Which Markdown files are pages (PAGES: every one git tracks), the address of each, and how a link between them is rewritten
src/content.config.mjs Those files as Astro’s pages collection, read from the repository root
src/pages/[...slug].astro One page per file, titled by its first heading
src/pages/404.astro What an address with no page answers
src/layouts/Page.astro Every page’s frame: GitHub’s Markdown styling, dark unless the reader picks light, the GitHub button, Cloudflare Web Analytics (no cookies)
public/ Copied as they are: the icons and stylesheets, robots.txt, _redirects
astro.config.mjs The build: Markdown through Astro’s Sätteri with the link rewriting, pictures copied beside their pages, a sitemap
test/ The link rewriting’s tests (npm test, Node’s own runner)
wrangler.jsonc The Worker: the files in dist/, the 404 page, and both names pointed at it

Addresses

Every Markdown file git tracks is a page, but CLAUDE.md and .github/’s templates. A page’s address is its path without .md: docs/emulator.md is /docs/emulator, a folder’s README.md is the folder (/devices/sonoff-s61), and the README is /. A link to another page goes to its address; a link to anything else in the repository - the tool, a config, LICENSE - goes to it on GitHub; pictures under docs/ are served beside the pages. The Worker also answers /docs/emulator.html (by redirect), the form the old GitHub Pages site’s addresses took.

public/_redirects sends /liberate.sh and /liberate.ps1 to the latest release’s, with a 302 so no browser keeps it past the next release. tests/repo/test_site.py checks each is a file every release carries.

Building it

Node 22 or newer:

cd website
npm ci --ignore-scripts
npm test
npm run build          # into dist/
npx astro preview      # dist/ at http://localhost:4321

Packages come only from package-lock.json, by hash, with no install scripts run. Dependabot moves them, a week after each release (.github/dependabot.yml), and the dependabot-merge workflow merges its pull request once it has been open a week with its checks passed.

Deploying it

.github/workflows/website.yml tests and builds it for each pull request, and on each push to main deploys it with wrangler from the website environment, which only main can use (tools/repo/github_settings.py). Its secret CLOUDFLARE_API_TOKEN is one Cloudflare API token, with these permissions:

A Worker can only be picked once it exists, so the very first one was uploaded by hand with an account-wide token, with no domain. A domain with DNS records of its own stops the deploy (Cloudflare’s code 100117): delete them, and wrangler makes the Worker’s.

Set it with gh secret set CLOUDFLARE_API_TOKEN --env website -R adamsthws/firmware-liberator.

The zone’s settings

What the zone is set to beyond the Worker - www. sent on to the bare domain, HTTPS only, TLS 1.2 or newer - is written down, with why, in tools/repo/cloudflare_settings.py. The cloudflare-settings workflow sets it on each push to main that changes it, and checks it weekly, failing if it was changed in the dashboard. Locally, with the token in CLOUDFLARE_API_TOKEN (the environment or .env), python3 tools/repo/cloudflare_settings.py says what differs, and ... apply sets it.