−25%

على الدفع السنوي لـ Windows حتى 31 أكتوبر. عرض الخطط

EQVPS
ابدأ الآن

VPS لمعاينات وكلاء البرمجة: كل فرع يدفعه الوكيل يحصل على عنوان URL خاص

أنهى وكيل برمجة فرعًا ويقول إنه يعمل. أعطه خادمًا يمكن التحقق من ذلك عليه: يُبنى كل فرع في حاوية ويُفتح على نطاق فرعي HTTPS خاص خلف كلمة مرور، ومفتاح SSH الخاص بالوكيل لا يستطيع إلا تشغيل المعاينات وإيقافها.

يقول وكيل البرمجة إن الميزة تعمل. ربما يكون محقًا. الاختبارات التي كتبها تنجح، وهذا يثبت أقل مما نتمنى جميعًا. ما تريده فعلًا هو فتحها في المتصفح قبل أن تقترب من المستخدمين، ومن دون أن تطلب من الوكيل لمس بيئة الإنتاج.

بيئات المعاينة تحل هذا، ولا تحتاج إلى منصة لها. خادم VPS واحد بعنوان IPv4 خاص، وDocker وCaddy وسكربتان قصيران تمنح كل فرع عنوانه الخاص مثل https://feature-login-form.preview.example.com، خلف كلمة مرور ويُعاد بناؤه مع كل push. يحصل الوكيل على مفتاح SSH يستطيع تشغيل المعاينات وإيقافها ولا شيء غير ذلك.

بنينا هذا على خادم اختبار ومررنا بالدورة كاملة قبل كتابتها: الإنشاء، والفتح، والدفع مجددًا، والإزالة، ومحاولة إساءة استخدام المفتاح.

ما تحتاجه

  • VPS بعنوان IPv4 عام. تتسع AI-Agent-IP لعدة معاينات في وقت واحد.
  • سجل DNS من نوع wildcard: *.preview.example.com ← عنوان IP للخادم.
  • مستودع تطبيقك مع Dockerfile يخدم على المنفذ 8080. أي لغة تصلح.
  • Ubuntu 24.04 أو Debian 12/13 مع apt install -y docker.io caddy git.

انسخ المستودع مرة واحدة إلى /srv/app بمفتاح نشر للقراءة فقط.

السكربتان

يبني /usr/local/bin/preview-up فرعًا وينشره:

#!/usr/bin/env bash
# preview-up <branch> — build the branch and open it at https://<name>.preview.example.com
set -euo pipefail
BRANCH="$1"
NAME=$(printf '%s' "$BRANCH" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/--*/-/g; s/^-//; s/-$//' | cut -c1-40)
DIR=/srv/previews/$NAME
git -C /srv/app fetch --quiet origin "$BRANCH"
rm -rf "$DIR"; git -C /srv/app worktree prune
git -C /srv/app worktree add --quiet --force --detach "$DIR" FETCH_HEAD
docker build --quiet -t "preview-$NAME" "$DIR" >/dev/null
docker rm -f "preview-$NAME" >/dev/null 2>&1 || true
docker run -d --name "preview-$NAME" --restart unless-stopped --memory 512m -p 127.0.0.1::8080 "preview-$NAME" >/dev/null
PORT=$(docker port "preview-$NAME" 8080/tcp | head -1 | awk -F: '{print $NF}')
cat > "/etc/caddy/previews/$NAME.caddy" <<CADDY
$NAME.preview.example.com {
	import preview_common
	reverse_proxy 127.0.0.1:$PORT
}
CADDY
systemctl reload caddy
echo "https://$NAME.preview.example.com"

ويزيله /usr/local/bin/preview-down:

#!/usr/bin/env bash
# preview-down <branch> — remove the branch preview
set -euo pipefail
NAME=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/--*/-/g; s/^-//; s/-$//' | cut -c1-40)
docker rm -f "preview-$NAME" >/dev/null 2>&1 || true
docker image rm "preview-$NAME" >/dev/null 2>&1 || true
rm -f "/etc/caddy/previews/$NAME.caddy"; rm -rf "/srv/previews/$NAME"; git -C /srv/app worktree prune
systemctl reload caddy

بعض الخيارات هنا مقصودة. تُنشر الحاويات على 127.0.0.1 فقط بمنفذ عشوائي، فلا يصل إليها شيء إلا عبر Caddy. هذا مهم لأن المنافذ التي ينشرها Docker تتجاوز ufw. لكل معاينة حد ذاكرة 512 MB، كي لا يُسقط فرع خرج عن السيطرة البقية. وتشغيل الفرع نفسه مجددًا يعيد بناءه في مكانه: push، ثم up، ثم تحديث الصفحة.

Caddy: ملف لكل معاينة، وكلمة مرور واحدة للجميع

mkdir -p /etc/caddy/previews /srv/previews
chmod +x /usr/local/bin/preview-up /usr/local/bin/preview-down
caddy hash-password --plaintext 'choose-a-review-password'

ضع التجزئة في /etc/caddy/Caddyfile:

(preview_common) {
	basicauth {
		reviewer PASTE-THE-HASH-HERE
	}
}
import /etc/caddy/previews/*.caddy

ثم systemctl reload caddy. أصبحت كل معاينة الآن خلف تسجيل الدخول نفسه، فلا يبقى فرع نصف منجز بمخرجات التصحيح مكشوفًا على الإنترنت. في إصدارات Caddy الأحدث يُكتب هذا التوجيه أيضًا basic_auth، وbasicauth لا يزال يعمل في كليهما.

مفتاح الوكيل: up وdown فقط

يحتاج الوكيل إلى إطلاق المعاينات، لا إلى امتلاك الخادم. أنشئ مستخدمًا deploy وسكربت بوابة يقبل أمرين بالضبط:

#!/usr/bin/env bash
# /usr/local/bin/preview-gate — the only thing the agent's key may run
set -euo pipefail
read -r ACTION BRANCH EXTRA <<< "${SSH_ORIGINAL_COMMAND:-}"
[[ -z "${EXTRA:-}" && "${BRANCH:-}" =~ ^[A-Za-z0-9][A-Za-z0-9._/-]{0,99}$ && "$BRANCH" != *..* ]] || { echo "usage: up|down <branch>" >&2; exit 2; }
case "$ACTION" in
  up)   exec sudo /usr/local/bin/preview-up "$BRANCH" ;;
  down) exec sudo /usr/local/bin/preview-down "$BRANCH" ;;
  *)    echo "usage: up|down <branch>" >&2; exit 2 ;;
esac
useradd -m -s /bin/bash deploy
echo 'deploy ALL=(root) NOPASSWD: /usr/local/bin/preview-up, /usr/local/bin/preview-down' > /etc/sudoers.d/preview
chmod 440 /etc/sudoers.d/preview && visudo -cf /etc/sudoers.d/preview
chmod 755 /usr/local/bin/preview-gate

في /home/deploy/.ssh/authorized_keys، اربط المفتاح العام للوكيل بذلك السكربت:

restrict,command="/usr/local/bin/preview-gate" ssh-ed25519 AAAA... coding-agent

من الجهاز الذي يعمل عليه الوكيل:

ssh deploy@preview.example.com up feature/login-form
# https://feature-login-form.preview.example.com
ssh deploy@preview.example.com down feature/login-form

في اختبارنا، بنى up الفرع ونشره، وعرض up الثاني بعد commit جديد النسخة الجديدة، وأزال down الحاوية والموقع. محاولات cat /etc/shadow وup x; id وأسماء فروع مثل -x أو a/../b أو تسجيل دخول تفاعلي بذلك المفتاح لم تحصل إلا على usage: up|down <branch>. يجب أن يبدأ اسم الفرع بحرف أو رقم ولا يمكن أن يحتوي على ...

ربطه بالوكيل

مع Claude Code أو Codex يكفي سطر واحد في ملف تعليمات المشروع:

بعد دفع فرع، شغّل ssh deploy@preview.example.com up <branch> وضع العنوان المطبوع في ملخصك. شغّل down عند دمج الفرع أو التخلي عنه.

إذا كان CI لديك يعمل أصلًا مع كل push، فاستدعِ الأمر نفسه من هناك وأبقِ المفتاح خارج بيئة الوكيل تمامًا. هذا الخيار الأكثر صرامة، وسنختاره لكل ما يتجاوز مشروعًا شخصيًا.

ما يجب مراقبته

  • القرص. كل بناء يترك طبقات صور. يكفي cron أسبوعي مع docker image prune -f لإبقائه تحت السيطرة، وdown يحذف صورة المعاينة نفسها أصلًا.
  • فروع لا يغلقها أحد. يفتح الوكلاء فروعًا أكثر من البشر. مهمة cron تسرد حاويات preview-* الأقدم من أسبوع وتشغّل عليها preview-down تجنّبك العثور على عشرين معاينة منسية.
  • الأسرار. يجب أن تعمل المعاينات ببيانات اعتماد اختبارية. كلمة مرور basic auth تُبعد الغرباء، لكنها لا تجعل ربط معاينة بقاعدة بيانات الإنتاج أمرًا مقبولًا.
  • وقت البناء. تُبنى الصور على الخادم، فيتنافس بناء ثقيل على المعالج مع المعاينات العاملة. إذا استغرق البناء دقائق، فابنِ في CI واسحب الصورة.

لا يلزم أن يعيش الوكيل نفسه على هذا الخادم. إن كان يعيش عليه، فراجع VPS لـ Claude Code، ولكي يطلب هذا الخادم ويعتني به بنفسه، اربطه بخادم MCP من EQVPS. يمكن للجهاز نفسه أيضًا استضافة بوابة MCP لفريقك. ولتشغيل كود غير موثوق دون أي خادم توجد صناديق الرمل، والخطط ذات IPv4 خاص تبدأ من $8 شهريًا.

الأسئلة الشائعة

لماذا لا ندع الوكيل ينشر مباشرة إلى بيئة الإنتاج؟

لأن «الاختبارات تنجح» و«يعمل» ليسا الشيء نفسه، ويجب أن يكون الوكيل آخر من يقرر ذلك. تمنح المعاينة إنسانًا شيئًا ينقر عليه قبل أن يصل أي شيء إلى المستخدمين، مقابل أمر إضافي واحد فقط.

ما الخطة المناسبة؟

يعتمد ذلك على عدد المعاينات التي تعمل معًا. خطة AI-Agent-IP ($15 شهريًا، 4 vCPU، 4 GB RAM، قرص 40 GB) تستوعب بسهولة عدة تطبيقات ويب صغيرة بحد 512 MB لكل منها. أثقل جزء هو بناء Docker، فراقب القرص والذاكرة إذا كانت صورك كبيرة.

هل أحتاج إلى شهادة wildcard؟

لا. يحصل Caddy بنفسه على شهادة منفصلة لكل نطاق فرعي للمعاينة. سجل DNS وحده هو wildcard. يسمح Let's Encrypt بنحو 50 شهادة جديدة لكل نطاق مسجّل أسبوعيًا، وهذا يكفي ما لم ينشئ الوكيل مئات الفروع.

هل يستطيع الوكيل تخريب الخادم بمفتاح SSH الخاص به؟

ليس بذلك المفتاح. فهو مقيّد بأمر واحد لا يقبل إلا «up <فرع>» أو «down <فرع>» مع اسم فرع آمن. يُرفض الوصول إلى shell والمعاملات الإضافية وإعادة توجيه المنافذ.

هل يعمل هذا على خطة NAT؟

لا. يجب أن تكون المعاينات متاحة على المنفذين 80 و443 على نطاقاتها الفرعية، وهذا يتطلب IPv4 عامًا. خطط NAT لا تعيد توجيه إلا منفذ SSH.

التعليقات

لا تعليقات بعد. كن الأول.

اترك تعليقًا

تُراجَع التعليقات قبل ظهورها.