Buckyverse Library (DB-free) — One-page PRD + Engineering Spec (DigitalOcean Ubuntu)
Hosting target: existing BUCKYVERSE Ubuntu server on DigitalOcean
Principle: no database for core functionality. Catalog + routing come from a build-generated manifest.json and immutable, versioned artifacts.
1) One-page PRD
Summary
Replace Wiki.js with a purpose-built Library + Reader platform that publishes math-heavy books from LaTeX repos as reflowable HTML (and optionally EPUB/PDF). The platform should be fast, mobile-friendly, and simple to operate on a single Ubuntu server.
Problem
Wiki.js is poorly suited for book-scale, math-heavy content and for a LaTeX-repo publishing workflow. Rendering, navigation, and performance degrade as manuscripts grow.
Goals
- Reader experience
- Responsive, reflowable reading on mobile/desktop
- Adjustable font size (and optionally theme)
- Reliable math rendering (converter output preferred; MathJax fallback)
- Table of Contents + next/prev navigation
- Shareable deep links to sections
- Maintainer experience
- CI publishes artifacts from LaTeX repos
- Deterministic versioned URLs
- No DB; minimal ops; easy rollback
- Performance
- Aggressive caching for versioned artifacts
- Small dynamic surface area (catalog + redirects)
Non-goals (MVP)
- In-browser editing/wiki authoring
- Accounts/personalization
- Full-corpus full-text search (Phase 2)
- Equation-level permalinks (Phase 2)
Key user flows
- Browse library → open a book → choose version (latest default) → read with TOC + font controls
- Share a section link
- Download PDF/EPUB as available
Success metrics
- “Hard pages” render correctly in the web reader (on mobile + desktop)
- No horizontal scrolling for body text on mobile; font-size controls work
- Publish workflow is repeatable: tag/merge → build → deploy → new version visible
2) Engineering Spec (Single Ubuntu server)
2.1 Recommended architecture
Nginx
- Terminates TLS (Let’s Encrypt)
- Serves static, versioned book artifacts directly from disk
- Adds caching headers (immutable for versioned paths)
- Proxies dynamic routes (
/library,/books/...) to Phoenix
Phoenix
- Loads
manifest.jsonfrom disk into memory (no DB) - Renders:
/librarycatalog/books/:book_idlanding page/books/:book_id/latest/*pathredirect
- Optionally renders a “reader chrome” page (header/TOC/font controls) and links/embeds artifact pages
Why Nginx serves artifacts
- Faster and simpler for large static trees
- Correct caching without custom Plug pipelines
- Keeps Phoenix workload small and predictable
2.2 Filesystem layout (atomic content deploys)
Use a release directory + symlink flip to update content atomically.
/srv/buckyverse/
books/
releases/
2026-02-03_120000/
manifest.json
regular_polytopes/1.4.0/...
order_in_space/0.9.1/...
2026-02-10_090500/
manifest.json
...
current -> /srv/buckyverse/books/releases/2026-02-10_090500
Nginx points to /srv/buckyverse/books/current as the static root.
Publishing = upload new release dir → update current symlink → reload services.
2.3 URL & routing design
Public pages (Phoenix)
GET /→ redirect to/libraryGET /library→ list books from manifestGET /books/:book_id→ book landing page (metadata + versions + links)GET /books/:book_id/latest/*path→ redirect to latest version path
Artifacts (served by Nginx)
GET /static/books/<book_id>/<version>/...→ files on disk under/srv/buckyverse/books/current/...
Important: Artifacts must be immutable by version. Never overwrite an existing version directory.
2.4 Manifest design
Location
/srv/buckyverse/books/current/manifest.json(symlink target changes on publish)
Minimal schema (draft)
{
"generated_at": "2026-02-03T12:00:00Z",
"books": [
{
"id": "regular_polytopes",
"title": "Regular Polytopes",
"authors": ["H. S. M. Coxeter"],
"description": "...",
"tags": ["geometry", "polytopes"],
"latest_version": "1.4.0",
"versions": [
{
"version": "1.4.0",
"html_root": "/static/books/regular_polytopes/1.4.0/",
"html_entry": "index.html",
"toc": [{"title":"Chapter 1","href":"ch01.html"}],
"pdf_url": "/static/books/regular_polytopes/1.4.0/book.pdf",
"epub_url": "/static/books/regular_polytopes/1.4.0/book.epub"
}
]
}
]
}
2.5 Phoenix module boundaries
Buckyverse.Library.Manifest
Responsibility: Load + parse + validate manifest, expose read-only queries.
manifest_path/0reads config (MANIFEST_PATH)load!/0reads file and decodes JSONget/0returns decoded manifest from memorybooks/0,book!(id),latest_version(id),versions(id)
Storage
- Store parsed manifest in
:persistent_term(fast reads). - Reload strategy:
- simplest: reload on app restart after publishing
- better: lightweight watcher (GenServer) polls file mtime every N seconds and reloads if changed
Buckyverse.Library
Responsibility: Business queries derived from manifest.
list_books(opts)sorting/filteringfind_book(id)resolve_latest(book_id)and helpers for URL construction
Controllers
BuckyverseWeb.LibraryControllerindex/2renders/library
BuckyverseWeb.BookControllershow/2renders/books/:book_idlatest/2redirects/books/:book_id/latest/*path
Plugs
BuckyverseWeb.Plugs.CacheControl- apply caching headers to dynamic routes only
BuckyverseWeb.Plugs.SecurityHeaders(recommended)- CSP tuned for math rendering strategy (e.g., MathJax), fonts, and static assets
2.6 Nginx configuration with caching headers
Assumptions:
- Phoenix at
127.0.0.1:4000 - Artifacts at
/srv/buckyverse/books/current - Artifacts exposed under
/static/books/
server {
server_name buckyverse.org www.buckyverse.org;
# TLS handled here (LetsEncrypt recommended)
# listen 443 ssl http2;
# ssl_certificate ...
# ssl_certificate_key ...
# 1) Versioned artifacts: cache forever (immutable)
location ^~ /static/books/ {
alias /srv/buckyverse/books/current/;
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
# 2) Manifest: short cache + revalidate
location = /static/books/manifest.json {
alias /srv/buckyverse/books/current/manifest.json;
add_header Cache-Control "public, max-age=60, must-revalidate" always;
try_files $uri =404;
}
# 3) Phoenix dynamic routes
location / {
proxy_pass http://127.0.0.1:4000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Dynamic HTML: conservative caching (manifest-driven anyway)
add_header Cache-Control "public, max-age=60" always;
}
}
Caching rules summary
/static/books/<book>/<version>/...
Cache-Control: public, max-age=31536000, immutable/static/books/manifest.json
Cache-Control: public, max-age=60, must-revalidate/books/<id>/latest/...(Phoenix redirect)
recommendedCache-Control: public, max-age=60(ormax-age=0, must-revalidateif releases are frequent)/library,/books/<id>
Cache-Control: public, max-age=60(safe default)
2.7 Deployment workflow (content-only, no DB)
Publish a new release
- CI builds artifacts +
manifest.jsoninto a folder like2026-02-10_090500/ - Upload to:
/srv/buckyverse/books/releases/2026-02-10_090500/
- Atomic flip:
ln -sfn /srv/buckyverse/books/releases/2026-02-10_090500 /srv/buckyverse/books/current
- Reload:
nginx -s reload- If Phoenix reloads manifest only on boot:
systemctl restart buckyverse - If Phoenix has mtime watcher: restart not needed
Rollback is the same: repoint current to prior release.
2.8 systemd unit example (Phoenix release)
[Unit]
Description=Buckyverse Phoenix App
After=network.target
[Service]
User=www-data
WorkingDirectory=/srv/buckyverse/app
Environment=PORT=4000
Environment=PHX_HOST=buckyverse.org
Environment=MANIFEST_PATH=/srv/buckyverse/books/current/manifest.json
ExecStart=/srv/buckyverse/app/bin/buckyverse start
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
2.9 Reader UX notes (DB-free)
- Font size/theme stored client-side (localStorage)
- Apply as CSS variables on reader container for reflowable HTML
- Math strategy:
- prefer converter output (MathML/structured HTML)
- fallback to client-side MathJax for TeX blocks (scoped to content container)
2.10 MVP decisions (defaults)
- Nginx serves artifacts from
/srv/buckyverse/books/current - Phoenix serves catalog + redirects only
- Manifest loaded into memory (persistent_term) with optional mtime polling reload
- HTML artifacts chunked by section (recommended for performance)