Project: infra-wiki — two-audience docs publishing
Status: live — full site on wiki (LXC 115) for the tailnet, curated portfolio subset on Cloudflare Pages.
Publish the sanitized documentation vault so people without Obsidian can browse it — friends/family get the full picture over the tailnet, and a strictly curated subset goes public for professional use. One source, two audiences, no forking of content.
Design
One source, two builds
The infra-wiki repo carries content + config only — no vendored
Quartz source. Builds run the pinned Quartz OCI image
(ghcr.io/jackyzha0/quartz:sha-*) under rootless podman, so the host
needs no Node toolchain:
./build.sh→public/— the complete site, deployed to wiki./build.sh --portfolio→public-portfolio/— staged throughportfolio.exclude(drops whole pages) plus marker-driven stripping, deployed to Pages (huevoconchorizo.pages.dev)./deploy.sh local|portfolio— rsync--deleteto the webroot, or containerized wrangler upload; creds resolve env →.deploy.env→pass./sync-content.sh— pulls the sanitized docs mirror intocontent/; curation markers live ininfra-docs, never edited in the wiki copy
Curation markers
private HTML-comment markers in the source strip content only from
the portfolio build — invisible comments on the full site. Three
patterns: standalone-line block regions, single-line inline spans, and
a trailing marker that drops a whole line (table rows). An empty pair
after a wikilink unlinks it for the portfolio while keeping the link
on the full site.
build.sh validates before stripping and fails closed on
malformed markers — the classes that actually bit during development:
- cross-line spans — open/close on different lines silently ate visible text (the residue rule deleted whole lines)
- markers in headings/code fences — Quartz renders them literally there; they’re not invisible comments in those contexts
- any surviving marker — hard error after the strip pass
Serving
Quartz emits leaf pages as x.html + folder x/index.html — “clean
URLs” that static hosts resolve natively but a bare file_server
doesn’t. The guest Caddyfile needs try_files {path} {path}.html {path}/index.html. Details in wiki.
Self-containment
No runtime CDN: fonts and the graph libraries (d3, pixi) are vendored.
A tiny local theme-default plugin seeds dark mode for first-time
visitors — exports must be factories returning components, since
Quartz’s registry calls the export and uses the return value (a
null-rendering component gets skipped before its resources collect).
Per-audience pageTitle overrides come from env/pass so the real
project name never enters the repo.
Gotchas worth remembering
- Quartz’s
CNAMEemission is a GitHub Pages convention — inert on Cloudflare Pages. rsync --deleteon the webroot keeps emitted-file maps (contentIndex.json) and HTML in sync; a partial deploy could leave stale link maps.- Cloudflare Pages
noindexs per-deploy preview URLs only — the production*.pages.devhostname is indexable.