تشغيل Claude Code على VPS بعيد باستخدام tmux
شغّل Claude Code على خادم Linux دائم التشغيل داخل tmux، لتبقى جلسات الوكيل بعد انقطاع SSH. تعرّف إلى التثبيت والتحصين وأخطاء SIGHUP المتوقعة.
المشكلة في غطاء الحاسوب المحمول، وليست في CLI
يعمل Claude Code بشكل طبيعي على حاسوبك المحمول إلى أن تغلق غطاءه: تنقطع جلسة SSH، وتتلقى الصدفة SIGHUP، ويتوقف الوكيل معها بعد ثلاث دقائق من بدء الاختبار. شغّل CLI على جهاز لا يدخل في وضع السكون مطلقاً، داخل مضاعِف طرفية لا تكون عملياته أبناءً لجلسة SSH. هذه هي الحيلة بأكملها. ويُعد tmux الجزء الأساسي هنا، وليس عملية التثبيت.
تتحدث هذه الصفحة عن تشغيل جهاز تترك الوكلاء يعملون عليه. إذا لم يكن لديك خادم Linux يمكنك إبقاءه قيد التشغيل، فلا ينطبق أي مما يلي. هذا هو المتطلب المسبق الوحيد والصريح.
ما الذي يفعله tmux فعلياً
عندما تتصل عبر SSH، تُنشئ sshd shell وتمنحها pseudo-terminal؛ وكل ما تشغّله من ذلك الـshell يكون تابعاً لها. إذا انقطع الاتصال، تلغي النواة pty، وتتلقى shell الإشارة SIGHUP، ثم ترسلها بدورها إلى العمليات التابعة لها. وتموت العمليات طويلة التشغيل التي تعمل في الواجهة الأمامية.
يعكس tmux علاقة الملكية هذه. الأمر tmux الذي تكتبه هو عميل بسيط يتصل عبر unix socket بـخادم tmux يعمل منفصلاً عن terminal الخاص بك. وتكون الـshells داخل الجلسة أبناء ذلك الخادم، لا أبناء sshd. عند إنهاء اتصال SSH، يتوقف العميل بينما يواصل الخادم والجلسة وagent الذي ينفّذ المهمة عملها. أعد الاتصال، ونفّذ tmux attach، وستعود إلى الـshell نفسها مع سجل التمرير نفسه. يصمد nohup أمام hangup أيضاً، لكنه لا يتيح لك العودة إلى الجلسة؛ فلا يمكنك إعادة attach إلى TUI تعمل في الخلفية. Claude Code تفاعلي؛ لذلك فإن tmux (أو screen) هو الأداة المناسبة.
تحديد حجم الخادم
إن واجهة CLI هي عملية Node، وليست هي ما يملأ الخادم. ما يملأ الخادم هو كل ما يشغّله الوكيل نيابةً عنك: عملية build، أو مجموعة اختبارات كاملة، أو tsc، أو language server، أو قاعدة بيانات داخل Docker. حدّد الموارد اللازمة لسلسلة الأدوات، لا لواجهة CLI. أضف swap حتى إذا كنت تخطط لعدم استخدامه مطلقاً، فهو يحوّل إنهاء العملية بسبب نفاد الذاكرة OOM إلى عملية 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راقب القرص أيضاً: تتراكم المستودعات وnode_modules وصور Docker بسرعة. وإذا تجاوزت سلسلة الأدوات الحاويات لتشمل أجهزة افتراضية كاملة، مثل ضيف KVM أو عقدة Kubernetes محلية، فتحقق من أن الخطة تتيح امتدادات المحاكاة الافتراضية للمعالج قبل الالتزام بها، لأن تشغيل المحاكاة الافتراضية المتداخلة على VPS يتطلب أن يفعّلها مزود الخدمة نيابةً عنك، وليس أن تفعّلها من داخل الضيف.
مستخدم غير root أولاً
أنشئ مستخدماً مخصصاً مع مجلده الرئيسي الخاص، وضع مفتاحك العام في مكانه:
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اختبر تسجيل الدخول من طرفية ثانية قبل المتابعة، ما دام توثيق كلمة المرور متاحاً كخيار احتياطي. إذا ظهرت الرسالة تم رفض الإذن (publickey)، فعادةً يكون السبب ملكية مجلد .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تحقق من الإعدادات ثم أعد تحميل الخدمة، مع إبقاء جلستك الحالية مفتوحة أثناء اختبار جلسة جديدة من طرفية ثانية:
sudo sshd -t && sudo systemctl restart sshهناك تفصيل مهم في Ubuntu 24.04: تُفعّل خدمة sshd عبر socket. تُطبَّق إعدادات المصادقة عند systemctl restart ssh، لكن تغيير Port الذي تستمع إليه الخدمة يتطلب أيضاً systemctl daemon-reload وإعادة تشغيل ssh.socket.
ثم اضبط جدار الحماية. اسمح باتصالات SSH قبل تفعيله، وإلا ستمنع نفسك من الوصول:
sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enableثبّت fail2ban بعد فهم فائدته بوضوح: بعد تعطيل المصادقة بكلمة المرور، لن تنجح هجمات القوة الغاشمة على أي حال، لكنه يمنع محاولات الدخول الفاشلة من الوصول إلى 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، ستتوقف جميع الجلسات معه. اتركه معطلاً وأعد التشغيل وفق جدولك أنت، عندما لا تكون هناك عملية قيد التنفيذ. ينطبق الاحتياط نفسه على ترقية الإصدار: نقل الخادم من Ubuntu 24.04 إلى 26.04 يعيد تشغيل sshd وkernel، لذلك يجب تنفيذها ضمن نافذة لا تحتوي على جلسة tmux تشغّل عملاً مهماً.
تثبيت Node.js وClaude Code على Ubuntu
Claude Code هو Node CLI، لذلك تحتاج إلى إصدار حديث من Node. غالباً ما تتأخر حزمة التوزيعة؛ ويُعد NodeSource المسار المعتاد على Ubuntu وDebian، كما يوفّر مستودعاً موقّعاً (من دون apt-key، فقد أُزيلت هذه الأداة):
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --versionالآن إلى النقطة التي يخطئ فيها كثيرون: ثبّت CLI باسم مستخدم agent، ولا تستخدم sudo npm -g مطلقاً. يؤدي ضبط بادئة عامة مملوكة لحساب root إلى أخطاء صلاحيات لاحقاً، ويترك ملفات مملوكة لـroot في ذاكرة npm المؤقتة. اضبط بادئة npm على مجلد المستخدم الرئيسي أولاً:
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يوضع التصدير في ~/.bashrc، وليس في ~/.profile، ويجب أن يأتي قبل الحارس "If not running interactively, don't do anything" قرب بداية الملف: قد يبدأ tmux أصدافاً غير مخصّصة لتسجيل الدخول، وهذه الأصداف تقرأ ~/.bashrc وتتجاوز ~/.profile، بينما يُنفَّذ ~/.profile فقط في أصداف تسجيل الدخول. يحقق استخدام Node لكل مستخدم عبر مدير إصدارات مثل nvm النتيجة نفسها؛ فالهدف في كلتا الحالتين هو ألا يحتاج npm install -g إلى sudo مطلقاً. يظل npm مناسباً، أو يمكنك استخدام برنامج التثبيت الأصلي من Anthropic، وهو الخيار الافتراضي الموثّق حالياً. راجع وثائق التثبيت لدى Anthropic قبل اللصق، لأن أساليب التثبيت تتغير.
شغّل claude داخل مستودع لبدء تشغيله. يرشدك التشغيل الأول خلال عملية المصادقة؛ ولا يحتوي الخادم الذي يعمل دون واجهة رسومية على متصفح، لذلك تعرض العملية عنوان URL تفتحه على جهازك، ورمزاً تعيده إلى الطرفية. (المسار الآخر هو استخدام مفتاح API في البيئة.) في كلتا الحالتين، تصبح بيانات الاعتماد هذه موجودة على الخادم، وهذا يقودنا إلى الجزء الذي يتجاهله كثيرون.
نقاش نطاق الأثر
عميل يملك وصولاً إلى shell هو shell. ويمكنه قراءة أي شيء يستطيع المستخدم الذي يعمل باسمه قراءته، والدفع إلى أي مكان يستطيع ذلك المستخدم الدفع إليه. هذا ليس انتقاداً للأداة، بل هو تعريفها. ولذلك فإن الحساب الذي تعمل الأداة تحته أهم من أي إعداد منفرد.
- مستخدم مخصص وغير مميّز. لا تنشئ عضوية في مجموعة
sudo، ولا تستخدم دليلاً منزلياً مشتركاً مع حسابك. - لا تضع بيانات اعتماد الإنتاج على الخادم. لا تضع ملف
~/.aws/credentialsالذي يحتوي على مفاتيح الإنتاج، ولا تنسخ.envمن بيئة الإنتاج، ولا تستخدم كلمة مرور قاعدة بيانات تملك صلاحية الكتابة إلى أي مورد مهم. امنح العميل بيانات اعتماد لبيئة الاختبار أو بيانات اعتماد للقراءة فقط. - رموز مقيّدة النطاق. استخدم رمز GitHub دقيق الصلاحيات ومحدوداً بمستودع واحد، أو deploy key عندما تكون صلاحية القراءة كافية.
يصدر Claude Code راية تتجاوز مطالبات الأذونات بالكامل. على حاسوب محمول أو في مشروع مؤقت، يعود ذلك إلى قرارك. أما على خادم يحتوي على رموز، فهي تزيل آخر حاجز بين تعليمات أسيء تفسيرها وgit push --force. كما أن مطالبات الأذونات التي ستتجاوزها ليست خياراً ثنائياً بالكامل. ومع وصول الوضع التلقائي بوصفه الإعداد الافتراضي الجديد، من المفيد معرفة وضع الأذونات الذي ينبغي تثبيته لخادم لا تراقبه. يشرح تشغيل Claude Code بأمان على خادم ما الذي تغيّره الراية فعلياً، وكيف تقيّد عميلاً يعمل بها، بدءاً من sandbox المضمّن ووصولاً إلى VPS مؤقت.
Deploy key مقابل إعادة توجيه وكيل SSH
قد تميل إلى ssh -A لكي يستخدم git المفتاح الموجود على حاسوبك المحمول. افهم الصلاحية التي يمنحها ذلك: تكشف إعادة توجيه وكيل SSH مقبس وكيل SSH المحلي للعمليات التي تعمل باسم ذلك المستخدم على الخادم. ويمكن لأي عملية تعمل باسم agent، بما في ذلك العميل، أن تطلب من مفتاحك توقيع طلب لأي مضيف يمكنها الوصول إليه، ما دمت متصلاً. وهذا يتجاوز كثيراً عبارة «دع git يسحب هذا المستودع الواحد».
ولّد مفتاحاً على الخادم بدلاً من ذلك، وسجّله بوصفه deploy key خاصاً بمستودع واحد، ولا تمنحه صلاحية الكتابة إلا إذا احتاج العميل إلى الدفع، ثم اضبط هوية git حتى يمكن تمييز الالتزامات المنشأة من الخادم:
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، فهو يعيد الاتصال بالجلسة إذا كانت موجودة، وينشئها إذا لم تكن موجودة. لذلك يغطي أمر واحد بدء العمل واستئنافه بعد انقطاع الاتصال. أنشئ له alias. داخل الجلسة، يفتح Ctrl-b c نافذة، ويتنقل Ctrl-b n وCtrl-b p بين النوافذ، بينما يفتح Ctrl-b [ وضع النسخ للتمرير إلى الخلف (ويخرج q منه).
هناك أمر واحد يجب معرفته عن الجلسات التي لا تنهيها: يعيد الوكيل إرسال المحادثة كاملة في كل دورة. لذلك اقرأ ما الذي تنفق عليه جلسة Claude Code طويلة الأمد رموزها قبل أن تترك جلسة قيد التشغيل لمدة أسبوع.
أوضاع الفشل
"اختفت جلستي." تطبع tmux ls الرمز no server running on /tmp/tmux-1000/default. يعني هذا غالباً أن العملية لم تكن تعمل داخل tmux، وأنك اتصلت عبر SSH ثم شغّلت claude مباشرة، فأدى انقطاع الاتصال إلى إنهائها. لا يوجد شيء يمكن استعادته. العادة التي تمنع ذلك هي أن يكون tmux new -A -s <project> أول أمر بعد كل تسجيل دخول.
تنكمش اللوحة إلى مربع صغير جداً. يضبط tmux حجم الجلسة وفقاً لأصغر عميل متصل، لذلك قد يؤدي عميل قديم لا يزال متصلاً من جهاز آخر إلى تقليص العرض. افصل العملاء الآخرين قسراً عند الاتصال باستخدام: tmux attach -d -t claude.
تطبع عملية البناء Killed. تظهر كلمة واحدة دون تتبع للمكدس. تحقّق باستخدام sudo dmesg -T | grep -i -E 'out of memory|killed process'؛ فقد اختار قاتل OOM في النواة العملية الأكبر وأنهاها. قد ترى من 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 أيضاً خادم tmux بدلاً من عملية البناء، فتختفي جلستك معه. وإذا كان systemd-oomd قيد التشغيل، فقد ينهي شريحة مستخدم كاملة بالنتيجة نفسها.
npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. حدث ذلك بسبب تثبيت عام داخل prefix مملوك لـroot. استخدم prefix ~/.npm-global المذكور أعلاه. إذا شغّلت sudo npm في وقت سابق، فقد ترى أيضاً Your cache folder contains root-owned files. أصلح ذلك باستخدام sudo chown -R $(id -u):$(id -g) ~/.npm.
claude: command not found، ولكن أحياناً فقط. يوجد تصدير PATH داخل ~/.bashrc أسفل الحارس "If not running interactively, don't do anything"، لذلك تتخطاه الصدَفات غير التفاعلية. انقل التصدير إلى أعلى ذلك الحارس، وأبقِه في ~/.bashrc لا في ~/.profile؛ فقد يبدأ tmux صدَفات غير مخصّصة لتسجيل الدخول، وهي تقرأ ~/.bashrc ولا تلمس ~/.profile.
ظهور ألوان مشوّهة بعد الاتصال. السبب هو عدم تطابق TERM، والسطر default-terminal المذكور أعلاه يعالج المشكلة.
اختفاء الجلسات بعد إعادة التشغيل. هذا ليس خللاً؛ خادم tmux عملية، وإعادة التشغيل تنهيها. تحقّق من uptime.
ما الذي يتعطل مع توسّع هذا الإعداد
مشاريع أكثر. أنشئ جلسة tmux واحدة لكل مستودع، وسمِّها باسم المستودع؛ عندها تصبح tmux ls لوحة المتابعة لديك. إذا أهملت أسلوب التسمية، فستحصل على جلسات باسم 0 و1 و2. وعندما تعمل عدة جلسات في الوقت نفسه، لا يلزم أن تعمل بمعزل عن بعضها، إذ يمكن لـجلسة واحدة تمرير رسالة إلى جلسة أخرى على الخادم نفسه، وهذا مفيد عندما يريد agent ينفّذ إعادة هيكلة طويلة أن يطلب من agent آخر تشغيل الاختبارات. وتتوسع مشكلة المنافذ بالطريقة نفسها؛ فعندما تريد 6 مستودعات استخدام :3000، يحين وقت التوقف عن تعيين المنافذ يدوياً، وترك Traefik reverse proxy يوجّه عدة تطبيقات ضمن Docker Compose وفق اسم المضيف.
مستخدمون أكثر. تكون tmux sockets خاصة بكل مستخدم، لذلك يحصل مطوّران على الخادم نفسه على tmux server مستقل لكل منهما، ولا يستطيع أحدهما رؤية جلسات الآخر. تؤدي مشاركة جلسة واحدة عبر socket مشترك إلى إدخال الجميع أوامر في shell نفسه وباسم Unix user نفسه، مع ما يترتب على ذلك من آثار في التدقيق والصلاحيات. استخدام users منفصلين هو الخيار الصحيح، حتى لو بدا مملاً.
أعمال غير مراقبة. صُممت tmux للجلسات التفاعلية التي تتصل بها. أما المهام المجدولة التي لا يراقبها أحد، فمكانها systemd unit وtimer، إذ تحصل تلقائياً على التسجيل وسياسة لإعادة التشغيل والاستمرار بعد الإقلاع. اللجوء إلى tmux لتشغيل مهمة شبيهة بمهام cron يدل على أن المهمة تريد أن تصبح service.
ملاحظة أخيرة: اربط dev servers التي يبدأها agent بالعنوان 127.0.0.1، لا بالعنوان 0.0.0.0، واتصل بها عبر SSH tunnel (ssh -L 3000:127.0.0.1:3000 agent@your-server) بدلاً من فتح منافذ في ufw. عندما تبدأ في تمرير 6 منافذ أو أكثر، أو عندما يريد هاتف وحاسوب محمول الوصول إلى المعاينة نفسها، ضع WireGuard VPN ذاتية الاستضافة على VPS أمامها بدلاً من ذلك: تستمع dev servers على private interface، بينما يواصل ufw رفض كل الاتصالات الواردة من الواجهة العامة. لا يفيد جدار الحماية إلا إذا توقفت عن فتح منافذ فيه.
لا تُعد Claude Code الخيار الوحيد: تشغيل coding AI agent على VPS يتيح لك أيضاً تقييم Aider وGoose.
FAQ
هل يواصل Claude Code العمل بعد انقطاع اتصال SSH؟
نعم، ولكن فقط إذا شغّلته داخل tmux. تكون العملية التي تبدأها مباشرة من shell الخاص بـSSH عمليةً فرعية لذلك الـshell، وتنتهي مع pty عند انقطاع الاتصال. داخل tmux، ينتمي الـshell إلى خادم tmux المنفصل، لذلك يواصل الوكيل العمل في منتصف المهمة، ويعيدك tmux attach إلى سجل التمرير نفسه. اجعل tmux new -A -s <project> أول أمر تنفذه بعد كل تسجيل دخول، وتنتهي المشكلة.
هل يجب أن أثبّت CLI باستخدام sudo npm install -g؟
لا. يؤدي تعيين prefix عام مملوك لـroot إلى ظهور أخطاء EACCES عند عمليات التثبيت اللاحقة، وإلى إنشاء ملفات مملوكة لـroot في ذاكرة npm المؤقتة. عيّن prefix الخاص بـnpm إلى ~/.npm-global، أو استخدم مدير إصدارات مثل nvm، وثبّت الحزمة باستخدام المستخدم غير المميّز agent، ثم صدّر ~/.npm-global/bin إلى PATH من ~/.bashrc، فوق شرط التحقق التفاعلي. إذا نفذت sudo npm من قبل، فأصلح ذاكرة التخزين المؤقت باستخدام sudo chown -R $(id -u):$(id -g) ~/.npm.
هل يُعدّ ssh -A agent forwarding آمناً على خادم يشغّل وكيلاً؟
يمنح ذلك صلاحيات تتجاوز احتياجات المهمة بكثير. يكشف forwarding مقبس SSH agent المحلي لكل عملية تعمل باسم ذلك المستخدم، لذلك يمكن لأي شيء على الخادم أن يطلب من مفتاحك توقيع طلب لأي مضيف يمكنه الوصول إليه، ما دمت متصلاً. أنشئ مفتاح ed25519 على الخادم وسجّله بوصفه deploy key خاصاً بالمستودع، ومنحه صلاحية الكتابة فقط إذا كان الوكيل يحتاج فعلاً إلى تنفيذ push.
لماذا يطبع البناء Killed فقط؟
تعني كلمة واحدة من دون stack trace أن kernel OOM killer أنهى العملية. أكّد ذلك باستخدام sudo dmesg -T | grep -i -E 'out of memory|killed process'؛ وقد ترى من Node بدلاً من ذلك JavaScript heap out of memory. طبّق الإصلاحات بالترتيب: أضف swapfile، وقيّد التوازي في الاختبارات والمصرّف، وارفع NODE_OPTIONS=--max-old-space-size=...، ثم انتقل إلى VPS أكبر. انتبه إلى أن OOM killer قد يختار خادم tmux بدلاً من عملية البناء، ما يؤدي إلى إنهاء جلستك بالكامل.
هل أستخدم tmux أم خدمة systemd؟
يناسب tmux الجلسات التفاعلية التي تتصل بها وتراقبها وتكتب فيها، وهذا يطابق جلسة الوكيل تماماً. أما العمل المجدول الذي لا يراقبه أحد فينتمي إلى systemd unit وtimer، حيث تتوفر السجلات وسياسة إعادة التشغيل والاستمرار بعد الإقلاع تلقائياً. إذا كنت تستخدم tmux لتشغيل مهمة تشبه مهام cron، فالمهمة تحتاج إلى خدمة.