diff --git a/KITTISAK_DEPLOYMENT_RUNBOOK.md b/KITTISAK_DEPLOYMENT_RUNBOOK.md new file mode 100644 index 0000000..4e13b69 --- /dev/null +++ b/KITTISAK_DEPLOYMENT_RUNBOOK.md @@ -0,0 +1,182 @@ +# Kittisak Deployment Runbook + +เอกสารนี้เป็น runbook เฉพาะลูกค้า `kittisak` โดยอิงจาก `DEPLOYMENT_GUIDE.md` และไฟล์ตัวอย่างใน `serverinfo/ddc-*` / `serverinfo/huahin-*` + +## Target + +- Project name: `kittisak` +- Website: `https://kittisak.com` +- SPA: `https://spa.kittisak.com` +- Admin: `https://admin.kittisak.com` +- API: `https://api.kittisak.com` +- Database: `kittisakdb` +- Website SSR port: `3005` +- Backend API port: `3054` +- Bare repo: `/home/ti/git/kittisak.com.git` +- Backend path: `/home/ti/projects/kittisak-api` +- Website path: `/home/ti/projects/kittisak-website` +- SPA static path: `/home/ti/projects/MyNginxMain/nginx/html/kittisak/spa` +- Admin static path: `/home/ti/projects/MyNginxMain/nginx/html/kittisak/admin` + +## Files Prepared In This Repo + +- `serverinfo/kittisak-post-receive` +- `serverinfo/kittisak-backend.env.example` +- `serverinfo/kittisak-nginx/kittisak.com.conf` +- `serverinfo/kittisak-nginx/spa.kittisak.com.conf` +- `serverinfo/kittisak-nginx/admin.kittisak.com.conf` +- `serverinfo/kittisak-nginx/api.kittisak.com.conf` + +## Preflight On Server + +Run on `kitizone.com` before changing live services: + +```bash +ss -ltnp | grep -E ':3005|:3054' || true +pm2 list +``` + +If ports `3005` or `3054` are already used, stop and choose new ports before applying these files. + +## 1. Create Bare Git Repository + +```bash +mkdir -p /home/ti/git/kittisak.com.git +cd /home/ti/git/kittisak.com.git +git init --bare +``` + +## 2. Create Project Directories + +```bash +mkdir -p /home/ti/projects/kittisak-api +mkdir -p /home/ti/projects/kittisak-website +mkdir -p /home/ti/projects/MyNginxMain/nginx/html/kittisak/spa +mkdir -p /home/ti/projects/MyNginxMain/nginx/html/kittisak/admin +``` + +## 3. Create Database + +```bash +docker exec -it arokro_postgres psql -U postgres -c "CREATE DATABASE kittisakdb;" +``` + +## 4. Open Firewall Ports + +```bash +sudo ufw allow 3005/tcp +sudo ufw allow 3054/tcp +sudo ufw reload +``` + +## 5. Request SSL Certificates + +```bash +cd /home/ti/projects/MyNginxMain +docker compose stop nginx + +docker compose run --rm certbot certonly --standalone \ + -d kittisak.com \ + -d spa.kittisak.com \ + -d admin.kittisak.com \ + -d api.kittisak.com +``` + +## 6. Install Nginx Configs + +Copy files from `serverinfo/kittisak-nginx/` to: + +```text +/home/ti/projects/MyNginxMain/nginx/conf.d/ +``` + +Then validate and restart nginx: + +```bash +cd /home/ti/projects/MyNginxMain +docker compose run --rm nginx nginx -t +docker compose up -d nginx +``` + +## 7. Install Backend Environment File + +Create this server file: + +```text +/home/ti/projects/kittisak-api/.env +``` + +Use `serverinfo/kittisak-backend.env.example` as the base and replace placeholders before production use: + +- `postgres:password` must match the real Postgres password. +- `JWT_SECRET` must be a new project-specific secret. +- `OMISE_SECRET_KEY` must be set if payments are enabled. + +The deploy hook copies this file into `/home/ti/projects/kittisak-api/apps/backend/.env` before Prisma commands. + +## 8. Install Git Hook + +Copy: + +```text +serverinfo/kittisak-post-receive +``` + +To: + +```text +/home/ti/git/kittisak.com.git/hooks/post-receive +``` + +Then run: + +```bash +chmod +x /home/ti/git/kittisak.com.git/hooks/post-receive +``` + +## 9. Deploy From Developer Machine + +```bash +git remote add kittisak-website ti@kitizone.com:/home/ti/git/kittisak.com.git +git push kittisak-website master +``` + +## 10. Seed Database + +Run after the first successful deployment: + +```bash +ssh ti@kitizone.com +cd /home/ti/projects/kittisak-api/apps/backend +export PATH=$PATH:/home/ti/.bun/bin:/home/ti/.nvm/versions/node/v22.17.1/bin +bun run prisma/seed.cjs +``` + +Default seeded admin account is currently `admin / homaroma123`. Change it before handing production access to the customer. + +## Verification + +```bash +pm2 list +curl -I https://kittisak.com +curl -I https://spa.kittisak.com +curl -I https://admin.kittisak.com +curl -I https://api.kittisak.com +``` + +Expected PM2 processes: + +- `kittisak-backend` +- `kittisak-website` + +Check browser console for: + +- CORS errors on admin and SPA +- missing static assets +- API 504 from website SSR build or runtime fetches + +## Known Risks + +- `serverinfo/kittisak-post-receive` rewrites `ecosystem.config.js` at deploy time from the base `homaroma` config. This follows the existing `pkh`/`ddc` pattern. +- The codebase still contains some hardcoded HomAroma URLs and storage keys in app code. This runbook only prepares infrastructure deployment, not full white-label branding cleanup. +- Payments and LINE LIFF need customer-specific production keys before launch. diff --git a/serverinfo/kittisak-backend.env.example b/serverinfo/kittisak-backend.env.example new file mode 100644 index 0000000..bb82050 --- /dev/null +++ b/serverinfo/kittisak-backend.env.example @@ -0,0 +1,8 @@ +PORT=3054 +DATABASE_URL="postgresql://postgres:password@localhost:5132/kittisakdb?schema=public" +NODE_ENV=production +APP_URL="https://api.kittisak.com" +BACKEND_PUBLIC_URL="https://api.kittisak.com" +CORS_ORIGIN="https://admin.kittisak.com,https://spa.kittisak.com,https://kittisak.com" +JWT_SECRET="replace-with-kittisak-production-secret" +OMISE_SECRET_KEY="" diff --git a/serverinfo/kittisak-nginx/admin.kittisak.com.conf b/serverinfo/kittisak-nginx/admin.kittisak.com.conf new file mode 100644 index 0000000..a6fa3db --- /dev/null +++ b/serverinfo/kittisak-nginx/admin.kittisak.com.conf @@ -0,0 +1,31 @@ +server { + listen 80; + server_name admin.kittisak.com; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name admin.kittisak.com; + + ssl_certificate /etc/letsencrypt/live/admin.kittisak.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/admin.kittisak.com/privkey.pem; + + root /usr/share/nginx/html/kittisak/admin; + index index.html index.htm; + + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires max; + log_not_found off; + } +} diff --git a/serverinfo/kittisak-nginx/api.kittisak.com.conf b/serverinfo/kittisak-nginx/api.kittisak.com.conf new file mode 100644 index 0000000..56cf304 --- /dev/null +++ b/serverinfo/kittisak-nginx/api.kittisak.com.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name api.kittisak.com; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name api.kittisak.com; + + client_max_body_size 20m; + + ssl_certificate /etc/letsencrypt/live/api.kittisak.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/api.kittisak.com/privkey.pem; + + location / { + proxy_pass http://192.168.1.167:3054; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + 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; + } +} diff --git a/serverinfo/kittisak-nginx/kittisak.com.conf b/serverinfo/kittisak-nginx/kittisak.com.conf new file mode 100644 index 0000000..30b5ef1 --- /dev/null +++ b/serverinfo/kittisak-nginx/kittisak.com.conf @@ -0,0 +1,38 @@ +server { + listen 80; + server_name kittisak.com; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name kittisak.com; + + ssl_certificate /etc/letsencrypt/live/kittisak.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/kittisak.com/privkey.pem; + + location / { + proxy_pass http://192.168.1.167:3005; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + 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_cache_bypass $http_upgrade; + } + + location /_next/static/ { + proxy_pass http://192.168.1.167:3005; + proxy_http_version 1.1; + proxy_set_header Host $host; + add_header Cache-Control "public, max-age=31536000, immutable"; + } +} diff --git a/serverinfo/kittisak-nginx/spa.kittisak.com.conf b/serverinfo/kittisak-nginx/spa.kittisak.com.conf new file mode 100644 index 0000000..971f30b --- /dev/null +++ b/serverinfo/kittisak-nginx/spa.kittisak.com.conf @@ -0,0 +1,31 @@ +server { + listen 80; + server_name spa.kittisak.com; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name spa.kittisak.com; + + ssl_certificate /etc/letsencrypt/live/spa.kittisak.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/spa.kittisak.com/privkey.pem; + + root /usr/share/nginx/html/kittisak/spa; + index index.html index.htm; + + location / { + try_files $uri $uri/ /index.html; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires max; + log_not_found off; + } +} diff --git a/serverinfo/kittisak-post-receive b/serverinfo/kittisak-post-receive new file mode 100644 index 0000000..921a181 --- /dev/null +++ b/serverinfo/kittisak-post-receive @@ -0,0 +1,156 @@ +#!/bin/bash + +# ============================================================================== +# Kittisak Project - Integrated Deployment Script (post-receive hook) +# ============================================================================== + +# 1. TARGET PATHS (Nginx & PM2) +TARGET_SPA="/home/ti/projects/MyNginxMain/nginx/html/kittisak/spa" +TARGET_ADMIN="/home/ti/projects/MyNginxMain/nginx/html/kittisak/admin" +TARGET_BACKEND="/home/ti/projects/kittisak-api" +TARGET_WEBSITE="/home/ti/projects/kittisak-website" + +# 2. REPOSITORY CONFIG +REPO="/home/ti/git/kittisak.com.git" +BRANCH="master" +TEMP_DIR="/tmp/kittisak_build_$(date +%s)" + +# 3. BINARY PATHS & PM2 +BUN="/home/ti/.bun/bin/bun" +PM2="/home/ti/.nvm/versions/node/v22.17.1/bin/pm2" +PM2_NAME_BACKEND="kittisak-backend" +PM2_NAME_WEBSITE="kittisak-website" +export PATH="$PATH:/home/ti/.bun/bin:/home/ti/.nvm/versions/node/v22.17.1/bin" + +# 4. BUILD ENVIRONMENT +export VITE_API_URL="https://api.kittisak.com/api" +export NEXT_PUBLIC_API_URL="https://api.kittisak.com/api" + +# Customer-specific values. Fill these in on the server if enabled. +export VITE_LIFF_ID="${VITE_LIFF_ID:-}" +export VITE_OMISE_PUBLIC_KEY="${VITE_OMISE_PUBLIC_KEY:-}" + +# Service ports. Nginx proxies to these ports. +BACKEND_PORT=3054 +SSR_PORT=3005 + +# ------------------------------------------------------------------------------ +# START DEPLOYMENT +# ------------------------------------------------------------------------------ +set -e +echo "------- Starting Full Deployment: Kittisak Platform -------" + +# A. Prepare Temp Source from Checkout +echo "Cloning branch $BRANCH to temporary directory: $TEMP_DIR" +mkdir -p $TEMP_DIR +git --work-tree=$TEMP_DIR --git-dir=$REPO checkout -f $BRANCH + +cd $TEMP_DIR +echo "Installing monorepo dependencies..." +$BUN install + +# B. Build & Deploy SPA +echo ">>> Building SPA..." +cd apps/spa +$BUN run build +echo "Deploying SPA to $TARGET_SPA..." +mkdir -p $TARGET_SPA +rm -rf $TARGET_SPA/* +cp -rv dist/* $TARGET_SPA/ +cd ../.. + +# C. Build & Deploy ADMIN PANEL +echo ">>> Building Admin Panel..." +cd apps/admin +$BUN run build +echo "Deploying Admin Panel to $TARGET_ADMIN..." +mkdir -p $TARGET_ADMIN +rm -rf $TARGET_ADMIN/* +cp -rv dist/* $TARGET_ADMIN/ +cd ../.. + +# D. Deploy & Manage BACKEND (API) +echo ">>> Updating Backend at $TARGET_BACKEND..." +mkdir -p $TARGET_BACKEND +mkdir -p $TARGET_BACKEND/apps +mkdir -p $TARGET_BACKEND/packages + +rsync -av --delete \ + --exclude='.env' \ + --exclude='public/' \ + --exclude='node_modules/' \ + apps/backend/ $TARGET_BACKEND/apps/backend/ + +rsync -av packages/ $TARGET_BACKEND/packages/ + +cp package.json bun.lock ecosystem.config.js $TARGET_BACKEND/ +sed -i 's/homaroma/kittisak/g' $TARGET_BACKEND/ecosystem.config.js +sed -i 's/PORT: 3002/PORT: 3005/g' $TARGET_BACKEND/ecosystem.config.js + +cd $TARGET_BACKEND +echo "Installing backend dependencies..." +$BUN install + +mkdir -p $TARGET_BACKEND/apps/backend/public + +cd $TARGET_BACKEND/apps/backend + +echo ">>> Generating Prisma Client..." +$BUN x prisma generate + +if [ ! -f "$TARGET_BACKEND/.env" ]; then + echo "Error: .env file is missing at $TARGET_BACKEND/.env. Deployment aborted." + exit 1 +fi + +cp "$TARGET_BACKEND/.env" "$TARGET_BACKEND/apps/backend/.env" +echo ">>> Running Prisma Migrate Deploy..." +$BUN x prisma migrate deploy + +# E. PM2 Management - BACKEND +echo ">>> Managing PM2 Process: $PM2_NAME_BACKEND..." +$PM2 startOrReload $TARGET_BACKEND/ecosystem.config.js --only $PM2_NAME_BACKEND --env production --update-env +echo "PM2 process '$PM2_NAME_BACKEND' is running. Waiting 10s for it to fully start..." +sleep 10 + +# F. Build & Deploy WEBSITE (Next.js SSR) +echo ">>> Building Next.js SSR App inside monorepo temp dir..." +cd $TEMP_DIR/apps/website +$BUN run build + +# G. Prepare Next.js SSR +echo ">>> Preparing Next.js SSR App..." +rsync -av --delete \ + $TEMP_DIR/apps/website/.next/standalone/ $TARGET_WEBSITE/ +rsync -av \ + $TEMP_DIR/apps/website/public/ $TARGET_WEBSITE/apps/website/public/ +rsync -av \ + $TEMP_DIR/apps/website/.next/static/ $TARGET_WEBSITE/apps/website/.next/static/ + +cp $TEMP_DIR/ecosystem.config.js $TARGET_WEBSITE/ +sed -i 's/homaroma/kittisak/g' $TARGET_WEBSITE/ecosystem.config.js +sed -i 's/PORT: 3002/PORT: 3005/g' $TARGET_WEBSITE/ecosystem.config.js + +# H. Fix Permissions +echo ">>> Fixing Permissions..." +chown -R ti:ti $TARGET_BACKEND $TARGET_WEBSITE + +# I. PM2 Management - SSR Next.js +cd $TARGET_WEBSITE +echo ">>> Creating symlink for .next..." +ln -sf apps/website/.next .next +echo ">>> Managing PM2 Process: $PM2_NAME_WEBSITE..." +$PM2 startOrReload $TARGET_WEBSITE/ecosystem.config.js --only $PM2_NAME_WEBSITE --env production --update-env +echo "PM2 process '$PM2_NAME_WEBSITE' is running." + +# J. Cleanup +echo "Cleaning up build artifacts..." +rm -rf $TEMP_DIR + +echo "------- ALL DEPLOYMENTS COMPLETED SUCCESSFULLY -------" +echo "" +echo "Service Summary:" +echo " Website (SSR) -> https://kittisak.com (PM2: $PM2_NAME_WEBSITE, Port: $SSR_PORT)" +echo " SPA -> https://spa.kittisak.com" +echo " Admin Panel -> https://admin.kittisak.com" +echo " API Backend -> https://api.kittisak.com (PM2: $PM2_NAME_BACKEND, Port: $BACKEND_PORT)"