Skip to content

🐛 fix(nojs): align Lighthouse audits and 404 footer #26

🐛 fix(nojs): align Lighthouse audits and 404 footer

🐛 fix(nojs): align Lighthouse audits and 404 footer #26

Workflow file for this run

name: ✅
on:
workflow_dispatch:
schedule:
- cron: "37 18 * * *"
pull_request:
push:
branches: [main]
concurrency:
group: ${{ github.event_name == 'schedule' && 'scheduled-maintenance' || format('validate-{0}', github.ref) }}
cancel-in-progress: ${{ github.event_name != 'schedule' }}
permissions:
contents: read
env:
TZ: Asia/Tokyo
jobs:
build:
name: Build and inspect
runs-on: ubuntu-latest
steps:
- name: Check out repository and theme
uses: actions/checkout@v7
with:
fetch-depth: 0
submodules: recursive
- name: Install Hugo Extended
uses: peaceiris/actions-hugo@v3
with:
hugo-version: latest
extended: true
- name: Validate configuration
run: hugo config > /dev/null
- name: Refresh project statistics
env:
GITHUB_TOKEN: ${{ github.token }}
run: node scripts/fetch-project-stats.mjs
- name: Build production site
run: hugo --gc --minify --panicOnWarning
- name: Audit client-side injection surfaces
run: node scripts/audit-client-security.mjs
- name: Verify required output
shell: bash
run: |
set -euo pipefail
test -f public/index.html
test -f public/whoami/index.html
test -f public/2025/11/01/tor-relays-project/index.html
test -f public/2025/12/15/running-tor-exit/index.html
test -f public/2026/01/31/health-checks/index.html
test -f public/2026/03/02/happy-families/index.html
test -f public/2026/04/07/linux-bsd-sunos/index.html
test -f public/2026/05/22/diversity-first/index.html
if [[ "$(date +%F)" > "2026-07-14" ]]; then
test -f public/2026/07/17/non-eu-relays/index.html
fi
test -f public/1984/11/05/cd-whoami/index.html
test -f public/nojs/index.html
test -f public/nojs/whoami/index.html
test -f public/nojs/posts/index.html
test -f public/nojs/donate/index.html
test -f public/nojs/tags/tor/index.html
test -f public/nojs/2025/11/01/tor-relays-project/index.html
test -f public/images/social/whoami.jpg
test -f public/images/social/writing.jpg
test -f public/images/social/donate.jpg
test -f public/.well-known/nostr.json
test -f public/.well-known/security.txt
test "$(cat public/CNAME)" = "brokenbotnet.com"
jq -e '.names.r3bo0tbx1 == "1c6234b2c80549b609be27522cbe2b73400859b24a913855d48503d5d819060d"' public/.well-known/nostr.json
grep -Fx 'Contact: mailto:r3bo0tbx1@brokenbotnet.com' public/.well-known/security.txt
grep -Fx 'Encryption: openpgp4fpr:33727F5377D296C320AF704AB3BD6196E1CFBFB4' public/.well-known/security.txt
grep -Fx 'Preferred-Languages: en' public/.well-known/security.txt
grep -Fx 'Canonical: https://brokenbotnet.com/.well-known/security.txt' public/.well-known/security.txt
node <<'NODE'
const fs = require('fs');
const file = fs.readFileSync('public/.well-known/security.txt', 'utf8');
const match = file.match(/^Expires:\s*(\S+)$/m);
if (!match) throw new Error('security.txt is missing Expires');
const expires = Date.parse(match[1]);
if (!Number.isFinite(expires) || expires <= Date.now()) {
throw new Error('security.txt is expired or has an invalid Expires value');
}
NODE
if grep -R -n -E 'https?://(localhost|127\.0\.0\.1)' public; then
echo "Local development URL found in production output"
exit 1
fi
if find public/nojs -type f -name '*.html' -exec grep -Eil '<script([[:space:]>])' {} + | grep -q .; then
echo "Executable or data script element found in no-JavaScript output"
exit 1
fi
if grep -Eiq '<script([[:space:]>])' public/404.html; then
echo "Script element found in shared 404 output"
exit 1
fi
- name: Validate generated HTML
shell: bash
run: |
python3 -m pip install --disable-pip-version-check html5validator==0.4.2
html5validator --root public --ignore-re 'CSS:.*color-scheme.*Property.*doesn.t exist'
- name: Audit external-link security
shell: bash
run: |
node <<'NODE'
const fs = require('fs');
const path = require('path');
const files = [];
const walk = directory => {
for (const entry of fs.readdirSync(directory, {withFileTypes: true})) {
const full = path.join(directory, entry.name);
if (entry.isDirectory()) walk(full);
else if (entry.name.endsWith('.html')) files.push(full);
}
};
walk('public');
const failures = [];
for (const file of files) {
const html = fs.readFileSync(file, 'utf8');
for (const match of html.matchAll(/<a\b[^>]*target=["']?_blank["']?[^>]*>/gi)) {
if (!/rel=["'][^"']*noopener[^"']*["']/i.test(match[0])) {
failures.push(`${file}: ${match[0]}`);
}
}
}
if (failures.length) {
console.error(failures.join('\n'));
process.exit(1);
}
NODE
- name: Audit SEO metadata
shell: bash
run: |
node --input-type=module <<'NODE'
import assert from 'node:assert/strict';
import fs from 'node:fs';
import { attributeValue, tokenizeHtml } from './scripts/html-tokenizer.mjs';
const read = file => fs.readFileSync(file, 'utf8');
const structuredTypes = html => {
const document = tokenizeHtml(html);
assert.deepEqual(document.errors, []);
const documents = document.scripts
.filter(script => attributeValue(script, 'type')?.toLowerCase() === 'application/ld+json')
.map(script => JSON.parse(script.body));
return documents.flatMap(document =>
(document['@graph'] || [document]).map(item => item['@type'])
);
};
const home = read('public/index.html');
const profile = read('public/whoami/index.html');
const article = read('public/2025/11/01/tor-relays-project/index.html');
const writing = read('public/posts/index.html');
const donate = read('public/donate/index.html');
const tag = read('public/tags/tor/index.html');
const nojsHome = read('public/nojs/index.html');
const nojsArticle = read('public/nojs/2025/11/01/tor-relays-project/index.html');
const sitemap = read('public/sitemap.xml');
assert(structuredTypes(home).includes('WebSite'));
assert(!structuredTypes(home).includes('ProfilePage'));
assert(structuredTypes(profile).includes('ProfilePage'));
assert(structuredTypes(article).includes('BlogPosting'));
assert(structuredTypes(article).includes('BreadcrumbList'));
assert.match(article, /<title>Why I Built Tor Guard Relay \| Br🤖ken B🤖tnet<\/title>/);
assert.match(article, /property=["']?og:image:alt["']?/i);
assert.match(article, /name=["']?twitter:image:alt["']?/i);
assert.match(profile, /property=["']?og:image["']?\s+content=["']?https:\/\/brokenbotnet\.com\/images\/social\/whoami\.jpg["']?/);
assert.match(writing, /property=["']?og:image["']?\s+content=["']?https:\/\/brokenbotnet\.com\/images\/social\/writing\.jpg["']?/);
assert.match(donate, /property=["']?og:image["']?\s+content=["']?https:\/\/brokenbotnet\.com\/images\/social\/donate\.jpg["']?/);
assert.match(home, /\/css\/site\.min\.[a-f0-9]+\.css/);
assert.match(home, /\/js\/site\.min\.[a-f0-9]+\.js/);
assert.match(home, /integrity=["']?sha384-/);
assert.match(home, /Project statistics last verified/);
assert.match(article, /class=["']?heading-anchor["']?/);
assert.match(article, /class=["']?footnotes["']?\s+role=["']?doc-endnotes["']?/);
assert.match(article, /role=["']?doc-backlink["']?/);
assert(!home.includes('cloud.umami.is'));
assert.match(tag, /name=["']?robots["']?\s+content=["']noindex, follow["']/i);
assert(!sitemap.includes('/tags/'));
assert(!sitemap.includes('/nojs/'));
assert.match(nojsHome, /<meta\s+name=["']?robots["']?\s+content=["']noindex, follow["']/i);
assert.match(nojsHome, /<link\s+rel=["']?canonical["']?\s+href=["']?https:\/\/brokenbotnet\.com\/["']?/i);
assert.match(nojsArticle, /<link\s+rel=["']?canonical["']?\s+href=["']?https:\/\/brokenbotnet\.com\/2025\/11\/01\/tor-relays-project\/["']?/i);
assert(!/<script\b/i.test(nojsHome));
assert(!/<script\b/i.test(nojsArticle));
for (const file of [home, profile, article, tag]) {
assert(!/<meta\s+name=["']?keywords["']?/i.test(file));
}
NODE
- name: Run Lighthouse quality budgets
shell: bash
env:
LHCI_BUILD_CONTEXT__CURRENT_HASH: ${{ github.sha }}
run: |
npx --yes @lhci/cli@0.15.1 collect --config=.lighthouserc.json
npx --yes @lhci/cli@0.15.1 assert --config=.lighthouserc.json
- name: Confirm generated output is not tracked
shell: bash
run: test -z "$(git ls-files public)"
- name: Write build summary
if: always()
shell: bash
run: |
{
echo "## Hugo validation"
echo
echo "- Commit: \`${GITHUB_SHA}\`"
echo "- Hugo: \`$(hugo version | head -1)\`"
echo "- Theme: \`$(git -C themes/risotto describe --tags --always)\`"
echo "- Checked: \`$(date +'%Y-%m-%d %H:%M')\`"
echo "- Result: \`${{ job.status }}\`"
} >> "$GITHUB_STEP_SUMMARY"
config:
name: Repository configuration
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Verify theme submodule registration
shell: bash
run: |
set -euo pipefail
mapfile -t configured_themes < <(
git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | awk '{ print $2 }'
)
mapfile -t tracked_themes < <(
git ls-files --stage themes | awk '$1 == "160000" { print $4 }'
)
test "${#configured_themes[@]}" -eq 1
test "${configured_themes[0]}" = "themes/risotto"
test "${#tracked_themes[@]}" -eq 1
test "${tracked_themes[0]}" = "themes/risotto"
test "$(git config -f .gitmodules --get submodule.themes/risotto.url)" = "https://github.com/joeroe/risotto.git"
- name: Validate JSON files
shell: bash
run: |
set -euo pipefail
jq empty .lighthouserc.json
jq empty .github/renovate.json
jq empty data/project_stats.json
jq empty static/.well-known/nostr.json
jq empty static/site.webmanifest
- name: Check workflow syntax
uses: raven-actions/actionlint@v2