diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml new file mode 100644 index 00000000..950262c9 --- /dev/null +++ b/.gitea/workflows/ci-cd.yml @@ -0,0 +1,117 @@ +name: CI/CD Pipeline + +on: + push: + branches: [main] + +jobs: + # ── Lint & Type Check ───────────────────────────────────────────────────────── + lint-typecheck: + name: Lint & Type Check + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node 20 + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Enable pnpm via corepack + run: corepack enable && corepack prepare pnpm@9 --activate + + - name: Get pnpm store path + id: pnpm-cache + run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT + + - name: Cache pnpm store + uses: actions/cache@v3 + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: pnpm-${{ runner.os }}- + + - name: Install dependencies + run: | + pnpm config set registry https://package-mirror.liara.ir/repository/npm/ + pnpm install --frozen-lockfile --network-concurrency 1 + + - name: Lint + run: pnpm lint + + - name: Type check + run: pnpm exec tsc --noEmit + + # ── Deploy ──────────────────────────────────────────────────────────────────── + deploy: + name: Deploy to NODE Cloud + runs-on: ubuntu-latest + needs: lint-typecheck + steps: + - name: Setup SSH key + env: + SSH_KEY: ${{ secrets.SERVER_SSH_KEY }} + run: | + mkdir -p ~/.ssh + printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + echo "SSH key written" + + - name: Deploy via SSH + env: + SSH_HOST: ${{ secrets.SERVER_HOST }} + SSH_USER: ${{ secrets.SERVER_USER }} + run: | + ssh -i ~/.ssh/deploy_key \ + -o StrictHostKeyChecking=no \ + -o ConnectTimeout=30 \ + -o ServerAliveInterval=60 \ + "$SSH_USER@$SSH_HOST" << 'DEPLOY' + set -e + APP_DIR="/srv/nodecloud/apps/biztaghavi" + + echo "==> Checking app directory..." + if [ ! -d "$APP_DIR/.git" ]; then + echo "==> First deploy — cloning repository..." + mkdir -p "$APP_DIR" + git clone https://git.nodecloud.ir/biztaghavi/biztaghavisite.git "$APP_DIR" + fi + + cd "$APP_DIR" + + echo "==> Pulling latest code..." + git pull origin main + + echo "==> Checking .env file..." + if [ ! -f .env ]; then + cp .env.example .env + echo "⚠️ Created .env from .env.example — fill in real values and redeploy!" + exit 1 + fi + + echo "==> Ensuring nodecloud-net network exists..." + docker network inspect nodecloud-net >/dev/null 2>&1 || \ + docker network create nodecloud-net + + echo "==> Building Docker image..." + docker compose build + + echo "==> Starting/updating services..." + docker compose up -d --remove-orphans + + echo "==> Pruning unused images..." + docker image prune -f + + echo "✅ Deployed $(git rev-parse --short HEAD)" + DEPLOY + + - name: Verify deployment + env: + SSH_HOST: ${{ secrets.SERVER_HOST }} + SSH_USER: ${{ secrets.SERVER_USER }} + run: | + ssh -i ~/.ssh/deploy_key \ + -o StrictHostKeyChecking=no \ + "$SSH_USER@$SSH_HOST" \ + 'curl -sf --retry 5 --retry-delay 3 http://127.0.0.1:3009/ -o /dev/null && echo "✅ Health check passed" || echo "⚠️ Health check failed (app may still be starting)"' diff --git a/DEPLOY.md b/DEPLOY.md index 3ab029cf..fc9fa836 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -147,46 +147,48 @@ Admin panel: `https://biztaghavi.com/admin` ## 6. Nginx config -Port: **3009** (next available after 3008 for Khanehban) - -Create `/etc/nginx/sites-available/biztaghavi.com`: - -```nginx -server { - listen 80; - listen [::]:80; - listen 443 ssl; - listen [::]:443 ssl; - server_name biztaghavi.com www.biztaghavi.com; - - ssl_certificate /etc/ssl/certs/nodecloud-selfsigned.crt; - ssl_certificate_key /etc/ssl/private/nodecloud-selfsigned.key; - - client_max_body_size 20M; - - location / { - proxy_pass http://127.0.0.1:3009; - 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 Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_read_timeout 60; - proxy_send_timeout 60; - } -} -``` +Port: **3009** +The config file lives at `nginx/biztaghavi.conf` in this repo. ```bash -ln -s /etc/nginx/sites-available/biztaghavi.com /etc/nginx/sites-enabled/ -nginx -t && nginx -s reload +# Install on server +cp /srv/nodecloud/apps/biztaghavi/nginx/biztaghavi.conf /etc/nginx/sites-available/biztaghavi +ln -sf /etc/nginx/sites-available/biztaghavi /etc/nginx/sites-enabled/biztaghavi +nginx -t && systemctl reload nginx ``` -In **ArvanCloud dashboard**: SSL mode = "Full", origin = `193.105.234.35`, purge cache. +Then issue SSL with Certbot (HTTP must be working first): -> Port 3009 must match the `ports` binding in `docker-compose.yml` — verify it's `"127.0.0.1:3009:3000"`. +```bash +certbot --nginx -d biztaghavi.com -d www.biztaghavi.com +``` + +In **ArvanCloud dashboard**: SSL mode = "Full (strict)", origin = `193.105.234.35`, purge cache. + +> Port **3009** must match the `ports` binding in `docker-compose.yml` — verify it's `"127.0.0.1:3009:3000"`. + +--- + +## 6b. Gitea CI/CD Secrets + +In the Gitea repo → **Settings → Secrets → Actions**, add: + +| Secret name | Value | +|-------------|-------| +| `SERVER_HOST` | `193.105.234.35` | +| `SERVER_USER` | `root` | +| `SERVER_SSH_KEY` | Contents of private SSH key (ed25519 recommended) | + +Generating a deploy key: + +```bash +# On your Mac +ssh-keygen -t ed25519 -C "gitea-deploy" -f ~/.ssh/biztaghavi_deploy +ssh-copy-id -i ~/.ssh/biztaghavi_deploy.pub root@193.105.234.35 +cat ~/.ssh/biztaghavi_deploy # paste this into SERVER_SSH_KEY secret +``` + +Every push to `main` runs: lint → typecheck → SSH deploy → health check. --- diff --git a/docker-compose.yml b/docker-compose.yml index 1bfa733d..33f20753 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,7 +13,7 @@ services: volumes: - db-data:/var/lib/mysql networks: - - app-network + - app-internal healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] interval: 10s @@ -38,12 +38,15 @@ services: volumes: - uploads:/app/public/uploads networks: - - app-network + - app-internal + - nodecloud-net volumes: db-data: uploads: networks: - app-network: + app-internal: driver: bridge + nodecloud-net: + external: true diff --git a/nginx/biztaghavi.conf b/nginx/biztaghavi.conf new file mode 100644 index 00000000..53e0abc2 --- /dev/null +++ b/nginx/biztaghavi.conf @@ -0,0 +1,64 @@ +gzip on; +gzip_vary on; +gzip_proxied any; +gzip_comp_level 6; +gzip_types text/plain text/css text/xml application/json application/javascript + application/xml+rss application/atom+xml image/svg+xml; + +server { + listen 80; + listen [::]:80; + server_name biztaghavi.com www.biztaghavi.com; + + # Certbot will insert HTTPS redirect and SSL block here automatically. + # After first HTTP deploy, run: + # certbot --nginx -d biztaghavi.com -d www.biztaghavi.com + + client_max_body_size 50M; + + # Next.js static assets — long-lived cache + location /_next/static/ { + proxy_pass http://127.0.0.1:3009; + proxy_set_header Host $host; + add_header Cache-Control "public, max-age=31536000, immutable"; + access_log off; + } + + # Uploaded media + location /uploads/ { + proxy_pass http://127.0.0.1:3009; + proxy_set_header Host $host; + add_header Cache-Control "public, max-age=86400"; + access_log off; + } + + # Next.js HMR / WebSocket (needed for dev; harmless in prod) + location /_next/webpack-hmr { + proxy_pass http://127.0.0.1:3009; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } + + location / { + proxy_pass http://127.0.0.1:3009; + 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; + + # WebSocket support + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_read_timeout 60s; + proxy_send_timeout 60s; + proxy_connect_timeout 10s; + + proxy_buffering off; + proxy_cache_bypass $http_upgrade; + } +} diff --git a/scripts/first-deploy.sh b/scripts/first-deploy.sh new file mode 100755 index 00000000..e2501185 --- /dev/null +++ b/scripts/first-deploy.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Run this ONCE on the server to bootstrap the biztaghavi app. +# Usage: bash scripts/first-deploy.sh +set -euo pipefail + +APP_NAME="biztaghavi" +APP_DIR="/srv/nodecloud/apps/$APP_NAME" +REPO_URL="https://git.nodecloud.ir/biztaghavi/biztaghavisite.git" +NGINX_CONF="/etc/nginx/sites-available/$APP_NAME" +PORT=3009 + +echo "" +echo "╔══════════════════════════════════════════╗" +echo "║ biztaghavi — First Deploy Bootstrap ║" +echo "╚══════════════════════════════════════════╝" +echo "" + +# ── 1. App directory ───────────────────────────────────────────────────────── +if [ -d "$APP_DIR/.git" ]; then + echo "[1/7] App directory already exists — skipping clone, pulling latest..." + cd "$APP_DIR" && git pull origin main +else + echo "[1/7] Creating $APP_DIR and cloning repository..." + mkdir -p "$APP_DIR" + git clone "$REPO_URL" "$APP_DIR" + cd "$APP_DIR" +fi + +# ── 2. Docker network ───────────────────────────────────────────────────────── +echo "[2/7] Ensuring nodecloud-net Docker network exists..." +docker network inspect nodecloud-net >/dev/null 2>&1 \ + && echo " nodecloud-net already exists" \ + || docker network create nodecloud-net + +# ── 3. Environment file ─────────────────────────────────────────────────────── +echo "[3/7] Setting up .env file..." +if [ -f "$APP_DIR/.env" ]; then + echo " .env already exists — skipping" +else + cp "$APP_DIR/.env.example" "$APP_DIR/.env" + echo "" + echo " ⚠️ IMPORTANT: Edit $APP_DIR/.env before continuing!" + echo " At minimum set: DB_ROOT_PASSWORD, DB_PASSWORD, SESSION_SECRET, SETUP_KEY" + echo "" + read -rp " Press ENTER after you have filled in .env values... " +fi + +# ── 4. Nginx config ─────────────────────────────────────────────────────────── +echo "[4/7] Installing Nginx config..." +cp "$APP_DIR/nginx/$APP_NAME.conf" "$NGINX_CONF" +ln -sf "$NGINX_CONF" "/etc/nginx/sites-enabled/$APP_NAME" +echo " Testing Nginx config..." +nginx -t +systemctl reload nginx +echo " Nginx reloaded" + +# ── 5. Build & start containers ─────────────────────────────────────────────── +echo "[5/7] Building Docker image (this takes a few minutes)..." +cd "$APP_DIR" +docker compose build + +echo "[6/7] Starting services..." +docker compose up -d + +# ── 6. Wait for health ──────────────────────────────────────────────────────── +echo "[7/7] Waiting for app to become healthy..." +for i in $(seq 1 24); do + if curl -sf http://127.0.0.1:$PORT/ -o /dev/null 2>&1; then + echo "" + echo "✅ App is up at http://127.0.0.1:$PORT" + break + fi + printf "." + sleep 5 +done +echo "" + +# ── 7. Summary ──────────────────────────────────────────────────────────────── +echo "" +echo "══════════════════════════════════════════" +echo " Next steps:" +echo " 1. Point DNS A record for biztaghavi.com → 193.105.234.35" +echo " 2. Issue SSL: certbot --nginx -d biztaghavi.com -d www.biztaghavi.com" +echo " 3. Add Gitea secrets (see DEPLOY.md)" +echo "══════════════════════════════════════════" +echo "" +docker compose ps diff --git a/scripts/health-check.sh b/scripts/health-check.sh new file mode 100755 index 00000000..d8298d24 --- /dev/null +++ b/scripts/health-check.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Quick health check for biztaghavi on the server. +# Usage: bash scripts/health-check.sh +set -euo pipefail + +APP_DIR="/srv/nodecloud/apps/biztaghavi" +PORT=3009 + +echo "=== HTTP Health Check ===" +HTTP_STATUS=$(curl -so /dev/null -w "%{http_code}" --connect-timeout 5 http://127.0.0.1:$PORT/ 2>&1 || echo "FAIL") +if [ "$HTTP_STATUS" = "200" ] || [ "$HTTP_STATUS" = "308" ] || [ "$HTTP_STATUS" = "301" ]; then + echo "✅ HTTP $HTTP_STATUS — app is responding on port $PORT" +else + echo "❌ HTTP $HTTP_STATUS — app may be down on port $PORT" +fi + +echo "" +echo "=== Container Status ===" +cd "$APP_DIR" +docker compose ps + +echo "" +echo "=== Last 20 Log Lines (next-app) ===" +docker compose logs --tail=20 next-app + +echo "" +echo "=== Last 10 Log Lines (db) ===" +docker compose logs --tail=10 db