diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..cfd4f6d --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI Tests + +on: + push: + branches: [ develop, feature/*, beta ] + pull_request: + branches: [ develop, beta, main ] + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x, 18.x] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run linting + run: npm run lint + + - name: Run tests + run: npm test + + - name: Run coverage + run: npm run test:coverage + + - name: Upload coverage reports + uses: codecov/codecov-action@v3 + if: matrix.node-version == '18.x' + + security-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Run security audit + run: | + npm audit --audit-level high + npm audit fix --dry-run \ No newline at end of file diff --git a/.gitea/workflows/deploy-beta.yml b/.gitea/workflows/deploy-beta.yml new file mode 100644 index 0000000..a139576 --- /dev/null +++ b/.gitea/workflows/deploy-beta.yml @@ -0,0 +1,56 @@ +name: Deploy to Beta + +on: + push: + branches: [ beta ] + +jobs: + deploy: + runs-on: ubuntu-latest + environment: beta + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci --only=production + + - name: Build application + run: npm run build + + - name: Create deployment package + run: | + mkdir -p deploy + cp -r src dist package*.json ecosystem.config.js scripts deploy/ + tar -czf deploy-$(date +%Y%m%d-%H%M%S).tar.gz -C deploy . + + - name: Deploy to Beta Server + env: + DEPLOY_HOST: ${{ secrets.BETA_HOST }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} + DEPLOY_PATH: ${{ secrets.BETA_DEPLOY_PATH }} + run: | + # Setup SSH + mkdir -p ~/.ssh + echo "$DEPLOY_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts + + # Upload and deploy + scp -i ~/.ssh/deploy_key deploy-*.tar.gz $DEPLOY_USER@$DEPLOY_HOST:/tmp/ + ssh -i ~/.ssh/deploy_key $DEPLOY_USER@$DEPLOY_HOST "bash -s" < scripts/deploy.sh beta + + - name: Health Check + env: + HEALTH_CHECK_URL: ${{ secrets.BETA_HEALTH_CHECK_URL }} + run: | + sleep 30 + curl -f $HEALTH_CHECK_URL || exit 1 \ No newline at end of file diff --git a/.gitea/workflows/deploy-production.yml b/.gitea/workflows/deploy-production.yml new file mode 100644 index 0000000..550c7da --- /dev/null +++ b/.gitea/workflows/deploy-production.yml @@ -0,0 +1,89 @@ +name: Deploy to Production + +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + environment: production + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci --only=production + + - name: Build application + run: npm run build + + - name: Run final tests + run: npm test + + - name: Create backup + env: + DEPLOY_HOST: ${{ secrets.PROD_HOST }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} + DEPLOY_PATH: ${{ secrets.PROD_DEPLOY_PATH }} + run: | + mkdir -p ~/.ssh + echo "$DEPLOY_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H $DEPLOY_HOST >> ~/.ssh/known_hosts + + # Create backup + ssh -i ~/.ssh/deploy_key $DEPLOY_USER@$DEPLOY_HOST " + if [ -d $DEPLOY_PATH ]; then + cp -r $DEPLOY_PATH $DEPLOY_PATH.backup.$(date +%Y%m%d-%H%M%S) + fi + " + + - name: Deploy to Production + env: + DEPLOY_HOST: ${{ secrets.PROD_HOST }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} + DEPLOY_PATH: ${{ secrets.PROD_DEPLOY_PATH }} + run: | + # Create deployment package + mkdir -p deploy + cp -r src dist package*.json ecosystem.config.js scripts deploy/ + tar -czf deploy-$(date +%Y%m%d-%H%M%S).tar.gz -C deploy . + + # Upload and deploy + scp -i ~/.ssh/deploy_key deploy-*.tar.gz $DEPLOY_USER@$DEPLOY_HOST:/tmp/ + ssh -i ~/.ssh/deploy_key $DEPLOY_USER@$DEPLOY_HOST "bash -s" < scripts/deploy.sh production + + - name: Health Check and Rollback on Failure + env: + DEPLOY_HOST: ${{ secrets.PROD_HOST }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }} + HEALTH_CHECK_URL: ${{ secrets.PROD_HEALTH_CHECK_URL }} + run: | + # Wait for application to start + sleep 30 + + # Health check with retry + for i in {1..5}; do + if curl -f $HEALTH_CHECK_URL; then + echo "Health check passed" + exit 0 + fi + echo "Health check failed, retry $i/5" + sleep 10 + done + + # If health check fails, rollback + echo "Health check failed, initiating rollback" + ssh -i ~/.ssh/deploy_key $DEPLOY_USER@$DEPLOY_HOST "bash -s" < scripts/rollback.sh + exit 1 \ No newline at end of file diff --git a/act_runner.service b/act_runner.service new file mode 100644 index 0000000..c5119ac --- /dev/null +++ b/act_runner.service @@ -0,0 +1,14 @@ +[Unit] +Description=Gitea Actions Runner +After=network.target + +[Service] +Type=simple +User=git +WorkingDirectory=/opt/act_runner +ExecStart=/usr/local/bin/act_runner daemon +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/ecosystem.config.js b/ecosystem.config.js new file mode 100644 index 0000000..57160c4 --- /dev/null +++ b/ecosystem.config.js @@ -0,0 +1,28 @@ +module.exports = { + apps: [{ + name: 'your-app-name', + script: './src/app.js', + instances: 'max', + exec_mode: 'cluster', + env: { + NODE_ENV: 'development', + PORT: 3000 + }, + env_beta: { + NODE_ENV: 'staging', + PORT: 3001 + }, + env_production: { + NODE_ENV: 'production', + PORT: 3000 + }, + error_file: './logs/err.log', + out_file: './logs/out.log', + log_file: './logs/combined.log', + time: true, + max_memory_restart: '1G', + restart_delay: 4000, + max_restarts: 10, + min_uptime: '10s' + }] +}; \ No newline at end of file diff --git a/index.js b/index.js index e69de29..991aa1a 100644 --- a/index.js +++ b/index.js @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100644 index 0000000..44ee014 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +set -e + +ENVIRONMENT=${1:-beta} +TIMESTAMP=$(date +%Y%m%d-%H%M%S) +APP_NAME="your-app-name" + +# مسیرهای مختلف برای محیط‌های مختلف +if [ "$ENVIRONMENT" = "production" ]; then + DEPLOY_PATH="/opt/production/$APP_NAME" + PM2_ECOSYSTEM="ecosystem.config.js" +else + DEPLOY_PATH="/opt/beta/$APP_NAME" + PM2_ECOSYSTEM="ecosystem.beta.config.js" +fi + +echo "🚀 Starting deployment to $ENVIRONMENT environment..." + +# ایجاد دایرکتوری در صورت عدم وجود +sudo mkdir -p $DEPLOY_PATH +sudo mkdir -p $DEPLOY_PATH/releases +sudo mkdir -p $DEPLOY_PATH/shared/logs + +# استخراج آرشیو جدید +RELEASE_DIR="$DEPLOY_PATH/releases/release-$TIMESTAMP" +sudo mkdir -p $RELEASE_DIR + +# پیدا کردن آخرین فایل آرشیو +ARCHIVE_FILE=$(ls -t /tmp/deploy-*.tar.gz | head -n1) +sudo tar -xzf $ARCHIVE_FILE -C $RELEASE_DIR + +# نصب dependencies +cd $RELEASE_DIR +sudo npm ci --only=production + +# کپی فایل‌های پیکربندی از shared +if [ -f "$DEPLOY_PATH/shared/.env" ]; then + sudo cp "$DEPLOY_PATH/shared/.env" $RELEASE_DIR/ +fi + +# ایجاد symlink برای logs +sudo ln -sfn $DEPLOY_PATH/shared/logs $RELEASE_DIR/logs + +# تست سریع برای اطمینان از صحت کد +echo "🧪 Running quick tests..." +sudo npm test + +# ذخیره کردن لینک فعلی برای rollback +if [ -L "$DEPLOY_PATH/current" ]; then + PREVIOUS_RELEASE=$(readlink $DEPLOY_PATH/current) + echo "Previous release: $PREVIOUS_RELEASE" +fi + +# بروزرسانی symlink به نسخه جدید +sudo ln -sfn $RELEASE_DIR $DEPLOY_PATH/current + +# تنظیم مالکیت فایل‌ها +sudo chown -R $USER:$USER $DEPLOY_PATH + +# راه‌اندازی/ری‌استارت PM2 +cd $DEPLOY_PATH/current + +if pm2 list | grep -q $APP_NAME; then + echo "🔄 Restarting existing PM2 process..." + pm2 restart $PM2_ECOSYSTEM --env $ENVIRONMENT +else + echo "🆕 Starting new PM2 process..." + pm2 start $PM2_ECOSYSTEM --env $ENVIRONMENT +fi + +# ذخیره پیکربندی PM2 +pm2 save + +echo "✅ Deployment completed successfully!" + +# پاک‌سازی نسخه‌های قدیمی (نگه‌داری 5 نسخه اخیر) +sudo find $DEPLOY_PATH/releases -maxdepth 1 -name "release-*" -type d | sort -r | tail -n +6 | xargs sudo rm -rf + +# پاک‌سازی فایل آرشیو +sudo rm -f $ARCHIVE_FILE + +echo "🧹 Cleanup completed!" \ No newline at end of file diff --git a/scripts/health-check.sh b/scripts/health-check.sh new file mode 100644 index 0000000..b9d55f4 --- /dev/null +++ b/scripts/health-check.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +APP_NAME="your-app-name" +HEALTH_URL=${1:-"http://localhost:3000/health"} +MAX_RETRIES=${2:-10} +RETRY_INTERVAL=${3:-5} + +echo "🔍 Starting health check for $APP_NAME..." + +for i in $(seq 1 $MAX_RETRIES); do + echo "Attempt $i/$MAX_RETRIES..." + + # بررسی وضعیت PM2 + if ! pm2 list | grep -q $APP_NAME; then + echo "❌ PM2 process not found" + sleep $RETRY_INTERVAL + continue + fi + + # بررسی وضعیت online بودن + if ! pm2 list | grep $APP_NAME | grep -q "online"; then + echo "❌ PM2 process not online" + sleep $RETRY_INTERVAL + continue + fi + + # بررسی HTTP endpoint + if curl -f -s $HEALTH_URL > /dev/null; then + echo "✅ Health check passed!" + exit 0 + fi + + echo "❌ Health check failed, retrying in ${RETRY_INTERVAL}s..." + sleep $RETRY_INTERVAL +done + +echo "💥 Health check failed after $MAX_RETRIES attempts" +exit 1 \ No newline at end of file diff --git a/scripts/rollback.sh b/scripts/rollback.sh new file mode 100644 index 0000000..c948109 --- /dev/null +++ b/scripts/rollback.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +set -e + +APP_NAME="your-app-name" +DEPLOY_PATH="/opt/production/$APP_NAME" + +echo "🔄 Starting rollback process..." + +# پیدا کردن آخرین نسخه سالم +RELEASES_DIR="$DEPLOY_PATH/releases" +CURRENT_RELEASE=$(readlink $DEPLOY_PATH/current 2>/dev/null || echo "") + +if [ -z "$CURRENT_RELEASE" ]; then + echo "❌ No current release found" + exit 1 +fi + +# پیدا کردن نسخه قبلی +PREVIOUS_RELEASE=$(find $RELEASES_DIR -maxdepth 1 -name "release-*" -type d | grep -v "$CURRENT_RELEASE" | sort -r | head -n1) + +if [ -z "$PREVIOUS_RELEASE" ]; then + echo "❌ No previous release found for rollback" + exit 1 +fi + +echo "📦 Rolling back from $(basename $CURRENT_RELEASE) to $(basename $PREVIOUS_RELEASE)" + +# بروزرسانی symlink +sudo ln -sfn $PREVIOUS_RELEASE $DEPLOY_PATH/current + +# ری‌استارت PM2 +cd $DEPLOY_PATH/current +pm2 restart ecosystem.config.js --env production + +# بررسی سلامت +sleep 10 +if ./scripts/health-check.sh; then + echo "✅ Rollback completed successfully!" + + # حذف نسخه معیوب + sudo rm -rf $CURRENT_RELEASE + echo "🗑️ Removed faulty release" +else + echo "❌ Rollback failed! Manual intervention required." + exit 1 +fi \ No newline at end of file