Docs Portal.
A single-file FastAPI application that discovers every project under a documentation root, renders their Markdown and HTML docs in the browser, and exports any document to PDF or an entire project to ZIP.
Install
Transcribed from the repository README; not yet executed from a clean environment:
git clone https://github.com/bulletproofsoftware-ai/bulletproof-docs-portal.git cd bulletproof-docs-portal python3 -m venv .venv .venv/bin/pip install -r requirements.txt .venv/bin/uvicorn app:app --host 127.0.0.1 --port 8090

bulletproof-docs-portal/docs/media/1Problem Statement
Documentation accumulates across many project repositories — READMEs, docs/ trees, specification folders — with no unified way to browse it. Finding a document means knowing which repository it lives in and reading raw Markdown in an editor.
Handing a document to a stakeholder is worse. Markdown in a Git repository is not a deliverable; producing a clean PDF usually means pasting into a word processor and losing the formatting.
The portal solves exactly this and deliberately nothing more. As the README states: no auth, no database, no compliance service — one process that reads a directory tree and serves it. The scope is intentionally minimal so it can be run locally against any collection of projects without infrastructure.
2Architecture
The whole application lives in app.py. DOCS_ROOT — configurable by environment variable, defaulting to ~/Code — is resolved once at import, and every route is a read-only view over that tree.
Project discovery
_list_projects() iterates the immediate subdirectories of DOCS_ROOT and keeps only those containing at least one matching document, returning a name and a document count. Projects with no docs are hidden.
Document indexing
_list_docs_in() globs a fixed pattern set (*.md, docs/**/*.md, docs/**/*.html, **/README.md, and others), dedupes through a seen set, and skips anything under .git, node_modules, .venv, __pycache__, dist, or build.
Path-traversal guard
_safe_path() resolves both the project directory and the requested document, raising HTTP 400 unless the project resolves under DOCS_ROOT and the file resolves under the project. Every file-serving route goes through it.
Rendering
_render_markdown() runs the markdown library with fenced_code, tables, toc, codehilite, sane_lists, and attr_list, giving syntax-highlighted code blocks and anchored headings.
PDF export
doc_pdf() composes a print-specific document with a provenance line and calls WeasyPrint's HTML(...).write_pdf(). PRINT_CSS sets letter paper, 1in margins, a counter(page) " / " counter(pages) footer, and a running header via string-set.
Presentation Details
- Two separate stylesheets —
PAGE_CSSfor the browser andPRINT_CSSfor PDF output — so screen and print are tuned independently. _page()wraps every body fragment in one shared HTML shell, keeping navigation and styling consistent across views.- The project view groups documents by their top-level directory into
<details open>blocks, so a largedocs/tree stays navigable. project_zip()streams an in-memoryzipfile.ZipFileusingZIP_DEFLATED, with archive names of the form{project}/{relative-path}.- PDF filenames are sanitized with
re.sub(r"[^\w.-]+", "_", ...)before being sent in the response.
3Requirements
Requirements reflect the behaviour implemented in app.py.
| ID | Requirement |
|---|---|
| REQ-DP-001 | Discover projects as immediate subdirectories of DOCS_ROOT containing at least one document. |
| REQ-DP-002 | Index .md and .html documents using a fixed include-pattern set. |
| REQ-DP-003 | Exclude build, dependency, and VCS directories from indexing. |
| REQ-DP-004 | Reject any request resolving outside DOCS_ROOT or its project directory with HTTP 400. |
| REQ-DP-005 | Render Markdown with fenced code, tables, table-of-contents, and syntax highlighting. |
| REQ-DP-006 | Serve a raw-source view of any indexed document. |
| REQ-DP-007 | Export any document to PDF with page numbers and a running header. |
| REQ-DP-008 | Export an entire project as a streamed ZIP archive. |
| REQ-DP-009 | Group documents by top-level directory in the project view. |
| REQ-DP-010 | Expose a health endpoint reporting the docs root and project count. |
4Interfaces
A small read-only HTTP surface. There is no CLI, no hook, and no MCP tool.
| Route | Purpose |
|---|---|
| GET / | Project index |
| GET /p/{project} | Grouped document tree for a project |
| GET /p/{project}/d/{doc_path} | Rendered document view |
| GET /p/{project}/raw/{doc_path} | Raw document source |
| GET /p/{project}/pdf/{doc_path} | PDF export of a single document |
| GET /p/{project}/zip | ZIP archive of an entire project |
| GET /healthz | Health check with docs root and project count |
| DOCS_ROOT (env) | Documentation root directory, default ~/Code |
5Integration Points
The portal integrates with the local filesystem and nothing else — the property that makes it safe to run anywhere.
- Filesystem — the only data source. Any directory of projects works without configuration beyond
DOCS_ROOT. - WeasyPrint — PDF rendering, which requires native Cairo, Pango, and gdk-pixbuf libraries on the host.
- Pygments — syntax highlighting, applied through the
codehiliteMarkdown extension. - No database, no authentication provider, no external network calls.
6Repository
A deliberately small repository: one application module and pinned dependencies, alongside the usual release documentation, SBOM and scan evidence.
| Path | Purpose |
|---|---|
| app.py | The complete application — discovery, rendering, PDF, ZIP, routing |
| requirements.txt | Exact-pinned dependencies (FastAPI, uvicorn, markdown, pygments, weasyprint) |
| README.md | Install, native dependency, and configuration notes |
| docs/ | Install, administrator and usage guides, CycloneDX SBOM, and the security scan report |
| .github/workflows/ci.yml | Python 3.12 CI |
| SECURITY.md | Vulnerability disclosure policy |
| LICENSE / NOTICE | Apache-2.0 |
7Implementation Notes
Constraints that matter when deciding where to run this.
- No authentication or authorization of any kind. The README says so explicitly and the intended bind address is
127.0.0.1. Do not expose it to a network without a proxy in front. - Rendered
.htmldocuments are injected into the page verbatim, so the portal trusts the contents of its docs root. - Only immediate subdirectories of
DOCS_ROOTare treated as projects; nested project layouts are not discovered. - PDF generation runs synchronously inside the async request handler, so large exports block that worker.
- WeasyPrint needs native libraries —
brew install cairo pango gdk-pixbuf libffion macOS, the equivalentlibcairo2/libpangopackages on Debian. DOCS_ROOTis the only environment variable the application reads. The set of indexed file patterns is the hardcodedINCLUDE_PATTERNSconstant inapp.py, so the include set is not currently configurable without editing the source.