Add generators.${BASE_DOMAIN}: a pinned fork of misode.github.io served
with its own mcmeta + vanilla-mcdoc mirror, so it works with zero internet
at the LAN party. Fork lives in ulicraft-generators (branch ulicraft),
pinned at upstream 2bfe5522.
The app is 100% client-side: every data fetch comes from the player's
browser, not from cochi. So offline capability is entirely about which
origins the bundle refers to, plus a mirror to serve them from. The vhost
sets Content-Security-Policy with connect-src 'self' as ENFORCEMENT: an
external origin that sneaks back in (or returns via an upstream merge)
then breaks loudly in prod, on an ordinary day, instead of silently at the
venue with no internet to debug it.
sync-generators-data.sh mirrors ~109M: the 1.21.1 summary/data/atlas tags,
assets-tiny (assets minus hundreds of MB of .ogg — hence no sounds
generator), vanilla-mcdoc symbols.json, and summary/versions/data.min.json,
which comes from a BRANCH head rather than a version tag and runs during
Spyglass init. It verifies a sentinel file per source instead of trusting a
clean clone exit, since a renamed upstream path would otherwise sync
"successfully" and only fail in the browser at the party.
The container serves the bundle and the mirror on one origin, so there is
no CORS, no second cert and no second DNS name.
DEPLOY ORDER MATTERS. GENERATORS_REPO must be set in cochi's .env BEFORE
pulling this: compose uses ${GENERATORS_REPO:?...}, and an unset required
variable makes docker compose fail to parse the file — taking down every
service including Minecraft, not just this one. Full runbook and the rest
of the prerequisites: plan/21-generators.md.
Verified offline in real Chromium with every non-origin request aborted:
zero off-origin attempts, zero same-origin failures, zero console errors,
generators rendering schemas and live JSON.
215 lines
8.8 KiB
Cheetah
215 lines
8.8 KiB
Cheetah
# ──────────────────────────────────────────────────────────────────
|
|
# Ulicraft host nginx — TLS terminator in FRONT of caddy.
|
|
# ──────────────────────────────────────────────────────────────────
|
|
# caddy (in docker-compose.yml) is the single internal ingress for ALL vhosts;
|
|
# this host nginx terminates TLS and reverse-proxies everything to caddy by Host
|
|
# header. Prefer tooling/render-nginx.sh over hand-rendering.
|
|
#
|
|
# Caddy must be published on a localhost port — set in .env:
|
|
# CADDY_HTTP_PORT=8880
|
|
# CADDY_HTTP_BIND=127.0.0.1
|
|
# and the stack brought up:
|
|
# docker compose up -d
|
|
#
|
|
# Render with the vars then install:
|
|
# CADDY_HTTP_PORT=8880 BASE_DOMAIN=ulicraft.net APP_DIR=/home/ubuntu/mc/ulicraft-server-v1 \
|
|
# envsubst '$CADDY_HTTP_PORT $BASE_DOMAIN $APP_DIR' \
|
|
# < nginx/ulicraft-caddy.conf.tmpl | sudo tee /etc/nginx/sites-available/ulicraft.conf
|
|
# sudo ln -sf /etc/nginx/sites-available/ulicraft.conf /etc/nginx/sites-enabled/
|
|
# sudo nginx -t && sudo systemctl reload nginx
|
|
#
|
|
# Prereqs:
|
|
# - certs issued: tooling/issue-letsencrypt.sh -> $APP_DIR/certs/<name>/{cert,key}.pem
|
|
# ──────────────────────────────────────────────────────────────────
|
|
|
|
upstream ulicraft_caddy {
|
|
server 127.0.0.1:${CADDY_HTTP_PORT};
|
|
}
|
|
|
|
# Brute-force throttle for the publicly-exposed Filestash /admin console (it is
|
|
# the only credential on files.), applied in its location block below.
|
|
limit_req_zone $binary_remote_addr zone=files_admin:10m rate=6r/m;
|
|
|
|
# HTTP -> HTTPS for every name.
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name ${BASE_DOMAIN} www.${BASE_DOMAIN} auth.${BASE_DOMAIN} distribution.${BASE_DOMAIN} avatar.${BASE_DOMAIN} status.${BASE_DOMAIN} files.${BASE_DOMAIN} generators.${BASE_DOMAIN};
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
|
|
# One TLS server block per name (each has its own LE cert). All proxy to caddy;
|
|
# caddy routes by the forwarded Host header (apex -> www, auth -> drasl).
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name ${BASE_DOMAIN};
|
|
|
|
ssl_certificate ${APP_DIR}/certs/${BASE_DOMAIN}/cert.pem;
|
|
ssl_certificate_key ${APP_DIR}/certs/${BASE_DOMAIN}/key.pem;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
client_max_body_size 4m; # skin uploads (auth) reuse the same proxy block
|
|
|
|
location / {
|
|
proxy_pass http://ulicraft_caddy;
|
|
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;
|
|
}
|
|
}
|
|
|
|
# www -> apex canonical redirect (needs its own cert; covered by LE_SUBDOMAINS).
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name www.${BASE_DOMAIN};
|
|
|
|
ssl_certificate ${APP_DIR}/certs/www.${BASE_DOMAIN}/cert.pem;
|
|
ssl_certificate_key ${APP_DIR}/certs/www.${BASE_DOMAIN}/key.pem;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
return 301 https://${BASE_DOMAIN}$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name auth.${BASE_DOMAIN};
|
|
|
|
ssl_certificate ${APP_DIR}/certs/auth.${BASE_DOMAIN}/cert.pem;
|
|
ssl_certificate_key ${APP_DIR}/certs/auth.${BASE_DOMAIN}/key.pem;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
client_max_body_size 4m; # skin uploads
|
|
|
|
location / {
|
|
proxy_pass http://ulicraft_caddy;
|
|
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;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name distribution.${BASE_DOMAIN};
|
|
|
|
ssl_certificate ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/cert.pem;
|
|
ssl_certificate_key ${APP_DIR}/certs/distribution.${BASE_DOMAIN}/key.pem;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
location / {
|
|
proxy_pass http://ulicraft_caddy;
|
|
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;
|
|
}
|
|
}
|
|
|
|
# Data pack generators — a pinned, self-contained fork of misode.github.io.
|
|
# Serves its own mcmeta/vanilla-mcdoc mirror from the same origin, so it works
|
|
# with ZERO internet at party time (the app is 100% client-side: every data
|
|
# fetch comes from the player's browser, not from this host). caddy sets the
|
|
# CSP that enforces that — see caddy/conf.d/70-generators.caddy.
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name generators.${BASE_DOMAIN};
|
|
|
|
ssl_certificate ${APP_DIR}/certs/generators.${BASE_DOMAIN}/cert.pem;
|
|
ssl_certificate_key ${APP_DIR}/certs/generators.${BASE_DOMAIN}/key.pem;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
location / {
|
|
proxy_pass http://ulicraft_caddy;
|
|
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;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name avatar.${BASE_DOMAIN};
|
|
|
|
ssl_certificate ${APP_DIR}/certs/avatar.${BASE_DOMAIN}/cert.pem;
|
|
ssl_certificate_key ${APP_DIR}/certs/avatar.${BASE_DOMAIN}/key.pem;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
location / {
|
|
proxy_pass http://ulicraft_caddy;
|
|
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;
|
|
}
|
|
}
|
|
|
|
# Filestash player file share. Uploads need a real body limit (nginx defaults to
|
|
# 1m, which rejects almost anything worth sharing) and streaming rather than
|
|
# spooling — a 2g upload buffered to disk would write it twice on a host whose
|
|
# disk is already the shared failure domain for Minecraft + Drasl + backups.
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name files.${BASE_DOMAIN};
|
|
|
|
ssl_certificate ${APP_DIR}/certs/files.${BASE_DOMAIN}/cert.pem;
|
|
ssl_certificate_key ${APP_DIR}/certs/files.${BASE_DOMAIN}/key.pem;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
client_max_body_size 2g; # uploads; other vhosts stay at 4m
|
|
proxy_request_buffering off; # stream uploads, don't spool them to disk
|
|
proxy_read_timeout 300s;
|
|
proxy_send_timeout 300s;
|
|
|
|
# /admin is reachable on purpose, so throttle it: it is the only password
|
|
# on this vhost. 6 req/min, small burst.
|
|
location /admin {
|
|
limit_req zone=files_admin burst=3 nodelay;
|
|
proxy_pass http://ulicraft_caddy;
|
|
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://ulicraft_caddy;
|
|
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;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
listen [::]:443 ssl http2;
|
|
server_name status.${BASE_DOMAIN};
|
|
|
|
ssl_certificate ${APP_DIR}/certs/status.${BASE_DOMAIN}/cert.pem;
|
|
ssl_certificate_key ${APP_DIR}/certs/status.${BASE_DOMAIN}/key.pem;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
|
|
location / {
|
|
proxy_pass http://ulicraft_caddy;
|
|
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;
|
|
|
|
# Uptime Kuma uses websockets (socket.io) for live status updates.
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
}
|
|
}
|