−25%

روی پرداخت سالانه Windows، تا ۳۱ اکتبر. مشاهده پلن‌ها

EQVPS
شروع کنید

VPS برای پیش‌نمایش ایجنت‌های کدنویس: هر شاخه‌ای که ایجنت push می‌کند، URL خودش را می‌گیرد

ایجنت کدنویس شاخه‌ای را تمام کرده و می‌گوید کار می‌کند. سروری به او بدهید که بشود این را بررسی کرد: هر شاخه در یک کانتینر ساخته می‌شود و پشت رمز روی زیردامنهٔ HTTPS خودش باز می‌شود، و کلید SSH ایجنت فقط می‌تواند پیش‌نمایش را روشن و خاموش کند.

ایجنت کدنویس می‌گوید قابلیت کار می‌کند. شاید هم درست باشد. تست‌هایی که نوشته پاس می‌شوند و این کمتر از چیزی را ثابت می‌کند که همه دوست دارند. چیزی که واقعاً می‌خواهید این است که پیش از نزدیک شدن به کاربران آن را در مرورگر باز کنید، آن هم بی‌آنکه از ایجنت بخواهید به محیط تولید دست بزند.

محیط پیش‌نمایش همین را حل می‌کند و برایش پلتفرم لازم نیست. یک VPS با IPv4 اختصاصی، Docker، Caddy و دو اسکریپت کوتاه به هر شاخه URL خودش را می‌دهند، مثل https://feature-login-form.preview.example.com، پشت رمز و با ساخت دوباره در هر push. ایجنت کلید SSH‌ای می‌گیرد که می‌تواند پیش‌نمایش‌ها را روشن و خاموش کند و کار دیگری نمی‌کند.

این را روی یک سرور آزمایشی ساختیم و پیش از نوشتن کل چرخه را اجرا کردیم: ساخت، باز کردن، push دوباره، برچیدن، تلاش برای سوءاستفاده از کلید.

چه لازم دارید

  • یک 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.

مخزن را یک بار با کلید deploy فقط‌خواندنی در /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 یک خط در فایل دستورالعمل پروژه کافی است:

بعد از push کردن یک شاخه، ssh deploy@preview.example.com up <branch> را اجرا کن و URL چاپ‌شده را در خلاصه‌ات بگذار. وقتی شاخه ادغام یا رها شد، down را اجرا کن.

اگر CI شما همین حالا با هر push اجرا می‌شود، همان دستور را از آنجا صدا بزنید و کلید را کاملاً از محیط ایجنت دور نگه دارید. این گزینهٔ سخت‌گیرانه‌تر است و برای هر چیزی فراتر از یک پروژهٔ شخصی همین را انتخاب می‌کنیم.

به چه چیزهایی باید توجه کرد

  • دیسک. هر ساخت لایه‌های ایمیج باقی می‌گذارد. یک cron هفتگی با docker image prune -f این را مهار می‌کند؛ down ایمیج خود پیش‌نمایش را از قبل حذف می‌کند.
  • شاخه‌هایی که کسی نمی‌بندد. ایجنت‌ها بیشتر از انسان‌ها شاخه باز می‌کنند. یک cron که کانتینرهای preview-* قدیمی‌تر از یک هفته را فهرست کند و روی آن‌ها preview-down اجرا کند، شما را از دیدن بیست پیش‌نمایش فراموش‌شده نجات می‌دهد.
  • رازها. پیش‌نمایش‌ها باید با اطلاعات ورود آزمایشی اجرا شوند. رمز basic auth غریبه‌ها را بیرون نگه می‌دارد، اما وصل کردن پیش‌نمایش به پایگاه دادهٔ تولید را درست نمی‌کند.
  • زمان ساخت. ایمیج‌ها روی سرور ساخته می‌شوند، پس ساخت سنگین با پیش‌نمایش‌های در حال اجرا سر CPU رقابت می‌کند. اگر ساخت چند دقیقه طول می‌کشد، در CI بسازید و ایمیج را pull کنید.

لازم نیست خود ایجنت روی این سرور زندگی کند. اگر زندگی می‌کند، 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 برای هر دامنهٔ ثبت‌شده هفته‌ای حدود ۵۰ گواهی جدید مجاز می‌داند که کافی است، مگر اینکه ایجنت صدها شاخه بسازد.

ایجنت می‌تواند با کلید SSH خودش سرور را خراب کند؟

با آن کلید نه. کلید به یک دستور محدود است که فقط «up <شاخه>» یا «down <شاخه>» را با نام شاخهٔ امن می‌پذیرد. دسترسی shell، آرگومان اضافه و فوروارد پورت رد می‌شوند.

روی پلن NAT کار می‌کند؟

نه. پیش‌نمایش‌ها باید روی زیردامنهٔ خودشان از پورت‌های ۸۰ و ۴۴۳ در دسترس باشند و این به IPv4 عمومی نیاز دارد. پلن‌های NAT فقط پورت SSH را فوروارد می‌کنند.

نظرات

هنوز نظری نیست. اولین نفر باشید.

یک نظر بگذارید

نظرات پیش از نمایش بررسی می‌شوند.