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.
  • Quartz q5vault Sandbox: Port 3005 ssl -> Local WS 3004.
  • Udytor q5vault Wiki: Port 3008 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;
    location / { try_files $uri $uri.html $uri/ =404; }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/quartz.loca.zone/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/quartz.loca.zone/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

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

server {
    server_name dev.quartz.loca.zone;
    auth_basic "Private Tinker Bench";
    auth_basic_user_file /etc/nginx/.htpasswd_q5vault;
    satisfy any;
    allow 127.0.0.1;
    allow 178.18.250.170;
    deny all;
    location /api/deploy {
        proxy_pass http://127.0.0.1:8084;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    location / {
        proxy_pass http://127.0.0.1:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/quartz.loca.zone/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/quartz.loca.zone/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
 
server {
    listen 3002 ssl;
    server_name dev.quartz.loca.zone;
    ssl_certificate /etc/letsencrypt/live/quartz.loca.zone/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/quartz.loca.zone/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    allow 127.0.0.1;
    allow 178.18.250.170;
    deny all;
    location / {
        proxy_pass http://127.0.0.1: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)

server {
    server_name q5vault.quartz.loca.zone;
    location / {
        proxy_pass http://127.0.0.1:8087;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        auth_basic "Private Vault";
        auth_basic_user_file /etc/nginx/.htpasswd_q5vault;
    }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/quartz.loca.zone/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/quartz.loca.zone/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
 
server {
    listen 3005 ssl;
    server_name q5vault.quartz.loca.zone;
    ssl_certificate /etc/letsencrypt/live/quartz.loca.zone/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/quartz.loca.zone/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    location / {
        proxy_pass http://127.0.0.1:3004;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        auth_basic "Private Vault WS";
        auth_basic_user_file /etc/nginx/.htpasswd_q5vault;
    }
}

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

server {
    server_name wiki.udyt.loca.zone;
    location / {
        proxy_pass http://127.0.0.1:8083;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/udyt.loca.zone/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/udyt.loca.zone/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
 
server {
    listen 3008 ssl;
    server_name wiki.udyt.loca.zone;
    ssl_certificate /etc/letsencrypt/live/udyt.loca.zone/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/udyt.loca.zone/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
    location / {
        proxy_pass http://127.0.0.1:3008;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
}

2. Let’s Encrypt Certificates

sudo certbot --nginx -d quartz.loca.zone -d dev.quartz.loca.zone -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 Server
After=network.target
 
[Service]
Type=simple
WorkingDirectory=/home/loca/dev/quartz/quartz
Environment=PATH=/home/loca/dev/quartz/quartz/node-v22.16.0-linux-x64/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/loca/dev/quartz/quartz/node-v22.16.0-linux-x64/bin/npm run serve:dev
Restart=on-failure
RestartSec=5
 
[Install]
WantedBy=default.target

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

[Unit]
Description=Quartz Deploy API
After=network.target
 
[Service]
Type=simple
WorkingDirectory=/home/loca/dev/quartz/quartz
Environment=PATH=/home/loca/dev/quartz/quartz/node-v22.16.0-linux-x64/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/loca/dev/quartz/quartz/node-v22.16.0-linux-x64/bin/node scripts/workflowz/deploy-api.js
Restart=on-failure
RestartSec=5
 
[Install]
WantedBy=default.target

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

[Unit]
Description=Quartz Private Vault
After=network.target
 
[Service]
Type=simple
WorkingDirectory=/home/loca/dev/quartz/quartz
Environment=PATH=/home/loca/dev/quartz/quartz/node-v22.16.0-linux-x64/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/loca/dev/quartz/quartz/node-v22.16.0-linux-x64/bin/npm run serve:q5vault
Restart=on-failure
RestartSec=5
 
[Install]
WantedBy=default.target

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

[Unit]
Description=Udytor Private Vault
After=network.target
 
[Service]
Type=simple
WorkingDirectory=/home/loca/dev/udytor
Environment=PATH=/home/loca/dev/quartz/quartz/node-v22.16.0-linux-x64/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/home/loca/dev/quartz/quartz/node-v22.16.0-linux-x64/bin/npm run serve:q5vault
Restart=on-failure
RestartSec=5
 
[Install]
WantedBy=default.target

Management Commands:

systemctl --user daemon-reload
systemctl --user enable --now quartz-tinker quartz-deployer quartz-q5vault udyt-q5vault
systemctl --user restart quartz-tinker

Domain Responsibilities & Subdomain Map

Domain / SubdomainPrimary Purpose / ResponsibilityPortBackend Service / Path
quartz.loca.zoneProduction homelab Quartz site (Static)443Static /var/www/quartz-loca/current
dev.quartz.loca.zoneQuartz Homelab Live Tinker Bench (Dev)443 / 3002quartz-tinker.service (Node port 8081 / WS 3003)
q5vault.quartz.loca.zoneIndependent Sandbox for q5vault (Dev)443 / 3005quartz-q5vault.service (Node port 8087 / WS 3004)
tree.loca.zonePython Knowledge Tree application443knowledge-tree.service (Python port 8080)
wiki.tree.loca.zoneKnowledge Tree Quartz static wiki443tree-wiki.service (static files served on port 8085)
wiki.less.loca.zonePaperless-ngx Quartz static wiki443less-wiki.service (static files served on port 8095)
wiki.trlm.loca.zoneTrilium Notes Quartz static wiki443trlm-wiki.service (static files served on port 8105)
wiki.surf.loca.zoneSurfboard Quartz static wiki443surf-wiki.service (static files served on port 8205)
wiki.khoj.loca.zoneKhoj Quartz static wiki443khoj-wiki.service (static files served on port 8305)
wiki.yuan.loca.zoneYuan Quartz static wiki443yuan-wiki.service (static files served on port 8405)
wiki.opno.loca.zoneOpen WebUI Quartz static wiki443opno-wiki.service (static files served on port 8415)
wiki.allm.loca.zoneAnythingLLM Quartz static wiki443allm-wiki.service (static files served on port 8425)
udyt.loca.zoneProduction Udytor editor SPA443Static /home/loca/dev/udytor/dist
udytor.loca.zoneProduction Udytor editor SPA (Alias)443Static /home/loca/dev/udytor/dist
wiki.udyt.loca.zoneUdytor q5vault live dev/serve server443 / 3008udyt-q5vault.service (Node port 8083 / WS 3008)
wiki.omp.loca.zoneOMP Single Source of Truth Wiki80Quartz serve process (port 8088 / WS 3006)
omp.loca.zoneOMP local proxy / console dashboard80OMP dashboard process (port 8086 - offline)
os.loca.zoneOperating System main landing app443OS backend service (port 44100)
api.os.loca.zoneOS API service443OS API service (port 44100)
app.os.loca.zoneOS client application wrapper443OS app wrapper (port 44100)
dev.os.loca.zoneOS development environment443OS dev service (port 44100)
s3.os.loca.zoneOS MinIO S3 console/API443MinIO S3 service (port 34900)
site.os.loca.zoneOS static site / portal443OS portal service (port 44100)

Static & Wiki Workflows

  • Static SPAs / Sites:
    • udyt.loca.zone/udytor.loca.zone serve files directly from /home/loca/dev/udytor/dist.
    • quartz.loca.zone serves static HTML from /var/www/quartz-loca/current.
  • Live Quartz Dev Servers:
    • wiki.udyt.loca.zone runs npx quartz build --serve on port 8083 (watches dev/udytor/q5vault, WS port 3008).
    • dev.quartz.loca.zone runs dev wiki on port 8081 (WS port 3003).
  • Static Wikis (Served via Systemd):
    • wiki.tree.loca.zone served via npx serve -s quartz-dist-tree -l 8085. Built using dev/udytor/scripts/quartz-tree-build.sh.

Subdomain Diagnostics

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

  • Symptom: dev.wiki.udyt.loca.zone HTTP returns 404; HTTPS returns 403.
  • Root Cause: Domain is missing from Nginx config.
    • HTTP hits default port 80 server block (proxies to Docker on 4173 which 404s).
    • HTTPS falls back to first loaded SSL block, triggering local-only ACL access blocks (403).
  • Resolution: Targeted HMR dev script to wiki.udyt.loca.zone directly.

B. Subdomain Port Conflict

  • Symptom: Conflict on port 8083 between wiki.udyt.loca.zone and q5vault.quartz.loca.zone.
  • Resolution:
    1. Remap q5vault.quartz.loca.zone to proxy to local port 8087 (WS 3004).
    2. Maintain wiki.udyt.loca.zone on port 8083 (WS 3008).
    3. Run separate systemd units: udyt-q5vault.service (port 8083) and quartz-q5vault.service (port 8087).

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

  • Symptom: Server returned directory index/file list instead of rendered wiki.
  • Root Cause: Quartz build ignored q5vault-tree/ due to .gitignore rules, resulting in an empty build directory.
  • Resolution: Updated quartz-tree-build.sh to temporarily strip q5vault-tree/ from .gitignore during npx quartz build, outputting proper HTML before restarting tree-wiki.service.