# # nginx for Solid Scribe on NixOS. # # Merge this into your configuration.nix and `nixos-rebuild switch`. # Replace REPO_PATH with the absolute path of the repo clone on the server. # # Four things differ from the previous config, and the first two are required # for the Vue 3 client to work at all: # # 1. SPA fallback. The old Vue 2 client used vue-router's default HASH mode # (no `mode:` was set), so every request was just "/" and routing happened # after the "#". That is why share links looked like /#/public/note/... # The Vue 3 client uses createWebHistory, so /notes, /settings and # /share-target are real HTTP requests. Without tryFiles they all 404. # # 2. root moves up to the vhost. It was inside locations."/", which left # /assets/, /sw.js and /manifest.json unresolvable once they needed their # own location blocks. # # It also no longer points into the repo. deploy.sh publishes the built # client to /var/www/ss/releases/ and atomically repoints the # /var/www/ss/current symlink at it. Pointing nginx at that stable path # means a deploy is one rename -- nginx resolves the symlink per request, # so no reload is needed and no request sees a half-copied directory. # Rollback is the same rename pointing at an older release. # # 3. proxyWebsockets on /socket. recommendedProxySettings does NOT add the # Upgrade/Connection headers, so Socket.IO has been silently falling back # to long-polling -- that's the live note-sync path running degraded. # # 4. Cache headers. See the warning below. # # !! CLOUDFLARE !! # The tunnel sits in front of this and Cloudflare caches .js by DEFAULT, which # includes /sw.js. If the edge caches the service worker, the update check can # never see a new one and the app is pinned to an old build with no remote way # to recover. The nginx headers below are necessary but NOT sufficient -- # add a Cloudflare Cache Rule that BYPASSES cache for /sw.js (and /index.html) # before cutting over. # # Also confirm which Host header cloudflared sends. This vhost is keyed on # "192.168.1.7"; if the tunnel forwards the public hostname instead, nginx will # not match this vhost and will fall through to the default server. Either add # the public hostname to serverAliases or set the tunnel's httpHostHeader. # services.nginx = { enable = true; recommendedProxySettings = true; recommendedGzipSettings = true; virtualHosts."192.168.1.7" = { # serverAliases = [ "notes.example.com" ]; # if the tunnel forwards the public host # Published by deploy.sh; a symlink to the live release. Not in the repo. root = "/var/www/ss/current"; locations."/" = { tryFiles = "$uri $uri/ /index.html"; }; # Never let the HTTP layer cache the service worker or the app shell. locations."= /sw.js".extraConfig = '' add_header Cache-Control "no-cache, no-store, must-revalidate"; expires -1; ''; locations."= /index.html".extraConfig = '' add_header Cache-Control "no-cache, no-store, must-revalidate"; expires -1; ''; # The manifest changes rarely but should not be pinned for a year. locations."= /manifest.json".extraConfig = '' add_header Cache-Control "no-cache"; expires -1; ''; # Vite content-hashes everything under /assets, so these are immutable. locations."/assets/".extraConfig = '' add_header Cache-Control "public, max-age=31536000, immutable"; ''; locations."/api" = { proxyPass = "http://localhost:3000"; }; locations."/socket" = { proxyPass = "http://localhost:3001"; proxyWebsockets = true; }; }; }; # Note on file access: nginx runs as its own user and needs read access to the # web root, with +x on every parent directory. Serving from /var/www/ss is what # makes that a non-issue -- the one-time `chmod 755 /var/www /var/www/ss` in # deploy.sh is the whole of it. This is the reason the root is not the repo # clone: a clone under /home/mab would require loosening the permissions on the # home directory itself. # # Uploads are NOT here. Express serves them from REPO_PATH/staticFiles at # /api/static via the proxy below, so they are untouched by deploys.