Skip to content

Commit 12eb419

Browse files
committed
feat: Migrate deploy to Bunny and revamp CI
Replaced the GitHub Pages workflow with a Bunny.net deploy pipeline and centralized Hugo setup in a new action. This ensures better consistency across my repo's.
1 parent 2b0beef commit 12eb419

21 files changed

Lines changed: 634 additions & 445 deletions
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright (C) 2026 Sten Tijhuis
2+
# SPDX-License-Identifier: MIT
3+
name: Set up Hugo
4+
description: >
5+
Downloads a pinned Hugo extended release, verifies its checksum and puts it
6+
on PATH. The version and the checksum live here and nowhere else, so the
7+
build in quality.yml can never drift away from the build in deploy-bunny.yml.
8+
9+
Extended rather than plain: this site has no SCSS today and does not need it,
10+
but every other Hugo site in the organisation runs extended, and extended is
11+
a superset. One flavour across the organisation is worth more than the few
12+
megabytes saved by running a build that behaves subtly differently here.
13+
14+
inputs:
15+
version:
16+
description: Hugo version to install, without the leading "v".
17+
required: false
18+
# renovate: datasource=github-releases depName=gohugoio/hugo
19+
default: "0.165.0"
20+
sha256:
21+
description: >
22+
SHA-256 of hugo_extended_<version>_linux-amd64.tar.gz. Bump this together
23+
with the version; the value is the matching line in the release's
24+
hugo_<version>_checksums.txt:
25+
26+
curl -sSL https://github.com/gohugoio/hugo/releases/download/v<version>/hugo_<version>_checksums.txt \
27+
| grep hugo_extended_<version>_linux-amd64.tar.gz
28+
29+
Mind the hugo_extended_ prefix: the plain hugo_ line is a different
30+
archive with a different checksum.
31+
32+
You should not normally have to touch this by hand. Renovate cannot
33+
compute a checksum, so update-checksums.yml recalculates it on Renovate's
34+
pull requests and commits it back. Doing it manually is only needed when
35+
the version is changed outside that flow.
36+
required: false
37+
default: "f43494894cdf4a8630a201d5c828051c77f523cc66bb3938b30806835470ac20"
38+
39+
runs:
40+
using: composite
41+
steps:
42+
# The release comes off the network, so nothing is executed before the
43+
# checksum says it is the archive we pinned.
44+
#
45+
# --retry: the release CDN hands out an occasional 503, and without this a
46+
# single one fails the whole build. curl retries 5xx and timeouts on its
47+
# own; --retry-all-errors extends that to connection failures.
48+
- name: Download and verify Hugo
49+
shell: bash
50+
env:
51+
HUGO_VERSION: ${{ inputs.version }}
52+
HUGO_SHA256: ${{ inputs.sha256 }}
53+
run: |
54+
curl -sSL --fail-with-body -o "${RUNNER_TEMP}/hugo.tar.gz" \
55+
--retry 5 --retry-delay 3 --retry-all-errors \
56+
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
57+
echo "${HUGO_SHA256} ${RUNNER_TEMP}/hugo.tar.gz" | sha256sum -c -
58+
tar -xzf "${RUNNER_TEMP}/hugo.tar.gz" -C "${RUNNER_TEMP}" hugo
59+
sudo install -m 0755 "${RUNNER_TEMP}/hugo" /usr/local/bin/hugo
60+
hugo version

.github/scripts/update-tool-checksums.sh

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
6666
readonly REPO_ROOT
6767
cd "$REPO_ROOT"
6868

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

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

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

84+
# Hugo's version and checksum are input defaults in the composite action, so
85+
# there is no key to match on. The version is the `default:` directly under the
86+
# renovate annotation; the checksum is the only `default:` holding 64 hex
87+
# characters.
88+
Get-HugoVersion() {
89+
grep -A1 'depName=gohugoio/hugo' "$HUGO_ACTION" | sed -n 's/.*default: "\([^"]*\)".*/\1/p' | head -n1
90+
}
91+
92+
Set-HugoSha() {
93+
sed -i "s|^\([[:space:]]*default: \"\)[a-f0-9]\{64\}\"|\1$1\"|" "$HUGO_ACTION"
94+
}
95+
8496
# ── Fetching and verifying ──────────────────────────────────────────────────
8597

8698
TEMP_DIR="$(mktemp -d)"
@@ -117,20 +129,27 @@ Get-PublishedHash() {
117129

118130
# ── The tools ───────────────────────────────────────────────────────────────
119131

120-
ACTIONLINT_VERSION="$(Get-KeyValue "$CONFIG_VALIDATION" ACTIONLINT_VERSION)"
121-
LYCHEE_VERSION="$(Get-KeyValue "$PR_CHECKS" LYCHEE_VERSION)"
132+
HUGO_VERSION="$(Get-HugoVersion)"
133+
ACTIONLINT_VERSION="$(Get-KeyValue "$QUALITY" ACTIONLINT_VERSION)"
134+
LYCHEE_VERSION="$(Get-KeyValue "$QUALITY" LYCHEE_VERSION)"
122135

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

127140
Write-Log INFO "Versions found in the repository:"
141+
echo " Hugo: $HUGO_VERSION"
128142
echo " actionlint: $ACTIONLINT_VERSION"
129143
echo " lychee: $LYCHEE_VERSION"
130144
echo
131145

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

148+
HUGO_SHA256="$(Get-VerifiedHash "hugo.tar.gz" \
149+
"https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz" \
150+
"$(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")")"
151+
Write-Log SUCCESS "Hugo: $HUGO_SHA256"
152+
134153
ACTIONLINT_SHA256="$(Get-VerifiedHash "actionlint.tar.gz" \
135154
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
136155
"$(Get-PublishedHash "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" "linux_amd64.tar.gz")")"
@@ -150,9 +169,10 @@ if [[ "$APPLY" != true ]]; then
150169
fi
151170
fi
152171

153-
Set-KeyValue "$CONFIG_VALIDATION" ACTIONLINT_SHA256 "$ACTIONLINT_SHA256"
154-
Set-KeyValue "$PR_CHECKS" LYCHEE_SHA256 "$LYCHEE_SHA256"
172+
Set-HugoSha "$HUGO_SHA256"
173+
Set-KeyValue "$QUALITY" ACTIONLINT_SHA256 "$ACTIONLINT_SHA256"
174+
Set-KeyValue "$QUALITY" LYCHEE_SHA256 "$LYCHEE_SHA256"
155175

156176
Write-Log SUCCESS "Updated:"
157-
echo " - $CONFIG_VALIDATION"
158-
echo " - $PR_CHECKS"
177+
echo " - $HUGO_ACTION"
178+
echo " - $QUALITY"

.github/workflows/config-validation.yml

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@ on:
1515
- '.github/dependabot.yml'
1616
- '.github/dependabot.yaml'
1717
- '.github/scripts/check-renovate-patterns.py'
18-
# Broader than the other repos: the actionlint job below covers every
19-
# workflow, so every workflow change is relevant here.
20-
- '.github/workflows/**'
18+
- '.github/workflows/config-validation.yml'
2119
pull_request:
2220
branches: [main, development]
2321
paths:
@@ -26,9 +24,7 @@ on:
2624
- '.github/dependabot.yml'
2725
- '.github/dependabot.yaml'
2826
- '.github/scripts/check-renovate-patterns.py'
29-
# Broader than the other repos: the actionlint job below covers every
30-
# workflow, so every workflow change is relevant here.
31-
- '.github/workflows/**'
27+
- '.github/workflows/config-validation.yml'
3228
workflow_dispatch:
3329

3430
permissions: {}
@@ -95,32 +91,3 @@ jobs:
9591
fi
9692
pipx install "check-jsonschema==${CHECK_JSONSCHEMA_VERSION}"
9793
check-jsonschema --builtin-schema vendor.dependabot "$config"
98-
99-
# De workflowbestanden zijn ook config. De andere repositories draaien
100-
# actionlint vanuit hun quality-workflow; deze had geen equivalent, dus
101-
# het hoort hier.
102-
#
103-
# Als stap en niet als eigen job: GitHub rekent per job en rondt naar
104-
# boven af op een hele minuut. actionlint is in vijf seconden klaar en
105-
# heeft dezelfde checkout nodig als de stappen hierboven, dus een eigen
106-
# job kostte een volle minuut extra voor niets.
107-
#
108-
# Vanaf hier draait elke stap op !cancelled(), zodat één rode controle de
109-
# andere niet verbergt. De job faalt alsnog zodra er iets fout is.
110-
- name: Install actionlint
111-
if: ${{ !cancelled() }}
112-
env:
113-
# renovate: datasource=github-releases depName=rhysd/actionlint
114-
ACTIONLINT_VERSION: "1.7.12"
115-
ACTIONLINT_SHA256: "8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8"
116-
run: |
117-
curl -sSL --fail-with-body -o actionlint.tar.gz \
118-
--retry 5 --retry-delay 3 --retry-all-errors \
119-
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
120-
echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum -c -
121-
tar -xzf actionlint.tar.gz actionlint
122-
sudo install -m 0755 actionlint /usr/local/bin/actionlint
123-
124-
- name: Run actionlint
125-
if: ${{ !cancelled() }}
126-
run: actionlint -color

.github/workflows/deploy-bunny.yml

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Copyright (C) 2026 Sten Tijhuis
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# The deploy: build, sync to Bunny Storage, purge the Pull Zone cache.
5+
#
6+
# This replaced a GitHub Pages deploy. The site now lives in a Bunny Storage
7+
# zone and is served from the edge by a Pull Zone, the same as every other Hugo
8+
# site in the organisation.
9+
name: Deploy to Bunny.net
10+
11+
on:
12+
push:
13+
branches:
14+
- main
15+
paths:
16+
- 'src/**'
17+
- '.github/workflows/deploy-bunny.yml'
18+
- '.github/actions/setup-hugo/**'
19+
workflow_dispatch:
20+
21+
concurrency:
22+
group: deploy
23+
cancel-in-progress: true
24+
25+
# No token needed; the job that reads the checkout asks for read access itself.
26+
permissions: {}
27+
28+
jobs:
29+
deploy:
30+
name: Build and deploy to Bunny Storage
31+
runs-on: ubuntu-latest
32+
permissions:
33+
contents: read
34+
steps:
35+
- name: Check out source code
36+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
37+
with:
38+
persist-credentials: false
39+
40+
# This site pulls Hextra in as a Hugo Module (src/go.mod), so Hugo needs
41+
# Go on PATH before it can build. The other repos in the organisation are
42+
# not module sites and skip this step.
43+
- name: Set up Go
44+
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
45+
with:
46+
go-version-file: src/go.mod
47+
48+
# Pinned by version and checksum inside the action, so this build and the
49+
# one in quality.yml always use the same Hugo.
50+
- name: Set up Hugo
51+
uses: ./.github/actions/setup-hugo
52+
53+
# No --baseURL: it lives in src/hugo.toml and belongs on one line only, so
54+
# a domain change happens in one place. HUGO_ENVIRONMENT=production is what
55+
# gives the live site the permissive robots.txt; see src/layouts/robots.txt.
56+
- name: Build site
57+
working-directory: src
58+
env:
59+
TZ: Europe/Amsterdam
60+
HUGO_ENVIRONMENT: production
61+
run: hugo --minify --gc
62+
63+
# An empty public/ would let the --delete below wipe the whole zone. That
64+
# can only happen if the build is broken, and then a failed deploy is far
65+
# better than an offline site.
66+
- name: Check that the build produced a site
67+
run: test -s src/public/index.html
68+
69+
- name: Sync to Bunny Storage (S3)
70+
env:
71+
AWS_ACCESS_KEY_ID: ${{ secrets.BUNNY_STORAGE_ZONE }}
72+
AWS_SECRET_ACCESS_KEY: ${{ secrets.BUNNY_ACCESS_KEY }}
73+
AWS_DEFAULT_REGION: de
74+
STORAGE_ZONE: ${{ secrets.BUNNY_STORAGE_ZONE }}
75+
STORAGE_ENDPOINT: ${{ secrets.BUNNY_STORAGE_ENDPOINT }}
76+
run: |
77+
aws s3 sync src/public/ "s3://${STORAGE_ZONE}/" \
78+
--endpoint-url "${STORAGE_ENDPOINT}" \
79+
--delete \
80+
--no-progress
81+
82+
- name: Wait for storage replication
83+
run: sleep 15
84+
85+
- name: Purge Bunny Pull Zone cache
86+
env:
87+
PULL_ZONE_ID: ${{ secrets.BUNNY_PULL_ZONE_ID }}
88+
API_KEY: ${{ secrets.BUNNY_API_KEY }}
89+
run: |
90+
curl -sS --fail-with-body -X POST \
91+
"https://api.bunny.net/pullzone/${PULL_ZONE_ID}/purgeCache" \
92+
-H "AccessKey: ${API_KEY}" \
93+
-H "Content-Type: application/json"

.github/workflows/hugo.yml

Lines changed: 0 additions & 81 deletions
This file was deleted.

0 commit comments

Comments
 (0)