VPS-এ systemd ব্যবহার করে dsh headless হিসেবে চালানোর নিয়ম
VPS-এ dsh সার্ভিস হিসেবে চালাতে systemd unit file তৈরির নিয়ম জানুন। ডেডিকেটেড ইউজার, Restart রুলস এবং journalctl লগ ব্যবহার করে কীভাবে এটি স্থায়ীভাবে চালু রাখবেন তা দেখুন।
একটি VPS-এ টার্মিনাল ছাড়াই dsh headless হিসেবে চালানো
একটি VPS-এ dsh headless হিসেবে চালানোর জন্য একটি systemd unit file এবং এটি পরিচালনার জন্য একটি ডেডিকেটেড user প্রয়োজন। dsh হলো DeepSeek Harness-এর কমান্ড লাইন লঞ্চার, যা DeepSeek-এর এজেন্ট রানটাইম। এটি 2026 সালের আগস্ট মাসে ডেভেলপার প্রিভিউ হিসেবে MIT লাইসেন্সের অধীনে প্রকাশিত হয়েছে। হারনেস (harness) হলো মডেলের চারপাশের প্রোগ্রাম, মডেলটি নিজে নয়। তাই আপনি systemd-এর অধীনে যা রাখছেন তা হলো লুপ, টুলস এবং পারমিশন, DeepSeek-এর ইনফারেন্স নয়। কুইকস্টার্ট গাইড আপনাকে npx @deepseek-ai/dsh web টাইপ করতে বলে, যা সঠিক, কিন্তু আপনার SSH (secure shell) সেশন বন্ধ করার সাথে সাথেই এটি বন্ধ হয়ে যায়।
একটি unit file একই সাথে চারটি সমস্যার সমাধান করে। রিবুট করার পর সার্ভিসটি নিজে থেকেই চালু হয়। এর আউটপুট স্ক্রিনে ভেসে না গিয়ে journal-এ জমা হয়। এটি root নয় এমন একটি অ্যাকাউন্টে চলে। এবং এটি আপনার বেছে নেওয়া ভার্সনেই চলে, যা এখানে সাধারণের চেয়ে বেশি গুরুত্বপূর্ণ, কারণ আপস্ট্রিম থেকে বড় হাতের অক্ষরে বলা হয়েছে:
DeepSeek Harness বর্তমানে ডেভেলপার প্রিভিউ পর্যায়ে আছে এবং দ্রুত পরিবর্তিত হচ্ছে। এখানে কম্প্যাটিবিলিটি নষ্ট করতে পারে এমন পরিবর্তন আসবে।
এই গাইডটি ধরে নিচ্ছে যে dsh আপনার হাতে আগে থেকেই কাজ করছে। যদি তা না হয়, তবে প্রথমে একটি VPS-এ DeepSeek Harness ইনস্টল করা সম্পন্ন করুন এবং যখন npx @deepseek-ai/dsh web একটি পেজ সার্ভ করবে, তখন ফিরে আসুন।
প্রথমে Node ইনস্টল করুন, কারণ npm আপনাকে সতর্ক করবে না
node -vUbuntu 24.04-এর নিজস্ব প্যাকেজে Node 18 (আগস্ট 2026 অনুযায়ী 18.19.1) রয়েছে, যা এই বছর প্রকাশিত কোনো প্যাকেজের জন্য বেশ পুরনো। @deepseek-ai/dsh কোনো engines ফিল্ড প্রকাশ করে না, তাই আপনার Node-এর সংস্করণ খুব পুরনো হলেও npm কোনো EBADENGINE সতর্কতা দেখায় না। এর পরিবর্তে রান-টাইমে সিনট্যাক্স এরর বা বিল্ট-ইন ফাংশন মিসিং হওয়ার মতো সমস্যা দেখা দেয়, যা খুঁজে বের করা অনেক বেশি কষ্টসাধ্য। NodeSource থেকে বর্তমানের একটি long term support (LTS) রিলিজ ইনস্টল করুন:
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 -vnode -v এখন একটি v22 সংস্করণ প্রিন্ট করবে। less লাইনটি এখানে রাখা হয়েছে কারণ একটি রিমোট স্ক্রিপ্ট সরাসরি bash-এ পাইপ করলে এমন কোড রান হয় যা আপনি কখনোই পড়েননি।
ইউনিট ফাইল লেখার আগে নিশ্চিত করুন যে এটি চলছে
npx @deepseek-ai/dsh@0.1.0-rc.7 webসেটিকে চলমান রাখুন। দ্বিতীয় একটি SSH সেশন থেকে:
curl -fsS http://127.0.0.1:3080/ -o /dev/null && echo upup এর অর্থ হলো ওয়েব প্রোফাইলটি loopback-এ লিসেন করছে, যা এর ডিফল্ট বাইন্ডিং। curl: (7) Failed to connect to 127.0.0.1 port 3080: Connection refused এর অর্থ হলো এটি লিসেন করছে না এবং প্রথম টার্মিনালটি আপনাকে এর কারণ জানিয়ে দিচ্ছে। পরবর্তী ধাপে যাওয়ার আগে Ctrl+C চেপে ম্যানুয়াল রান বন্ধ করুন: কোনো ইউনিট যদি এমন কোনো পোর্ট বাইন্ড করার চেষ্টা করে যা অন্য কোনো প্রসেস দখল করে রেখেছে, তবে সেটি Error: listen EADDRINUSE: address already in use 127.0.0.1:3080 এর মাধ্যমে ব্যর্থ হবে।
0.1.0-rc.7 ছিল 18 আগস্ট 2026 তারিখে প্রকাশিত সংস্করণ। npm view @deepseek-ai/dsh version ব্যবহার করে বর্তমান সংস্করণটি যাচাই করুন এবং আপনি যা চালাতে চান তা পিন (pin) করে রাখুন।
আপনার পিন করা সংস্করণটি গ্লোবালি ইনস্টল করুন
unit file-এর ভেতরে npx ব্যবহার করা ভুল। এটি প্রসেস শুরু হওয়ার সময় প্যাকেজের সংস্করণ নির্ধারণ করে, তাই তিন মাস পরে রিস্টার্ট দিলে আপনার কোনো পরিবর্তন ছাড়াই প্রিভিউ-স্টেজ এজেন্টের একটি ভিন্ন বিল্ড চালু হতে পারে। এছাড়া বুট করার সময় npm registry-তে সংযোগ থাকা প্রয়োজন, যার ফলে registry ধীরগতির থাকলে একটি সচল মেশিনও ব্যর্থ ইউনিটে পরিণত হয়। আপনার লিখে রাখা নির্দিষ্ট সংস্করণে একবারই ইনস্টল করুন:
sudo npm install -g @deepseek-ai/dsh@0.1.0-rc.7
command -v dsh
npm ls -g --depth=0 @deepseek-ai/dshcommand -v dsh কমান্ডটি /usr/bin/dsh প্রিন্ট করে যদি npm NodeSource থেকে আসে, এবং /usr/local/bin/dsh প্রিন্ট করে যদি এটি Ubuntu-এর নিজস্ব প্যাকেজ থেকে আসে। unit file-এ যে পাথটি প্রিন্ট হয়েছে সেটি ব্যবহার করুন। npm ls -g সঠিক সংস্করণটি প্রিন্ট করে, যা ছয় সপ্তাহ পর আচরণ পরিবর্তিত হলে এবং আপনি কী ইনস্টল করেছিলেন তা মনে না থাকলে আপনার কাজে আসবে। যদি ইনস্টলেশন ব্যর্থ হয়, অথবা command -v dsh পরে কিছু প্রিন্ট না করে, কিংবা আপনি যে সংস্করণটি চেয়েছেন তা না পান, তবে unit file লেখার আগে সাধারণ dsh ইনস্টল এবং ভার্সন সংক্রান্ত ত্রুটিগুলো সমাধান করুন।
সার্ভিসের মালিকানা থাকা একটি ইউজার এবং অন্য কিছু নয়
এজেন্টটি শেল কমান্ড চালায়। এটাই এর কাজ। এটিকে root হিসেবে চালালে প্রতিটি টুল কল root-এর ক্ষমতায় চলে, তাই এটিকে একটি নিজস্ব অ্যাকাউন্ট দিন যার কোনো 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 প্রোফাইলগুলো রাখে। একটি প্রোফাইল হলো প্লাগইন বান্ডেলের একটি নামযুক্ত স্ট্যাক, যার উপরে আপনার নিজস্ব প্যাচ লেয়ার থাকে। web এবং headless প্রোফাইলগুলো প্রথমবার বুট করার সময় শিপড টেমপ্লেট থেকে নিজেদের তৈরি করে নেয়। পরবর্তীতে সেই স্ট্যাকে আপনি যা কিছু যোগ করবেন তা এই ইউজারের অধীনে চলবে, যার এজেন্টের নিজস্ব ফাইল এবং শেল অ্যাক্সেস থাকবে। তাই ইনস্টল করার আগে একটি প্লাগইন যাচাই করা অ্যাকাউন্ট তৈরির মতোই একই কাজের অংশ। প্রথম বুট ফাইল লেখে এবং বান্ডেল ফেচ করতে পারে, তাই এটি নিজে হাতে করুন যেখানে আপনি তা পর্যবেক্ষণ করতে পারবেন।
sudo -u dsh env HOME=/var/lib/dsh DSH_HOME=/var/lib/dsh/harness /usr/bin/dsh --profile websudo কী করে তার ওপর ভরসা না করে বরং HOME স্পষ্টভাবে সেট করুন। কারণ নন-লগিন কমান্ডের জন্য sudo কি HOME রিরাইট করবে কি না, তা /etc/sudoers-এর set_home সেটিংয়ের ওপর নির্ভর করে। ভুল করলে প্রথমবার চালানোর সময় ক্যাশ ডিরেক্টরিগুলো আপনার হোম ডিরেক্টরিতে তৈরি হবে যার মালিক হবে dsh, এবং পরবর্তীতে সার্ভিসটি তার নিজস্ব স্টেট খুঁজে পাবে না। curl চেক up রিটার্ন করলে Ctrl+C চেপে এটি থামিয়ে দিন।
ইউনিট ফাইল
/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.targetExecStart=-এ command -v dsh থেকে পাওয়া অ্যাবসোলিউট পাথ ব্যবহার করুন। systemd একটি নির্দিষ্ট পাথ লিস্টে কমান্ডের নাম খোঁজে, কিন্তু সেই লিস্ট আপনার শেলের PATH নয়, তাই অ্যাবসোলিউট পাথ ব্যবহার করলে কোনো অনিশ্চয়তা থাকে না।
WorkingDirectory= হলো সেই জায়গা যেখানে রিলেটিভ পাথগুলো রেজলভ হয় এবং কোনো টুল যখন কোনো আর্গুমেন্ট ছাড়া ls চালায়, তখন সেটি এখান থেকেই শুরু হয়। এটিকে সেই ওয়ার্কস্পেসের দিকে নির্দেশ করুন যা আপনি এজেন্টকে দিচ্ছেন। যদি ডিরেক্টরিটির অস্তিত্ব না থাকে বা সার্ভিস ইউজার সেখানে প্রবেশ করতে না পারে, তবে dsh চলার আগেই ইউনিটটি status=200/CHDIR এর কারণে ব্যর্থ হবে।
ProtectHome=true প্রসেস থেকে /home এবং /root কে লুকিয়ে রাখে। এটি এখানে নিরাপদ কারণ সার্ভিসটি যা কিছু ব্যবহার করে তার সবই /var/lib/dsh এর অধীনে থাকে। ওয়ার্কস্পেসকে যদি /home এর অধীনে কোনো পাথে নির্দেশ করেন, তবে এজেন্ট রিপোর্ট করবে যে ডিরেক্টরিটি নেই; এই লাইনটি মনে না থাকলে বিষয়টি বিভ্রান্তিকর হতে পারে। ProtectSystem=full এর মাধ্যমে /usr, /boot এবং /etc কে রিড-অনলি করা হয়, কারণ সার্ভিসটির এগুলোতে লেখার কোনো প্রয়োজন নেই।
এর চেয়ে বেশি কিছু করার প্রলোভন হওয়া স্বাভাবিক, কিন্তু সাধারণত তা ভুল হয়। ProtectSystem=strict কার্নেল সিউডো-ফাইলসিস্টেম বাদে পুরো ফাইলসিস্টেমকে রিড-অনলি করে দেয়, ফলে প্রথম যে টুলটি কোনো ফাইল লিখতে যাবে সেটি EROFS: read-only file system এর কারণে ব্যর্থ হবে। যদি আপনি সেই পর্যায়ের নিরাপত্তা চান, তবে একই এডিটে ReadWritePaths=/var/lib/dsh যোগ করুন।
এখানে কোন Type= ব্যবহার করা উচিত
Type=exec, কারণ dsh ফোরগ্রাউন্ডে থাকে এবং কখনোই ফর্ক (fork) করে না। ডিফল্ট অপশনের তুলনায় এটি ব্যবহারের সুবিধা হলো, আপনি সরাসরি সঠিক এরর মেসেজ দেখতে পাবেন। Type=simple ব্যবহার করলে, বাইনারি ফাইলটি আদৌ আছে কি না তা জানার আগেই systemd ফর্ক হওয়ার সাথে সাথে স্টার্ট সফল হয়েছে বলে ধরে নেয়। ফলে systemctl start dsh কোনো সমস্যা ছাড়াই রিটার্ন করে এবং ব্যর্থতার বিষয়টি শুধুমাত্র জার্নালে দেখা যায়। Type=exec ব্যবহার করলে, systemd execve() সফল হওয়ার জন্য অপেক্ষা করে। তাই ExecStart=-এ কোনো টাইপো থাকলে আপনি যে কমান্ডটি টাইপ করেছেন সেটিই ব্যর্থ হবে এবং আপনি তা সরাসরি দেখতে পাবেন।
অন্য দুটি ভুল পদ্ধতিই সিস্টেমকে হ্যাং করে দেয়। 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 কোনো নন-জিরো এক্সিট বা ফ্যাটাল সিগন্যালের ক্ষেত্রে রিস্টার্ট করে এবং ক্লিন এক্সিটের পর ইউনিটটিকে আর পরিবর্তন করে না। প্রিভিউ বিল্ডের ক্ষেত্রে আপনি এই আচরণই আশা করবেন। যদি dsh কোনো কনফিগারেশন পছন্দ না হওয়ার কারণে 0 এক্সিট কোড দেয়, তবে ইউনিটটি থেমে যায় এবং বন্ধই থাকে, আর systemctl status dsh তখন inactive (dead) দেখায় যেখানে আপনি বিষয়টি দেখতে পাবেন। Restart=always একই ঘটনাকে একটি রিস্টার্ট লুপে পরিণত করে, যা দূর থেকে দেখলে স্বাভাবিক মনে হয়।
রেট লিমিট হলো সেই অংশ যা মানুষ এড়িয়ে যায়। systemd-এর ডিফল্ট হলো দশ সেকেন্ডের মধ্যে পাঁচটি স্টার্ট, আর RestartSec=5s-এর সাথে আপনি দশ সেকেন্ডের উইন্ডোতে কখনোই পাঁচটি স্টার্টে পৌঁছাতে পারবেন না। ফলে স্টার্টআপে ক্র্যাশ করা একটি ইউনিট চিরকাল রিস্টার্ট হতে থাকে এবং শুধুমাত্র জার্নালেই তা ধরা পড়ে। StartLimitBurst=5 সহ StartLimitIntervalSec=300 ব্যবহারের অর্থ হলো পাঁচ মিনিটে পাঁচটি ব্যর্থতাই যথেষ্ট: systemd হাল ছেড়ে দেয় এবং ইউনিটটিকে 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 dshenable --now দুটি কাজ করে। enable রিবুটের পরে সার্ভিসটিকে পুনরায় চালু করে এবং --now বর্তমান বুটে এটিকে শুরু করে। শুধুমাত্র systemctl start ব্যবহার করলে পরবর্তী রিবুটের পর সেটি আর কার্যকর থাকে না, আর কার্নেল আপডেটের কারণে রিবুট করা প্রয়োজন হয়।
systemctl status dsh কমান্ডটি চালালে Active: active (running), একটি Main PID এবং একটি Memory: লাইন দেখা উচিত। এরপর সার্ভিসটি কোন পোর্টে লিসেন করছে তা নিশ্চিত করুন:
sudo ss -lntp | grep 3080আপনার কাঙ্ক্ষিত আউটপুট হলো 127.0.0.1:3080। যদি আপনি 0.0.0.0:3080 দেখতে পান, তবে বুঝতে হবে বাইন্ড অ্যাড্রেস পরিবর্তন করা হয়েছে এবং আপনার এজেন্ট পাবলিক ইন্টারনেটে উন্মুক্ত রয়েছে। এই আউটপুটে প্রসেসের নাম node, dsh নয়, কারণ dsh বাইনারিটি একটি Node স্ক্রিপ্ট, তাই pgrep -x dsh কোনো ফলাফল খুঁজে পায় না। এর পরিবর্তে systemctl show -p MainPID dsh ব্যবহার করুন।
এরপর একবার রিবুট করুন। যে সার্ভিস রিবুটের পরেও টিকে থাকতে পারে না, সেটি এখনো পূর্ণাঙ্গ সার্ভিস হিসেবে গণ্য হয় না।
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 প্রায়োরিটি অনুযায়ী ফিল্টার করে। unit-এর মধ্যে SyslogIdentifier=dsh থাকার কারণেই লাইনগুলো dsh হিসেবে ট্যাগ করা হয়, node হিসেবে নয়। যখন আপনি unit দিয়ে ফিল্টার করা হয়নি এমন journal আউটপুট প্রথমবার পড়বেন, তখন এটি গুরুত্বপূর্ণ।
আপনার প্রয়োজনে 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 রিস্টার্ট করুন:
sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journaldপাবলিক পোর্টের পরিবর্তে SSH টানেলের মাধ্যমে UI-তে প্রবেশ করুন
dsh তার ওয়েব UI (user interface) শুধুমাত্র 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 (application programming interface) এজেন্টকে নিয়ন্ত্রণ করে এবং এজেন্ট শেল কমান্ড চালায়। তাই, যে কেউ এই পোর্টটি খুঁজে পেলে সে আপনার VPS-এ একটি শেল অ্যাক্সেস পেয়ে যাবে। রক্ষণাবেক্ষণকারীরা জানিয়েছেন যে, রিমোট অথেন্টিকেশন তৈরি করা হয়নি বলেই বাইন্ডটি শুধুমাত্র লুপব্যাকে সীমাবদ্ধ রাখা হয়েছে। এটি পরিবর্তন করার আগে স্টার্টআপ আউটপুটে 127.0.0.1:3080 লাইনটির প্রকৃত অর্থ কী তা পড়ে নেওয়া জরুরি। এর পরিবর্তে আপনার নিজের মেশিন থেকে পোর্টটি ফরওয়ার্ড করুন:
ssh -N -L 3080:127.0.0.1:3080 you@203.0.113.10-L 3080:127.0.0.1:3080 আপনার ল্যাপটপে 3080 পোর্টটি খোলে এবং সেখানে আসা যেকোনো ট্রাফিককে VPS-এ সমাধানকৃত 127.0.0.1:3080-এ পাঠিয়ে দেয়। -N মানে হলো কোনো রিমোট কমান্ড চালানো হবে না, তাই সেশনটি শুধুমাত্র টানেলটি চালু রাখার কাজ করবে। এটি চালু রাখুন এবং আপনার ব্রাউজারে http://127.0.0.1:3080/ খুলুন। এখানেই আপনি Settings-এর অধীনে Models-এ গিয়ে DeepSeek API key প্রবেশ করাবেন এবং ওয়ার্কস্পেস ডিরেক্টরি নির্বাচন করবেন। ওয়ার্কস্পেসটিকে /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: 3080ssh -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 হবে সম্পূর্ণ কমান্ড। এই টানেলটি এখন আপনার এজেন্টের একমাত্র প্রবেশপথ, তাই SSH daemon-ই একে সুরক্ষা দিচ্ছে: শুধুমাত্র কি (key) ব্যবহার করুন, পাসওয়ার্ড অথেন্টিকেশন বন্ধ রাখুন এবং আপনার VPS-এ SSH হার্ডেনিং-এর বাকি নিয়মগুলো স্বাভাবিকের চেয়েও বেশি গুরুত্বের সাথে প্রয়োগ করুন। যদি সেই VPS-টি একটি ছোট প্রাইভেট নেটওয়ার্কে পরিণত হয়, যার পেছনে কোনো ডাটাবেস বা স্টেজিং বক্স থাকে, তবে সাবনেট রাউটারের মাধ্যমে সেই ঠিকানাগুলো আপনার tailnet-এ বিজ্ঞাপন দেওয়া প্রতিটি সার্ভিসের জন্য আলাদা ফরওয়ার্ডিংয়ের ঝামেলা কমাবে, যদিও dsh-এর লুপব্যাক বাইন্ডের কারণে UI-টি এখনও টানেলের মাধ্যমেই আসবে।
API কি (key) ইউনিট ফাইলে রাখা উচিত নয়। Environment= মানগুলো systemctl show dsh -p Environment দ্বারা প্রিন্ট করা হয়, যা বক্সের যেকোনো ব্যবহারকারী চালাতে পারে। যদি আপনার ইনস্টল করা কোনো প্লাগইনের এনভায়রনমেন্টে কি (key)-এর প্রয়োজন হয়, তবে সেটিকে root-এর মালিকানাধীন 600 মোডে /etc/dsh.env ফাইলে রাখুন এবং EnvironmentFile=/etc/dsh.env দিয়ে রেফারেন্স করুন। systemd এক্সিকিউশন সময়ে root হিসেবে সেই ফাইলটি পড়ে এবং systemctl show এর বিষয়বস্তু প্রিন্ট করে না। ডিস্কের কোন ফাইলে প্রতিটি সেটিং জমা হয় এবং DeepSeek API-এর পরিবর্তে dsh-কে লোকাল Ollama এন্ডপয়েন্টে নির্দেশ করলে আপনার বক্স থেকে কী তথ্য বাইরে যায়, তা dsh-এর কি, মডেল এবং এন্ডপয়েন্ট কনফিগার করা বিষয়ে বিস্তারিত আলোচনা করা হয়েছে।
পরিচালনা ব্যয়
ইনফারেন্স (Inference) আপনার VPS-এ নয়, বরং DeepSeek-এর API-তে সম্পন্ন হয়। আপনার সার্ভারে Node প্রসেস, এর পরিবেশিত UI এবং এজেন্ট যে কমান্ডগুলো চালানোর সিদ্ধান্ত নেয়, তার খরচ বহন করতে হয়। প্রথম দুটি খরচ স্থিতিশীল এবং সামান্য। তৃতীয়টির ক্ষেত্রে এই unit file-এ কোনো সীমাবদ্ধতা নেই।
অন্য কারো দেওয়া তথ্যের ওপর নির্ভর না করে আপনার নিজের সার্ভারে ন্যূনতম খরচ পরিমাপ করুন:
systemctl show dsh -p MemoryCurrent
systemd-cgtop -1 --depth 2MemoryCurrent বাইট এককে থাকে। এজেন্ট যখন কাজ করছে তখন এটি পর্যবেক্ষণ করুন, অলস অবস্থায় নয়।
টুল কলগুলো (Tool calls) মূল সার্ভিসের চাইল্ড প্রসেস, তাই এগুলো একই কন্ট্রোল গ্রুপে থাকে এবং একই সীমার মধ্যে গণ্য হয়। যে এজেন্ট ওয়ার্কস্পেসের ভেতরে npm install বা কোনো টেস্ট স্যুট চালায়, তা মূল হার্নেসের চেয়ে অনেক বেশি মেমোরি ব্যবহার করতে পারে। 1 GB RAM-এর VPS-এ এখানেই সমস্যা হয়: কার্নেল একটি প্রসেস বেছে নিয়ে সেটিকে বন্ধ (kill) করে দেয় এবং journalctl -k | grep -i "out of memory" কমান্ডটি Out of memory: Killed process লাইনটি দেখায়, যেখানে কার্নেল যে প্রসেসটিকে বেছে নিয়েছে তার নাম থাকে। অনেক সময় সেই প্রসেসটি সমস্যার মূল কারণ হয় না।
এর সমাধান হলো আপনার নিজের নির্ধারণ করা একটি সীমা। [Service] সেকশনে MemoryMax= এবং CPUQuota= ব্যবহার করলে ক্ষতির পরিমাণ ইউনিটটির ভেতরেই সীমাবদ্ধ থাকে, ফলে পুরো সার্ভার হ্যাং হওয়ার বদলে শুধুমাত্র অনিয়ন্ত্রিত বিল্ড প্রসেসটি বন্ধ হয়ে যায়। systemd দিয়ে মেমোরি ও CPU সীমাবদ্ধ করা অংশে সংখ্যা এবং ব্যর্থতার আচরণ সম্পর্কে বিস্তারিত আলোচনা করা হয়েছে। DSH_HOME-এর অধীনে সেশন হিস্ট্রি এবং এজেন্ট ওয়ার্কস্পেসে যা কিছু লেখে তার কারণে ডিস্কের ব্যবহারও বাড়তে থাকে, তাই ডিস্ক পর্যবেক্ষণের জন্য আপনি যা ব্যবহার করেন তাতে du -sh /var/lib/dsh যুক্ত করুন।
আপনি যদি এমন একটি ইন্টারঅ্যাক্টিভ এজেন্ট চান যাতে আপনি যুক্ত হতে এবং বিচ্ছিন্ন হতে পারবেন, তবে সার্ভিস হিসেবে চালানো সঠিক পদ্ধতি নয়; সেক্ষেত্রে একটি পারসিস্টেন্ট tmux সেশনে এজেন্ট চালানো বেশি কার্যকর। যখন আপনি চান যে এজেন্টটি সবসময় চালু থাকুক এবং টানেলের মাধ্যমে অ্যাক্সেসযোগ্য হোক, তখনই কেবল এটিকে সার্ভিস হিসেবে চালান।
ব্যর্থতার ধরন এবং যে বার্তাগুলো আপনি দেখবেন
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-এর অধীনে মালিকানা (ownership) ভুল আছে, সাধারণত প্রথমবার root হিসেবে বা ভুল HOME দিয়ে চালানোর কারণে এমনটি হয়। sudo chown -R dsh:dsh /var/lib/dsh এটি ঠিক করে দেয়।
Start request repeated too quickly. ইউনিটটি স্টার্ট রেট লিমিটে পৌঁছেছে এবং চেষ্টা করা বন্ধ করে দিয়েছে। আসল ত্রুটিটি এর উপরের লাইনগুলোতে থাকে। পুনরায় চেষ্টা করার আগে sudo systemctl reset-failed dsh চালান।
ইউনিটটি active (running) অবস্থায় আছে কিন্তু ব্রাউজারে কিছু দেখা যাচ্ছে না। VPS-এ চেকটি চালান: যদি curl -fsS http://127.0.0.1:3080/ -o /dev/null && echo up সেখানে up প্রিন্ট করে, তবে সার্ভিসটি সুস্থ আছে এবং সমস্যাটি পোর্ট ফরোয়ার্ডে রয়েছে।
ইচ্ছাকৃতভাবে আপগ্রেড করা
Pinning-এর অর্থ হলো আপগ্রেড এমন একটি কাজ যা আপনি নিজে করেন, এটি কোনো স্বয়ংক্রিয় ঘটনা নয়। আপগ্রেড করার আগে রিলিজ নোটগুলো পড়ুন, কারণ আপস্ট্রিম থেকে সামঞ্জস্যতা নষ্টকারী পরিবর্তনগুলো (compatibility-breaking changes) সম্পর্কে যে সতর্কতা দেওয়া থাকে, তা মূলত এই Pinning-এর কারণেই দেওয়া হয়। প্রথমে 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রোলব্যাক করার প্রক্রিয়াটি পুরনো ভার্সনের সাথে npm install -g একই, তবে এর সাথে সেই tarball ফাইলটি রিস্টোর করতে হয়, যা কেবল তখনই সম্ভব যদি আপনি আগে ব্যাকআপ নিয়ে থাকেন। একটি প্রিভিউ-স্টেজ এজেন্ট রানটাইম এমন একটি সফটওয়্যার যেখানে আপগ্রেডের ফলে আপনার অজান্তেই কনফিগারেশন ফরম্যাট পরিবর্তিত হয়ে যেতে পারে।
FAQ
SSH সেশন বন্ধ করলে dsh কেন বন্ধ হয়ে যায়?
কারণ npx @deepseek-ai/dsh web আপনার লগইন সেশনের অধীনে একটি foreground প্রসেস হিসেবে চলে, তাই সেশন শেষ হলে এটি বন্ধ হয়ে যায়। অন্যদিকে, একটি systemd unit init সিস্টেমের অধীনে থাকে, যার ফলে সংযোগ বিচ্ছিন্ন করার পরেও এটি চলতে থাকে এবং রিবুট হওয়ার পর পুনরায় চালু হয়। sudo systemctl enable --now dsh হলো সেই ধাপ যা আপনাকে উভয় সুবিধা দেয়: রিবুটের জন্য enable এবং বর্তমান সেশনের জন্য --now।
dsh-এর জন্য কি Type=simple নাকি Type=exec ব্যবহার করা উচিত?
Type=exec। dsh foreground-এ চলে এবং কখনোই fork করে না, তাই উভয়ই কাজ করে। তবে Type=exec ব্যবহার করলে systemd নিশ্চিত করে যে execve() সফল হয়েছে, তারপরই এটি স্টার্ট সফল হয়েছে বলে গণ্য করে। ExecStart=-এ ভুল পাথ থাকলে systemctl start ব্যর্থ হয় এবং আপনার সামনে status=203/EXEC দেখায়। Type=simple ব্যবহার করলে একই ভুল সফল হিসেবে গণ্য হয় এবং তা শুধু journal-এ লুকিয়ে থাকে। Type=forking এবং Type=notify উভয়ই এখানে ভুল এবং উভয়ই 90 সেকেন্ড পর TimeoutStartSec শেষ না হওয়া পর্যন্ত হ্যাং হয়ে থাকে।
আমার ল্যাপটপ থেকে কীভাবে dsh ওয়েব UI খুলব?
SSH-এর মাধ্যমে পোর্টটি ফরওয়ার্ড করুন: ssh -N -L 3080:127.0.0.1:3080 you@your-vps, তারপর আপনার ব্রাউজারে http://127.0.0.1:3080/ খুলুন। সার্ভিসটিকে পাবলিক অ্যাড্রেসে বাইন্ড করার চেষ্টা করবেন না। 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 এজেন্টকে শেল কমান্ড চালানোর সুযোগ দেয় এবং এর সামনে কোনো রিমোট অথেন্টিকেশন নেই।
পারমিশন সহজ রাখতে আমি কি root হিসেবে dsh চালাতে পারি?
না। এই হারনেসটি কমান্ড চালানো এবং ফাইল লেখার জন্য তৈরি, তাই সার্ভিসের যে প্রিভিলেজ থাকবে, এজেন্টেরও সেই প্রিভিলেজ থাকবে। useradd --system --shell /usr/sbin/nologin dsh দিয়ে একটি সিস্টেম অ্যাকাউন্ট তৈরি করুন, /var/lib/dsh-এর মালিকানা সেই অ্যাকাউন্টের অধীনে দিন এবং unit-এ NoNewPrivileges=true যোগ করুন। যদি এরপরও EACCES: permission denied পান, তবে সাধারণত এর কারণ হলো আগে root হিসেবে চালানোর ফলে কিছু ফাইল root-এর মালিকানাধীন রয়ে গেছে; sudo chown -R dsh:dsh /var/lib/dsh দিয়ে সেগুলো পরিষ্কার করুন।
unit-এ dsh-এর কোন ভার্সনটি পিন করা উচিত?
সার্ভিস সেটআপ করার সময় npm view @deepseek-ai/dsh version যা রিপোর্ট করে, সেটিই ব্যবহার করুন। এটি npm install -g @deepseek-ai/dsh@<that version> দিয়ে ইনস্টল করুন এবং এমন কোথাও লিখে রাখুন যেখানে আপনি সহজেই খুঁজে পাবেন। 18 আগস্ট 2026 তারিখে 0.1.0-rc.7 কারেন্ট ভার্সন ছিল। মূল বিষয় ভার্সন নম্বর নয়, বরং এটি যে npx-এ কোনো ভার্সন উল্লেখ না থাকলে এটি স্টার্ট হওয়ার সময় প্যাকেজটি রিজলভ করে, ফলে unattended রিস্টার্টের সময় এটি নীরবে এমন একটি বিল্ডে চলে যেতে পারে যার কনফিগারেশন ফরম্যাট ভিন্ন।