Files
matrix/cinny/nginx.conf
Lotus CIandClaude Opus 5.5 b6380b6c8a
Lint / Shell (shellcheck) (push) Successful in 14s
Lint / JS (eslint) (push) Successful in 11s
Lint / No secrets in webhook configs (push) Successful in 5s
Lint / Landing page is rendered (matrix (push) Successful in 4s
Lint / Python (ruff) (push) Successful in 6s
Lint / Python deps (pip-audit) (push) Successful in 32s
Lint / Secret scan (gitleaks) (push) Successful in 4s
Lint / Shell (shellcheck) (pull_request) Successful in 7s
Lint / JS (eslint) (pull_request) Successful in 5s
Lint / No secrets in webhook configs (pull_request) Successful in 4s
Lint / Landing page is rendered (matrix (pull_request) Successful in 3s
Lint / Python (ruff) (pull_request) Successful in 4s
Lint / Python deps (pip-audit) (pull_request) Successful in 30s
Lint / Secret scan (gitleaks) (pull_request) Successful in 4s
feat(cinny-web): serve avatar decorations from chat.lotusguild.org/decorations (#22)
Decorations came through Nextcloud's public DAV: a new PHP session (four
cookies) per image and no cache headers, on every decorated avatar. The
set (629 decorations + thumbnails, 573 MB) is now a versioned Gitea
generic package (avatar-decorations 1.0.0, 4 tar parts), pinned by
sha256 in cinny/decorations.sha256. LXC 106's deploy installs it to
/var/www/decorations-<version> (verified against the pin and the
in-package manifest; previous version kept; failures leave the live set
alone), and nginx serves /decorations/ with a one-week public cache.
deploy/publish-decorations.sh publishes a new version and writes the pin.

Fixes #22

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-19 12:32:34 -04:00

184 lines
6.7 KiB
Nginx Configuration File

# [matrix #23] SPA routes (/, /home/…, /login/…) are rewritten to /index.html
# inside `location /`, where no cache headers were set: browsers then reused a
# heuristically fresh shell after a deploy and requested deleted assets. Only
# the rewritten shell gets no-cache; other files there keep their defaults.
map $uri $lotus_shell_cache_control {
/index.html "no-cache, no-store, must-revalidate";
default "";
}
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 (incl. CSP) — see the snippet
include snippets/cinny-security-headers.conf;
# HSTS: TLS terminates upstream (this server is listen 80), so this reaches
# the browser only if the front proxy passes upstream response headers
# through; otherwise set it at the TLS terminator. includeSubDomains covers
# all *.lotusguild.org (all HTTPS); `preload` is inert until submitted to
# hstspreload.org.
# Permissions-Policy: allow only what the app uses (self) — calls
# (camera/microphone/display-capture), location share (geolocation), sounds
# (autoplay), Element Call (fullscreen/encrypted-media) — and deny the rest.
# 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;
}
# Service worker must never be cached so updates are picked up immediately
location = /sw.js {
include snippets/cinny-security-headers.conf;
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
}
# Bundled Element Call: framed by the app itself, so it must not carry the
# app CSP (frame-ancestors 'none' blocked every web call). Same caching as
# below: HTML never cached, hashed assets for a year.
location ^~ /public/element-call/ {
include snippets/cinny-security-headers-framed.conf;
location ~* \.html$ {
include snippets/cinny-security-headers-framed.conf;
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
}
location ~* \.(?:js|css|woff2?|png|svg|ico|webp|wasm)$ {
include snippets/cinny-security-headers-framed.conf;
expires 1y;
add_header Cache-Control "public, immutable" always;
}
}
# Cache content-addressed static assets aggressively
location ~* \.(?:js|css|woff2?|png|svg|ico|webp)$ {
include snippets/cinny-security-headers.conf;
expires 1y;
add_header Cache-Control "public, immutable" always;
}
# Never cache HTML or JSON (index.html, config.json, manifest.json)
location ~* \.(json|html)$ {
include snippets/cinny-security-headers.conf;
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
}
# [Gitea #155] PWA share target. The service worker normally answers this
# POST itself; if it isn't controlling the page yet, land on /share
# (the shared files are lost, but nothing 405s).
location = /share-target {
absolute_redirect off;
return 303 /share;
}
# 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;
}
# [matrix #22] Avatar decorations, installed by deploy/lxc106-cinny.sh from
# the Gitea generic package (outside the web root, so app deploys'
# rsync --delete never touches them). ^~ beats the image regex below: these
# can be replaced in place under the same name, so a week, not immutable.
location ^~ /decorations/ {
alias /var/www/decorations/;
include snippets/cinny-security-headers.conf;
add_header Cache-Control "public, max-age=604800" always;
location ~ /MANIFEST\.sha256$ { return 404; }
}
location / {
# add_header here drops the server-level headers, so include them again.
include snippets/cinny-security-headers.conf;
add_header Cache-Control $lotus_shell_cache_control always;
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;
}
}
# [cinny #43] Element Call on its own origin. The web app frames
# https://call.chat.lotusguild.org/public/element-call/index.html (config.json
# `elementCallUrl`), so the call page can no longer read the app's storage
# (login token, crypto store) or use its service worker. Serves ONLY the call
# page — the same files as chat.lotusguild.org/public/element-call/ — and 404s
# everything else, so this hostname exposes nothing new.
server {
listen 80;
listen [::]:80;
server_name call.chat.lotusguild.org;
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;
limit_req zone=chat_limit burst=60 nodelay;
limit_conn chat_conn 25;
include snippets/cinny-security-headers-call.conf;
location ^~ /public/element-call/ {
include snippets/cinny-security-headers-call.conf;
location ~* \.map$ {
return 404;
}
location ~ /\. {
return 404;
}
location ~* \.html$ {
include snippets/cinny-security-headers-call.conf;
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
}
location ~* \.(?:js|css|woff2?|png|svg|ico|webp|wasm)$ {
include snippets/cinny-security-headers-call.conf;
expires 1y;
add_header Cache-Control "public, immutable" always;
}
try_files $uri =404;
}
location / {
return 404;
}
}