SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-10-20

اجرای Claude Code روی VPS با tmux بدون قطع نشست

یاد بگیرید Claude Code را روی VPS لینوکسی همیشه‌روشن و داخل tmux اجرا کنید تا با قطع SSH نشست ایجنت باقی بماند؛ نصب، ایمن‌سازی و خطاهای رایج را ببینید.

مشکل از درب لپ‌تاپ است، نه CLI

اجرای Claude Code روی لپ‌تاپ شما تا زمانی که درب آن را نبندید بدون مشکل پیش می‌رود: با بستن درب، نشست SSH قطع می‌شود، سیگنال SIGHUP به شل ارسال می‌گردد و ایجنت که سه دقیقه از اجرای تست آن گذشته، همراه با نشست متوقف می‌شود. CLI را روی ماشینی اجرا کنید که هرگز به حالت خواب (sleep) نمی‌رود و این کار را درون یک ترمینال مالتی‌پلکسر انجام دهید که پردازش‌هایش فرزند نشست SSH شما نباشند. تمام ترفند همین است؛ در واقع tmux، و نه نحوه نصب، بخش اصلی و حیاتی این راهکار است.

این صفحه درباره نحوه مدیریت سیستمی است که ایجنت‌ها را روی آن در حال اجرا می‌گذارید. اگر سرور لینوکسی ندارید که بتوانید آن را روشن بگذارید، هیچ‌کدام از این موارد برای شما کاربرد ندارد. این تنها پیش‌نیاز صادقانه این راهنماست.

عملکرد واقعی tmux

هنگامی که از طریق SSH متصل می‌شوید، sshd یک shell ایجاد می‌کند (fork) و یک pseudo-terminal به آن اختصاص می‌دهد؛ هر چیزی که از آن shell اجرا کنید، فرزند آن محسوب می‌شود. اگر اتصال قطع شود، kernel آن pty را از بین می‌برد، shell سیگنال SIGHUP را دریافت می‌کند و به نوبهٔ خود، پروسه‌های فرزندش را می‌بندد. در نتیجه، پروسه‌های طولانی‌مدتی که در foreground اجرا می‌شوند، متوقف می‌گردند.

tmux این مالکیت را معکوس می‌کند. دستوری که شما با عنوان tmux تایپ می‌کنید، در واقع یک کلاینت سبک است که از طریق یک unix socket با یک tmux server در ارتباط است؛ سروری که مستقل از ترمینال شما اجرا می‌شود. shellهای داخل یک session، فرزندان آن سرور هستند، نه فرزندان sshd. اگر اتصال SSH را قطع کنید، کلاینت از بین می‌رود اما سرور، session و پروسه‌ای که در حال اجراست، به کار خود ادامه می‌دهند. با اتصال مجدد و اجرای tmux attach، شما به همان shell قبلی با همان تاریخچهٔ اسکرول بازمی‌گردید. nohup نیز در برابر قطع اتصال مقاوم است، اما راهی برای بازگشت به آن ارائه نمی‌دهد؛ شما نمی‌توانید به یک TUI که در پس‌زمینه رها شده است، دوباره متصل شوید. Claude Code تعاملی است؛ بنابراین tmux (یا screen) ابزار مناسب برای این کار است.

تخمین ابعاد سرور

خودِ CLI یک باینری بومی واحد است؛ این ابزار فضای سرور را اشغال نمی‌کند. آنچه فضای سرور را پر می‌کند، هر آن چیزی است که agent از طرف شما اجرا می‌کند: یک build، یک مجموعه تست کامل، tsc، یک language server یا یک دیتابیس در Docker. ابعاد سرور را بر اساس toolchain انتخاب کنید، نه CLI. حتی اگر قصد استفاده از آن را ندارید، swap اضافه کنید؛ این کار باعث می‌شود در صورت کمبود حافظه، به‌جای OOM kill ناگهانی، با یک build کند مواجه شوید:

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

فضای دیسک را نیز زیر نظر داشته باشید: مخازن (repos)، node_modules و ایمیج‌های Docker به‌سرعت انباشته می‌شوند. اگر toolchain فراتر از کانتینرها رفته و ماشین‌های مجازی کامل، یک KVM guest یا یک نود محلی Kubernetes را در بر می‌گیرد، پیش از نهایی کردن تصمیم، بررسی کنید که پلن شما دسترسی به قابلیت‌های مجازی‌سازی CPU را فراهم می‌کند یا خیر؛ چرا که اجرای مجازی‌سازی تو در تو روی یک VPS قابلیتی است که باید توسط ارائه‌دهنده برای شما فعال شود و نمی‌توان آن را از داخل guest فعال کرد.

ایجاد کاربر غیر روت (non-root) در ابتدا

یک کاربر اختصاصی با دایرکتوری home مخصوص به خود ایجاد کنید و کلید عمومی (public key) خود را در جای مناسب قرار دهید:

sudo adduser --disabled-password --gecos "" agent
sudo install -d -m 700 -o agent -g agent /home/agent/.ssh
sudo cp ~/.ssh/authorized_keys /home/agent/.ssh/authorized_keys
sudo chown agent:agent /home/agent/.ssh/authorized_keys
sudo chmod 600 /home/agent/.ssh/authorized_keys

پیش از ادامه، ورود به سیستم را از یک ترمینال دوم تست کنید، در حالی که احراز هویت با رمز عبور همچنان به عنوان پشتیبان فعال است؛ اگر با خطای Permission denied (publickey) مواجه شدید، مشکل معمولاً از مالکیت (ownership) یا مجوزهای (mode) دایرکتوری .ssh است، نه خودِ کلید.

به‌طور عمدی، کاربر agent در گروه sudo قرار ندارد. اگر به بسته نرم‌افزاری سیستمی نیاز داشتید، آن را نصب می‌کنید. همین یک تصمیم، بسیاری از راه‌هایی که یک دستور shell سرگردان می‌تواند میزبان را تخریب کند، از بین می‌برد.

بهداشت SSH برای سیستمی که آن را همیشه روشن می‌گذارید

احراز هویت با گذرواژه روی ماشینی که تمام روز در اینترنت عمومی قرار دارد و agent و کد منبع شما را نگه می‌دارد، ریسکی نیست که ارزش پذیرفتن داشته باشد. آن را خاموش کنید. در Ubuntu 24.04 و Debian 13، /etc/ssh/sshd_config شامل /etc/ssh/sshd_config.d/*.conf است؛ بنابراین به‌جای ویرایش پیکربندی اصلی، یک فایل در آن قرار دهید:

# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no

Key exchange را از آن فایل حذف کنید، مگر این‌که دلیل مشخصی داشته باشید: خط KexAlgorithms که از یک راهنمای قدیمی hardening کپی شده باشد، اغلب فقط الگوریتم‌های کلاسیک را فعال نگه می‌دارد. در نتیجه، یک ssh client جدید هشدار این‌که اتصال از key exchange پساکوانتومی استفاده نمی‌کند را چاپ می‌کند.

پیکربندی را اعتبارسنجی و سرویس را reload کنید. هنگام آزمایش یک نشست جدید از ترمینال دوم، نشست فعلی خود را باز نگه دارید:

sudo sshd -t && sudo systemctl restart ssh

در Ubuntu 24.04 یک نکته وجود دارد: sshd به‌صورت socket-activated اجرا می‌شود. تنظیمات احراز هویت در systemctl restart ssh اعمال می‌شوند؛ اما تغییر در Port مربوط به listening نیز به systemctl daemon-reload و restart کردن ssh.socket نیاز دارد.

سپس به سراغ firewall بروید. پیش از فعال‌کردن آن، SSH را مجاز کنید؛ در غیر این صورت دسترسی خود را قطع می‌کنید:

sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

fail2ban را نصب کنید، اما دقیقاً بدانید چه چیزی به دست می‌آورید: وقتی احراز هویت با گذرواژه خاموش باشد، brute force به‌هرحال نمی‌تواند موفق شود و تلاش‌های ناموفق را از journal شما خارج نگه می‌دارد.

# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1h

در پایان، با sudo apt install unattended-upgrades و sudo dpkg-reconfigure -plow unattended-upgrades وصله‌ها را به‌صورت خودکار نصب کنید. به تعامل آن با tmux توجه کنید: Unattended-Upgrade::Automatic-Reboot را فعال کنید تا به‌روزرسانی kernel سیستم را reboot کند و همه نشست‌ها را نیز همراه آن ببندد. آن را خاموش بگذارید و reboot را خودتان، در زمانی که هیچ کاری در میانه اجرا نیست، انجام دهید. همین احتیاط برای release upgrade نیز لازم است: انتقال سیستم از Ubuntu 24.04 به 26.04، sshd و kernel را restart می‌کند؛ بنابراین باید در بازه‌ای انجام شود که هیچ نشست tmux کاری را که برایتان مهم است در دست اجرا نداشته باشد.

نصب Node.js و Claude Code روی Ubuntu

روش npm به Node.js نسخه 22 یا بالاتر نیاز دارد. (این بسته اکنون یک باینری نیتیو دریافت می‌کند که روی Node اجرا نمی‌شود، اما npm همچنان نسخه engine را بررسی می‌کند و toolchain خودِ agent معمولاً به یک نسخه به‌روز از Node نیاز دارد.) بسته‌های موجود در مخازن توزیع‌ها معمولاً قدیمی هستند؛ NodeSource روش معمول در Ubuntu و Debian است و یک مخزن امضاشده ارائه می‌دهد (بدون apt-key، که در Ubuntu 24.04 منسوخ شده و در Debian 13 حذف شده است):

curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --version

اکنون بخشی که معمولاً اشتباه انجام می‌شود: CLI را به عنوان کاربر agent خود نصب کنید، هرگز از sudo npm -g استفاده نکنید. نصب در سطح global با مالکیت root، بعداً منجر به خطاهای دسترسی می‌شود و فایل‌هایی با مالکیت root در کش npm باقی می‌گذارد. ابتدا prefix مربوط به npm را به دایرکتوری home کاربر هدایت کنید:

mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
npm install -g @anthropic-ai/claude-code
claude --version

این export باید در ~/.bashrc قرار گیرد و جای آن بالای شرط "If not running interactively, don't do anything" در ابتدای فایل است. یک پنل پیش‌فرض در tmux یک login shell اجرا می‌کند: tmux مقدار default-command را خالی می‌گذارد، که به آن می‌گوید default-shell را به عنوان یک login shell شروع کند، و یک bash لاگین‌شونده فایل ~/.profile را می‌خواند که در Ubuntu فایل ~/.bashrc را source می‌کند. شل‌های غیرلاگین (non-login shells)، برای مثال اگر خودتان default-command را تنظیم کنید، مستقیماً ~/.bashrc را می‌خوانند. بنابراین ~/.bashrc هر دو حالت را پوشش می‌دهد، در حالی که ~/.profile به تنهایی حالت دوم را نادیده می‌گیرد. یک نکته: اگر ~/.bash_profile یا ~/.bash_login وجود داشته باشد، bash فایل ~/.profile را نادیده می‌گیرد، بنابراین اگر یکی از آن‌ها را ایجاد کرده‌اید، مطمئن شوید که ~/.bashrc را نیز در آن source کنید. استفاده از یک Node اختصاصی برای هر کاربر از طریق یک مدیریت نسخه مانند nvm نیز به همین نتیجه می‌رسد؛ هدف در هر دو حالت این است که npm install -g هرگز به sudo نیاز نداشته باشد. npm همچنان به خوبی کار می‌کند، اما مستندات Anthropic اسکریپت نصب نیتیو را به عنوان روش پیشنهادی معرفی کرده‌اند و مخازن apt، dnf و apk امضاشده نیز گزینه دیگری هستند. پیش از کپی و اجرای دستورات، مستندات نصب Anthropic را بررسی کنید، چرا که روش‌های نصب تغییر می‌کنند.

برای شروع، claude را داخل یک مخزن (repo) اجرا کنید. اولین اجرا شما را برای احراز هویت راهنمایی می‌کند؛ یک سرور بدون رابط گرافیکی (headless) مرورگر ندارد، بنابراین این فرآیند یک URL به شما می‌دهد تا در سیستم خود باز کنید و کدی را برای بازگرداندن به ترمینال دریافت کنید. (استفاده از یک API key در متغیرهای محیطی روش دیگر است.) در هر صورت، آن اعتبارنامه اکنون روی سرور قرار می‌گیرد، که ما را به بخشی می‌رساند که افراد معمولاً از آن صرف‌نظر می‌کنند.

مبحث شعاع انفجار

یک عامل (agent) با دسترسی shell، در واقع خودِ shell است. این عامل می‌تواند هر چیزی را که کاربرِ اجراکننده آن اجازه خواندنش را دارد، بخواند و در هر مسیری که آن کاربر اجازه نوشتن دارد، تغییر ایجاد کند. این یک نقد به ابزار نیست، بلکه تعریف ماهیت آن است و به همین دلیل، حسابی که عامل تحت آن اجرا می‌شود، از هر تنظیمات فردی دیگری اهمیت بیشتری دارد.

  • کاربر اختصاصی و بدون امتیاز (unprivileged). بدون عضویت در گروه sudo و بدون دایرکتوری home مشترک با حساب کاربری خودتان.
  • عدم وجود اعتبارنامه‌های تولید (production) روی سیستم. هیچ فایلی مانند ~/.aws/credentials نباید حاوی کلیدهای تولید باشد، هیچ .env نباید از محیط تولید کپی شده باشد و هیچ رمز عبور دیتابیسی با دسترسی نوشتن روی منابع حیاتی نباید وجود داشته باشد. به عامل فقط اعتبارنامه‌های محیط staging یا دسترسی فقط‌خواندنی (read-only) بدهید.
  • توکن‌های محدودشده (Scoped tokens). از یک توکن GitHub با دقت بالا که فقط به یک مخزن محدود شده است استفاده کنید؛ یا اگر دسترسی خواندن کافی است، از یک deploy key استفاده کنید.

ابزار Claude Code دارای فلگی به نام --dangerously-skip-permissions است که تقریباً تمام درخواست‌های مجوز (permission prompts) را نادیده می‌گیرد. روی لپ‌تاپ شخصی یا یک پروژه موقت، این تصمیم با شماست. اما روی سروری که حاوی توکن‌هاست، این فلگ آخرین سد دفاعی بین یک دستور اشتباه خوانده‌شده و یک git push --force را از بین می‌برد. درخواست‌هایی که نادیده می‌گیرید همگی از نوع «همه یا هیچ» نیستند و با توجه به اینکه حالت خودکار اکنون حالت پیش‌فرض شروع است (از نسخه Claude Code v2.1.283)، دانستن اینکه سروری که تحت نظارت مستقیم شما نیست باید روی کدام حالت مجوز قفل شود، اهمیت دارد. اینکه این فلگ دقیقاً چه چیزی را تغییر می‌دهد و چگونه می‌توان عاملی را که با آن اجرا می‌شود محدود کرد (از sandbox داخلی گرفته تا یک VPS یک‌بارمصرف)، در اجرای ایمن Claude Code روی سرور توضیح داده شده است.

کلید Deploy در مقابل SSH agent forwarding

وسوسه‌انگیز است که از ssh -A استفاده کنید تا git بتواند از کلید موجود روی لپ‌تاپ شما استفاده کند. درک کنید که این کار چه دسترسی‌هایی می‌دهد: agent forwarding سوکت SSH agent محلی شما را در اختیار پردازش‌هایی قرار می‌دهد که با آن کاربر روی سرور اجرا می‌شوند. هر چیزی که با دسترسی agent اجرا شود، از جمله خودِ عامل، می‌تواند تا زمانی که شما متصل هستید، از کلید شما برای امضای درخواست به هر هاستی که به آن دسترسی دارد استفاده کند. این دسترسی بسیار فراتر از «اجازه دادن به git برای pull کردن یک مخزن خاص» است.

به‌جای آن، یک کلید روی سرور تولید کنید، آن را به عنوان یک deploy key مختص همان مخزن ثبت کنید (فقط در صورتی که عامل نیاز به push دارد دسترسی نوشتن بدهید) و یک هویت git تنظیم کنید تا commitهای ارسالی از سرور قابل شناسایی باشند:

ssh-keygen -t ed25519 -C "agent deploy key" -f ~/.ssh/id_ed25519_repo
cat ~/.ssh/id_ed25519_repo.pub   # paste into the repo's Deploy Keys
git config --global user.name "Agent (build box)"
git config --global user.email "agent@example.com"

گردش‌کار tmux

آن را نصب کنید (sudo apt install tmux)، سپس یک ~/.tmux.conf حداقلی بسازید:

set -g mouse on
set -g history-limit 50000
set -g default-terminal "tmux-256color"

چهار دستور، استفادهٔ روزمره را پوشش می‌دهند:

tmux new -A -s claude     # attach to session "claude", creating it if absent
# ...run `claude` inside it, work normally...
# Ctrl-b then d           -> detach; everything keeps running
tmux ls                   # list sessions
tmux attach -t claude     # reattach, from this machine or any other
tmux kill-session -t claude

tmux new -A -s claude دستوری است که باید به خاطر بسپارید؛ اگر session وجود داشته باشد به آن متصل می‌شود و اگر نباشد آن را می‌سازد، بنابراین یک دستور هم برای شروع روز و هم برای بازگشت پس از قطع اتصال کافی است. برای آن یک alias تعریف کنید. داخل یک session، دستور Ctrl-b c یک پنجره باز می‌کند، Ctrl-b n و Ctrl-b p بین آن‌ها جابه‌جا می‌شوند و Ctrl-b [ وارد حالت copy mode می‌شود تا بتوانید به عقب اسکرول کنید (q از این حالت خارج می‌شود).

یک نکته درباره sessionهایی که هرگز نمی‌بندید (kill نمی‌کنید): agent در هر نوبت کل گفتگو را دوباره ارسال می‌کند، بنابراین پیش از آنکه یک session از Claude Code را برای یک هفته باز بگذارید، مواردی که یک session طولانی‌مدت Claude Code توکن‌هایش را صرف آن‌ها می‌کند را مطالعه کنید.

حالت‌های شکست

«نشست من از بین رفته است.» tmux ls عبارت no server running on /tmp/tmux-1000/default را چاپ می‌کند. این تقریباً همیشه به این معنی است که پردازش هرگز داخل tmux نبوده است؛ شما با SSH وارد شده‌اید، claude را مستقیماً اجرا کرده‌اید و قطع اتصال باعث پایان یافتن آن شده است. چیزی برای بازیابی وجود ندارد. عادتی که از این اتفاق جلوگیری می‌کند: tmux new -A -s <project> باید اولین دستوری باشد که پس از هر ورود اجرا می‌کنید.

پنل به یک کادر کوچک تبدیل می‌شود. گزینه window-size تعیین می‌کند که کدام کلاینت متصل، اندازه را مشخص کند. از نسخه tmux 3.1 (که در Ubuntu 24.04 نسخه 3.4 ارائه شده)، مقدار پیش‌فرض latest است، یعنی کلاینتی که بیشترین فعالیت اخیر را داشته است. اما اگر در فایل پیکربندی window-size smallest تنظیم شده باشد، اندازه بر اساس کوچک‌ترین کلاینت متصل تعیین می‌شود؛ بنابراین یک کلاینت قدیمی که از دستگاه دیگری متصل مانده، نمایشگر را فشرده می‌کند. هنگام اتصال، بقیه را با این دستور خارج کنید: tmux attach -d -t claude.

یک بیلد عبارت Killed را چاپ می‌کند. یک کلمه، بدون stack trace. با sudo dmesg -T | grep -i -E 'out of memory|killed process' تأیید کنید؛ OOM killer هسته، بزرگ‌ترین پردازش را انتخاب کرده است. در Node ممکن است به جای آن FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory را ببینید. راه‌حل‌ها به ترتیب: افزودن swap (در بالا)، محدود کردن موازی‌سازی تست و کامپایلر، افزایش heap در Node با NODE_OPTIONS=--max-old-space-size=...، یا ارتقای اندازه VPS. ممکن است OOM killer به جای بیلد، سرور tmux را انتخاب کند و نشست شما را با خود ببرد؛ اگر systemd-oomd در حال اجرا باشد، می‌تواند کل slice کاربر را بکشد که نتیجه مشابهی دارد.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. نصب سراسری در مسیری که متعلق به root است. از پیشوند ~/.npm-global که در بالا ذکر شد استفاده کنید. اگر قبلاً sudo npm را اجرا کرده‌اید، ممکن است Your cache folder contains root-owned files را نیز ببینید؛ با sudo chown -R $(id -u):$(id -g) ~/.npm آن را تعمیر کنید.

claude: command not found، اما فقط گاهی اوقات. دستور export مربوط به PATH شما در ~/.bashrc زیر شرط «اگر به صورت تعاملی اجرا نمی‌شود، کاری انجام نده» قرار گرفته است، بنابراین shellهای غیرتعاملی آن را نادیده می‌گیرند. آن export را به بالای آن شرط منتقل کنید و در ~/.bashrc نگه دارید: یک پنل پیش‌فرض tmux یک login shell است که از طریق ~/.profile در Ubuntu به ~/.bashrc می‌رسد و هر shell غیر-login مستقیماً ~/.bashrc را می‌خواند، بنابراین همین یک فایل هر دو حالت را پوشش می‌دهد.

به‌هم‌ریختگی رنگ‌ها پس از اتصال. عدم تطابق در TERM؛ خط default-terminal در بالا راه‌حل این مشکل است.

نشست‌ها پس از reboot ناپدید می‌شوند. این یک باگ نیست: سرور tmux یک پردازش است و با reboot پایان می‌یابد. uptime را بررسی کنید.

چه چیزی با رشد این ساختار دچار اختلال می‌شود

پروژه‌های بیشتر. برای هر مخزن یک session در tmux ایجاد کنید و آن را همنام مخزن بگذارید؛ tmux ls در این حالت داشبورد شما خواهد بود. اگر نظم در نام‌گذاری را رعایت نکنید، با sessionهایی مثل 0، 1 و 2 مواجه می‌شوید. وقتی چندین session همزمان فعال باشند، لزومی ندارد که در انزوا کار کنند، چرا که یک session می‌تواند پیامی را به session دیگری روی همان سیستم ارسال کند؛ این قابلیت زمانی مفید است که agent مسئول یک refactor طولانی، بخواهد agent دیگری را برای اجرای تست‌ها فراخوانی کند. پورت‌ها نیز به همین ترتیب دچار آشفتگی می‌شوند؛ وقتی شش مخزن همگی نیاز به پورت :3000 دارند، زمان آن رسیده که تخصیص دستی را متوقف کنید و اجازه دهید یک reverse proxy مانند Traefik که با Docker Compose کار می‌کند، هدایت درخواست‌ها را بر اساس نام دامنه (hostname) انجام دهد.

افراد بیشتر. سوکت‌های tmux مختص هر کاربر هستند، بنابراین دو توسعه‌دهنده روی یک سیستم، هر کدام سرور tmux خود را دارند و نمی‌توانند sessionهای یکدیگر را ببینند. اشتراک‌گذاری یک session از طریق یک سوکت مشترک به این معناست که همه به عنوان یک کاربر یونیکس واحد در یک shell تایپ می‌کنند که پیامدهای امنیتی و نظارتی خاص خود را دارد. استفاده از کاربران مجزا، راهکار اصولی و صحیح است.

کارهای بدون نظارت. ابزار tmux برای sessionهای تعاملی است که به آن‌ها متصل می‌شوید. کارهایی که طبق زمان‌بندی و بدون نظارت مستقیم اجرا می‌شوند، باید در قالب یک unit و timer در systemd تعریف شوند؛ جایی که لاگ‌گیری، سیاست restart و پایداری پس از reboot به‌صورت خودکار فراهم است. استفاده از tmux برای اجرای کارهای زمان‌بندی‌شده (cron-like)، نشان‌دهنده این است که آن کار باید به یک سرویس تبدیل شود.

یک نکته پایانی: سرورهای توسعه (dev servers) که توسط agent اجرا می‌شوند را روی 127.0.0.1 تنظیم کنید، نه 0.0.0.0، و به جای باز کردن پورت‌ها در ufw، از طریق یک تونل SSH (ssh -L 3000:127.0.0.1:3000 agent@your-server) به آن‌ها دسترسی پیدا کنید. زمانی که تعداد پورت‌های فوروارد شده زیاد شد، یا نیاز داشتید همزمان با گوشی و لپ‌تاپ به یک پیش‌نمایش دسترسی داشته باشید، به‌جای آن یک WireGuard VPN روی VPS خود راه‌اندازی کنید: سرورهای توسعه به یک اینترفیس خصوصی متصل می‌شوند و ufw همچنان دسترسی‌ها از اینترفیس عمومی را مسدود نگه می‌دارد. فایروال تنها در صورتی کارآمد است که از ایجاد حفره‌های امنیتی در آن خودداری کنید.

Claude Code تنها گزینه موجود نیست: اجرای یک AI agent برای کدنویسی روی VPS شامل بررسی گزینه‌هایی مانند Aider و Goose نیز می‌شود.

FAQ

آیا Claude Code پس از قطع اتصال SSH به اجرا ادامه می‌دهد؟

فقط در صورتی که آن را داخل tmux اجرا کرده باشید. فرآیندی که مستقیماً از shell مربوط به SSH شروع شود، فرزند آن shell محسوب شده و با بسته شدن pty در هنگام قطع اتصال، از بین می‌رود. داخل tmux، آن shell متعلق به سرورِ جداشدهٔ (detached) tmux است؛ بنابراین agent در حین انجام وظیفه به کار خود ادامه می‌دهد و tmux attach شما را به همان وضعیت اسکرول قبلی بازمی‌گرداند. کافی است tmux new -A -s <project> را به اولین دستور پس از هر بار ورود تبدیل کنید تا این مشکل برطرف شود.

آیا باید CLI را با sudo npm install -g نصب کنم؟

خیر. استفاده از پیشوند سراسری (global prefix) که متعلق به root باشد، در نصب‌های بعدی باعث بروز خطاهای EACCES و ایجاد فایل‌هایی با مالکیت root در کش npm می‌شود. پیشوند npm را روی ~/.npm-global تنظیم کنید (یا از یک مدیریت‌کننده نسخه مانند nvm استفاده کنید)، آن را به عنوان کاربر بدون امتیاز agent نصب کنید و ~/.npm-global/bin را از فایل ~/.bashrc، پیش از بخش interactive guard، به PATH صادر (export) کنید. اگر قبلاً یک بار sudo npm را اجرا کرده‌اید، کش را با sudo chown -R $(id -u):$(id -g) ~/.npm تعمیر کنید.

آیا استفاده از agent forwarding در ssh -A روی سروری که خودش agent دارد، امن است؟

این کار دسترسی بسیار بیشتری از آنچه برای انجام وظیفه نیاز است، فراهم می‌کند. Forwarding باعث می‌شود socket مربوط به SSH agent محلی شما در معرض تمام فرآیندهایی قرار بگیرد که با آن کاربر اجرا می‌شوند؛ بنابراین هر چیزی روی آن سرور می‌تواند تا زمانی که شما متصل هستید، از کلید شما برای احراز هویت در هر هاستی که به آن دسترسی دارد، استفاده کند. یک کلید ed25519 روی سرور تولید کنید و آن را به عنوان یک deploy key مختص همان مخزن (repository) ثبت کنید؛ و تنها در صورتی که agent واقعاً نیاز به push کردن دارد، دسترسی نوشتن (write access) به آن بدهید.

چرا بیلد من فقط Killed را چاپ می‌کند؟

یک کلمه بدون stack trace نشان‌دهنده عملکرد OOM killer در هسته سیستم‌عامل است. آن را با sudo dmesg -T | grep -i -E 'out of memory|killed process' تأیید کنید؛ در Node ممکن است به جای آن JavaScript heap out of memory را ببینید. اصلاحات را به ترتیب انجام دهید: یک swapfile اضافه کنید، موازی‌سازی (parallelism) تست‌ها و کامپایلر را محدود کنید، مقدار NODE_OPTIONS=--max-old-space-size=... را افزایش دهید و در نهایت ظرفیت VPS را ارتقا دهید. مراقب باشید که OOM killer ممکن است به جای فرآیند بیلد، سرور tmux را انتخاب کند و کل نشست (session) شما را از بین ببرد.

tmux یا یک سرویس systemd؟

tmux برای نشست‌های تعاملی که به آن‌ها متصل می‌شوید، آن‌ها را نظارت می‌کنید و در آن‌ها تایپ می‌کنید مناسب است؛ که دقیقاً همان چیزی است که یک نشست agent به آن نیاز دارد. کارهایی که طبق زمان‌بندی و بدون نظارت اجرا می‌شوند، باید در قالب یک unit و timer در systemd قرار بگیرند، جایی که ثبت لاگ، سیاست راه‌اندازی مجدد و بقا پس از بوت به صورت خودکار فراهم است. اگر برای اجرای یک کار زمان‌بندی‌شده (cron-shaped) به سراغ tmux می‌روید، آن کار باید به یک سرویس تبدیل شود.

آیا این مقاله مفید بود؟