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

اجرای dsh به‌صورت headless روی VPS با systemd

آموزش اجرای dsh به‌عنوان سرویس systemd روی VPS، با کاربر اختصاصی، نسخه ثابت، Restart، لاگ‌های journalctl و تونل SSH برای دسترسی به رابط کاربری.

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

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

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

DeepSeek Harness در حال حاضر در وضعیت developer preview قرار دارد و با سرعت زیادی تغییر می‌کند. تغییرات ناسازگارکننده در سازگاری رخ خواهد داد.

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

ابتدا Node، زیرا npm به شما هشدار نمی‌دهد

node -v

بستهٔ خود Ubuntu 24.04، نسخهٔ Node 18 است (تا August 2026 نسخهٔ 18.19.1). این نسخه برای بسته‌ای که امسال منتشر شده است قدیمی محسوب می‌شود. @deepseek-ai/dsh هیچ فیلد engines منتشر نمی‌کند؛ بنابراین وقتی نسخهٔ Node شما قدیمی باشد، npm هیچ هشدار EBADENGINE نمایش نمی‌دهد. خطا در عوض هنگام اجرا رخ می‌دهد؛ به‌صورت خطای نحوی یا نبود یک قابلیت داخلی. این محل، جای بسیار نامناسب‌تری برای شناسایی مشکل است. یک نسخهٔ فعلی long term support (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 به این دلیل وجود دارد که pipe کردن مستقیم یک اسکریپت راه دور به 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 یعنی web profile روی loopback در حال گوش‌دادن است؛ این همان جایی است که به‌طور پیش‌فرض به آن bind می‌شود. 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 نیست. این دستور هنگام شروع فرایند، نسخه بسته را resolve می‌کند؛ بنابراین ممکن است restart سه ماه بعد، بدون هیچ تغییری از طرف شما، build متفاوتی از یک agent در مرحله preview را اجرا کند. همچنین هنگام boot به registry مربوط به npm دسترسی نیاز دارد؛ در نتیجه، ماشینی که به‌درستی کار می‌کند، در روزی که 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 را چاپ می‌کند و اگر از package خود Ubuntu نصب شده باشد، /usr/local/bin/dsh را چاپ می‌کند. در فایل unit از مسیری استفاده کنید که واقعاً چاپ شده است. npm ls -g نسخه دقیق را چاپ می‌کند؛ این همان پاسخی است که شش هفته بعد، هنگام تغییر رفتار برنامه و فراموش‌کردن نسخه نصب‌شده، به آن نیاز دارید. اگر نصب ناموفق بود، یا command -v dsh پس از آن چیزی چاپ نکرد، یا نسخه‌ای که دریافت می‌کنید همان نسخه درخواستی شما نیست، پیش از نوشتن فایل unit، خطاهای معمول نصب و نسخه در dsh را بررسی کنید.

کاربری که فقط مالک سرویس است

این agent فرمان‌های shell را اجرا می‌کند. وظیفه آن همین است. اجرای آن با root باعث می‌شود هر فراخوانی ابزار با مجوزهای root اجرا شود؛ بنابراین برای آن یک account اختصاصی بدون 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 تبدیل می‌شود؛ این همان directory است که dsh پروفایل‌ها را در آن نگه می‌دارد. یک profile، stack نام‌گذاری‌شده‌ای از plugin bundleهاست که لایه patch اختصاصی شما روی آن قرار می‌گیرد. پروفایل‌های web و headless نیز نخستین بار که آن‌ها را راه‌اندازی می‌کنید، بر اساس templateهای همراه نرم‌افزار ساخته می‌شوند. هر چیزی که بعداً به این stack اضافه کنید، با این user و دسترسی file و shell اختصاصی agent اجرا می‌شود؛ بنابراین بررسی plugin پیش از نصب آن بخشی از همان کاری است که برای ایجاد account انجام می‌دهید. نخستین راه‌اندازی فایل‌هایی ایجاد می‌کند و ممکن است bundleهایی را دریافت کند؛ بنابراین این کار را دستی انجام دهید تا بتوانید آن را monitor کنید.

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 بستگی دارد. اگر آن را اشتباه تنظیم کنید، اجرای نخست directoryهای cache را در home directory خودتان ایجاد می‌کند و مالک آن‌ها 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 مربوط به shell شما نیست. بنابراین استفاده از مسیر مطلق، حدس‌زدن را حذف می‌کند.

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

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

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

کدام Type= باید در اینجا قرار بگیرد؟

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

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

قوانین restart که خطا را آشکار اعلام می‌کنند

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

محدودیت نرخ بخشی است که معمولاً نادیده گرفته می‌شود. مقادیر پیش‌فرض systemd برابر با پنج بار start در مدت ده ثانیه است و با RestartSec=5s هرگز به پنج start در یک بازه ده‌ثانیه‌ای نمی‌رسید؛ بنابراین unitای که هنگام startup crash می‌کند، برای همیشه restart می‌شود و فقط journal از این موضوع اطلاع دارد. StartLimitIntervalSec=300 همراه با StartLimitBurst=5 یعنی پنج failure در پنج دقیقه کافی است: systemd تسلیم می‌شود و unit را در failed قرار می‌دهد و Start request repeated too quickly. را در log ثبت می‌کند. پس از رفع علت، این وضعیت را با sudo systemctl reset-failed dsh پاک کنید. هر دو تنظیم باید در [Unit] قرار بگیرند، نه در [Service]؛ systemd این تنظیمات را در section نادرست به‌صورت بی‌صدا نادیده می‌گیرد.

آن را اجرا کنید، سپس بررسی کنید

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: را نمایش دهد. سپس بررسی کنید سرویس روی کجا listening است:

sudo ss -lntp | grep 3080

باید 127.0.0.1:3080 را ببینید. اگر 0.0.0.0:3080 را مشاهده کردید، چیزی bind address را تغییر داده است و agent شما روی اینترنت عمومی در دسترس است. نام process در این خروجی node است، نه dsh؛ زیرا binary مربوط به 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 بر اساس priority فیلتر می‌کند. مقدار 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 tunnel، نه یک پورت عمومی

dsh رابط کاربری وب را روی 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

این محدودیتی نیست که بتوان آن را دور زد. web API رابط کاربری را کنترل می‌کند و agent فرمان‌های shell را اجرا می‌کند؛ بنابراین پورتی که از بیرون قابل دسترسی باشد، برای هر کسی که آن را پیدا کند مانند یک shell روی VPS شما است. maintainers، نبودن remote authentication را دلیل ثابت‌بودن bind روی loopback اعلام کرده‌اند. پیش از تلاش برای تغییر آن، مطالعهٔ معنای واقعی خط 127.0.0.1:3080 در خروجی راه‌اندازی مفید است. در عوض، پورت را از ماشین خودتان forward کنید:

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

-L 3080:127.0.0.1:3080 پورت 3080 را روی laptop شما باز می‌کند و هر چیزی را که به آن‌جا برسد، به 127.0.0.1:3080 ارسال می‌کند؛ این نام در VPS resolve می‌شود. -N یعنی هیچ فرمان remoteی اجرا نشود؛ بنابراین session فقط tunnel را باز نگه می‌دارد. آن را در حال اجرا بگذارید و http://127.0.0.1:3080/ را در browser باز کنید. در آن‌جا، در بخش Settings و سپس Models، DeepSeek API key را وارد می‌کنید و directory مربوط به workspace را انتخاب می‌کنید. workspace را روی /var/lib/dsh/workspace قرار دهید؛ این همان directoryای است که service user مالک آن است. در غیر این صورت، file tools مربوط به agent با EACCES: permission denied شکست می‌خورند.

اگر پورت 3080 روی laptop شما اشغال باشد، 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 یک پورت local متفاوت انتخاب کنید و سپس به 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 کل فرمان موردنیاز است. اکنون این tunnel تنها مسیر دسترسی به agent شما است؛ بنابراین SSH daemon از آن محافظت می‌کند: فقط key، بدون password authentication، و سایر موارد سخت‌سازی SSH روی VPS شما با اهمیت بیشتری از حالت معمول اعمال می‌شوند. اگر این VPS به یک شبکهٔ خصوصی کوچک تبدیل شود و database یا staging box در پشت آن قرار بگیرد، اعلام این آدرس‌ها به tailnet با subnet router نیاز به یک forward جداگانه برای هر service را از بین می‌برد؛ با این حال، به‌دلیل loopback bind در dsh، خود رابط کاربری همچنان از طریق tunnel در دسترس خواهد بود.

key نباید در unit file قرار بگیرد. مقادیر Environment= را systemctl show dsh -p Environment چاپ می‌کند و هر user روی همان box می‌تواند آن را اجرا کند. اگر plugin نصب‌شده به key در environment نیاز دارد، آن را در /etc/dsh.env قرار دهید؛ mode آن باید 600 باشد و مالکیت فایل نیز به root تعلق داشته باشد، سپس با EnvironmentFile=/etc/dsh.env به آن ارجاع دهید. systemd این فایل را هنگام exec و با دسترسی root می‌خواند و systemctl show محتوای آن را چاپ نمی‌کند. این‌که هر setting واقعاً در کدام فایل روی disk ذخیره می‌شود و وقتی dsh را به یک endpoint محلی Ollama به‌جای API متعلق به DeepSeek متصل می‌کنید چه داده‌ای از box شما خارج می‌شود، موضوع پیکربندی keyها، modelها و endpointهای dsh است.

هزینه اجرای سرویس

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

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

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

MemoryCurrent برحسب bytes است. زمانی که agent در حال کار است آن را monitor کنید، نه زمانی که idle است.

فراخوانی‌های ابزار، فرزندهای سرویس هستند. بنابراین در همان control group قرار می‌گیرند و از همان limitها استفاده می‌کنند. agentی که npm install یا یک test suite را داخل workspace اجرا می‌کند، ممکن است با اختلاف زیادی بیشتر از خود harness حافظه مصرف کند. در یک VPS با 1 GB، مشکل معمولاً از همین‌جا شروع می‌شود: kernel یک process را انتخاب و آن را kill می‌کند و journalctl -k | grep -i "out of memory" خط Out of memory: Killed process را نشان می‌دهد که نام process انتخاب‌شده در آن آمده است. این process اغلب همان processی نیست که مشکل را ایجاد کرده است.

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

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

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

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. واحد systemd به محدودیت نرخ شروع رسیده و متوقف شده است. خطای واقعی در خطوط بالاتر قرار دارد. پیش از تلاش دوباره، 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 را بخوانید، زیرا هشدار upstream درباره تغییرات ناسازگار با نسخه‌های قبلی، دلیل اصلی استفاده از pin است. از state directory نسخه پشتیبان تهیه کنید و سپس نسخه را جایگزین کنید:

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؛ البته این کار فقط زمانی ممکن است که از آن tarball نسخه پشتیبان گرفته باشید. runtime مربوط به agent در مرحله preview دقیقاً از آن دسته نرم‌افزارهایی است که ممکن است هنگام upgrade قالب یک فایل configuration را در پس‌زمینه تغییر دهد.

FAQ

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

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

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

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

چگونه web UI مربوط به dsh را از لپ‌تاپم باز کنم؟

پورت را از طریق SSH forward کنید: ssh -N -L 3080:127.0.0.1:3080 you@your-vps، سپس http://127.0.0.1:3080/ را در browser باز کنید. سعی نکنید سرویس را به یک public address 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 رد می‌کند، زیرا web API می‌تواند agent را وادار به اجرای shell command کند و هیچ remote authenticationای در برابر آن قرار ندارد.

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

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

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

همان نسخه‌ای که هنگام راه‌اندازی سرویس، npm view @deepseek-ai/dsh version گزارش می‌کند؛ آن را با npm install -g @deepseek-ai/dsh@<that version> نصب کنید و در مکانی ثبت کنید که بعداً بتوانید آن را پیدا کنید. 0.1.0-rc.7 در 18 August 2026 نسخه فعلی بود. مسئله خود عدد نیست؛ مسئله این است که npx بدون تعیین نسخه، package را هنگام start resolve می‌کند. بنابراین یک restart خودکار و بدون نظارت می‌تواند بی‌سروصدا شما را به buildای با قالب configuration متفاوت منتقل کند.