Quartz Hosting

Canonical deployment guide for hosting this Quartz site and its live dev environment on 178.18.250.170.

Deployment Architecture

Utilizes Nginx as a reverse proxy (TLS, HTTP routing, WS upgrades) and systemd user services for background Node.js processes.

1. Nginx Configuration & Port Split Design

HMR WebSockets share domains and SSL certificates by proxying upgrades inside HTTPS server blocks:

  • Tinker Bench (Dev): Port 3002 ssl -> Local WS 3003.
  • Standalone Vault (q5vault): Port 3005 ssl -> Local WS 3006.
  • Udytor Vault: Port 3007 ssl -> Local WS 3008.

Private services are secured via basic auth (/etc/nginx/.htpasswd_q5vault).

A. Main Site (/etc/nginx/sites-available/quartz.loca.zone)

server {
    server_name quartz.loca.zone;
    root /var/www/quartz-loca/current;
    index index.html;
    error_page 404 /404.html;
 
    location / {
        try_files $uri $uri/ =404;
    }
 
    listen 443 ssl;
    # ... certbot ssl lines ...
}
server {
    if ($host = quartz.loca.zone) {
        return 301 https://$host$request_uri;
    }
    server_name quartz.loca.zone;
    listen 80;
    return 404;
}

B. Tinker Bench (/etc/nginx/sites-available/dev.quartz.loca.zone)

server {
    server_name dev.quartz.loca.zone;
 
    # Primary Web Traffic -> Node.js HTTP Server
    location / {
        proxy_pass http://localhost:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
 
    # Hot-Reload WebSocket -> Node.js WS Server
    location /ws {
        proxy_pass http://localhost:3003;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
    }
 
    listen 443 ssl;
    # ... certbot ssl lines ...
}
 
# The WS Public Proxy (Listens 3002 TLS, forwards to 3003)
server {
    listen 3002 ssl;
    server_name dev.quartz.loca.zone;
    # ... certbot ssl lines ...
 
    location / {
        proxy_pass http://localhost:3003;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
    }
}

C. Independent Sandbox (/etc/nginx/sites-available/q5vault.quartz.loca.zone)

Proxying to 8087 (HTTP) and 3006 (WS). Secured with .htpasswd_q5vault.

D. Udytor q5vault Wiki (/etc/nginx/sites-available/wiki.udyt.loca.zone)

Proxying to 8083 (HTTP) and 3008 (WS).

1.1 Stream Block for Hot-Reload (WebSockets) [Alternative/Legacy]

Verify before use: The architecture section above handles WS proxying via HTTP server blocks with standard Nginx configuration (which Certbot supports natively). Below is the stream block method, which is an alternative setup where Certbot doesn’t configure stream blocks automatically.

Certbot doesn’t configure stream blocks for WebSockets over TLS, so the following can be appended to /etc/nginx/nginx.conf (outside the http { ... } block):

stream {
    server {
        listen 3002 ssl;
        proxy_pass 127.0.0.1:3003;
 
        ssl_certificate /etc/letsencrypt/live/dev.quartz.loca.zone/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/dev.quartz.loca.zone/privkey.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;
    }
}

2. Let’s Encrypt Certificates

sudo certbot --nginx -d quartz.loca.zone
sudo certbot --nginx -d dev.quartz.loca.zone
sudo certbot --nginx -d q5vault.quartz.loca.zone

3. systemd Services

Configured in ~/.config/systemd/user/.

A. Tinker Bench: quartz-tinker.service (Port 8081)

[Unit]
Description=Quartz Tinker Bench
After=network.target
 
[Service]
Type=simple
WorkingDirectory=/home/loca/dev/quartz/quartz
ExecStart=/home/loca/dev/quartz/quartz/node-v22.16.0-linux-x64/bin/npx quartz build --serve --port 8081 --wsPort 3003
Restart=always
 
[Install]
WantedBy=default.target

B. Deployer API: quartz-deployer.service (Port 8084)

Webhook listener for automated deployments.

C. Standalone Vault: quartz-q5vault.service (Port 8087)

Runs independent q5vault.

D. Udytor q5vault Wiki: udyt-q5vault.service (Port 8083)

Runs Udytor-specific wiki environment.

Management Commands:

systemctl --user daemon-reload
systemctl --user enable quartz-tinker
systemctl --user start quartz-tinker
systemctl --user status quartz-tinker
loginctl enable-linger loca

Domain Responsibilities & Subdomain Map

Domain / SubdomainPrimary Purpose / ResponsibilityPortBackend Service / Path
quartz.loca.zoneMain site (Static Generation)80/443/var/www/quartz-loca/current
dev.quartz.loca.zoneLive editing (Tinker Bench)8081quartz-tinker.service
:3002 (dev)WebSocket HMR proxy3002->3003Local Node WS server
q5vault.quartz.loca.zoneStandalone private vault8087quartz-q5vault.service
wiki.udyt.loca.zoneStandalone udyt project wiki8083udyt-q5vault.service

Alternative Hosting Options

Alternative A β€” Local-only DNS

Use /etc/hosts or Pi-hole/dnsmasq on your LAN. TLS would be HTTP-only or require mkcert. For hot reload, omit --remoteDevHost and connect to ws://localhost:3002. Not chosen: No trusted public HTTPS.

Alternative B β€” Split-horizon DNS

Internal resolver returns LAN IP, public returns none. Not chosen: Public reference would be unreachable without a VPN.

Alternative C β€” Tailscale / WireGuard MagicDNS

Use the mesh hostname (no public A record). Set --remoteDevHost to your MagicDNS name. Not chosen: Limits access to the VPN mesh.

Alternative D β€” Cloudflare Tunnel

Run cloudflared to :8081. You would need a separate TCP ingress for the WebSocket port 3002. Not chosen: More moving parts than using Nginx on an owned VPS.

Alternative E β€” Docker-only

Use the repo’s Dockerfile to run npx quartz build --serve on 8080. Not chosen: Lack of persistent homelab Nginx integration.

Alternative F β€” Static-only

Skip the dev subdomain and systemd service completely; run local npm run serve:docs only when needed. Not chosen: The goal is a persistent live tinker bench.


Subdomain Diagnostics

A. dev.wiki.udyt.loca.zone 404/403 Root Cause

  • Symptom: dev.wiki.udyt.loca.zone HTTP returns 404; HTTPS returns 403.
  • Cause: Port mismatch in Nginx (8082 vs 8080 in .service), and Certbot missing valid SSL. Basic auth path error (.htpasswd_q5vault).

B. Subdomain Port Conflict

  • Symptom: Conflict on port 8083 between wiki.udyt.loca.zone and q5vault.quartz.loca.zone.
  • Resolution: Reassigned q5vault to 8087.

C. wiki.tree.loca.zone Directory Listing Issue

  • Symptom: Server returned directory index/file list instead of rendered wiki.
  • Cause: Nginx root pointed to unbuilt source instead of public/.