RUNBOOK — blog-quarto
Operational reference for the Quarto blog at pietstam.nl, hosted on Codeberg Pages via Forgejo CI.
Architecture overview
Local (Mac) Forgejo CI (Codeberg) Live site
────────────────────── ────────────────────── ─────────
posts/, talks/, *.qmd push to main triggers:
_freeze/ (frozen outputs) → quarto render → pages branch
data/zotero-cache.json force-push _site/ to pages pietstam.nl
scripts/fetch-zotero.js
Key branches: - main — source; edit here, never rebase - pages — auto-generated by CI; never edit manually
Required Forgejo secrets (Settings → Secrets): - CODEBERG_SECRET — personal access token with write access to the repo - ZOTERO_API_KEY — only needed if the Zotero endpoint becomes authenticated
Day-to-day operations
Publish a new blog post (no code)
- Create
posts/YYYY-MM-DD-title/index.qmd - Commit and push to
main - CI renders and deploys automatically (~2 min)
Publish a new blog post (with R/Python code chunks)
Create
posts/YYYY-MM-DD-title/index.qmdRender locally to freeze outputs:
quarto render posts/YYYY-MM-DD-title/index.qmdCommit both the
.qmdand_freeze/posts/YYYY-MM-DD-title/Push to
main— CI uses the frozen outputs, does not re-execute code
Preview locally before pushing
quarto preview # live-reload dev server at localhost:4200Render the full site locally
quarto renderZotero cache
How it works
scripts/fetch-zotero.js fetches all items from the Zotero “My Publications” public API and writes them to data/zotero-cache.json. It uses If-Modified-Since-Version to skip unchanged libraries (304 response). Pagination is automatic via the Total-Results response header.
The cache is consumed client-side by assets/js/zotero.js on publications.qmd.
Automatic refresh
The scheduled workflow (.forgejo/workflows/scheduled.yml) runs daily at 03:00 UTC and commits an updated cache to main only if the Zotero library changed (no-op otherwise). This triggers the publish workflow, redeploying the site.
Manual refresh
Option A — Forgejo UI: Go to Actions → “Refresh Zotero cache” → Run workflow.
Option B — local:
node scripts/fetch-zotero.js
git add data/zotero-cache.json
git commit -m "Update Zotero cache"
git push origin mainTroubleshooting the cache
| Symptom | Check |
|---|---|
| Publications page blank | Open browser console; check /data/zotero-cache.json loads (200) |
| Cache not updating | Check scheduled workflow logs in Forgejo Actions |
| API returns 429 | Zotero rate limit; the public endpoint allows ~100 req/min |
| Stale items in cache | Confirm items are in Zotero “My Publications” collection, then force refresh |
CI workflows
publish.yml — triggered on push to main
- Checkout source
- Install Quarto 1.8.27
quarto render(uses frozen outputs for posts/talks; runsscripts/fetch-zotero.jsas pre-render hook)- Force-push
_site/topagesbranch
To update the Quarto version: edit the download URL and version string in .forgejo/workflows/publish.yml.
scheduled.yml — daily Zotero cache refresh
Runs at 03:00 UTC. Can also be triggered manually via workflow_dispatch from the Forgejo Actions UI.
Computational freeze
Posts and talks use freeze: true (set in posts/_metadata.yml and talks/_metadata.yml). Frozen HTML outputs live in _freeze/. CI never executes R or Python.
To re-execute a single post:
rm -rf _freeze/posts/YYYY-MM-DD-title
quarto render posts/YYYY-MM-DD-title/index.qmd
# commit the new _freeze/ outputsTo re-execute everything: rm -rf _freeze/ && quarto render
Adding a talk
- Create
talks/YYYY-MM-DD-title/index.qmd(talk landing page) - Optionally add
talks/YYYY-MM-DD-title/slides.qmd(Revealjs) - Add an entry to
talks/talks_YYYY.yml(create the file if it’s a new year) - Commit and push
Secrets rotation
- Generate a new Codeberg personal access token with
repowrite scope - Update
CODEBERG_SECRETin Forgejo repo Settings → Secrets - Verify by triggering a manual workflow run
Recovering from a broken pages branch
The pages branch is always reconstructed by CI from _site/. If it becomes corrupt:
- Delete the
pagesbranch on Codeberg - Push any commit to
mainto trigger CI - CI will create a fresh
pagesbranch
File reference
| Path | Purpose |
|---|---|
_quarto.yml |
Site config, navbar, theme, pre-render hook |
posts/_metadata.yml |
Shared frontmatter for all posts (freeze, banner, TOC) |
talks/_metadata.yml |
Shared frontmatter for all talks |
scripts/fetch-zotero.js |
Zotero API fetch → data/zotero-cache.json |
assets/js/zotero.js |
Client-side Zotero cache renderer |
data/zotero-cache.json |
Cached Zotero API response (committed to repo) |
.forgejo/workflows/publish.yml |
CI: render + deploy on push to main |
.forgejo/workflows/scheduled.yml |
CI: daily Zotero cache refresh |
_freeze/ |
Frozen computation outputs (committed) |
_site/ |
Built site (not committed; lives only on pages branch) |