Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/actions/setup-hugo/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright (C) 2026 Sten Tijhuis
# SPDX-License-Identifier: MIT
name: Set up Hugo
description: >
Downloads a pinned Hugo extended release, verifies its checksum and puts it
on PATH. The version and the checksum live here and nowhere else, so the
build in quality.yml can never drift away from the build in deploy-bunny.yml.

Extended rather than plain: this site has no SCSS today and does not need it,
but every other Hugo site in the organisation runs extended, and extended is
a superset. One flavour across the organisation is worth more than the few
megabytes saved by running a build that behaves subtly differently here.

inputs:
version:
description: Hugo version to install, without the leading "v".
required: false
# renovate: datasource=github-releases depName=gohugoio/hugo
default: "0.165.0"
sha256:
description: >
SHA-256 of hugo_extended_<version>_linux-amd64.tar.gz. Bump this together
with the version; the value is the matching line in the release's
hugo_<version>_checksums.txt:

curl -sSL https://github.com/gohugoio/hugo/releases/download/v<version>/hugo_<version>_checksums.txt \
| grep hugo_extended_<version>_linux-amd64.tar.gz

Mind the hugo_extended_ prefix: the plain hugo_ line is a different
archive with a different checksum.

You should not normally have to touch this by hand. Renovate cannot
compute a checksum, so update-checksums.yml recalculates it on Renovate's
pull requests and commits it back. Doing it manually is only needed when
the version is changed outside that flow.
required: false
default: "f43494894cdf4a8630a201d5c828051c77f523cc66bb3938b30806835470ac20"

runs:
using: composite
steps:
# The release comes off the network, so nothing is executed before the
# checksum says it is the archive we pinned.
#
# --retry: the release CDN hands out an occasional 503, and without this a
# single one fails the whole build. curl retries 5xx and timeouts on its
# own; --retry-all-errors extends that to connection failures.
- name: Download and verify Hugo
shell: bash
env:
HUGO_VERSION: ${{ inputs.version }}
HUGO_SHA256: ${{ inputs.sha256 }}
run: |
curl -sSL --fail-with-body -o "${RUNNER_TEMP}/hugo.tar.gz" \
--retry 5 --retry-delay 3 --retry-all-errors \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
echo "${HUGO_SHA256} ${RUNNER_TEMP}/hugo.tar.gz" | sha256sum -c -
tar -xzf "${RUNNER_TEMP}/hugo.tar.gz" -C "${RUNNER_TEMP}" hugo
sudo install -m 0755 "${RUNNER_TEMP}/hugo" /usr/local/bin/hugo
hugo version
38 changes: 29 additions & 9 deletions .github/scripts/update-tool-checksums.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
readonly REPO_ROOT
cd "$REPO_ROOT"

readonly CONFIG_VALIDATION=".github/workflows/config-validation.yml"
readonly PR_CHECKS=".github/workflows/pr-checks.yml"
readonly HUGO_ACTION=".github/actions/setup-hugo/action.yml"
readonly QUALITY=".github/workflows/quality.yml"

# ── Reading and writing the pinned values ───────────────────────────────────

Expand All @@ -81,6 +81,18 @@ Set-KeyValue() {
sed -i "s|^\([[:space:]]*$2:[[:space:]]*\"\)[^\"]*\"|\1$3\"|" "$1"
}

# Hugo's version and checksum are input defaults in the composite action, so
# there is no key to match on. The version is the `default:` directly under the
# renovate annotation; the checksum is the only `default:` holding 64 hex
# characters.
Get-HugoVersion() {
grep -A1 'depName=gohugoio/hugo' "$HUGO_ACTION" | sed -n 's/.*default: "\([^"]*\)".*/\1/p' | head -n1
}

Set-HugoSha() {
sed -i "s|^\([[:space:]]*default: \"\)[a-f0-9]\{64\}\"|\1$1\"|" "$HUGO_ACTION"
}

# ── Fetching and verifying ──────────────────────────────────────────────────

TEMP_DIR="$(mktemp -d)"
Expand Down Expand Up @@ -117,20 +129,27 @@ Get-PublishedHash() {

# ── The tools ───────────────────────────────────────────────────────────────

ACTIONLINT_VERSION="$(Get-KeyValue "$CONFIG_VALIDATION" ACTIONLINT_VERSION)"
LYCHEE_VERSION="$(Get-KeyValue "$PR_CHECKS" LYCHEE_VERSION)"
HUGO_VERSION="$(Get-HugoVersion)"
ACTIONLINT_VERSION="$(Get-KeyValue "$QUALITY" ACTIONLINT_VERSION)"
LYCHEE_VERSION="$(Get-KeyValue "$QUALITY" LYCHEE_VERSION)"

for pair in "actionlint:$ACTIONLINT_VERSION" "lychee:$LYCHEE_VERSION"; do
for pair in "Hugo:$HUGO_VERSION" "actionlint:$ACTIONLINT_VERSION" "lychee:$LYCHEE_VERSION"; do
[[ -n "${pair#*:}" ]] || Stop-Script "Could not read the ${pair%%:*} version. Did the file layout change?"
done

Write-Log INFO "Versions found in the repository:"
echo " Hugo: $HUGO_VERSION"
echo " actionlint: $ACTIONLINT_VERSION"
echo " lychee: $LYCHEE_VERSION"
echo

Write-Log INFO "Downloading and verifying against the published checksums..."

HUGO_SHA256="$(Get-VerifiedHash "hugo.tar.gz" \
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz" \
"$(Get-PublishedHash "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_checksums.txt" "hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz")")"
Write-Log SUCCESS "Hugo: $HUGO_SHA256"

ACTIONLINT_SHA256="$(Get-VerifiedHash "actionlint.tar.gz" \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
"$(Get-PublishedHash "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" "linux_amd64.tar.gz")")"
Expand All @@ -150,9 +169,10 @@ if [[ "$APPLY" != true ]]; then
fi
fi

Set-KeyValue "$CONFIG_VALIDATION" ACTIONLINT_SHA256 "$ACTIONLINT_SHA256"
Set-KeyValue "$PR_CHECKS" LYCHEE_SHA256 "$LYCHEE_SHA256"
Set-HugoSha "$HUGO_SHA256"
Set-KeyValue "$QUALITY" ACTIONLINT_SHA256 "$ACTIONLINT_SHA256"
Set-KeyValue "$QUALITY" LYCHEE_SHA256 "$LYCHEE_SHA256"

Write-Log SUCCESS "Updated:"
echo " - $CONFIG_VALIDATION"
echo " - $PR_CHECKS"
echo " - $HUGO_ACTION"
echo " - $QUALITY"
37 changes: 2 additions & 35 deletions .github/workflows/config-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ on:
- '.github/dependabot.yml'
- '.github/dependabot.yaml'
- '.github/scripts/check-renovate-patterns.py'
# Broader than the other repos: the actionlint job below covers every
# workflow, so every workflow change is relevant here.
- '.github/workflows/**'
- '.github/workflows/config-validation.yml'
pull_request:
branches: [main, development]
paths:
Expand All @@ -26,9 +24,7 @@ on:
- '.github/dependabot.yml'
- '.github/dependabot.yaml'
- '.github/scripts/check-renovate-patterns.py'
# Broader than the other repos: the actionlint job below covers every
# workflow, so every workflow change is relevant here.
- '.github/workflows/**'
- '.github/workflows/config-validation.yml'
workflow_dispatch:

permissions: {}
Expand Down Expand Up @@ -95,32 +91,3 @@ jobs:
fi
pipx install "check-jsonschema==${CHECK_JSONSCHEMA_VERSION}"
check-jsonschema --builtin-schema vendor.dependabot "$config"

# De workflowbestanden zijn ook config. De andere repositories draaien
# actionlint vanuit hun quality-workflow; deze had geen equivalent, dus
# het hoort hier.
#
# Als stap en niet als eigen job: GitHub rekent per job en rondt naar
# boven af op een hele minuut. actionlint is in vijf seconden klaar en
# heeft dezelfde checkout nodig als de stappen hierboven, dus een eigen
# job kostte een volle minuut extra voor niets.
#
# Vanaf hier draait elke stap op !cancelled(), zodat één rode controle de
# andere niet verbergt. De job faalt alsnog zodra er iets fout is.
- name: Install actionlint
if: ${{ !cancelled() }}
env:
# renovate: datasource=github-releases depName=rhysd/actionlint
ACTIONLINT_VERSION: "1.7.12"
ACTIONLINT_SHA256: "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
run: |
curl -sSL --fail-with-body -o actionlint.tar.gz \
--retry 5 --retry-delay 3 --retry-all-errors \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c -
tar -xzf actionlint.tar.gz actionlint
sudo install -m 0755 actionlint /usr/local/bin/actionlint

- name: Run actionlint
if: ${{ !cancelled() }}
run: actionlint -color
93 changes: 93 additions & 0 deletions .github/workflows/deploy-bunny.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Copyright (C) 2026 Sten Tijhuis
# SPDX-License-Identifier: MIT
#
# The deploy: build, sync to Bunny Storage, purge the Pull Zone cache.
#
# This replaced a GitHub Pages deploy. The site now lives in a Bunny Storage
# zone and is served from the edge by a Pull Zone, the same as every other Hugo
# site in the organisation.
name: Deploy to Bunny.net

on:
push:
branches:
- main
paths:
- 'src/**'
- '.github/workflows/deploy-bunny.yml'
- '.github/actions/setup-hugo/**'
workflow_dispatch:

concurrency:
group: deploy
cancel-in-progress: true

# No token needed; the job that reads the checkout asks for read access itself.
permissions: {}

jobs:
deploy:
name: Build and deploy to Bunny Storage
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Check out source code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# This site pulls Hextra in as a Hugo Module (src/go.mod), so Hugo needs
# Go on PATH before it can build. The other repos in the organisation are
# not module sites and skip this step.
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: src/go.mod

# Pinned by version and checksum inside the action, so this build and the
# one in quality.yml always use the same Hugo.
- name: Set up Hugo
uses: ./.github/actions/setup-hugo

# No --baseURL: it lives in src/hugo.toml and belongs on one line only, so
# a domain change happens in one place. HUGO_ENVIRONMENT=production is what
# gives the live site the permissive robots.txt; see src/layouts/robots.txt.
- name: Build site
working-directory: src
env:
TZ: Europe/Amsterdam
HUGO_ENVIRONMENT: production
run: hugo --minify --gc

# An empty public/ would let the --delete below wipe the whole zone. That
# can only happen if the build is broken, and then a failed deploy is far
# better than an offline site.
- name: Check that the build produced a site
run: test -s src/public/index.html

- name: Sync to Bunny Storage (S3)
env:
AWS_ACCESS_KEY_ID: ${{ secrets.BUNNY_STORAGE_ZONE }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.BUNNY_ACCESS_KEY }}
AWS_DEFAULT_REGION: de
STORAGE_ZONE: ${{ secrets.BUNNY_STORAGE_ZONE }}
STORAGE_ENDPOINT: ${{ secrets.BUNNY_STORAGE_ENDPOINT }}
run: |
aws s3 sync src/public/ "s3://${STORAGE_ZONE}/" \
--endpoint-url "${STORAGE_ENDPOINT}" \
--delete \
--no-progress

- name: Wait for storage replication
run: sleep 15

- name: Purge Bunny Pull Zone cache
env:
PULL_ZONE_ID: ${{ secrets.BUNNY_PULL_ZONE_ID }}
API_KEY: ${{ secrets.BUNNY_API_KEY }}
run: |
curl -sS --fail-with-body -X POST \
"https://api.bunny.net/pullzone/${PULL_ZONE_ID}/purgeCache" \
-H "AccessKey: ${API_KEY}" \
-H "Content-Type: application/json"
81 changes: 0 additions & 81 deletions .github/workflows/hugo.yml

This file was deleted.

Loading
Loading