Pin Hugo to a deliberate, hash-verified v0.164.0 and switch hugo.toml to locale (closes #26, closes #18) #27

Merged
clawbot merged 4 commits from 26-hugo-deliberate-version into main 2026-08-09 17:18:08 +02:00
5 changed files with 143 additions and 13 deletions

View File

@@ -9,7 +9,11 @@ FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4
WORKDIR /src
# Install build dependencies first so the layer caches until the
# scripts change (script/bootstrap installs git, make, hugo, node/npm).
# scripts change (script/bootstrap installs git, make, go, hugo,
# node/npm). Hugo is not an apk package here: script/bootstrap builds
# the exact pinned version with `go install`, hash-verified against
# sum.golang.org, so the published artifact does not depend on whatever
# hugo this base image's repos happen to serve.
COPY script/ script/
RUN script/bootstrap

View File

@@ -46,8 +46,10 @@ standard: normalized scripts in `script/` are the entrypoints for the
development workflow, and the Makefile targets are thin shims that call them. We
provide:
- `script/bootstrap` — install all build dependencies (git, make, hugo,
node/npm) idempotently
- `script/bootstrap` — install all build dependencies (git, make, go, hugo,
node/npm) idempotently. Hugo is pinned to an exact version and installed with
`go install`, which verifies it against `sum.golang.org`; the version is the
`HUGO_VERSION` constant at the top of the script
- `script/setup` — prepare a fresh clone: run `script/bootstrap` and install the
git pre-commit hook
- `script/test` — the correctness check: a clean `hugo --minify` production

27
TODO.md
View File

@@ -15,7 +15,9 @@ 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. Every external reference in the repo is now pinned by
cryptographic hash (or, for the wrangler CLI install, an exact version).
cryptographic hash (or, for the wrangler CLI install, an exact version), and the
Hugo that builds the published site is a deliberate pinned version rather than
whatever the base image's package repo serves.
# Next Step
@@ -25,6 +27,29 @@ Update `README.md` accordingly.
# Completed Steps
- 2026-08-09: replaced `hugo.toml`'s deprecated `languageCode` key with `locale`
(closes #18). Hugo deprecated `languageCode` in v0.158.0, so the Hugo pinned
in the preceding commit warns about it; left alone it would become a third
routinely-ignored warning, and a latent breakage when the key is removed.
Deliberately sequenced **after** the Hugo version move and in the same branch:
under the apk hugo 0.139.0 that CI ran until now, `locale` is an unknown key
that is silently ignored, which downgrades the generated RSS from
`<language>en-us</language>` to `<language>en</language>` with no warning and
exit 0. Verified on hugo v0.164.0 that the RSS `<language>` still reads
`en-us`, the `lang` attribute is unchanged, and `public/` is byte-identical to
the preceding commit's output
- 2026-08-09: installed Hugo at a deliberate, hash-verified version instead of
taking whatever alpine ships (closes #26). `script/bootstrap` no longer does
`pkg_install hugo`; it installs `github.com/gohugoio/hugo@v0.164.0` with
`go install`, which verifies the module against `sum.golang.org`. The version
is a commented constant, as is the Go toolchain (`go1.26.5`) — hugo v0.164.0
requires go >= 1.26.0 and alpine 3.21 ships go 1.23.9 with
`GOTOOLCHAIN=local`, so a bare `go install` refuses to run. `CGO_ENABLED=0` is
deliberate: standard Hugo, not extended, because this site has no SCSS, no
`resources.ToCSS`, no PostCSS and no image processing. This moves the build
off apk's hugo 0.139.0, about two years behind, onto the current stable.
Rendered output across the whole `public/` tree is unchanged except the
`meta name=generator` version string
- 2026-08-09: made `script/check` run `script/lint` (closes #9). It previously
ran only `fmt-check` then `test`, so `script/lint` executed nowhere — not in
`make check`, not in the pre-commit hook, and not in CI, even though the

View File

@@ -1,5 +1,5 @@
baseURL = 'https://lora.vegas/'
languageCode = 'en-us'
locale = 'en-us'
title = 'LoRa Vegas — Las Vegas Meshtastic Community'
theme = 'loravega'

View File

@@ -2,9 +2,10 @@
# script/bootstrap: install all dependencies needed to build and develop
# this Hugo site, idempotently. Base tooling comes from nix, apt, brew,
# or apk (detected in that order); assumes NOTHING is present (not git,
# make, hugo, or node). Installs hugo (the site build) and node/npm
# (prettier, used to format the repo's own markdown docs). Every install
# is guarded by a check so already-installed tools are skipped.
# make, go, hugo, or node). Installs hugo (the site build, at the exact
# version pinned below) and node/npm (prettier, used to format the
# repo's own markdown docs). Every install is guarded by a check so
# already-installed tools are skipped.
set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
@@ -12,6 +13,50 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
PKGMGR=""
SUDO=""
# --- Hugo -------------------------------------------------------------
#
# Hugo produces the published artifact, so its version is a property of
# the site's output, not of the build environment. It is therefore
# pinned here rather than taken from whatever the distro serves: before
# this, alpine 3.21's apk supplied hugo 0.139.0 -- a version chosen by
# nobody, roughly two years behind upstream, and liable to change
# silently whenever the base image digest moves.
#
# `go install` is the hash-verified mechanism: Go checks the module
# against the sum.golang.org checksum database. That is the mechanism
# REPO_POLICIES.md already names for Go, and it needs no hand-maintained
# sha256. The other tools this script installs stay on the package
# manager, which #19 settled is fine for build-time conveniences.
#
# hugo v0.164.0, 2026-07-06
HUGO_VERSION="v0.164.0"
# hugo v0.164.0's go.mod requires go >= 1.26.0, and alpine 3.21's `go`
# package is 1.23.9 built with GOTOOLCHAIN=local, so a bare `go install`
# refuses to run at all. Naming the toolchain explicitly makes Go fetch
# it through the module proxy and verify it against sum.golang.org like
# any other module, so the chain stays hash-verified end to end -- and
# the Go version that compiles hugo becomes deliberate too, instead of
# being inherited from whatever the base image happens to ship.
# go1.26.5, 2026-08-09
HUGO_GOTOOLCHAIN="go1.26.5"
# Standard hugo, not hugo extended: CGO_ENABLED=0 is deliberate.
# Verified that this site uses nothing extended provides -- there are no
# .scss/.sass files, no resources.ToCSS, no PostCSS, and no image
# processing (.Resize/.Fill/.Fit/images.* are all absent). The CSS is
# plain and inlined by `readFile` in baseof.html. The `+extended` on the
# apk build this replaces was incidental, not a requirement, so do not
# assume a future change needs it without rechecking the above.
HUGO_CGO_ENABLED="0"
# Where the hugo binary lands. It has to be on the default PATH of a
# *fresh* shell, not just of this script: the Dockerfile's `RUN make
# check` and deploy.yml's `script/test` step each start their own shell
# and would never see a GOPATH bin directory. Overridable so an
# unprivileged install can point somewhere writable.
HUGO_BIN_DIR="${HUGO_BIN_DIR:-/usr/local/bin}"
detect_pkgmgr() {
[ -n "$PKGMGR" ] && return 0
if command -v nix-env >/dev/null 2>&1; then
@@ -28,13 +73,17 @@ detect_pkgmgr() {
fi
if [ "$PKGMGR" = "apt" ]; then
export DEBIAN_FRONTEND=noninteractive
if [ "$(id -u)" != "0" ]; then
SUDO="sudo"
fi
detect_sudo
$SUDO env DEBIAN_FRONTEND=noninteractive apt-get update
fi
}
detect_sudo() {
if [ -z "$SUDO" ] && [ "$(id -u)" != "0" ]; then
SUDO="sudo"
fi
}
# pkg_install <nix-attr> <apt-pkg> <brew-formula> <apk-pkg>
pkg_install() {
detect_pkgmgr
@@ -50,6 +99,56 @@ missing() {
! command -v "$1" >/dev/null 2>&1
}
# True when the hugo already on PATH is the pinned version. Unlike the
# other tools, mere presence is not good enough here: an older hugo has
# to be replaced, not accepted, or the pin means nothing.
hugo_pinned() {
command -v hugo >/dev/null 2>&1 || return 1
# `hugo version` prints e.g. "hugo v0.164.0 linux/amd64 ..." for a
# `go install` build, or "hugo v0.164.0-ce2470e+extended ..." for an
# official release binary. Compare only the vX.Y.Z part: a build of
# the same version that happens to be extended renders this site
# identically (see HUGO_CGO_ENABLED above), so there is no reason to
# overwrite a developer's existing matching install.
have="$(hugo version 2>/dev/null | awk '{print $2}' | sed 's/[-+].*//')"
[ "$have" = "$HUGO_VERSION" ]
}
install_hugo() {
detect_sudo
# The Go toolchain is a build-time convenience like git and make, so
# it comes from the package manager; the thing that must be
# deliberate is what it builds, which HUGO_VERSION and
# HUGO_GOTOOLCHAIN pin.
if missing go; then pkg_install go golang-go go go; fi
# Build as the invoking user into a scratch GOBIN, then place the
# binary with `install`. Running the whole `go install` under sudo
# would work but would populate root's module cache instead of the
# user's, which is needlessly slow and surprising on a workstation.
gobin="$(mktemp -d)"
CGO_ENABLED="$HUGO_CGO_ENABLED" \
GOTOOLCHAIN="$HUGO_GOTOOLCHAIN" \
GOBIN="$gobin" \
go install "github.com/gohugoio/hugo@${HUGO_VERSION}"
$SUDO install -d "$HUGO_BIN_DIR"
$SUDO install -m 0755 "$gobin/hugo" "$HUGO_BIN_DIR/hugo"
rm -rf "$gobin"
# Drop any cached PATH lookup of the hugo we just replaced, so the
# check below tests the new binary and not the old one.
hash -r 2>/dev/null || true
# Fail loudly rather than let a later build run on a shadowing hugo
# from somewhere earlier in PATH.
if ! hugo_pinned; then
echo "bootstrap: installed $HUGO_VERSION into $HUGO_BIN_DIR but" \
"'hugo' on PATH is still $(hugo version 2>/dev/null || echo absent)" >&2
exit 1
fi
}
main() {
cd "$ROOT"
@@ -63,8 +162,8 @@ main() {
git submodule update --init --recursive
fi
# Site build.
if missing hugo; then pkg_install hugo hugo hugo hugo; fi
# Site build. Pinned and hash-verified -- see the HUGO_* constants.
if ! hugo_pinned; then install_hugo; fi
# node/npm provide prettier (via npx) for formatting the docs.
if missing node; then pkg_install nodejs nodejs node nodejs; fi