diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 3967d2e..8263c4a 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -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 }} diff --git a/TODO.md b/TODO.md index c8a581c..7936071 100644 --- a/TODO.md +++ b/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)