SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-21

اجرای dsh به صورت سرویس در systemd روی VPS

برای اجرای دائم dsh روی VPS، یک فایل unit در systemd بسازید. این راهنما نحوه تنظیم کاربر اختصاصی، قوانین Restart، مدیریت لاگ با journalctl و ایجاد تونل SSH برای UI را آموزش می‌دهد.

اجرای dsh به صورت headless روی VPS، نه در ترمینال

اجرای dsh به صورت headless روی یک VPS، تنها به یک فایل unit در systemd و یک کاربر اختصاصی برای مدیریت آن نیاز دارد. dsh رابط خط فرمان برای DeepSeek Harness است؛ محیط اجرای عامل (agent runtime) شرکت DeepSeek که در اوت 2026 تحت مجوز MIT به صورت پیش‌نمایش توسعه‌دهنده (developer preview) منتشر شد. راهنمای شروع سریع به شما می‌گوید که npx @deepseek-ai/dsh web را تایپ کنید، که صحیح است، اما به محض بستن نشست SSH (پوسته امن)، این دستور نیز متوقف می‌شود.

یک فایل unit چهار مشکل را هم‌زمان حل می‌کند. سرویس پس از reboot دوباره بالا می‌آید. خروجی آن به‌جای اینکه از جلوی چشمان شما بگذرد، به journal ارسال می‌شود. سرویس با کاربری غیر از root اجرا می‌شود. و نسخه‌ای که اجرا می‌شود همان نسخه‌ای است که شما انتخاب کرده‌اید؛ موضوعی که در اینجا اهمیت بیشتری نسبت به موارد معمول دارد، زیرا توسعه‌دهنده اصلی در مستندات با حروف بزرگ تأکید کرده است:

DeepSeek Harness در حال حاضر در مرحله پیش‌نمایش توسعه‌دهنده است و به‌سرعت در حال تغییر است. تغییرات ناسازگار (BREAKING CHANGES) رخ خواهد داد.

این راهنما فرض می‌کند که dsh هم‌اکنون به صورت دستی برای شما کار می‌کند. اگر این‌طور نیست، با نصب DeepSeek Harness روی VPS شروع کنید و پس از اینکه npx @deepseek-ai/dsh web یک صفحه را ارائه داد، به اینجا بازگردید.

ابتدا Node را نصب کنید، زیرا npm به شما هشدار نخواهد داد

node -v

بسته پیش‌فرض Node در Ubuntu 24.04 نسخه 18 است (تا اوت 2026 نسخه 18.19.1)، که برای بسته‌ای که امسال منتشر شده، قدیمی محسوب می‌شود. @deepseek-ai/dsh هیچ فیلد engines را منتشر نمی‌کند، بنابراین وقتی نسخه Node شما بیش از حد قدیمی باشد، npm هیچ هشدار EBADENGINE نمایش نمی‌دهد. در عوض، خطا در زمان اجرا (run time) به صورت خطای نحوی (syntax error) یا نبود یک قابلیت داخلی (missing built-in) ظاهر می‌شود که شناسایی آن در آن مرحله بسیار دشوارتر است. یک نسخه پشتیبانی بلندمدت (LTS) فعلی را از NodeSource نصب کنید:

curl -fsSL https://deb.nodesource.com/setup_22.x -o /tmp/nodesource_setup.sh
less /tmp/nodesource_setup.sh
sudo -E bash /tmp/nodesource_setup.sh
sudo apt install -y nodejs
node -v

اکنون node -v باید یک نسخه v22 را چاپ کند. خط less به این دلیل وجود دارد که ارسال مستقیم یک اسکریپت از راه دور به bash باعث اجرای کدی می‌شود که هرگز آن را نخوانده‌اید.

پیش از نوشتن unit، اجرای آن را تست کنید

npx @deepseek-ai/dsh@0.1.0-rc.7 web

آن را در حال اجرا رها کنید. از یک نشست SSH دوم:

curl -fsS http://127.0.0.1:3080/ -o /dev/null && echo up

up به این معنی است که پروفایل وب روی loopback در حال گوش دادن است، که محل اتصال پیش‌فرض آن است. curl: (7) Failed to connect to 127.0.0.1 port 3080: Connection refused به این معنی است که چنین نیست و ترمینال اول دلیل آن را به شما می‌گوید. پیش از ادامه، اجرای دستی را با Ctrl+C متوقف کنید: unitای که سعی کند پورتی را که توسط سرویس دیگری اشغال شده است bind کند، با خطای Error: listen EADDRINUSE: address already in use 127.0.0.1:3080 مواجه می‌شود.

0.1.0-rc.7 نسخه منتشرشده در تاریخ 18 August 2026 بود. با استفاده از npm view @deepseek-ai/dsh version بررسی کنید چه نسخه‌ای در حال حاضر موجود است، سپس نسخه‌ای را که تصمیم به اجرای آن دارید، pin کنید.

نصب نسخه پین‌شده به‌صورت سراسری

استفاده از npx درون یک unit file اشتباه است. این دستور نسخه بسته را در لحظه شروع پردازش تعیین می‌کند، بنابراین راه‌اندازی مجدد سیستم پس از سه ماه ممکن است باعث اجرای بیلد متفاوتی از یک agent در مرحله پیش‌نمایش شود، بدون اینکه تغییری در تنظیمات شما ایجاد شده باشد. همچنین این روش نیازمند دسترسی به npm registry در زمان بوت است که باعث می‌شود یک ماشین سالم، در روزی که registry کند است، به یک unit ناموفق تبدیل شود. نصب را یک‌بار و با نسخه‌ای که یادداشت کرده‌اید انجام دهید:

sudo npm install -g @deepseek-ai/dsh@0.1.0-rc.7
command -v dsh
npm ls -g --depth=0 @deepseek-ai/dsh

دستور command -v dsh در صورتی که npm از NodeSource آمده باشد /usr/bin/dsh و اگر از بسته خود Ubuntu باشد /usr/local/bin/dsh را چاپ می‌کند. از مسیری که در خروجی چاپ شده است در unit file استفاده کنید. دستور npm ls -g نسخه دقیق را چاپ می‌کند؛ این همان پاسخی است که شش هفته بعد، زمانی که رفتار برنامه تغییر کرده و به خاطر نمی‌آورید چه نسخه‌ای نصب کرده‌اید، به آن نیاز خواهید داشت.

کاربری که مالک سرویس است و هیچ دسترسی دیگری ندارد

این عامل (agent) دستورات shell را اجرا می‌کند. این وظیفهٔ اصلی آن است. اجرای آن با دسترسی root باعث می‌شود هر فراخوانی ابزار، به یک فراخوانی با دسترسی root تبدیل شود؛ بنابراین، یک حساب کاربری اختصاصی بدون shell ورود (login shell) برای آن ایجاد کنید.

sudo useradd --system --create-home --home-dir /var/lib/dsh --shell /usr/sbin/nologin dsh
sudo install -d -o dsh -g dsh -m 750 /var/lib/dsh/harness /var/lib/dsh/workspace
id dsh

/var/lib/dsh/harness به DSH_HOME تبدیل می‌شود، دایرکتوری که dsh پروفایل‌ها را در آن نگهداری می‌کند. یک پروفایل، مجموعه‌ای نام‌گذاری‌شده از بسته‌های افزونه است که لایهٔ patch اختصاصی شما روی آن قرار دارد، و پروفایل‌های web و headless در اولین باری که آن‌ها را بوت می‌کنید، خود را از روی قالب‌های پیش‌فرض (shipped templates) می‌سازند. آن بوت اولیه فایل‌هایی را می‌نویسد و ممکن است بسته‌هایی را دریافت کند، بنابراین این کار را به‌صورت دستی انجام دهید تا بتوانید روند آن را مشاهده کنید.

sudo -u dsh env HOME=/var/lib/dsh DSH_HOME=/var/lib/dsh/harness /usr/bin/dsh --profile web

مقدار HOME را به‌صورت صریح تنظیم کنید و به آنچه sudo با آن انجام می‌دهد اعتماد نکنید، زیرا اینکه آیا sudo فایل HOME را برای یک دستور غیر-ورودی (non-login) بازنویسی می‌کند یا خیر، به تنظیمات set_home در /etc/sudoers بستگی دارد. اگر این کار را اشتباه انجام دهید، در اولین اجرا، دایرکتوری‌های کش در دایرکتوری home شما و با مالکیت dsh ایجاد می‌شوند و سرویس بعداً نمی‌تواند وضعیت (state) خود را پیدا کند. هنگامی که بررسی curl مقدار up را برگرداند، با استفاده از Ctrl+C آن را متوقف کنید.

فایل unit

عبارت /etc/systemd/system/dsh.service را بنویسید:

[Unit]
Description=DeepSeek Harness (dsh) web profile
Documentation=https://github.com/deepseek-ai/deepseek-harness
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5

[Service]
Type=exec
User=dsh
Group=dsh
WorkingDirectory=/var/lib/dsh/workspace
Environment=HOME=/var/lib/dsh
Environment=DSH_HOME=/var/lib/dsh/harness
ExecStart=/usr/bin/dsh --profile web
Restart=on-failure
RestartSec=5s
TimeoutStopSec=30s
SyslogIdentifier=dsh
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true

[Install]
WantedBy=multi-user.target

ExecStart= مسیر مطلق به‌دست‌آمده از command -v dsh را می‌گیرد. systemd برای یک نام دستور ساده، لیستی از مسیرهای ثابت را جستجو می‌کند، اما آن لیست همان PATH شل شما نیست؛ بنابراین استفاده از مسیر مطلق، ابهام را از بین می‌برد.

WorkingDirectory= جایی است که مسیرهای نسبی در آن حل می‌شوند و نقطه‌ای است که فراخوانی ابزاری که ls را بدون آرگومان اجرا می‌کند، از آنجا آغاز می‌شود. آن را به فضای کاری (workspace) که به agent می‌دهید اشاره دهید. اگر دایرکتوری موجود نباشد یا کاربر سرویس نتواند وارد آن شود، unit پیش از اجرای dsh با خطای status=200/CHDIR شکست می‌خورد.

ProtectHome=true مسیرهای /home و /root را از دید پردازش پنهان می‌کند. این کار در اینجا ایمن است زیرا هر چیزی که سرویس با آن سروکار دارد، درون /var/lib/dsh قرار دارد. اگر فضای کاری را به مسیری زیر /home اشاره دهید، agent گزارش می‌دهد که دایرکتوری وجود ندارد؛ این موضوع تا زمانی که این خط را به یاد نیاورید، گیج‌کننده است. ProtectSystem=full باعث می‌شود /usr، /boot و /etc فقط‌خواندنی (read-only) شوند، که سرویس هرگز نیازی به نوشتن در آن‌ها ندارد.

پیشروی بیشتر وسوسه‌انگیز است اما معمولاً اشتباه است. ProtectSystem=strict کل سیستم فایل را به‌جز شبه‌سیستم‌فایل‌های هسته، فقط‌خواندنی می‌کند؛ بنابراین اولین فراخوانی ابزاری که فایلی را بنویسد، با خطای EROFS: read-only file system شکست می‌خورد. اگر چنین سطحی از امنیت را می‌خواهید، ReadWritePaths=/var/lib/dsh را در همان ویرایش اضافه کنید.

کدام Type= در اینجا مناسب است

Type=exec، زیرا dsh در پیش‌زمینه (foreground) باقی می‌ماند و هرگز fork نمی‌شود. مزیت این کار نسبت به حالت پیش‌فرض، دریافت یک پیام خطای واقعی است. با Type=simple، سیستم systemd به محض fork شدن، شروع سرویس را موفقیت‌آمیز تلقی می‌کند، حتی پیش از آنکه بداند آیا فایل اجرایی اصلاً وجود دارد یا خیر؛ بنابراین systemctl start dsh با موفقیت بازمی‌گردد و خطا تنها در journal قابل مشاهده است. با Type=exec، سیستم systemd منتظر می‌ماند تا execve() با موفقیت اجرا شود؛ در نتیجه، یک غلط تایپی در ExecStart= باعث شکست دستوری می‌شود که همان لحظه وارد کرده‌اید و می‌توانید آن را مشاهده کنید.

دو پاسخ نادرست دیگر باعث توقف (hang) سرویس می‌شوند. Type=forking به systemd دستور می‌دهد منتظر خروج پردازش والد بماند، اما dsh هرگز خارج نمی‌شود؛ بنابراین شروع سرویس تا زمان اتمام TimeoutStartSec (به‌طور پیش‌فرض 90 ثانیه) مسدود می‌ماند و سپس خطای Job for dsh.service failed because a timeout was exceeded. گزارش می‌شود. Type=notify منتظر پیام READY=1 از طریق sd_notify می‌ماند و پردازش Node که هرگز چنین پیامی ارسال نمی‌کند، به همان شکل متوقف می‌شود. مقایسه کامل انواع سرویس‌های systemd باقی موارد، از جمله زمان مناسب برای صرف هزینه جهت تنظیم notify را پوشش می‌دهد.

قوانین راه‌اندازی مجدد که با صدای بلند شکست می‌خورند

Restart=on-failure در صورت خروج غیر صفر یا دریافت سیگنال مهلک، سرویس را مجدداً راه‌اندازی می‌کند و پس از خروج موفقیت‌آمیز، unit را به حال خود رها می‌کند. این همان رفتاری است که برای یک نسخه پیش‌نمایش (preview build) انتظار دارید. اگر dsh به دلیل خواندن یک پیکربندی نامناسب با کد 0 خارج شود، unit متوقف شده و در همان حالت باقی می‌ماند و systemctl status dsh وضعیت inactive (dead) را نشان می‌دهد که می‌توانید آن را مشاهده کنید. Restart=always همین رویداد را به یک حلقه راه‌اندازی مجدد تبدیل می‌کند که از دور سالم به نظر می‌رسد.

محدودیت نرخ (rate limit) بخشی است که افراد از آن غافل می‌شوند. مقادیر پیش‌فرض systemd پنج بار شروع در ده ثانیه است و با RestartSec=5s شما هرگز به پنج بار شروع در بازه ده ثانیه‌ای نمی‌رسید، بنابراین unitای که هنگام شروع کرش می‌کند برای همیشه ری‌استارت می‌شود و فقط journal از آن باخبر است. StartLimitIntervalSec=300 به همراه StartLimitBurst=5 به این معنی است که پنج شکست در پنج دقیقه کافی است: systemd تسلیم شده و unit را در وضعیت failed قرار می‌دهد و Start request repeated too quickly. را لاگ می‌کند. پس از رفع علت، این وضعیت را با sudo systemctl reset-failed dsh پاک کنید. هر دو تنظیمات باید در [Unit] قرار بگیرند، نه [Service]، و systemd در صورت قرارگیری در بخش اشتباه، آن‌ها را بدون هیچ هشداری نادیده می‌گیرد.

سرویس را اجرا و وضعیت آن را بررسی کنید

sudo systemctl daemon-reload
sudo systemctl enable --now dsh
systemctl status dsh

دستور enable --now دو وظیفه دارد. enable باعث می‌شود سرویس پس از reboot دوباره بالا بیاید و --now آن را در boot فعلی اجرا می‌کند. یک systemctl start ساده پس از reboot بعدی از بین می‌رود و به‌روزرسانی‌های kernel نیز مستلزم reboot هستند.

دستور systemctl status dsh باید وضعیت Active: active (running)، یک Main PID و یک خط Memory: را نشان دهد. سپس تأیید کنید که سرویس روی چه پورتی گوش می‌دهد:

sudo ss -lntp | grep 3080

شما به 127.0.0.1:3080 نیاز دارید. اگر 0.0.0.0:3080 را مشاهده کردید، یعنی چیزی آدرس bind را تغییر داده و agent شما روی اینترنت عمومی در دسترس است. نام پردازش در آن خروجی node است، نه dsh، زیرا باینری dsh یک اسکریپت Node است و به همین دلیل pgrep -x dsh چیزی پیدا نمی‌کند. به‌جای آن از systemctl show -p MainPID dsh استفاده کنید.

سپس یک بار سیستم را reboot کنید. سرویسی که هرگز یک reboot را پشت سر نگذاشته است، هنوز یک سرویس کامل محسوب نمی‌شود.

sudo reboot

دوباره متصل شوید و systemctl is-active dsh را اجرا کنید. این دستور active را چاپ می‌کند.

خواندن لاگ‌ها با journalctl

هر چیزی که dsh در stdout و stderr می‌نویسد، تحت نام unit در journal ثبت می‌شود.

journalctl -u dsh -f
journalctl -u dsh -n 200 --no-pager
journalctl -u dsh --since "10 min ago" -p err

دستور -f خطوط جدید را دنبال می‌کند، -n تعداد N خط آخر را نمایش می‌دهد و -p err بر اساس اولویت فیلتر می‌کند. مقدار SyslogIdentifier=dsh در unit دلیل برچسب‌گذاری خطوط با dsh به‌جای node است؛ این موضوع زمانی که برای اولین بار خروجی journal را می‌خوانید و توسط unit فیلتر نشده است، اهمیت پیدا می‌کند.

پیش از آنکه به لاگ‌ها نیاز پیدا کنید، بررسی کنید که آیا journal پس از reboot باقی می‌ماند یا خیر:

journalctl -u dsh -b -1

اگر این دستور Specifying boot ID or boot offset has no effect, no persistent journal was found را چاپ کرد، یعنی journal در /run قرار دارد و با هر بار reboot پاک می‌شود. دایرکتوری مربوطه را ایجاد کرده و daemon را restart کنید:

sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald

دسترسی به رابط کاربری از طریق تونل SSH، نه پورت عمومی

dsh رابط کاربری وب (UI) را روی 127.0.0.1:3080 ارائه می‌دهد و از ارائه آن در هر جای دیگری خودداری می‌کند. اگر --host 0.0.0.0 را درخواست کنید، با این خطا متوقف می‌شود:

error: --host 0.0.0.0 is intentionally not supported yet for safety: it would expose remote code execution to the network; use 127.0.0.1 instead

این یک محدودیت نیست که بخواهید آن را دور بزنید. رابط برنامه‌نویسی وب (API) عامل (agent) را هدایت می‌کند و عامل دستورات shell را اجرا می‌کند؛ بنابراین، یک پورت در دسترس، به معنای یک shell روی VPS شما برای هر کسی است که آن را پیدا کند. توسعه‌دهندگان دلیل ثابت بودن bind روی loopback را عدم پیاده‌سازی احراز هویت از راه دور عنوان کرده‌اند. به جای آن، پورت را از دستگاه خودتان فوروارد کنید:

ssh -N -L 3080:127.0.0.1:3080 you@203.0.113.10

دستور -L 3080:127.0.0.1:3080 پورت 3080 را روی لپ‌تاپ شما باز می‌کند و هر چیزی که به آن برسد را به 127.0.0.1:3080 (آن‌طور که روی VPS حل می‌شود) می‌فرستد. -N به این معنی است که هیچ دستور از راه دوری اجرا نشود، بنابراین نشست (session) فقط تونل را باز نگه می‌دارد. آن را در حال اجرا بگذارید و http://127.0.0.1:3080/ را در مرورگر خود باز کنید. در اینجا است که کلید DeepSeek API را در بخش Settings و سپس Models وارد می‌کنید و دایرکتوری workspace را انتخاب می‌کنید. workspace را روی /var/lib/dsh/workspace تنظیم کنید، یعنی دایرکتوری که متعلق به کاربر سرویس است؛ در غیر این صورت ابزارهای فایل عامل با خطای EACCES: permission denied مواجه می‌شوند.

اگر پورت 3080 روی لپ‌تاپ شما اشغال باشد، ssh این را اعلام می‌کند:

bind [127.0.0.1]:3080: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 3080

یک پورت محلی دیگر را با ssh -N -L 3081:127.0.0.1:3080 you@203.0.113.10 انتخاب کنید و سپس به http://127.0.0.1:3081/ بروید. برای صرفه‌جویی در تایپ کردن، ~/.ssh/config را روی دستگاه خود ایجاد کنید:

Host dsh-vps
  HostName 203.0.113.10
  User you
  LocalForward 3080 127.0.0.1:3080

پس از آن، ssh -N dsh-vps کل دستور مورد نیاز خواهد بود. این تونل اکنون تنها درِ ورودی به عامل شماست، بنابراین daemon مربوط به SSH همان چیزی است که از آن محافظت می‌کند: فقط از کلید استفاده کنید، احراز هویت با رمز عبور را غیرفعال کنید و سایر موارد مربوط به ایمن‌سازی SSH روی VPS را با جدیت بیشتری اعمال کنید.

کلید نباید در فایل unit قرار بگیرد. مقادیر Environment= توسط systemctl show dsh -p Environment چاپ می‌شوند که هر کاربری روی سیستم می‌تواند آن را اجرا کند. اگر پلاگینی که نصب می‌کنید به کلید در محیط (environment) نیاز دارد، آن را در /etc/dsh.env با مجوز 600 و مالکیت root قرار دهید و با EnvironmentFile=/etc/dsh.env به آن ارجاع دهید. systemd این فایل را در زمان اجرا به عنوان root می‌خواند و systemctl show محتویات آن را چاپ نمی‌کند.

هزینه‌های اجرا

پردازش استنتاج (Inference) در API شرکت DeepSeek انجام می‌شود، نه روی VPS شما. سرور شما هزینه اجرای فرآیند Node، رابط کاربری که ارائه می‌دهد و هر دستوری که ایجنت تصمیم به اجرای آن می‌گیرد را می‌پردازد. دو مورد اول ثابت و ناچیز هستند، اما مورد سوم در این فایل unit هیچ محدودیتی ندارد.

به‌جای اعتماد به ارقام دیگران، کف مصرف منابع را روی سرور خودتان اندازه‌گیری کنید:

systemctl show dsh -p MemoryCurrent
systemd-cgtop -1 --depth 2

مقدار MemoryCurrent بر حسب بایت است. آن را زمانی که ایجنت در حال کار است مشاهده کنید، نه زمانی که در حالت بیکار (idle) قرار دارد.

فراخوانی ابزارها (Tool calls) فرزندان سرویس محسوب می‌شوند، بنابراین در همان گروه کنترلی (control group) قرار گرفته و در محدودیت‌های یکسان لحاظ می‌شوند. ایجنتی که npm install یا یک مجموعه تست را داخل فضای کاری اجرا می‌کند، می‌تواند حافظه‌ای بسیار بیشتر از خودِ برنامه اصلی مصرف کند. روی یک VPS با 1 GB رم، اینجاست که مشکلات بروز می‌کنند: هسته سیستم‌عامل یک فرآیند را انتخاب و آن را متوقف (kill) می‌کند و journalctl -k | grep -i "out of memory" خط Out of memory: Killed process را نشان می‌دهد که نام فرآیند انتخاب‌شده را ذکر می‌کند. آن فرآیند اغلب همان موردی نیست که باعث بروز مشکل شده است.

راه‌حل، اعمال محدودیتی است که شما به‌صورت آگاهانه تعیین می‌کنید. پارامترهای MemoryMax= و CPUQuota= در بخش [Service]، آسیب را درون همان unit نگه می‌دارند تا در صورت اجرای یک build خارج از کنترل، به‌جای قفل شدن کل سرور، فقط همان فرآیند متوقف شود. مطلب محدودسازی حافظه و CPU با systemd اعداد و رفتار سیستم در هنگام خطا را پوشش می‌دهد. فضای دیسک نیز به دلیل تاریخچه نشست‌ها در مسیر DSH_HOME و هر آنچه ایجنت در فضای کاری می‌نویسد افزایش می‌یابد، بنابراین du -sh /var/lib/dsh را در ابزاری که برای پایش دیسک استفاده می‌کنید، قرار دهید.

اگر به دنبال یک ایجنت تعاملی هستید که بتوانید به آن متصل و از آن جدا شوید، استفاده از سرویس انتخاب مناسبی نیست و اجرای ایجنت در یک نشست tmux پایدار گزینه بهتری است. زمانی که می‌خواهید dsh همیشه بالا باشد و از طریق تونل در دسترس قرار گیرد، آن را به عنوان یک unit اجرا کنید.

حالت‌های شکست و پیام‌هایی که مشاهده خواهید کرد

status=203/EXEC. سیستم systemd نتوانست فایل را اجرا کند و لاگ‌ها Failed to locate executable /usr/local/bin/dsh: No such file or directory را نشان می‌دهند. مسیر موجود در ExecStart= با آنچه command -v dsh چاپ کرده است مطابقت ندارد. این همان خطایی است که Type=exec در زمان systemctl start گزارش می‌دهد و دیگر آن را پنهان نمی‌کند.

status=217/USER. حساب کاربری در User= وجود ندارد. با استفاده از id dsh آن را تأیید کنید.

status=200/CHDIR. دایرکتوری WorkingDirectory= وجود ندارد یا کاربر سرویس اجازه ورود به آن را ندارد. دستور sudo -u dsh ls /var/lib/dsh/workspace آن را مستقیماً بازتولید می‌کند.

Error: listen EADDRINUSE: address already in use 127.0.0.1:3080. چیزی در حال حاضر پورت را اشغال کرده است؛ معمولاً یک اجرای npx که در ترمینال دیگری باز مانده است. دستور sudo ss -lntp | grep 3080 نام آن پردازش را مشخص می‌کند.

EACCES: permission denied به همراه یک مسیر. مالکیت فایل‌ها در /var/lib/dsh اشتباه است؛ معمولاً به این دلیل که اولین اجرا با کاربر root یا با HOME اشتباه انجام شده است. دستور sudo chown -R dsh:dsh /var/lib/dsh این مشکل را برطرف می‌کند.

Start request repeated too quickly. واحد (unit) به محدودیت نرخ شروع (start rate limit) رسیده و متوقف شده است. خطای اصلی در خطوط بالای آن قرار دارد. پیش از تلاش مجدد، دستور sudo systemctl reset-failed dsh را اجرا کنید.

وضعیت واحد active (running) است اما مرورگر چیزی نشان نمی‌دهد. بررسی را روی VPS انجام دهید: اگر curl -fsS http://127.0.0.1:3080/ -o /dev/null && echo up در آنجا up را چاپ می‌کند، سرویس سالم است و مشکل در port forward است.

ارتقای هدفمند

پین کردن (Pinning) به این معناست که ارتقا عملی است که شما انجام می‌دهید، نه اتفاقی که برای شما می‌افتد. ابتدا یادداشت‌های انتشار (release notes) را مطالعه کنید، زیرا هشدار خودِ توسعه‌دهنده درباره تغییرات ناسازگار، دقیقاً همان دلیلی است که باید نسخه را پین کنید. از دایرکتوری state نسخه پشتیبان تهیه کنید و سپس نسخه را تغییر دهید:

sudo systemctl stop dsh
sudo tar czf /root/dsh-home-$(date +%F).tgz -C /var/lib/dsh harness
sudo npm install -g @deepseek-ai/dsh@0.1.0-rc.7
sudo systemctl start dsh
journalctl -u dsh -n 50 --no-pager

بازگشت به نسخه قبلی (Rollback) همان فرآیند npm install -g با نسخه قدیمی است، به اضافه بازیابی آن فایل tarball که تنها در صورتی ممکن است که از آن نسخه پشتیبان گرفته باشید. یک runtime عامل در مرحله پیش‌نمایش (preview-stage)، دقیقاً همان نرم‌افزاری است که در آن ارتقا می‌تواند فرمت پیکربندی را بدون اطلاع شما بازنویسی کند.

FAQ

چرا وقتی نشست SSH خود را می‌بندم، dsh متوقف می‌شود؟

زیرا npx @deepseek-ai/dsh web یک پردازش پیش‌زمینه است که مالکیت آن با نشست ورود شماست؛ بنابراین با پایان نشست، این پردازش نیز خاتمه می‌یابد. در مقابل، یک unit در systemd تحت مدیریت سیستم init قرار دارد و به همین دلیل پس از قطع اتصال به کار خود ادامه می‌دهد و پس از reboot نیز دوباره شروع به کار می‌کند. sudo systemctl enable --now dsh مجموعه‌ای از دو مرحله است که هر دو قابلیت را به شما می‌دهد: enable برای reboot و --now برای همین نشست جاری.

آیا باید از Type=simple یا Type=exec برای dsh استفاده کنم؟

Type=exec. برنامه dsh در پیش‌زمینه اجرا می‌شود و هرگز fork نمی‌کند، بنابراین هر دو گزینه کار می‌کنند؛ اما Type=exec باعث می‌شود systemd پیش از اعلام موفقیت‌آمیز بودن شروع سرویس، منتظر بماند تا execve() با موفقیت اجرا شود. در این حالت، اگر مسیر اشتباهی در ExecStart= باشد، systemctl start با نمایش status=203/EXEC به شما هشدار می‌دهد. با استفاده از Type=simple، همان اشتباه به عنوان موفقیت گزارش شده و در journal پنهان می‌ماند. گزینه‌های Type=forking و Type=notify هر دو در اینجا اشتباه هستند و باعث می‌شوند سرویس تا زمان انقضای TimeoutStartSec (پس از 90 ثانیه) معلق بماند.

چگونه رابط کاربری وب dsh را از لپ‌تاپ خود باز کنم؟

پورت را از طریق SSH فوروارد کنید: ssh -N -L 3080:127.0.0.1:3080 you@your-vps، سپس http://127.0.0.1:3080/ را در مرورگر خود باز کنید. سعی نکنید سرویس را به یک آدرس عمومی bind کنید. برنامه dsh اتصال به --host 0.0.0.0 را با خطای error: --host 0.0.0.0 is intentionally not supported yet for safety: it would expose remote code execution to the network; use 127.0.0.1 instead رد می‌کند، زیرا API وب می‌تواند به agent دستور دهد تا دستورات shell را اجرا کند و هیچ لایه احراز هویت از راه دوری در مقابل آن وجود ندارد.

آیا می‌توانم dsh را به عنوان root اجرا کنم تا مدیریت مجوزها ساده‌تر شود؟

خیر. این ابزار برای اجرای دستورات و نوشتن فایل‌ها طراحی شده است، بنابراین هر امتیازی که سرویس داشته باشد، agent نیز همان امتیازات را خواهد داشت. یک حساب کاربری سیستمی با useradd --system --shell /usr/sbin/nologin dsh ایجاد کنید، مالکیت /var/lib/dsh را به آن بدهید و NoNewPrivileges=true را به unit اضافه کنید. اگر پس از آن با EACCES: permission denied مواجه شدید، دلیل معمول آن اجرای قبلی برنامه با کاربر root است که باعث شده فایل‌هایی با مالکیت root باقی بمانند؛ با sudo chown -R dsh:dsh /var/lib/dsh این مشکل برطرف می‌شود.

کدام نسخه از dsh را باید در unit ثابت (pin) کنم؟

هر نسخه‌ای که npm view @deepseek-ai/dsh version هنگام راه‌اندازی سرویس گزارش می‌دهد، که با npm install -g @deepseek-ai/dsh@<that version> نصب شده و در جایی یادداشت کرده‌اید تا بعداً آن را پیدا کنید. نسخه 0.1.0-rc.7 در تاریخ 18 اوت 2026 نسخه جاری بود. نکته مهم شماره نسخه نیست، بلکه این است که npx بدون تعیین نسخه، بسته را در زمان شروع (start time) شناسایی می‌کند؛ بنابراین یک restart خودکار ممکن است شما را بدون اطلاع قبلی به نسخه‌ای با فرمت پیکربندی متفاوت منتقل کند.