Why this matters beyond the homelab: Docs-as-code pipelines, Git-backed knowledge bases, and retrieval-grounded AI assistants are how platform teams are attacking documentation drift right now. This is that pattern, end to end, on self-hosted infrastructure.
The Problem With Documentation
Documentation has a failure mode worse than not existing: existing and being wrong. A wiki that drifted from reality six months ago actively misleads you, and nothing in a traditional wiki tells you it drifted.
The Alliance Fleet Codex is my answer. Human-friendly authoring in BookStack, version control in Gitea, and a consumer that actually reads it every day: Jocasta, the fleet’s AI agent. When documentation feeds an agent that people query, staleness stops being invisible. Wrong docs produce wrong answers, and wrong answers get noticed.
Architecture
BookStack (authoring) Yavin-IV, Node-B
│ nightly export (n8n)
▼
Gitea (version control) git.tima.dev
│ 4am cron pull
▼
Jocasta (consumer) /root/.hermes/fleet-codex, Coruscant
Three properties fall out of this design:
- BookStack stays the source of truth for humans. Shelves, books, pages, search, a UI anyone can edit. The Codex spans 18 shelves covering every fleet system, including the ADR shelf.
- Gitea makes the documentation diffable. Every nightly export is a commit. Documentation changes get history, blame, and rollback, the same as code.
- Jocasta closes the loop. The agent’s knowledge directory refreshes from Git at 4am. Ask it how the backup pipeline works and the answer is grounded in yesterday’s documentation, not in whatever a model hallucinated from training data.
Identity across the stack is unified: BookStack, Gitea, and NetBox all authenticate through Authentik OIDC. One identity provider, one MFA posture, no local account sprawl. That part extends the zero-trust identity work from earlier in the fleet’s life, and it means the documentation platform has the same login story as everything else.
The 403 That Almost Won
The n8n export workflow built cleanly, authenticated cleanly, and then failed every push to Gitea with a 403. Token was valid. Permissions were correct. Repo existed. The kind of failure where every individual component reports healthy and the pipeline still refuses to work.
The root cause lived in Gitea’s app.ini: a mismatch between ROOT_URL and the URL the internal service traffic actually used. Gitea validates incoming requests against its configured root URL, and traffic arriving via the internal path did not match the external git.tima.dev identity, so Gitea rejected it as a cross-origin style violation. The fix was declaring LOCAL_ROOT_URL so internal API traffic validates against the internal address while the public identity stays intact.
[server]
ROOT_URL = https://git.tima.dev/
LOCAL_ROOT_URL = http://localhost:3000/
One sed edit, one service restart, and the pipeline that had been failing for days pushed clean. Reverse-proxied applications with split internal/external identities produce this class of bug constantly: the app’s self-concept of its own URL disagrees with how traffic reaches it. Grafana OAuth taught me this lesson once already with its own root_url gotcha. Apparently I needed the Gitea edition too.
What Nightly Sync Buys
The 4am cadence is deliberate. Real-time sync sounds better and adds failure modes I do not need: webhook delivery, retry logic, partial-state handling. A nightly batch export is boring, observable, and easy to verify. The freshness requirement for infrastructure documentation is “today,” not “this second.”
The practical result: I edit a BookStack page after changing a firewall rule, and by the next morning the change is a Git commit and the agent answers questions with the new reality. The overhead of keeping docs current dropped to just editing the docs. The pipeline handles the rest.
Key Takeaways
- Docs-as-code does not require abandoning a friendly authoring UI. BookStack for humans, Git for history, both automated together.
- Giving documentation a consumer (an agent, a pipeline, anything that reads it daily) converts staleness from an invisible problem into a visible one.
- The ROOT_URL/LOCAL_ROOT_URL split is the recurring boss fight of self-hosted services behind reverse proxies. Learn the pattern once and you will see it everywhere.
- Boring sync beats clever sync. Nightly batch, one commit, verifiable in one glance.