يقول وكيل البرمجة إن الميزة تعمل. ربما يكون محقًا. الاختبارات التي كتبها تنجح، وهذا يثبت أقل مما نتمنى جميعًا. ما تريده فعلًا هو فتحها في المتصفح قبل أن تقترب من المستخدمين، ومن دون أن تطلب من الوكيل لمس بيئة الإنتاج.
بيئات المعاينة تحل هذا، ولا تحتاج إلى منصة لها. خادم 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 شهريًا.
التعليقات
لا تعليقات بعد. كن الأول.