النشر عبر SSH
ميزة مُخططة
أمر forge deploy:ssh مُخطط لإصدار مستقبلي. سير عمل النشر الموضح أدناه يوثّق التصميم المُراد. في الوقت الحالي، استخدم سكربتات النشر المُولّدة (forge deploy:scripts) لعمليات النشر اليدوية عبر SSH.
سيتمكن FORGE من نشر تطبيقك مباشرة إلى أي خادم Linux عبر SSH. هذا النهج يمنحك تحكماً كاملاً في بيئة الخادم بدون الحاجة لـ Docker أو Kubernetes. مناسب تماماً لإعدادات VPS التقليدية والخوادم المعدنية أو البيئات التي لا يكون فيها الحاويات خياراً.
النشر المباشر
انشر إلى خادم بعيد بأمر واحد:
forge deploy:ssh --host=production.myapp.com --user=deployيتصل FORGE بالخادم عبر SSH ويسحب أحدث كود ويبني التطبيق وينفّذ الترحيلات ويُعيد تشغيل الخدمات.
خيارات الأمر
| الخيار | النوع | الافتراضي | الوصف |
|---|---|---|---|
--host | string | مطلوب | عنوان IP أو اسم مضيف الخادم |
--user | string | deploy | اسم مستخدم SSH |
--key | string | ~/.ssh/id_rsa | مسار مفتاح SSH الخاص |
--port | number | 22 | منفذ SSH |
--env | string | production | البيئة المستهدفة |
--branch | string | main | فرع Git للنشر |
--dry-run | flag | false | معاينة الأوامر بدون تنفيذ |
أمثلة
forge deploy:ssh \
--host=production.myapp.com \
--user=deployforge deploy:ssh \
--host=staging.myapp.com \
--user=deploy \
--env=staging \
--branch=developforge deploy:ssh \
--host=1.2.3.4 \
--user=deploy \
--key=~/.ssh/deploy_rsa \
--port=2222# معاينة ما سيحدث بدون تنفيذ
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:
bashssh-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 مستقلة:
forge deploy:scripts --env=productionهذا يُنشئ ثلاثة سكربتات:
deploy/production/
├── deploy.sh # سكربت النشر الرئيسي
├── rollback.sh # التراجع للإصدار السابق
└── setup-server.sh # إعداد الخادم الأوليdeploy.sh
سكربت النشر الرئيسي يتعامل مع دورة البناء والنشر الكاملة:
#!/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
التراجع للنشر السابق:
#!/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
سكربت إعداد الخادم لمرة واحدة لتجهيز خادم جديد:
#!/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"تشغيل السكربتات عن بُعد
نفّذ السكربتات المُولّدة على خادمك:
# 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 12 | Ubuntu 24.04 LTS |
| CPU | 1 vCPU | 2+ vCPU |
| RAM | 2 GB | 4+ GB |
| القرص | 20 GB | 40+ GB SSD |
| PostgreSQL | 15+ | 16 |
| Redis | 7+ | 7 |
| Node.js | 18+ | 20 LTS |
| Rust | أحدث إصدار مستقر | أحدث إصدار مستقر |
وضع الصيانة
سكربتات النشر تُنشئ ملف .maintenance أثناء عمليات النشر. يمكنك إعداد reverse proxy (Nginx أو Caddy) لتقديم صفحة صيانة عند وجود هذا الملف:
# 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;
}الخطوات التالية
- النشر باستخدام Docker Compose -- نشر خادم واحد بالحاويات
- النشر على Kubernetes -- نشر cluster مع التوسع التلقائي
- شهادات SSL -- إعداد HTTPS لخادمك