Operations Guide — Oracle Linux 9
This is the operating manual for the production deployment. It accompanies the deploy kit in
deploy/ (setup script, systemd unit, Nginx config, backup script) and covers the operational
runbook: server setup, reverse proxy, TLS, monitoring, backups, logging, and recovery.
1. Server setup
Provision a fresh Oracle Linux 9 (minimal) instance, then run:
bash deploy/setup-oracle-linux-9.sh
The script installs Node 20 LTS, pnpm 9.12.3, MariaDB 11, Nginx, Certbot, creates the unprivileged
yas user and directories, generates the environment file with strong secrets, and installs the
systemd unit. Never run the app as root — the unit file enforces an unprivileged user.
2. Application deployment
From a build machine:
rsync -av --exclude node_modules --exclude .next ./ yas@HOST:/opt/yas/
On the server:
sudo -u yas bash -c 'cd /opt/yas && pnpm install --frozen-lockfile && pnpm db:generate && pnpm db:push && BUILD_STANDALONE=true pnpm build'
cp -r /opt/yas/.next/standalone/. /opt/yas/
cp -r /opt/yas/.next/static /opt/yas/.next/
cp -r /opt/yas/public /opt/yas/
systemctl enable --now yas
The standalone build contains its own minimal node_modules, so the runtime depends only on the
system Node binary.
3. Reverse proxy
deploy/nginx-yas.conf terminates TLS and proxies to the app on 127.0.0.1:3000. Key behaviors:
- HTTP → HTTPS redirect
- Security headers (HSTS, nosniff, X-Frame-Options, Referrer-Policy, Permissions-Policy)
/_next/staticcached immutable (1 year),/blog-imgcached 1 day/api/markedno-store- Real client IP forwarded (
X-Forwarded-For,X-Real-IP) — the app's rate limiting and geo/device capture depend on these headers being set by the proxy TRUST_PROXY=truemust be set in/etc/yas/yas.env(the setup script already writes it) so the app trusts these headers. Nginx overwritesX-Real-IPwith$remote_addr, so external callers cannot spoof it. (v3.2.5)
Validate any change with nginx -t before reloading.
4. SSL/TLS
certbot --nginx -d yas.sh -d www.yas.sh
Certbot rewrites the Nginx config with certificate paths and installs the renewal timer
(systemctl list-timers | grep certbot). HSTS is already configured; after confirming the
certificate works, submit yas.sh to the HSTS preload list.
5. Monitoring
Built-in:
GET /api/health— JSON liveness + database check + latency (cached 30s)/status— human-facing status page (client polls the health endpoint every 60s)
Wire into your monitoring:
curl -s https://yas.sh/api/health
# → {"status":"ok","db":"ok","latencyMs":2,"time":"...","version":"3.0.0"}
Alert on: status != "ok", latency above budget, 5xx rate from journalctl, disk usage of
/var/backups/yas.
6. Backups
Nightly via cron (as root): 0 2 * * * /opt/yas/deploy/backup.sh
The script dumps the database (single-transaction, gzipped), archives non-generated files, and
optionally encrypts with AES-256 (key at /etc/yas/backup-key, created with
openssl rand -hex 32 > /etc/yas/backup-key). Retention: 14 daily backups. Test a restore at
least quarterly — an untested backup is a hope.
Restore:
gunzip -c yas-db-2026-08-08.sql.gz | mysql yas # (decrypt first if .enc)
tar xzf yas-files-2026-08-08.tar.gz -C /opt/yas
systemctl restart yas
7. Logging
- App logs:
journalctl -u yas -fand/var/log/yas/yas.log(append) - Nginx access/error logs:
/var/log/nginx/ - Log rotation: journald handles the service; add
logrotatefor/var/log/yasand/var/log/nginx
8. Recovery procedures
| Scenario | Procedure |
|---|---|
| App down | systemctl status yas → journalctl -u yas -n 50 → fix env or code → systemctl restart yas |
| Database down | systemctl status mariadb → check /var/lib/mysql disk → restore latest backup |
| Corrupt data | Restore DB from backup; restart app; verify /api/health |
| Certificate expiry | certbot renew (timer handles it); check openssl x509 -enddate -noout -in /etc/letsencrypt/live/yas.sh/cert.pem |
| Disk full | Largest growth points: DB, /var/backups/yas, logs — clean per retention |
| Traffic spike | App scales vertically; Nginx handles concurrency; check rate-limit headers for abuse |
| Security incident | Revoke session secret → rotate all sessions; rotate DB password; restore from pre-incident backup |
9. Upgrades
- Pull the new source to the build machine.
pnpm install --frozen-lockfile+pnpm build(standalone) locally to validate.- Sync to
/opt/yas, rebuild on the server,systemctl restart yas. - Run the smoke suite:
BASE=https://yas.sh bash scripts/smoke.sh. - Watch
/api/healthand the status page for the first hour.