Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
1014 lines
24 KiB
Markdown
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
|
|
```
|