Book402 runs on minimal infrastructure — a single VPS handles everything.
System Architecture¶
graph TD
subgraph Internet
U[Users / Agents]
DNS[book402.com
docs.book402.com] end subgraph QuimServer["QuimServer (Hetzner CX23)"] subgraph Nginx["Nginx (port 80)"] N1["/ → React SPA"] N2["/graph, /books, /search → API proxy"] N3["docs.book402.com → MkDocs"] end subgraph App["Application Layer"] API["Book402 API
(Node.js, PM2, port 3002)"] DOC["MkDocs Static Files
(/opt/book402-docs)"] end subgraph Data["Data Layer"] DB["SQLite DB
(3,807 books, 23,145 chunks)"] end subgraph Infra["Infrastructure"] FAC["x402 Facilitator
(Docker, port 8402)"] SWAP["2GB Swap"] end end subgraph Blockchain BASE["Base L2
(USDC settlements)"] end U --> DNS --> Nginx N1 --> DOC N2 --> API API --> DB API --> FAC FAC --> BASE
docs.book402.com] end subgraph QuimServer["QuimServer (Hetzner CX23)"] subgraph Nginx["Nginx (port 80)"] N1["/ → React SPA"] N2["/graph, /books, /search → API proxy"] N3["docs.book402.com → MkDocs"] end subgraph App["Application Layer"] API["Book402 API
(Node.js, PM2, port 3002)"] DOC["MkDocs Static Files
(/opt/book402-docs)"] end subgraph Data["Data Layer"] DB["SQLite DB
(3,807 books, 23,145 chunks)"] end subgraph Infra["Infrastructure"] FAC["x402 Facilitator
(Docker, port 8402)"] SWAP["2GB Swap"] end end subgraph Blockchain BASE["Base L2
(USDC settlements)"] end U --> DNS --> Nginx N1 --> DOC N2 --> API API --> DB API --> FAC FAC --> BASE
Server Specs¶
| Spec | Value |
|---|---|
| Provider | Hetzner |
| Plan | CX23 |
| Cost | €2.99/month |
| CPU | 2 vCPU (Intel Xeon Skylake) |
| RAM | 4 GB + 2 GB swap |
| Disk | 38 GB SSD |
| OS | Ubuntu 24.04 LTS |
| Location | Helsinki, Finland |
Services¶
| Service | Port | Manager | Purpose |
|---|---|---|---|
| Nginx | 80 | systemd | Reverse proxy + static files |
| Book402 API | 3002 | PM2 | REST API + x402 middleware |
| x402 Facilitator | 8402 | Docker | Payment verification & settlement |
Network¶
graph LR
subgraph External
A[book402.com] -->|DNS| IP[135.181.111.17]
B[docs.book402.com] -->|DNS| IP
end
subgraph QuimServer["135.181.111.17"]
IP --> NGX[Nginx :80]
NGX -->|"book402.com /"| SPA[React SPA]
NGX -->|"book402.com /api/*"| API[Node.js :3002]
NGX -->|"docs.book402.com"| DOCS[MkDocs static]
API -->|localhost| FAC[Facilitator :8402]
end
subgraph Blockchain
FAC -->|RPC| BASE[mainnet.base.org]
end
Firewall¶
UFW rules (deny by default):
All internal services (API 3002, Facilitator 8402) bind to 127.0.0.1 only.
Wallet¶
| Field | Value |
|---|---|
| Address | 0x6B1925e4a1f779797eF51A18A4694B59FFb60Aba |
| Network | Base (Chain ID 8453) |
| Asset | USDC |
| Purpose | Receive x402 payments, sign facilitator settlements |
Security
The private key is stored in /opt/x402-facilitator/config.json (chmod 600, root only). The API server does NOT have access to the private key — only the facilitator container does.
Deployment¶
# API updates
cd /opt/feralgrind
git pull
cd tools && npx tsc -p tsconfig.json
pm2 restart feralgrind-api
# Landing page updates
cd ~/projects/book402.com
npm run build
rsync -avz --delete dist/ quimserver:/opt/book402-landing/
# Docs updates
cd ~/projects/book402.com/docs
mkdocs build
rsync -avz --delete site/ quimserver:/opt/book402-docs/