Install Hugo at a deliberate, hash-verified version instead of taking whatever alpine ships #26
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Decision
From @sneak: the Hugo version must be deliberate. Use a container or
go installby hash. Do not use distro Hugo.This narrows the resolution of #19. That issue settled that
apkpackages on adigest-pinned base are acceptable for build-time conveniences, and that stands
for
git,make,nodejs, andnpm. Hugo is different: it is the tool thatproduces the published artifact, so its version is a property of the output,
not of the build environment.
Current state
script/bootstrapline 67:Whatever the distro serves. On the pinned alpine 3.21 that is Hugo
0.139.0+extended; upstream current is 0.164.0. So the site is built by a
Hugo roughly two years behind, chosen by nobody, and it changes silently
whenever the base image digest is bumped.
It already has a visible effect: #18 exists because a current Hugo emits a
languageCodedeprecation warning that CI's 0.139.0 cannot see. CI is green ona Hugo old enough not to know about the deprecation while a developer with a
current Hugo sees it locally.
Extended is not required — verified
Checked before specifying options, because it determines whether
go installis viable at all (Hugo extended needs CGO):
.scss/.sassfiles, noresources.ToCSS, noPostCSS.
{{ readFile "themes/loravega/static/css/style.css" | safeCSS }}inbaseof.html— plain CSS inlined, no asset pipeline..Resize/.Fill/.Fit/images.*all absent).So standard Hugo is sufficient. The current image happening to carry
+extendedis incidental, not a requirement. Note this explicitly in the PR sonobody later assumes extended is needed.
Options
Option A —
go installat a pinned version (recommended). Inscript/bootstrap:Go verifies the module against
sum.golang.org, so this is genuinelyhash-verified rather than version-pinned — it is the mechanism
REPO_POLICIES.mdalready names for Go dependencies, and it satisfies "byhash" without hand-maintaining a sha256. Costs:
script/bootstrapgains a Gotoolchain (itself from apk, which #19 permits), and the build compiles Hugo
rather than fetching a binary, which will add time to a cold build. Set
CGO_ENABLED=0explicitly since extended is not needed.Option B — a Hugo container pinned by digest. Reference an official/known
Hugo image by
@sha256:...with the canonical# name version, YYYY-MM-DDcomment, and use it as a build stage or as the workflow container. Strongest
supply-chain position and no compile cost. Costs: it reintroduces a second
pinned base image alongside the alpine one — which is exactly what #7 removed
when it dropped
klakegg/hugo:ext-alpine— and the deploy build job and theDockerfilewould need to agree on it.My recommendation: Option A. It keeps one pinned base image, uses the
checksum-database mechanism the policy already prefers, and makes the Hugo
version a single visible constant in
script/bootstrap. Option B is defensibleif cold-build time turns out to matter, but reintroducing a second image is a
real cost and #7 was specifically about consolidating onto one.
Either way, the chosen Hugo version must appear as an explicit, commented
constant, not be implied.
Definition of done
script/bootstrapinstalls a specific, explicitly-stated Hugo version by ahash-verified mechanism. No
pkg_install hugo.# hugo vX.Y.Z, YYYY-MM-DDcomment.
hugo versionin the built image reports exactly it.image processing exists in this site.
public/before and after anddiff it. Any difference must be explained and justified in the PR, not
waved through — this is a two-year version jump.
make checkandscript/cibuildpass, with the check layer confirmedgenuinely executed and not
CACHED(see #23).5-minute policy budget, say so.
TODO.mdupdated in the same commit.Critical — this touches the live deploy path
.gitea/workflows/deploy.yml'sbuildjob runsscript/bootstrapinside thepinned alpine container. Changing how Hugo is installed changes the
production deploy path. This repo has already taken one outage from a
deploy.ymlchange that passed two adversarial reviews (#7), and the onlything that actually caught it was making the job executable pre-merge.
So this work must use the same approach:
deploy.yml'son.push.branchesso thebuildjob really executes underact_runner.if: github.ref_name == 'main'guard on thedeployjob is already inplace and must stay, so branch runs cannot reach Cloudflare.
(
/api/v1/repos/sneak/lora.vegas/commits/<sha>/status) — readable byclawboteven though the Actions jobs/logs API 403s.buildjob is green on the branch.deploy.ymlbetween the runner-verifiedcommit and the merge candidate. "It passed on the branch" and "it will pass
on main" are different claims.
A green
script/cibuilddoes not cover this:docker buildrunsRUNunder
/bin/shwith no Actions runtime.Sequencing
languageCodedeprecationwarning in CI, where it is currently invisible. Either fix #18 in the same
branch or land #18 first; do not merge this and leave CI newly noisy.
--panicOnWarning) must come after this, #18, and #13, or the gategoes red immediately.
layer.
Out of scope
a finding to report, not something to accommodate by editing content.
git,make,nodejs, ornpmare installed — #19 settledthat apk is fine for those.
Implementation plan
Taking Option A (
go install), as recommended. #18 rides in the samebranch, committed on top, per its second sequencing comment.
Version: hugo v0.164.0 (2026-07-06) — the current stable release, per
proxy.golang.org/github.com/gohugoio/hugo/@latest.One thing the issue body did not anticipate
hugo v0.164.0'sgo.moddeclaresgo 1.26.0. Alpine 3.21 ships go1.23.9, and the alpine
gopackage builds withGOTOOLCHAIN=local, so abare
go installfails outright:Fix: name the toolchain explicitly as a second commented constant
(
GOTOOLCHAIN=go1.26.5). Go then fetchesgolang.org/toolchainthrough themodule proxy and verifies it against
sum.golang.orgexactly like any othermodule, so this stays hash-verified end to end and the Go version becomes
deliberate too rather than inherited from the base image. Measured cold:
apk add go4s +go install41s.script/bootstrapchangesHUGO_VERSIONandHUGO_GOTOOLCHAIN, each withthe canonical
# name version, YYYY-MM-DDform.pkg_install hugo hugo hugo hugois replaced by: install a Go toolchainfrom the package manager (#19 permits apk for build-time conveniences), then
CGO_ENABLED=0 GOTOOLCHAIN=... go install github.com/gohugoio/hugo@$HUGO_VERSIONinto a temp
GOBIN, theninstall -m 0755the binary into/usr/local/bin./usr/local/binand not the defaultGOPATH/bin: theDockerfile'sRUN make checkanddeploy.yml'sscript/teststep each start a freshshell, so an install location that is not already on the default
PATHwould not be found.
HUGO_BIN_DIRstays overridable for unprivilegedinstalls.
CGO_ENABLED=0explicitly, with a comment recording the verified findingthat standard (non-extended) Hugo is sufficient for this site: no
.scss/.sass, noresources.ToCSS, no PostCSS, no image processing; CSSis inlined via
readFileinbaseof.html. The+extendedon the currentapk build is incidental.
missing hugo— an old hugo already onPATHmust be replaced, notaccepted.
Verification
public/three ways and diff: (a)main+ apk hugo 0.139.0, (b) newhugo 0.164.0 + unchanged
hugo.toml, (c) 0.164.0 +locale. (b) isolatesthe version jump from the config change. Any difference gets reported and
justified, not accommodated.
<language>asserted to still readen-uson the Hugo the buildactually uses.
deploy.ymlgets a temporary branch trigger so thebuildjob really runsunder
act_runner; results read from the commit-status API; the trigger isremoved in the final commit and the
deployjob'sif: github.ref_name == 'main'guard stays untouched throughout.script/cibuildtime reported, with the check layer confirmedgenuinely executed rather than
CACHED(#23).Two commits, one per issue.