2 Commits
Author SHA1 Message Date
Yuriy PanovandClaude Opus 5 9736024e60 deploy: fix nginx security headers dropped by add_header inheritance
Found while verifying the first real provision: none of the four
security headers reached the browser on any HTML page.

nginx's add_header inheritance is all-or-nothing — a location that sets
any add_header of its own discards every header inherited from the
server block. "/" resolves through try_files to `location = /index.html`,
which sets Cache-Control, so HSTS, X-Content-Type-Options,
X-Frame-Options and Referrer-Policy were silently dropped exactly where
they matter. /assets/ lost them the same way.

Move the four into snippets/security-headers.conf and include it in the
server block and in both locations that add a header of their own.

Also from the same provision run:
- ssl_stapling is dead config now that Let's Encrypt certificates carry
  no OCSP responder URL; it only logs a warning per cert on each reload;
- README step 3 chmod'ed /etc/exo to 750 but never set its group, so the
  exo user could not traverse it and exo-deploy died on "cannot read
  /etc/exo/<app>.env";
- README step 7 dropped http-extras.conf into conf.d without disabling
  the same directives in Debian's stock nginx.conf, and nginx refuses to
  start on a duplicate gzip / server_tokens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 23:56:16 +06:00
yuriy.pandClaude Opus 5 d99b9cb0bb Add deployment tooling for a single production VPS
The repo had no infrastructure at all: no Dockerfile, no nginx config, no
systemd unit, no deploy script, no CI. Deployment guidance existed only
as two prose lines in each README.

Target shape is one reg.ru KVM box serving all four landings, each on its
own domain root. nginx serves the built SPA off disk and proxies only
/api/* to a small Express process per app, so a crashed or restarting API
takes down the form rather than the page. Releases are built on the
server into a timestamped directory and swapped in by renaming the
`current` symlink.

  deploy/apps.conf      domains and ports, the one place to edit
  deploy/README.md      hardware/software spec, provisioning, runbook
  deploy/bin/           exo-deploy, exo-render-nginx
  deploy/nginx/         vhost template, shared snippets, http extras
  deploy/systemd/       one template unit for all four instances
  deploy/env/           annotated templates for the two env files

exo-deploy asserts the VITE_SITE_URL substitution actually happened,
prunes devDependencies after building, precompresses for gzip_static,
health-checks after the swap and rolls back on its own if the new release
fails to answer. exo-render-nginx refuses to emit a vhost while a
placeholder domain is still in apps.conf.

Secrets are split across two env files: the amoCRM token stays in
/etc/exo at mode 640 and is injected by systemd, while only public
build-time vars are copied into a release. dotenv does not override
variables already in the environment, so the two coexist safely.

Chose systemd over Docker deliberately — no database, no conflicting
runtimes, no CI or registry to build images in, and containerising fights
the serve-static-from-disk design. Reasoning and the triggers to revisit
it are recorded in the README.

Domains are still placeholders; nothing here has been run against a
server yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 15:57:49 +06:00