MkDocs Material, Forge Markdown Viewer¶
Static site that renders the forge repo as a browsable, searchable docs site. Built with MkDocs + Material theme + awesome-pages auto-nav. Architecture redone 2026-04-29: split into a static HTTP server + a periodic-rebuild timer, replacing mkdocs serve (which had a flaky file watcher that silently dropped new files when render errors occurred upstream).
Architecture (post 2026-04-29)¶
Three systemd units, separation of concerns:
| Unit | Type | Role |
|---|---|---|
forge-mkdocs.service |
simple | python3 -m http.server 8000 from /home/justinwieb/forge-mkdocs-site; static files only |
forge-mkdocs-build.service |
oneshot | mkdocs build --quiet; rebuilds the static site dir |
forge-mkdocs-build.timer |
timer | fires forge-mkdocs-build.service every 2 minutes |
Effect: any new or edited .md is visible at the URL within ~2 minutes. No watcher state to corrupt, no in-memory rebuild crashes, and a single bad markdown file no longer takes down siblings (the static site keeps the prior good build for any file that fails to render).
Quick reference¶
| Thing | Path / value |
|---|---|
| Config | infra/mkdocs/mkdocs.yml |
| Venv | ~/.forge-venvs/mkdocs/ |
| Build output | /home/justinwieb/forge-mkdocs-site/ (this is what's served) |
| Service files | /etc/systemd/system/forge-mkdocs.service, forge-mkdocs-build.service, forge-mkdocs-build.timer |
| Local URL | http://192.168.86.50:8000/ (LAN), also Tailscale-reachable |
| Public URL | https://mkdocs.justinsforge.com/ (behind CF Access, Justin only) |
| Tunnel | media-server (id a55b7768-b42a-4d78-82b5-4a427bdbb9b7), via /cf-add |
How it works¶
- HTTP server is dumb-static:
python3 -m http.serverfrom the build dir. Doesn't crash on doc errors; just serves whatever is on disk. - Build timer fires
mkdocs buildevery 2 min. If a doc has a render error, the build keeps the prior good output for that file. docs_dir: ../../in mkdocs.yml points at the forge repo root, so paths in nav match git paths.exclude_docs:(gitignore-style, MkDocs ≥ 1.6) keeps secrets, scripts, logs, node_modules, etc. out.mkdocs-awesome-pages-pluginauto-generates the nav from the filesystem.
Why we migrated off mkdocs serve (2026-04-29 incident)¶
mkdocs serve ran continuously and was supposed to rebuild on file changes. In practice its watcher silently dropped specific files when their markdown had unbalanced markers (e.g. unmatched ** or stray quote), creating an empty target directory with no index.html, returning 404 with no error in the logs. Long-standing intermittent. Replacing serve with build on a timer + python -m http.server removed the watcher entirely.
Force a rebuild now¶
sudo systemctl start forge-mkdocs-build.service # one-shot rebuild (sync)
sudo systemctl status forge-mkdocs-build.service --no-pager
journalctl -u forge-mkdocs-build --no-pager -n 20 # see warnings/errors from the build
Restart the HTTP server¶
See timer schedule¶
Backup of old service unit¶
/etc/systemd/system/forge-mkdocs.service.bak-2026-04-29 keeps the prior mkdocs serve config in case rollback is ever needed.
Excluded paths¶
logs/ tasks/ data/ comms/ dashboard/ assets/ sites/ scripts/ personal/ clients/ notifications/ skills/ agents/ .claude/ .git/ .openclaw/ .vscode/ infra/mkdocs/ node_modules/ __pycache__/ .venv/ venv/
Plus ._* (macOS metadata), *.bak*, and most non-markdown extensions.
Cloudflare Access¶
The site MUST stay behind Cloudflare Access — it contains daily logs, infra docs, and references to credentials. Pattern matches n8n.justinkrystal.com:
- Zero Trust → Access → Applications → Self-hosted
- Application domain:
mkdocs.justinsforge.com - Policy: email =
[email protected](Justin's hardened account) - Session duration: 24h is fine
If the Access app is missing, remove the tunnel ingress with /home/justinwieb/forge/scripts/cloudflare/cf.py rm forge justinkrystal.com until it's back in place.
The CF token at ~/.forge-secrets/cloudflare.env does NOT have Access:Apps scope as of 2026-04-28 — Access apps must be created manually via dashboard, or the token must be rescoped first.
Adding new content¶
Just write markdown anywhere in the forge repo (memory/, nexus/, docs/, brands/, infra/, etc.). The 2-min rebuild timer picks it up automatically, or run sudo systemctl start forge-mkdocs-build.service to rebuild immediately.
Adjusting nav order¶
Drop a .pages file in any directory:
See awesome-pages docs for full syntax.
Encoding gotchas¶
MkDocs requires strict UTF-8. macOS AppleDouble ._* files and stray byte sequences will crash the build. The exclude list handles ._* already; if you hit UnicodeDecodeError, run:
python3 -c "
import sys; p = sys.argv[1]
with open(p,'rb') as f: data = f.read()
with open(p,'w',encoding='utf-8') as f: f.write(data.decode('utf-8', errors='replace').replace('
','\n'))
" path/to/bad.md
Why MkDocs Material¶
- Static, no DB, no live process risk
- Beautiful + mobile-responsive Material theme
- Built-in lunr.js full-text search (ranked, instant)
- Free and OSS (mkdocs-material 9.x)
- Periodic rebuild via systemd timer keeps content fresh without watcher fragility
Adding more zones / hostnames¶
To expose forge under a different host, use /cf-add <host> <zone> http://192.168.86.50:8000. The systemd service binds 0.0.0.0:8000 so any tunnel ingress pointing at that origin works.