Hash-pin every external reference in deploy.yml, verified on a real runner (closes #7) #22
@@ -1,52 +1,110 @@
|
||||
name: Build and Deploy to Cloudflare Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- feat/initial-site
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: klakegg/hugo:ext-alpine
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
# Same digest the Dockerfile pins: one pinned base image and the
|
||||
# same dependency list (script/bootstrap) for both the check build
|
||||
# and the deploy build. The one extra thing this job needs on top
|
||||
# of the Dockerfile is the Actions runner's own prerequisites --
|
||||
# see the first step.
|
||||
# alpine 3.21, 2026-02-28
|
||||
image: alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
||||
defaults:
|
||||
run:
|
||||
# The default step shell is bash; this image has only busybox
|
||||
# sh, so say so explicitly rather than rely on a fallback.
|
||||
shell: sh
|
||||
steps:
|
||||
# This image is bare busybox+musl. act_runner executes JavaScript
|
||||
# actions (checkout, upload-artifact) with `node` *inside* the job
|
||||
# container and does not inject one, so node has to exist before
|
||||
# the first `uses:` step -- script/bootstrap runs too late. git is
|
||||
# needed for checkout's `submodules: recursive` (without it
|
||||
# checkout degrades to a tarball download that cannot do
|
||||
# submodules). An inline `run:` needs only a shell, so this step
|
||||
# works on the bare image. These apk packages resolve at run time
|
||||
# and are not hash-pinned; that gap is repo-wide (script/bootstrap
|
||||
# has it too) and is tracked in #19.
|
||||
- name: Install runner prerequisites
|
||||
run: apk add --no-cache nodejs git tar
|
||||
|
||||
- name: Build site
|
||||
run: hugo --minify
|
||||
- name: Checkout
|
||||
# actions/checkout v4.2.2, 2026-02-28
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Archive site
|
||||
run: tar -czf site.tar.gz public
|
||||
- name: Install build dependencies
|
||||
run: script/bootstrap
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: site
|
||||
path: site.tar.gz
|
||||
- name: Build site
|
||||
run: script/test
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
container:
|
||||
image: node:20
|
||||
steps:
|
||||
- name: Download artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: site
|
||||
- name: Archive site
|
||||
run: tar -czf site.tar.gz public
|
||||
|
||||
- name: Extract site
|
||||
run: tar -xzf site.tar.gz
|
||||
# v3, not v4: artifacts v4 is a different wire protocol and this
|
||||
# Gitea Actions instance does not serve it. That is what broke the
|
||||
# deploy in run 25 -- measured by running two otherwise identical
|
||||
# jobs on a branch, one ending in upload-artifact v4 (failed) and
|
||||
# one without that step (passed). Tracked in #20. This SHA is the
|
||||
# exact commit the mutable `@v3` used to resolve to, i.e. the code
|
||||
# that was already deploying this site, now pinned rather than
|
||||
# floating.
|
||||
- name: Upload artifact
|
||||
# actions/upload-artifact v3.2.1, 2026-08-09
|
||||
uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5
|
||||
with:
|
||||
name: site
|
||||
path: site.tar.gz
|
||||
|
||||
- name: Install Wrangler
|
||||
run: npm install -g wrangler
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
# Publishing guard. This job spends CLOUDFLARE_API_TOKEN and creates a
|
||||
# real Cloudflare Pages deployment, so it must never run off main --
|
||||
# not even if a branch is added to the push trigger above, deliberately
|
||||
# or by accident. Costs one line; the build job stays exercisable from
|
||||
# a branch without this job touching anything external.
|
||||
if: github.ref_name == 'main'
|
||||
container:
|
||||
# node 20.20.2-bookworm, 2026-08-09
|
||||
image: node@sha256:8f693eaa7e0a8e71560c9a82b55fd54c2ae920a2ba5d2cde28bac7d1c01c9ba5
|
||||
steps:
|
||||
# Must match the upload-artifact major above -- v4 artifacts and
|
||||
# v3 artifacts are different protocols and do not interoperate.
|
||||
# Like the upload above, this is the exact commit `@v3` used to
|
||||
# resolve to.
|
||||
- name: Download artifact
|
||||
# actions/download-artifact v3.0.2, 2026-08-09
|
||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a
|
||||
with:
|
||||
name: site
|
||||
|
||||
- name: Deploy to Cloudflare Pages
|
||||
run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }}
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
- name: Extract site
|
||||
run: tar -xzf site.tar.gz
|
||||
|
||||
# 4.86.0, not the 4.120.0 that `latest` points at: wrangler
|
||||
# 4.120.0 requires node >= 22 and refuses to start on this
|
||||
# container's node 20. Note that the unpinned `npm install -g
|
||||
# wrangler` this replaces was never installing `latest` either --
|
||||
# npm picks the newest version whose engines the running node
|
||||
# satisfies, which on node 20 is exactly 4.86.0. So this pins the
|
||||
# version that has actually been deploying this site, rather than
|
||||
# silently changing it. Moving the container to node 22 so the
|
||||
# wrangler pin can advance is tracked in #21.
|
||||
- name: Install Wrangler
|
||||
# wrangler 4.86.0, 2026-08-09
|
||||
run: npm install -g wrangler@4.86.0
|
||||
|
||||
- name: Deploy to Cloudflare Pages
|
||||
run: wrangler pages deploy public --project-name=lora-vegas --branch=${{ github.ref_name }}
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
26
TODO.md
26
TODO.md
@@ -14,7 +14,8 @@ pre-1.0
|
||||
|
||||
No git tags. The site is live and now has the scripts-to-rule-them-all scaffold
|
||||
(`Makefile`, `script/`, `Dockerfile`, `check.yml`); still missing `LICENSE` and
|
||||
policy files.
|
||||
policy files. Every external reference in the repo is now pinned by
|
||||
cryptographic hash (or, for the wrangler CLI install, an exact version).
|
||||
|
||||
# Next Step
|
||||
|
||||
@@ -24,6 +25,21 @@ Update `README.md` accordingly.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-09: hash-pinned every external reference in
|
||||
`.gitea/workflows/deploy.yml` (closes #7): both job container images are
|
||||
pinned by digest, all three `uses:` are pinned by 40-hex commit SHA, and the
|
||||
wrangler install is pinned to an exact version. The abandoned
|
||||
`klakegg/hugo:ext-alpine` image is gone: the build job now runs on the same
|
||||
pinned `alpine` digest the `Dockerfile` uses, with a pre-checkout
|
||||
`apk add nodejs git tar` step (the Actions runner needs `node` inside the job
|
||||
container to execute JavaScript actions), an explicit `shell: sh` default,
|
||||
then `script/bootstrap` and `script/test`. The `deploy` job is guarded with
|
||||
`if: github.ref_name == 'main'` so it can never publish from a branch. Also
|
||||
dropped the dead `feat/initial-site` push trigger and reindented the file to
|
||||
4-space YAML to match `check.yml`. This is the second attempt; the first broke
|
||||
the deploy and was reverted, so this one was verified by temporarily
|
||||
triggering the workflow on the PR branch and iterating until the `build` job
|
||||
ran green for real
|
||||
- 2026-07-25: added the scripts-to-rule-them-all scaffold (closes #4): `script/`
|
||||
entrypoints, `Makefile` shims, a Hugo `Dockerfile` (sha256-pinned alpine) plus
|
||||
`.dockerignore` that runs `make check`, `.gitea/workflows/check.yml` running
|
||||
@@ -40,9 +56,11 @@ Update `README.md` accordingly.
|
||||
|
||||
# Future Steps
|
||||
|
||||
- Pin the images and actions in `deploy.yml` by sha256
|
||||
(`klakegg/hugo:ext-alpine`, `node:20`, `actions/checkout`,
|
||||
`upload`/`download-artifact` are all unpinned)
|
||||
- Move the artifact actions to v4 once this Gitea Actions instance serves the v4
|
||||
artifact protocol; they are pinned on the deprecated v3 line because v4 fails
|
||||
here (#20)
|
||||
- Move the deploy container to a pinned node 22 so the wrangler pin can advance
|
||||
past 4.86.0 (#21)
|
||||
- Rework README.md into the standard sections: Description, Getting Started,
|
||||
Rationale, Design, TODO, License, Author (currently About, Contributing,
|
||||
Technical Details, License)
|
||||
|
||||
Reference in New Issue
Block a user