From 40ceb436724cc784936adc50164f2d0a9e40c9ba Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Tue, 30 Jun 2026 13:14:49 -0400 Subject: [PATCH] cinny: version-control the production nginx site config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chat.lotusguild.org nginx config (LXC 106) was edited directly on the box and never tracked — which is how its CSP drifted (kept a dead Sentry URL and blocked matrix.org logins). Snapshot it as cinny/nginx.conf (verbatim from prod, incl. the corrected connect-src that now allows matrix.org/*.matrix.org) and deploy it via lxc106-cinny.sh: back up the live file, swap, `nginx -t`, and reload only on success (auto-restore the backup if validation fails, so a bad config can't take the site down). TLS terminates at the NPM proxy, so this is a plain HTTP server block with no secrets. Co-Authored-By: Claude Opus 4.8 --- cinny/nginx.conf | 79 ++++++++++++++++++++++++++++++++++++++++++ deploy/lxc106-cinny.sh | 24 +++++++++++-- 2 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 cinny/nginx.conf diff --git a/cinny/nginx.conf b/cinny/nginx.conf new file mode 100644 index 0000000..608495c --- /dev/null +++ b/cinny/nginx.conf @@ -0,0 +1,79 @@ +server { + listen 80; + listen [::]:80; + server_name chat.lotusguild.org; + + # Brotli compression (better than gzip for modern browsers) + brotli on; + brotli_static on; + brotli_comp_level 6; + brotli_types text/plain text/css application/javascript application/json + image/svg+xml application/wasm font/woff2; + + root /var/www/html; + server_tokens off; + client_max_body_size 50m; + + limit_req zone=chat_limit burst=60 nodelay; + limit_conn chat_conn 25; + index index.html; + + # Security headers + add_header X-Frame-Options SAMEORIGIN always; + add_header X-Content-Type-Options nosniff always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy strict-origin-when-cross-origin always; + + # Block all source map files and dotfiles from public access + location ~* \.(js|css)\.map$ { + deny all; + return 404; + } + location ~ /\. { + deny all; + return 404; + } + location = /netlify.toml { + deny all; + return 404; + } + + # Content Security Policy + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://matrix.lotusguild.org https://drive.lotusguild.org https://media.giphy.com https://media0.giphy.com https://media1.giphy.com https://media2.giphy.com https://media3.giphy.com https://media4.giphy.com https://www.openstreetmap.org https://tile.openstreetmap.org https://api.qrserver.com; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https://matrix.lotusguild.org wss://matrix.lotusguild.org https://matrix.org https://*.matrix.org https://api.giphy.com https://*.giphy.com wss:; media-src 'self' https: blob:; frame-src 'self' https://www.openstreetmap.org; worker-src 'self' blob:; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always; + + # Service worker must never be cached so updates are picked up immediately + location = /sw.js { + expires -1; + add_header Cache-Control "no-cache, no-store, must-revalidate" always; + } + + # Cache content-addressed static assets aggressively + location ~* \.(?:js|css|woff2?|png|svg|ico|webp)$ { + expires 1y; + add_header Cache-Control "public, immutable" always; + } + + # Never cache HTML or JSON (index.html, config.json, manifest.json) + location ~* \.(json|html)$ { + expires -1; + add_header Cache-Control "no-cache, no-store, must-revalidate" always; + } + + # Auto-deploy webhook — proxied to local webhook service + location = /hooks/lotus-deploy { + proxy_pass http://127.0.0.1:9001/hooks/lotus-deploy; + proxy_set_header Host $host; + proxy_read_timeout 300; + proxy_connect_timeout 5; + } + + location / { + rewrite ^/config\.json$ /config.json break; + rewrite ^/manifest\.json$ /manifest.json break; + rewrite ^/sw\.js$ /sw.js break; + rewrite ^/pdf\.worker\.min\.js$ /pdf.worker.min.js break; + rewrite ^/public/(.*)$ /public/$1 break; + rewrite ^/assets/(.*)$ /assets/$1 break; + rewrite ^(.+)$ /index.html break; + } +} diff --git a/deploy/lxc106-cinny.sh b/deploy/lxc106-cinny.sh index 9915e15..ab403a7 100644 --- a/deploy/lxc106-cinny.sh +++ b/deploy/lxc106-cinny.sh @@ -1,7 +1,8 @@ #!/bin/bash # Auto-deploy script for LXC 106 (cinny) -# Handles: cinny/config.json, cinny/upstream-check.sh, cinny/lotus-build.sh, -# deploy/hooks-lxc106.json, systemd/cinny-upstream-check.cron +# Handles: cinny/config.json, cinny/nginx.conf, cinny/upstream-check.sh, +# cinny/lotus-build.sh, deploy/hooks-lxc106.json, +# systemd/cinny-upstream-check.cron # Triggered by: Gitea webhook on push to main set -euo pipefail @@ -14,7 +15,7 @@ echo "=== $(date) === LXC106 deploy triggered ===" if [ ! -d "$REPO_DIR/.git" ]; then git clone "$CLONE_URL" "$REPO_DIR" - CHANGED="cinny/config.json cinny/upstream-check.sh cinny/lotus-build.sh deploy/hooks-lxc106.json systemd/cinny-upstream-check.cron" + CHANGED="cinny/config.json cinny/nginx.conf cinny/upstream-check.sh cinny/lotus-build.sh deploy/hooks-lxc106.json systemd/cinny-upstream-check.cron" else cd "$REPO_DIR" git fetch --all @@ -31,6 +32,23 @@ if echo "$CHANGED" | grep -q '^cinny/config.json'; then echo "✓ config.json deployed" fi +if echo "$CHANGED" | grep -q '^cinny/nginx.conf'; then + echo "Deploying cinny nginx site config..." + # Back up the live config, swap in the repo copy, and validate before + # reloading. If `nginx -t` fails, restore the backup and skip the reload so + # a bad config can never take the site down. + BACKUP="/etc/nginx/sites-available/cinny.bak-$(date +%Y%m%d%H%M%S)" + cp /etc/nginx/sites-available/cinny "$BACKUP" + cp "$REPO_DIR/cinny/nginx.conf" /etc/nginx/sites-available/cinny + if nginx -t; then + systemctl reload nginx + echo "✓ nginx site config deployed + reloaded" + else + echo "✗ nginx -t FAILED — restoring previous config, skipping reload" + cp "$BACKUP" /etc/nginx/sites-available/cinny + fi +fi + if echo "$CHANGED" | grep -q '^cinny/upstream-check.sh'; then echo "Deploying upstream-check.sh..." cp "$REPO_DIR/cinny/upstream-check.sh" /usr/local/bin/cinny-upstream-check.sh