Skip to content

النشر عبر SSH

ميزة مُخططة

أمر forge deploy:ssh مُخطط لإصدار مستقبلي. سير عمل النشر الموضح أدناه يوثّق التصميم المُراد. في الوقت الحالي، استخدم سكربتات النشر المُولّدة (forge deploy:scripts) لعمليات النشر اليدوية عبر SSH.

سيتمكن FORGE من نشر تطبيقك مباشرة إلى أي خادم Linux عبر SSH. هذا النهج يمنحك تحكماً كاملاً في بيئة الخادم بدون الحاجة لـ Docker أو Kubernetes. مناسب تماماً لإعدادات VPS التقليدية والخوادم المعدنية أو البيئات التي لا يكون فيها الحاويات خياراً.

النشر المباشر

انشر إلى خادم بعيد بأمر واحد:

bash
forge deploy:ssh --host=production.myapp.com --user=deploy

يتصل FORGE بالخادم عبر SSH ويسحب أحدث كود ويبني التطبيق وينفّذ الترحيلات ويُعيد تشغيل الخدمات.

خيارات الأمر

الخيارالنوعالافتراضيالوصف
--hoststringمطلوبعنوان IP أو اسم مضيف الخادم
--userstringdeployاسم مستخدم SSH
--keystring~/.ssh/id_rsaمسار مفتاح SSH الخاص
--portnumber22منفذ SSH
--envstringproductionالبيئة المستهدفة
--branchstringmainفرع Git للنشر
--dry-runflagfalseمعاينة الأوامر بدون تنفيذ

أمثلة

bash
forge deploy:ssh \
  --host=production.myapp.com \
  --user=deploy
bash
forge deploy:ssh \
  --host=staging.myapp.com \
  --user=deploy \
  --env=staging \
  --branch=develop
bash
forge deploy:ssh \
  --host=1.2.3.4 \
  --user=deploy \
  --key=~/.ssh/deploy_rsa \
  --port=2222
bash
# معاينة ما سيحدث بدون تنفيذ
forge deploy:ssh \
  --host=production.myapp.com \
  --user=deploy \
  --dry-run

ما يحدث أثناء النشر

عند تشغيل forge deploy:ssh، تُنفّذ الخطوات التالية على الخادم البعيد:

الجهاز المحلي                       الخادم البعيد
─────────────                       ─────────────

1. الاتصال عبر SSH ─────────────────► المصادقة بالمفتاح

2. ────────────────────────────────► cd /var/www/myapp

3. ────────────────────────────────► git fetch origin
                                     git checkout main
                                     git pull origin main

4. ────────────────────────────────► بناء الخلفية
                                     cargo build --release

5. ────────────────────────────────► بناء الواجهة
                                     cd apps/web && npm ci && npm run build
                                     cd apps/admin && npm ci && npm run build

6. ────────────────────────────────► تنفيذ ترحيلات قاعدة البيانات
                                     ./target/release/forge-cli migrate

7. ────────────────────────────────► إعادة تشغيل الخدمات
                                     systemctl restart myapp-api
                                     pm2 restart myapp-web
                                     pm2 restart myapp-admin

8. ────────────────────────────────► فحص الصحة
                                     curl http://localhost:8080/health

9. ◄────────────────────────────────  تقرير الحالة

كل خطوة تُسجّل في الوقت الفعلي. إذا فشلت أي خطوة، يتوقف النشر ويُبلّغ عن الخطأ.

المعاينة الجافة أولاً

شغّل دائماً مع --dry-run قبل أول نشر لخادم جديد. هذا يُظهر كل أمر سيُنفّذ، مما يتيح لك التحقق من خطة النشر قبل إجراء تغييرات.

أفضل ممارسات الأمان

أمان SSH

اتبع هذه الممارسات للحفاظ على أمان خط أنابيب النشر.

موصى به

  • استخدم SSH agent -- أضف مفتاحك إلى SSH agent حتى لا يُمرر ملف المفتاح مباشرة إلى FORGE:

    bash
    ssh-add ~/.ssh/deploy_rsa
    forge deploy:ssh --host=myapp.com --user=deploy
  • استخدم مستخدم نشر مخصص -- أنشئ مستخدماً بالصلاحيات اللازمة للنشر فقط:

    bash
    # On the server
    sudo adduser --disabled-password deploy
    sudo usermod -aG www-data deploy
  • استخدم المصادقة بالمفتاح -- عطّل مصادقة كلمة المرور على الخادم:

    bash
    # /etc/ssh/sshd_config
    PasswordAuthentication no
    PubkeyAuthentication yes
  • حدد صلاحيات مستخدم النشر -- قيّد وصول sudo للأوامر المطلوبة فقط:

    bash
    # /etc/sudoers.d/deploy
    deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp-api
    deploy ALL=(ALL) NOPASSWD: /bin/systemctl status myapp-api

تجنّب

  • تمرير محتوى المفتاح الخاص مباشرة (استخدم مسار الملف بدلاً من ذلك)
  • استخدام مستخدم root للنشر
  • تخزين مفاتيح SSH في متغيرات البيئة
  • النشر من أجهزة مشتركة أو عامة

كيف يتعامل FORGE مع المفاتيح

FORGE يمرر مسار ملف المفتاح لعميل SSH القياسي. المفتاح لا يُقرأ في ذاكرة FORGE أو يُخزّن أو يُسجّل. اتصال SSH يستخدم نفس تدفق المصادقة عند تشغيل ssh يدوياً.

توليد سكربتات النشر

إذا كنت تفضل إدارة عمليات النشر يدوياً بدلاً من استخدام forge deploy:ssh، أنشئ سكربتات shell مستقلة:

bash
forge deploy:scripts --env=production

هذا يُنشئ ثلاثة سكربتات:

deploy/production/
├── deploy.sh          # سكربت النشر الرئيسي
├── rollback.sh        # التراجع للإصدار السابق
└── setup-server.sh    # إعداد الخادم الأولي

deploy.sh

سكربت النشر الرئيسي يتعامل مع دورة البناء والنشر الكاملة:

bash
#!/bin/bash
set -euo pipefail

APP_NAME="myapp"
APP_DIR="/var/www/${APP_NAME}"
BRANCH="${1:-main}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

echo "==> Deploying ${APP_NAME} (branch: ${BRANCH})"

# Save current version for rollback
cd ${APP_DIR}
git rev-parse HEAD > .last-deploy-commit

# Enter maintenance mode
touch ${APP_DIR}/.maintenance

# Pull latest code
git fetch origin
git checkout ${BRANCH}
git pull origin ${BRANCH}

# Backend (Rust)
echo "==> Building API..."
cd ${APP_DIR}/apps/api
cargo build --release

# Frontend (Next.js Web)
echo "==> Building web app..."
cd ${APP_DIR}/apps/web
npm ci --production=false
npm run build

# Frontend (Next.js Admin)
echo "==> Building admin app..."
cd ${APP_DIR}/apps/admin
npm ci --production=false
npm run build

# Run migrations
echo "==> Running migrations..."
cd ${APP_DIR}
./target/release/forge-cli migrate

# Restart services
echo "==> Restarting services..."
sudo systemctl restart ${APP_NAME}-api
pm2 restart ${APP_NAME}-web
pm2 restart ${APP_NAME}-admin

# Exit maintenance mode
rm -f ${APP_DIR}/.maintenance

# Health check
echo "==> Running health check..."
sleep 5
if curl -sf http://localhost:8080/health > /dev/null; then
    echo "==> Deployment successful!"
else
    echo "==> Health check failed! Rolling back..."
    ./rollback.sh
    exit 1
fi

echo "==> Deployment completed at ${TIMESTAMP}"

rollback.sh

التراجع للنشر السابق:

bash
#!/bin/bash
set -euo pipefail

APP_NAME="myapp"
APP_DIR="/var/www/${APP_NAME}"

echo "==> Rolling back ${APP_NAME}..."

cd ${APP_DIR}

# Get previous commit
if [ ! -f .last-deploy-commit ]; then
    echo "Error: No previous deployment found"
    exit 1
fi

PREV_COMMIT=$(cat .last-deploy-commit)
echo "==> Reverting to commit: ${PREV_COMMIT}"

# Enter maintenance mode
touch ${APP_DIR}/.maintenance

# Revert code
git checkout ${PREV_COMMIT}

# Rebuild
cd ${APP_DIR}/apps/api && cargo build --release
cd ${APP_DIR}/apps/web && npm ci --production=false && npm run build
cd ${APP_DIR}/apps/admin && npm ci --production=false && npm run build

# Rollback migrations (if needed)
cd ${APP_DIR}
./target/release/forge-cli migrate:rollback

# Restart services
sudo systemctl restart ${APP_NAME}-api
pm2 restart ${APP_NAME}-web
pm2 restart ${APP_NAME}-admin

# Exit maintenance mode
rm -f ${APP_DIR}/.maintenance

echo "==> Rollback completed!"

setup-server.sh

سكربت إعداد الخادم لمرة واحدة لتجهيز خادم جديد:

bash
#!/bin/bash
set -euo pipefail

APP_NAME="myapp"
APP_DIR="/var/www/${APP_NAME}"
DOMAIN="myapp.com"

echo "==> Setting up server for ${APP_NAME}..."

# Update system
sudo apt update && sudo apt upgrade -y

# Install dependencies
sudo apt install -y \
    curl git build-essential \
    postgresql postgresql-contrib \
    redis-server \
    nginx certbot python3-certbot-nginx \
    nodejs npm

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source $HOME/.cargo/env

# Install PM2 for Node.js process management
sudo npm install -g pm2

# Create application directory
sudo mkdir -p ${APP_DIR}
sudo chown deploy:deploy ${APP_DIR}

# Clone repository
git clone git@github.com:yourorg/${APP_NAME}.git ${APP_DIR}

# Setup PostgreSQL
sudo -u postgres createuser ${APP_NAME}
sudo -u postgres createdb ${APP_NAME} -O ${APP_NAME}
sudo -u postgres psql -c \
  "ALTER USER ${APP_NAME} WITH PASSWORD 'SET_STRONG_PASSWORD';"

# Setup systemd service for API
sudo tee /etc/systemd/system/${APP_NAME}-api.service << 'EOF'
[Unit]
Description=FORGE API Server
After=network.target postgresql.service

[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/myapp/apps/api
ExecStart=/var/www/myapp/target/release/myapp-api
Restart=always
RestartSec=5
Environment=DATABASE_URL=postgres://myapp:SET_STRONG_PASSWORD@localhost/myapp
Environment=RUST_LOG=info

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable ${APP_NAME}-api

# Setup PM2 for frontend apps
cd ${APP_DIR}/apps/web && pm2 start npm --name "${APP_NAME}-web" -- start
cd ${APP_DIR}/apps/admin && pm2 start npm --name "${APP_NAME}-admin" -- start
pm2 save
pm2 startup

echo "==> Server setup completed!"
echo "==> Next steps:"
echo "    1. Set a strong database password"
echo "    2. Configure SSL with certbot"
echo "    3. Run: ./deploy.sh"

تشغيل السكربتات عن بُعد

نفّذ السكربتات المُولّدة على خادمك:

bash
# Initial server setup (one time)
ssh deploy@myapp.com 'bash -s' < deploy/production/setup-server.sh

# Deploy
ssh deploy@myapp.com 'bash -s' < deploy/production/deploy.sh

# Deploy specific branch
ssh deploy@myapp.com 'bash -s' < deploy/production/deploy.sh develop

# Rollback
ssh deploy@myapp.com 'bash -s' < deploy/production/rollback.sh

متطلبات الخادم

الخادم المستهدف يحتاج التالي:

المتطلبالحد الأدنىالموصى به
نظام التشغيلUbuntu 22.04 / Debian 12Ubuntu 24.04 LTS
CPU1 vCPU2+ vCPU
RAM2 GB4+ GB
القرص20 GB40+ GB SSD
PostgreSQL15+16
Redis7+7
Node.js18+20 LTS
Rustأحدث إصدار مستقرأحدث إصدار مستقر

وضع الصيانة

سكربتات النشر تُنشئ ملف .maintenance أثناء عمليات النشر. يمكنك إعداد reverse proxy (Nginx أو Caddy) لتقديم صفحة صيانة عند وجود هذا الملف:

nginx
# Nginx maintenance mode check
location / {
    if (-f /var/www/myapp/.maintenance) {
        return 503;
    }
    # ... normal proxy rules
}

error_page 503 @maintenance;
location @maintenance {
    root /var/www/myapp/public;
    rewrite ^(.*)$ /maintenance.html break;
}

الخطوات التالية

Released under the MIT License.