main
yml 156 lines 6.14 KB
Raw
1 name: Deploy Hugo site
2
3 on: # zizmor: ignore[dangerous-triggers] intentional post-crawl auto-deploy; build job is guarded by conclusion==success and actor checks
4 push:
5 branches:
6 - main
7 # Auto-deploy after a successful crawl-and-publish run so the site always
8 # reflects the latest content committed to the publish branch.
9 workflow_run:
10 workflows: ["Crawl and publish weekly data"]
11 types: [completed]
12 branches: [main]
13 workflow_dispatch:
14
15 permissions:
16 contents: read
17
18 concurrency:
19 group: pages
20 cancel-in-progress: true
21
22 jobs:
23 build:
24 # Allow manual triggers, human pushes to main, and post-crawl auto-runs.
25 # Bot pushes are excluded from push events to avoid loops; workflow_run
26 # covers the bot-written publish branch updates.
27 if: ${{ github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.actor != 'github-actions[bot]') || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') }}
28 runs-on: ubuntu-latest
29 permissions:
30 contents: read
31 pages: write
32 env:
33 HUGO_VERSION: 0.161.1
34 HUGO_PARAMS_GA_MEASUREMENT_ID: ${{ secrets.GA_MEASUREMENT_ID }}
35 steps:
36 - name: Check out repository
37 uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
38 with:
39 fetch-depth: 0
40 submodules: recursive
41 persist-credentials: false
42
43 - name: Configure GitHub Pages
44 uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0
45
46 - name: Set up Node.js
47 uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
48 with:
49 node-version: '24'
50
51 - name: Install Hugo
52 run: | # zizmor: ignore[github-env] writes a fixed runner path (${HOME}/.local/hugo) to GITHUB_PATH, not attacker input; needed so later steps can run hugo
53 set -euo pipefail
54 RELEASE_URL="https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}"
55 TARBALL="hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
56 CHECKSUM_FILE="hugo_${HUGO_VERSION}_checksums.txt"
57 curl --fail --silent --show-error --location --retry 10 --retry-delay 5 --retry-max-time 300 --retry-all-errors \
58 --output "${TARBALL}" "${RELEASE_URL}/${TARBALL}"
59
60 # Download and verify checksums
61 curl --fail --silent --show-error --location --retry 10 --retry-delay 5 --retry-max-time 300 --retry-all-errors \
62 --output "${CHECKSUM_FILE}" "${RELEASE_URL}/${CHECKSUM_FILE}"
63 checksum_line="$(awk -v file="${TARBALL}" '$NF == file {print; found=1} END {if (!found) exit 1}' "${CHECKSUM_FILE}")" || {
64 echo "Error: No checksum entry for ${TARBALL} found in ${CHECKSUM_FILE}" >&2
65 exit 1
66 }
67 if ! printf '%s\n' "${checksum_line}" | sha256sum --check; then
68 echo "Error: Checksum verification failed for ${TARBALL}" >&2
69 exit 1
70 fi
71 rm "${CHECKSUM_FILE}"
72
73 mkdir -p "${HOME}/.local/hugo"
74 tar -C "${HOME}/.local/hugo" -xf "${TARBALL}"
75 rm "${TARBALL}"
76 echo "${HOME}/.local/hugo" >> "${GITHUB_PATH}"
77 export PATH="${HOME}/.local/hugo:${PATH}"
78 hugo version
79
80 - name: Hydrate generated content from publish
81 # Architecture: code/theme/config come from main; all generated content
82 # (weekly pages, rollups, raw data) comes from the canonical publish branch.
83 # This prevents the site from ever diverging from the crawl-and-publish output.
84 run: |
85 set -euo pipefail
86 git fetch origin publish
87 # Wipe and re-checkout to mirror publish exactly (deletions propagate)
88 rm -rf content/weekly content/monthly content/yearly data/analyzed data/raw data/metrics data/snapshots
89 git checkout origin/publish -- \
90 content/weekly/ \
91 content/monthly/ \
92 content/yearly/ \
93 data/analyzed/ \
94 data/raw/ \
95 data/metrics/ \
96 data/snapshots/ || true
97 echo "Hydrated from publish:"
98 ls content/weekly/2026/ 2>/dev/null || true
99
100 - name: Build site
101 run: hugo --minify
102
103 - name: Build Pagefind index
104 run: npx pagefind --site public/
105
106 - name: Upload Pages artifact
107 uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
108 with:
109 path: ./public
110
111 deploy:
112 needs: build
113 runs-on: ubuntu-latest
114 permissions:
115 pages: write
116 id-token: write
117 environment:
118 name: github-pages
119 url: ${{ steps.deployment.outputs.page_url }}
120 steps:
121 - name: Deploy to GitHub Pages
122 id: deployment
123 uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
124
125 notify-failure:
126 needs: [build, deploy]
127 if: failure()
128 runs-on: ubuntu-latest
129 permissions:
130 issues: write
131 steps:
132 - name: Create or update failure issue
133 env:
134 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
135 GH_REPO: ${{ github.repository }}
136 SERVER_URL: ${{ github.server_url }}
137 RUN_ID: ${{ github.run_id }}
138 run: |
139 RUN_URL="${SERVER_URL}/${GH_REPO}/actions/runs/${RUN_ID}"
140 EXISTING=$(gh issue list --label 'deploy-failure' --state open --json number --jq length)
141 if [ "$EXISTING" -gt 0 ]; then
142 ISSUE_NUM=$(gh issue list --label 'deploy-failure' --state open --json number --jq '.[0].number')
143 gh issue comment "$ISSUE_NUM" --body "Deploy failed again: ${RUN_URL}"
144 echo "Updated existing issue #$ISSUE_NUM"
145 else
146 BODY=$(printf '%s\n\n%s\n\n%s\n%s\n%s' \
147 "The Deploy Hugo site workflow failed." \
148 "**Run:** ${RUN_URL}" \
149 "Please triage:" \
150 "- If transient (rate limit, network), close with comment" \
151 "- If real bug, assign to the right squad member")
152 gh issue create \
153 --title "🔴 Deploy Hugo site failed (run ${RUN_ID})" \
154 --label 'squad,deploy-failure' \
155 --body "$BODY"
156 fi