Files
teknav-bak/app/DEPLOY.md
T
arsam e9cd12dda6 Import remaining project documentation and reference notes
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
2026-09-18 23:33:04 +03:30

1014 lines
24 KiB
Markdown

# DEPLOY.md - Native Debian Deployment Guide
> **IMPORTANT — Phase 12 (Web Push Notifications):** A new `PushSubscription` table and the `web-push` backend package were added. Before starting the service, run:
> ```bash
> cd backend
> npm ci # installs web-push
> npm run prisma:generate
> npm run prisma:apply # creates PushSubscription table
> npm run build
> ```
> Then generate VAPID keys once and add them to `/etc/teknav/backend.env` (see Section 6). Without VAPID keys the push routes return 503 and push delivery is silently skipped — all other features continue to work normally.
This is the complete production runbook for deploying Teknav natively on Debian, without Docker.
Production model:
- Public site: `https://www.teknav.ir`
- Frontend: Vite build served by nginx from `/opt/teknav/current/dist`
- Backend: Fastify systemd service on `127.0.0.1:3010`
- Database: local PostgreSQL
- Cache/session/SSE bus: local Redis
- Uploads: `/var/lib/teknav/uploads`
- Backups: `/var/backups/teknav`
- Env file: `/etc/teknav/backend.env`
The deployed surface now also includes SEO topic hubs, glossary pages, Q&A, jobs, courses, premium membership, sponsored article labels, verified expert badges, 2FA, article corrections, first-party analytics/error capture, Zarinpal payment flow, and web push notifications (Phase 12).
Run these commands as a sudo-capable Linux user unless a command explicitly uses `sudo -u teknav`.
## 0. Local Development (Windows/Tuned)
If you are running locally and commands like `prisma` or `tsx` are not in your path, use the following "Tuned" commands from the **root** directory:
### Update Database Seed (2026 Content)
```powershell
cd backend
.\node_modules\.bin\tsx prisma\seed.ts
```
### Force Clear Browser Cache (Articles)
If articles are missing or outdated, I have added a `DATA_VERSION` to the frontend. Simply **refresh the page** or clear your `localStorage` in the browser console:
```javascript
localStorage.clear(); window.location.reload();
```
---
## 1. Base OS
Before server setup, point DNS to the server public IP:
```text
www.teknav.ir A SERVER_IPV4
teknav.ir A SERVER_IPV4
www.teknav.ir AAAA SERVER_IPV6 optional
teknav.ir AAAA SERVER_IPV6 optional
```
Verify DNS from your workstation:
```bash
dig +short www.teknav.ir
dig +short teknav.ir
```
## 1. Base OS
Use Debian 12/13. Update the server:
```bash
sudo apt update
sudo apt full-upgrade -y
sudo reboot
```
Reconnect, then install base packages:
```bash
sudo apt update
sudo apt install -y \
ca-certificates \
curl \
gnupg \
git \
rsync \
build-essential \
nginx \
postgresql \
postgresql-contrib \
redis-server \
certbot \
python3-certbot-nginx \
logrotate \
ufw \
unzip
```
Install Node.js 20 from NodeSource:
```bash
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
node -v
npm -v
```
Optional but useful:
```bash
sudo timedatectl set-timezone Asia/Tehran
timedatectl
```
## 2. System User and Directories
Create the non-login service user:
```bash
sudo useradd --system --home /opt/teknav --shell /usr/sbin/nologin teknav || true
```
Create required directories:
```bash
sudo mkdir -p \
/opt/teknav/current \
/opt/teknav/releases \
/etc/teknav \
/var/lib/teknav/uploads \
/var/backups/teknav
```
Set ownership and permissions:
```bash
sudo chown -R teknav:teknav /opt/teknav /var/lib/teknav /var/backups/teknav
sudo chown root:teknav /etc/teknav
sudo chmod 755 /opt/teknav
sudo chmod 750 /opt/teknav/current /opt/teknav/releases /var/lib/teknav /var/backups/teknav /etc/teknav
```
## 3. PostgreSQL
Enable PostgreSQL:
```bash
sudo systemctl enable --now postgresql
sudo systemctl status postgresql --no-pager
```
Create a strong database password:
```bash
openssl rand -base64 32
```
Create the database user and database. Replace `CHANGE_ME_STRONG_DB_PASSWORD` first:
```bash
sudo -u postgres psql
```
Inside `psql`:
```sql
CREATE USER teknav WITH PASSWORD 'CHANGE_ME_STRONG_DB_PASSWORD';
CREATE DATABASE teknav OWNER teknav;
\q
```
Confirm local access:
```bash
psql "postgresql://teknav:CHANGE_ME_STRONG_DB_PASSWORD@127.0.0.1:5432/teknav?schema=public" -c "SELECT 1;"
```
Keep PostgreSQL local-only. Confirm it is not listening publicly:
```bash
sudo ss -ltnp | grep 5432
```
## 4. Redis
Enable Redis:
```bash
sudo systemctl enable --now redis-server
sudo systemctl status redis-server --no-pager
redis-cli ping
```
Expected:
```text
PONG
```
Keep Redis local-only. Confirm:
```bash
sudo ss -ltnp | grep 6379
```
## 5. Upload Project Files
Choose one deployment source.
Option A, clone from Git:
```bash
sudo -u teknav git clone REPLACE_WITH_REPO_URL /opt/teknav/current
cd /opt/teknav/current
```
Option B, sync from your local machine to the server:
```bash
rsync -az --delete \
--exclude node_modules \
--exclude backend/node_modules \
--exclude dist \
--exclude .git \
--exclude '*.zip' \
--exclude 'backend/query_engine*.node' \
--exclude 'backend/query_engine*.node.gz' \
--exclude 'backend/schema-engine*' \
--exclude 'backend/schema-engine*.gz' \
./ USER@SERVER_IP:/tmp/teknav-upload/
ssh USER@SERVER_IP
sudo rsync -a --delete /tmp/teknav-upload/ /opt/teknav/current/
sudo chown -R teknav:teknav /opt/teknav/current
cd /opt/teknav/current
```
Confirm key files exist:
```bash
test -f package.json
test -f backend/package.json
test -f backend/prisma/schema.prisma
test -f nginx/nginx.conf.example
test -f deploy/systemd/teknav-backend.service
```
## 6. Environment File
Create the production env file:
```bash
cd /opt/teknav/current
sudo cp .env.prod.example /etc/teknav/backend.env
sudo chown root:teknav /etc/teknav/backend.env
sudo chmod 640 /etc/teknav/backend.env
sudo nano /etc/teknav/backend.env
```
Set at least these values:
```env
DATABASE_URL="postgresql://teknav:CHANGE_ME_STRONG_DB_PASSWORD@127.0.0.1:5432/teknav?schema=public"
REDIS_URL="redis://127.0.0.1:6379"
SESSION_SECRET=CHANGE_ME_48_BYTE_RANDOM_SECRET
COOKIE_DOMAIN=.teknav.ir
CORS_ORIGINS=https://www.teknav.ir,https://teknav.ir
OAUTH_CALLBACK_BASE=https://www.teknav.ir
NODE_ENV=production
PORT=3010
UPLOAD_DIR=/var/lib/teknav/uploads
BACKUP_DIR=/var/backups/teknav
LOG_LEVEL=info
```
Generate `SESSION_SECRET`:
```bash
openssl rand -base64 48
```
Set SMTP when email verification, password reset, campaign sending, and the weekly digest should work. If `SMTP_HOST` is empty, email jobs log what they would send and do not deliver mail:
```env
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=...
SMTP_PASS=...
SMTP_FROM=no-reply@teknav.ir
```
Set SMS OTP through Kavenegar:
```env
SMS_PROVIDER=kavenegar
SMS_API_KEY=...
SMS_SENDER=
```
Web push notifications (Phase 12). Generate VAPID keys once on the server:
```bash
node -e "const wp = require('web-push'); const k = wp.generateVAPIDKeys(); console.log(JSON.stringify(k,null,2))"
```
Then add to `/etc/teknav/backend.env`:
```env
VAPID_PUBLIC_KEY=<paste_generated_public_key>
VAPID_PRIVATE_KEY=<paste_generated_private_key>
VAPID_SUBJECT=mailto:admin@teknav.ir
```
If these are not set, the push endpoints return 503 and `sendPushToUser` is a no-op — no crash, no side effects. Set them before announcing push to users.
Zarinpal payment gateway (required for membership subscriptions; until set, subscribe endpoint returns `payment_unavailable`):
```env
ZARINPAL_MERCHANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
ZARINPAL_SANDBOX=0
MEMBERSHIP_PRICE_IRR=5000000
MEMBERSHIP_DURATION_DAYS=365
```
File storage — **local disk is the default and requires no env vars**. Uploads go to `UPLOAD_DIR` (default `/var/lib/teknav/uploads`). S3-compatible storage is a future option wired but not yet active:
```env
# Leave unset (or set to "local") to use local disk — recommended for now
# STORAGE_BACKEND=local
# S3 migration (Phase 12+): uncomment and fill in when needed
# STORAGE_BACKEND=s3
# S3_ENDPOINT=https://storage.example.ir
# S3_BUCKET=teknav-uploads
# S3_ACCESS_KEY=...
# S3_SECRET_KEY=...
```
OAuth callback URLs, if Google/GitHub login is enabled:
```text
https://www.teknav.ir/api/v1/auth/oauth/google/callback
https://www.teknav.ir/api/v1/auth/oauth/github/callback
```
Validate env file readability for the service user:
```bash
sudo -u teknav test -r /etc/teknav/backend.env
```
Run the static production preflight before dependency installation/build steps:
```bash
sudo -u teknav npm run preflight:prod
```
The current expected warning is that no Prisma migrations directory exists yet; production schema application uses the documented `prisma:apply` fallback.
## 7. Install Dependencies
Use the committed lockfiles and install dependencies on the Linux host. Do not copy `node_modules`, generated Prisma engines, or any Windows-generated Prisma binaries from a workstation.
```bash
cd /opt/teknav/current
sudo -u teknav npm ci
```
Install backend dependencies:
```bash
cd /opt/teknav/current/backend
sudo -u teknav npm ci
```
If the server has enough memory and native image conversion is required, ensure optional `sharp` installed successfully:
```bash
cd /opt/teknav/current/backend
sudo -u teknav npm ls sharp || true
```
## 8. Prisma Generate, Schema Apply, and Seed
Generate Prisma client:
```bash
cd /opt/teknav/current/backend
sudo -u teknav npm run prisma:generate
```
This must run on the Linux server after `npm ci`. Prisma downloads/generates platform-specific engines for the target host; Windows-generated `query_engine*.node` and `schema-engine*` files are local artifacts and must not be deployed.
Apply schema:
```bash
sudo -u teknav npm run prisma:apply
```
`prisma:apply` is intentional. It uses migrations if they exist and falls back to `prisma db push --skip-generate` while this repo has no full migrations directory.
Seed production data:
```bash
sudo -u teknav npm run seed
```
Important:
- Operator-facing seed credentials are in root `ACCOUNTS.md`.
- `ACCOUNTS.md` is gitignored and nginx-blocked.
- Every seeded user gets a URL-safe username and can log in with email or `@username`.
- Rotate all seeded passwords after first login.
- After rotation, remove `resetPasswordOnSeed` for accounts that must not be reset on future seeds.
## 9. Generate SEO Assets and Build
Generate crawler artifacts:
```bash
cd /opt/teknav/current
sudo -u teknav npm run seo:sitemap
```
Generate local Open Graph images:
```bash
sudo -u teknav npm run og:images
```
Build frontend:
```bash
sudo -u teknav npm run preflight:prod
sudo -u teknav npm run build
```
Build backend:
```bash
cd /opt/teknav/current/backend
sudo -u teknav npm run build
```
Confirm build output:
```bash
test -f /opt/teknav/current/dist/index.html
test -f /opt/teknav/current/public/sitemap.xml
test -f /opt/teknav/current/public/robots.txt
test -f /opt/teknav/current/public/feed.xml
test -f /opt/teknav/current/public/feeds/topic-ai.xml
test -f /opt/teknav/current/backend/dist/src/server.js
```
## 10. systemd Services and Timers
Install units:
```bash
cd /opt/teknav/current
sudo cp deploy/systemd/*.service /etc/systemd/system/
sudo cp deploy/systemd/*.timer /etc/systemd/system/
sudo cp deploy/logrotate/teknav /etc/logrotate.d/teknav
sudo chmod 644 /etc/systemd/system/teknav-*.service /etc/systemd/system/teknav-*.timer
sudo chmod 644 /etc/logrotate.d/teknav
sudo chmod +x scripts/systemd-backup.sh
sudo systemctl daemon-reload
```
Start backend:
```bash
sudo systemctl enable --now teknav-backend.service
sudo systemctl status teknav-backend.service --no-pager
```
Start scheduled publishing and backups:
```bash
sudo systemctl enable --now teknav-publish-due.timer
sudo systemctl enable --now teknav-weekly-digest.timer
sudo systemctl enable --now teknav-backup.timer
sudo systemctl list-timers 'teknav-*'
```
Useful service commands:
```bash
sudo journalctl -u teknav-backend -f
sudo systemctl restart teknav-backend
sudo systemctl start teknav-migrate
sudo journalctl -u teknav-migrate -n 100 --no-pager
sudo systemctl start teknav-publish-due
sudo systemctl start teknav-weekly-digest
sudo systemctl start teknav-backup
```
Local backend health before nginx:
```bash
curl -fsS http://127.0.0.1:3010/api/v1/health
curl -fsS http://127.0.0.1:3010/api/v1/ready
```
Expected:
```json
{"ok":true}
```
`/api/v1/health` is minimal public health. `/api/v1/ready` returns 503 if PostgreSQL or Redis is unavailable. `/api/v1/admin/health` is the authenticated diagnostics endpoint with runtime feature flags.
## 11. nginx Before TLS
The repository nginx template contains placeholder certificate paths for an already-provisioned certificate. For a fresh Certbot install, create a temporary HTTP-only nginx config first:
```bash
sudo tee /etc/nginx/sites-available/teknav >/dev/null <<'NGINX'
server {
listen 80;
listen [::]:80;
server_name teknav.ir www.teknav.ir;
root /opt/teknav/current/dist;
index index.html;
charset utf-8;
location = /ACCOUNTS.md {
deny all;
return 404;
}
location /api/ {
proxy_pass http://127.0.0.1:3010;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
location /uploads/ {
alias /var/lib/teknav/uploads/;
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
}
}
NGINX
```
Enable site:
```bash
sudo ln -sf /etc/nginx/sites-available/teknav /etc/nginx/sites-enabled/teknav
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl enable --now nginx
sudo systemctl reload nginx
```
Confirm HTTP works:
```bash
curl -I http://www.teknav.ir/
curl -fsS http://www.teknav.ir/api/v1/health
```
## 12. TLS with Certbot
Issue certificates:
```bash
sudo certbot --nginx -d www.teknav.ir -d teknav.ir
```
Choose redirect to HTTPS when Certbot asks.
Test renewal:
```bash
sudo certbot renew --dry-run
```
## 13. Final nginx Production Config
Install the production nginx template:
```bash
cd /opt/teknav/current
sudo cp nginx/nginx.conf.example /etc/nginx/sites-available/teknav
```
Edit certificate paths in `/etc/nginx/sites-available/teknav`:
```bash
sudo nano /etc/nginx/sites-available/teknav
```
Replace:
```nginx
ssl_certificate /etc/nginx/ssl/_.teknav.ir-fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/_.teknav.ir-privateKey.pem;
```
With Certbot paths:
```nginx
ssl_certificate /etc/letsencrypt/live/www.teknav.ir/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.teknav.ir/privkey.pem;
```
Validate and reload:
```bash
sudo nginx -t
sudo systemctl reload nginx
```
The production template does all of this:
- Redirects apex `teknav.ir` to `https://www.teknav.ir`.
- Serves the SPA from `dist/`.
- Proxies `/api/` to Fastify.
- Disables buffering/compression for SSE paths.
- Serves uploads from `/var/lib/teknav/uploads`.
- Blocks dotfiles and `/ACCOUNTS.md`.
- Enables gzip for JS, CSS, JSON, XML, RSS, fonts, and SVG.
- Caches immutable static assets.
- Keeps crawler files public: `/robots.txt`, `/sitemap.xml`, `/feed.xml`.
After the site is stable for a few days, increase HSTS in nginx from one week to one year:
```nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
```
Then:
```bash
sudo nginx -t
sudo systemctl reload nginx
```
## 14. Firewall
Allow only SSH, HTTP, and HTTPS:
```bash
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
```
Confirm private services are not public:
```bash
sudo ss -ltnp | grep -E ':(3010|5432|6379)'
```
Expected listeners should be local only.
## 15. Production Validation
Run these checks after nginx and TLS are live:
```bash
curl -fsS https://www.teknav.ir/api/v1/health
curl -fsS https://www.teknav.ir/api/v1/ready
curl -I https://www.teknav.ir/
curl -I https://www.teknav.ir/articles
curl -I https://www.teknav.ir/topics/ai
curl -I https://www.teknav.ir/newsletter
curl -I https://www.teknav.ir/tag/AI
curl -I https://www.teknav.ir/sitemap.xml
curl -I https://www.teknav.ir/robots.txt
curl -I https://www.teknav.ir/feed.xml
curl -I https://www.teknav.ir/feeds/topic-ai.xml
curl -I https://www.teknav.ir/ACCOUNTS.md
```
Expected:
- `/api/v1/health` returns minimal `ok: true`.
- `/api/v1/ready` returns 200 while PostgreSQL and Redis are healthy; it returns 503 if either dependency is unavailable.
- `/`, `/articles`, `/topics/ai`, `/newsletter`, and a known `/tag/...` page return 200.
- `/robots.txt`, `/sitemap.xml`, `/feed.xml`, and topic/author feeds under `/feeds/` return 200.
- `/ACCOUNTS.md` returns 403 or 404.
Run the production smoke script:
```bash
cd /opt/teknav/current
sudo -u teknav npm run preflight:prod
sudo -u teknav TEKNAV_SMOKE_URL=https://www.teknav.ir npm run smoke:prod
```
Optional custom article smoke list:
```bash
sudo -u teknav TEKNAV_SMOKE_URL=https://www.teknav.ir TEKNAV_SMOKE_ARTICLES=/article/agentic-ai-production,/article/ai-clean-data npm run smoke:prod
```
## 16. Functional Validation Checklist
Validate on Linux/staging or production-like infrastructure:
```bash
cd /opt/teknav/current/backend
sudo -u teknav npm run prisma:generate
sudo -u teknav npm run prisma:apply
sudo -u teknav npm run seed
cd /opt/teknav/current
sudo -u teknav npm run preflight:prod
sudo -u teknav npm run seo:sitemap
sudo -u teknav npm run og:images
sudo -u teknav npm run build
cd /opt/teknav/current/backend
sudo -u teknav npm run build
sudo -u teknav TEST_REQUIRE_SERVER=1 TEST_BASE_URL=http://127.0.0.1:3010 npm test
cd /opt/teknav/current
sudo -u teknav TEKNAV_SMOKE_URL=https://www.teknav.ir npm run smoke:prod
```
Manually verify:
- Login by email.
- Login by `@username`.
- Profile edit rules for reader, writer, editor, reviewer, and admin.
- Password change with current password.
- Comments, likes, and flags.
- Reading history and reading lists.
- Writer follows.
- Topic follow/unfollow on `/topics/ai`.
- Writer panel submission: draft or review only, no direct public publish.
- Reviewer/editor approval publishes only after Phase 10 quality checks pass.
- `GET /api/v1/admin/panel-metrics` returns role-aware dashboard metrics for admin, editor, writer, and reviewer accounts.
- Article share buttons record first-party `share_clicked` events.
- Notifications and SSE.
- Newsletter subscribe/unsubscribe/campaign send and public archive.
- Article series pages.
- Topic hub pages.
- Admin analytics, SEO audit, and review workflow.
## 17. Updates
For a normal update:
```bash
cd /opt/teknav/current
sudo -u teknav git pull
sudo -u teknav npm ci
cd /opt/teknav/current/backend
sudo -u teknav npm ci
sudo -u teknav npm run prisma:generate
sudo -u teknav npm run prisma:apply
cd /opt/teknav/current
sudo -u teknav npm run seo:sitemap
sudo -u teknav npm run og:images
sudo -u teknav npm run build
cd /opt/teknav/current/backend
sudo -u teknav npm run build
sudo systemctl restart teknav-backend
sudo nginx -t
sudo systemctl reload nginx
cd /opt/teknav/current
sudo -u teknav TEKNAV_SMOKE_URL=https://www.teknav.ir npm run smoke:prod
```
Run `npm run seo:sitemap` after changing public routes, article slugs, article dates, categories, authors, topic hubs, newsletter archive routes, series, or crawler files.
Run `npm run og:images` after changing article Open Graph metadata.
## 18. Safer Release Directory Flow
For lower-risk deployments, deploy to a timestamped release and atomically switch `current`:
```bash
RELEASE="$(date +%Y%m%d-%H%M%S)"
sudo mkdir -p "/opt/teknav/releases/$RELEASE"
sudo rsync -a /tmp/teknav-upload/ "/opt/teknav/releases/$RELEASE/"
sudo chown -R teknav:teknav "/opt/teknav/releases/$RELEASE"
cd "/opt/teknav/releases/$RELEASE"
sudo -u teknav npm ci
cd backend
sudo -u teknav npm ci
sudo -u teknav npm run prisma:generate
sudo -u teknav npm run prisma:apply
cd ..
sudo -u teknav npm run seo:sitemap
sudo -u teknav npm run og:images
sudo -u teknav npm run build
cd backend
sudo -u teknav npm run build
cd ..
sudo ln -sfn "/opt/teknav/releases/$RELEASE" /opt/teknav/current
sudo systemctl restart teknav-backend
sudo nginx -t
sudo systemctl reload nginx
```
Rollback to the previous release:
```bash
ls -1 /opt/teknav/releases
sudo ln -sfn /opt/teknav/releases/PREVIOUS_RELEASE /opt/teknav/current
sudo systemctl restart teknav-backend
sudo nginx -t
sudo systemctl reload nginx
```
Only roll back code after a schema change if the older code is compatible with the newer schema.
## 19. Backups
Run backup manually:
```bash
sudo systemctl start teknav-backup
sudo journalctl -u teknav-backup -n 100 --no-pager
ls -lh /var/backups/teknav
```
Timers:
```bash
sudo systemctl list-timers 'teknav-*'
```
Default retention is 14 days. Override in `/etc/teknav/backend.env`:
```env
BACKUP_RETENTION_DAYS=30
```
## 20. Restore
Stop backend before a full restore:
```bash
sudo systemctl stop teknav-backend
```
Restore a backup:
```bash
set -a
. /etc/teknav/backend.env
set +a
gunzip -c /var/backups/teknav/teknav-YYYYMMDD-HHMMSS.sql.gz | psql "$DATABASE_URL"
```
Restart and verify:
```bash
sudo systemctl start teknav-backend
curl -fsS https://www.teknav.ir/api/v1/health
```
## 21. Logs and Operations
Backend logs:
```bash
sudo journalctl -u teknav-backend -f
sudo journalctl -u teknav-backend -n 200 --no-pager
```
nginx logs:
```bash
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log
```
Database:
```bash
sudo -u postgres psql -d teknav
```
Redis:
```bash
redis-cli info server
redis-cli info memory
```
Disk:
```bash
df -h
du -sh /var/lib/teknav/uploads /var/backups/teknav /opt/teknav/current
```
## 22. Security Notes
- Never deploy Docker for this production model unless the architecture changes.
- Never put `ACCOUNTS.md` under `public/` or `dist/`.
- Keep `/etc/teknav/backend.env` mode `640` and owned by `root:teknav`.
- Rotate seeded passwords after first login.
- Keep PostgreSQL, Redis, and Fastify bound to localhost.
- Do not add remote analytics scripts, remote fonts, remote images, or CDN assets.
- Keep nginx gzip enabled for static assets and JSON.
- Keep SSE paths unbuffered:
- `/api/v1/auth/notifications/stream`
- `/api/v1/admin/stream`
- `VAPID_PRIVATE_KEY` must stay in `/etc/teknav/backend.env` only. The public key is exposed via `/api/v1/auth/push/vapid-key` (authenticated) — never embed it in nginx config or any public file.
## 23. Troubleshooting
Backend does not start:
```bash
sudo systemctl status teknav-backend --no-pager
sudo journalctl -u teknav-backend -n 200 --no-pager
```
Schema apply fails:
```bash
cd /opt/teknav/current/backend
sudo -u teknav npm run prisma:generate
sudo -u teknav npm run prisma:apply
```
Frontend 404s on refresh:
```bash
sudo nginx -T | grep -A20 "location /"
```
Confirm `try_files $uri $uri/ /index.html;` exists.
API returns 502:
```bash
curl -fsS http://127.0.0.1:3010/api/v1/health
sudo journalctl -u teknav-backend -n 100 --no-pager
```
Uploads return 404:
```bash
grep UPLOAD_DIR /etc/teknav/backend.env
sudo ls -lah /var/lib/teknav/uploads
sudo chown -R teknav:teknav /var/lib/teknav/uploads
sudo nginx -T | grep -A12 "location /uploads/"
```
SSE notifications or admin live widgets do not update:
```bash
redis-cli ping
sudo nginx -T | grep -A20 "admin/stream"
sudo nginx -T | grep -A20 "notifications/stream"
```
Certbot renewal fails:
```bash
sudo certbot renew --dry-run
sudo nginx -t
sudo journalctl -u nginx -n 100 --no-pager
```
SMTP fails:
```bash
grep '^SMTP_' /etc/teknav/backend.env
sudo journalctl -u teknav-backend -n 200 --no-pager | grep -i smtp
```
Kavenegar OTP fails:
```bash
grep '^SMS_' /etc/teknav/backend.env
sudo journalctl -u teknav-backend -n 200 --no-pager | grep -i otp
```
Web push not delivering:
```bash
# Confirm VAPID keys are set
grep '^VAPID_' /etc/teknav/backend.env
# Check for 410 stale-subscription cleanup in logs
sudo journalctl -u teknav-backend -n 200 --no-pager | grep -i push
# Verify PushSubscription table exists
sudo -u postgres psql -d teknav -c '\dt "PushSubscription"'
# Verify at least one subscription row exists for a test user
sudo -u postgres psql -d teknav -c 'SELECT "userId", "endpoint" FROM "PushSubscription" LIMIT 5;'
```
If the table is missing, re-run `cd backend && npm run prisma:apply`.
Crawler files missing:
```bash
cd /opt/teknav/current
sudo -u teknav npm run seo:sitemap
ls -lh public/sitemap.xml public/robots.txt public/feed.xml
sudo nginx -t
sudo systemctl reload nginx
```