Yggdrasil
Personal knowledge base built as a static website with Calepin, a Typst-based static site generator.
Layout
site/— website sources:calepin.toml+.typpagessite/themes/yggdrasil/— local theme extending the bundledcalepintheme (scoped sidebars, academic-styled landing)public/— generated site (gitignored)tools/— pinnedcalepinandtypstbinaries (gitignored, downloaded on demand)scripts/— helper scripts used by the Justfile and CIlegacy_docs/— the old Docsify documentation, kept unmodified for reference
Quick start
just is the only prerequisite — the toolchain is downloaded and
checksum-verified into tools/ on first use (no global installs, no cargo):
just # list recipes
just tools # download the pinned toolchain into tools/
just build # compile site/ into public/
just preview # build, then serve the site locally
just watch # rebuild on change and serve
just clean # remove public/ and build caches
just ci # run the CI workflow locally with act (needs act + docker)
Adding a page
Create site/<category>/<page>.typ:
#set document(title: [Page title])
#metadata((title: "Page title", translation_key: "category-page")) <website-metadata>
#title()
= Page title
Content…
and reference it in site/calepin.toml:
[[sidebar.section.item]]
target = "<category>/<page>.typ"
Sidebar labels come from the page's #metadata(...) line, not from the config.
Every page also renders a printable PDF twin (pdf = true in
calepin.toml, selectable in the page's view picker). Add pdf: false to a
page's #metadata(...) to skip its PDF, as 404.typ does.
Adding a category
- Create
site/<category>/with anindex.typ, using#metadata((title: "Overview", ...))for its sidebar label. - Add a
[[sidebar.section]]named after the category incalepin.toml, listing the index page and its sub-pages. - Add a
[[menus.main]]entry with the category'sindex.typastargetand the category name aslabel(e.g.Home,IT), so it appears in the top navigation bar. Thelabelis required here because the index page's metadata title isOverview.
Pages inside a category show only that category's sidebar section — the
local theme (site/themes/yggdrasil/partials/site-nav-sidebar.html) filters
the sidebar to the section containing the current page. Pages outside every
category (landing page, 404, standalone pages) fall back to the full
sidebar.
Deployment
just build produces a fully static site in public/: HTML pages with
printable PDF twins, plus sitemap.xml, robots.txt, and llms.txt. The
Gitea Actions workflow in
.gitea/workflows/build.yml builds the site on every push with the same
pinned toolchain and uploads public/ as an artifact.
Pushes to main are also published: the deploy job force-pushes public/
to the repo's pages branch using the automatic Actions token (no secrets to
configure). A Gitea webhook on the repo then notifies the self-hosted
git-pages server, which clones
the pages branch and serves the site. The push step is gated with
if: ${{ !env.ACT }} so local act runs never publish, and
workflow_dispatch enables manual publishes from the Gitea UI.