SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-27

Chạy dsh headless trên VPS bằng systemd

Cấu hình dsh chạy headless trên VPS với user riêng, version cố định, Restart, log qua journalctl và SSH tunnel để truy cập UI sau khi đóng phiên SSH.

Chạy dsh ở chế độ không có terminal trên VPS

Chạy dsh ở chế độ không có terminal trên VPS chỉ cần một file unit của systemd và một user riêng để sở hữu service này. dsh là CLI launcher cho DeepSeek Harness, runtime agent của DeepSeek, được phát hành theo giấy phép MIT ở bản developer preview vào tháng 8 năm 2026. Harness là chương trình chạy xung quanh model, không phải bản thân model. Vì vậy, thứ bạn đưa vào systemd là vòng lặp, các tool và quyền truy cập, không phải phần inference của DeepSeek. Quickstart yêu cầu bạn nhập npx @deepseek-ai/dsh web. Cách này đúng, nhưng tiến trình cũng dừng ngay khi bạn đóng phiên SSH (secure shell).

Một file unit giải quyết đồng thời 4 vấn đề. Service sẽ khởi động lại sau khi reboot. Output được ghi vào journal thay vì chạy hết trên terminal. Service chạy bằng một account không phải root. Phiên bản được chạy là phiên bản bạn đã chọn. Điều này đặc biệt quan trọng trong trường hợp này vì upstream cảnh báo bằng chữ in hoa:

DeepSeek Harness hiện đang ở giai đoạn developer preview và được phát triển rất nhanh. SẼ CÓ CÁC THAY ĐỔI LÀM MẤT TƯƠNG THÍCH.

Hướng dẫn này giả định dsh đã chạy được khi bạn khởi động thủ công. Nếu chưa, hãy bắt đầu với cài đặt DeepSeek Harness trên VPS, rồi quay lại sau khi npx @deepseek-ai/dsh web phục vụ được một trang.

Cài Node trước, vì npm sẽ không cảnh báo bạn

node -v

Gói Node của Ubuntu 24.04 là Node 18 (18.19.1 tính đến tháng 8 năm 2026). Phiên bản này đã cũ đối với một package được phát hành trong năm nay. @deepseek-ai/dsh không có trường engines, nên npm không in cảnh báo EBADENGINE khi Node của bạn quá cũ. Thay vào đó, lỗi xuất hiện lúc chạy, dưới dạng lỗi cú pháp hoặc built-in bị thiếu. Đây là thời điểm tệ hơn nhiều để phát hiện lỗi. Hãy cài một bản phát hành long term support (LTS) hiện tại từ 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 lúc này sẽ in phiên bản v22. Dòng less có mặt vì việc pipe trực tiếp một script từ xa vào bash sẽ chạy đoạn code mà bạn chưa đọc.

Xác nhận dịch vụ chạy được trước khi viết unit

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

Để tiến trình đó chạy. Mở một phiên SSH thứ hai:

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

up nghĩa là web profile đang lắng nghe trên loopback, đây là nơi nó bind mặc định. curl: (7) Failed to connect to 127.0.0.1 port 3080: Connection refused nghĩa là không phải như vậy, và terminal thứ nhất đang cho biết lý do. Dừng tiến trình chạy thủ công bằng Ctrl+C trước khi tiếp tục: unit cố bind vào một cổng đã bị tiến trình khác chiếm sẽ fail với Error: listen EADDRINUSE: address already in use 127.0.0.1:3080.

0.1.0-rc.7 là phiên bản được phát hành vào ngày 18 August 2026. Kiểm tra phiên bản hiện tại bằng npm view @deepseek-ai/dsh version, sau đó pin phiên bản bạn quyết định chạy.

Cài đặt phiên bản đã ghim trên toàn hệ thống

npx không phải công cụ phù hợp trong unit file. Nó phân giải phiên bản package khi process khởi động. Vì vậy, lần restart sau ba tháng có thể chạy một build khác của agent đang ở giai đoạn preview mà phía bạn không có thay đổi nào. Công cụ này cũng cần truy cập được npm registry trong lúc boot. Nếu registry chậm, một máy đang hoạt động bình thường có thể khiến unit fail. Hãy cài đặt một lần, với phiên bản bạn đã ghi lại:

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 in /usr/bin/dsh khi npm được cài từ NodeSource và in /usr/local/bin/dsh khi npm được cài từ package riêng của Ubuntu. Dùng đúng path mà lệnh đã in trong unit file. npm ls -g in chính xác phiên bản hiện tại. Đây là thông tin bạn cần sau sáu tuần, khi hành vi thay đổi và bạn không còn nhớ đã cài phiên bản nào. Nếu quá trình cài đặt fail, command -v dsh không in gì sau đó, hoặc phiên bản trả về không phải phiên bản bạn yêu cầu, hãy xử lý các lỗi cài đặt và kiểm tra phiên bản dsh thường gặp trước khi viết unit file.

Một user chỉ sở hữu service và không có quyền nào khác

Agent chạy các shell command. Đó là nhiệm vụ của nó. Chạy agent bằng root khiến mọi tool call đều chạy với quyền root, vì vậy hãy tạo một account riêng cho agent và tắt login shell của account đó.

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 trở thành DSH_HOME, là thư mục nơi dsh lưu các profile. Một profile là stack có tên gồm các plugin bundle, bên trên có patch layer của riêng bạn. Các profile webheadless tự tạo từ các template được phát hành trong lần đầu bạn khởi động chúng. Mọi thứ bạn thêm vào stack sau đó sẽ chạy bằng user này với quyền truy cập file và shell riêng của agent. Vì vậy, kiểm tra plugin trước khi cài cũng thuộc cùng quy trình với việc tạo account. Lần khởi động đầu tiên sẽ ghi file và có thể tải bundle, nên hãy thực hiện thủ công ở nơi bạn có thể theo dõi.

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

Đặt HOME một cách rõ ràng thay vì tin vào cách sudo xử lý biến này, vì việc sudo có ghi lại HOME cho một command không đăng nhập hay không phụ thuộc vào thiết lập set_home trong /etc/sudoers. Nếu đặt sai, lần chạy đầu tiên sẽ tạo các thư mục cache trong home directory của bạn với owner là dsh, rồi service không thể tìm thấy state của chính nó. Dừng bằng Ctrl+C ngay khi kiểm tra curl trả về up.

Tệp unit

Viết /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= nhận đường dẫn tuyệt đối lấy được từ command -v dsh. systemd sẽ tìm tên lệnh không kèm đường dẫn trong một danh sách đường dẫn cố định. Danh sách đó không phải PATH của shell. Vì vậy, dùng đường dẫn tuyệt đối sẽ loại bỏ việc phỏng đoán.

WorkingDirectory= là nơi các đường dẫn tương đối được phân giải. Đây cũng là nơi một lần gọi tool chạy ls không có đối số sẽ bắt đầu. Trỏ nó đến workspace bạn cấp cho agent. Nếu thư mục không tồn tại hoặc service user không thể truy cập thư mục đó, unit sẽ fail với status=200/CHDIR trước khi dsh chạy.

ProtectHome=true ẩn /home/root khỏi process. Cách này an toàn ở đây vì mọi thứ service truy cập đều nằm dưới /var/lib/dsh. Hãy trỏ workspace đến một đường dẫn bên dưới /home. Khi đó agent sẽ báo thư mục không tồn tại. Điều này dễ gây nhầm lẫn nếu bạn quên dòng này. ProtectSystem=full đặt /usr, /boot/etc ở chế độ chỉ đọc. Service không cần ghi vào các thư mục này.

Tiếp tục hạn chế thêm thường rất hấp dẫn nhưng thường là sai. ProtectSystem=strict làm toàn bộ filesystem ở chế độ chỉ đọc, ngoại trừ các pseudo-filesystem của kernel. Vì vậy, lần gọi tool đầu tiên ghi file sẽ fail với EROFS: read-only file system. Nếu muốn mức hạn chế đó, hãy thêm ReadWritePaths=/var/lib/dsh trong cùng lần chỉnh sửa.

Type= nào phù hợp ở đây

Type=exec, vì dsh chạy ở foreground và không bao giờ fork. So với giá trị mặc định, cách này cho bạn một thông báo lỗi thực sự. Với Type=simple, systemd coi thao tác start là thành công ngay sau khi đã fork, trước khi biết binary có tồn tại hay không. Vì vậy, systemctl start dsh trả về bình thường và lỗi chỉ xuất hiện trong journal. Với Type=exec, systemd chờ execve() thành công. Do đó, lỗi gõ sai trong ExecStart= sẽ làm lệnh bạn vừa nhập thất bại và bạn thấy lỗi ngay tại đó.

Hai đáp án sai đều bị treo. Type=forking yêu cầu systemd chờ một process cha thoát, nhưng dsh không bao giờ thoát. Vì vậy, thao tác start bị chặn cho đến khi TimeoutStartSec hết thời gian chờ (mặc định là 90 giây), rồi báo Job for dsh.service failed because a timeout was exceeded. Type=notify chờ một thông báo READY=1 qua sd_notify. Một process Node không bao giờ gửi thông báo này cũng bị treo tương tự. So sánh đầy đủ các loại service của systemd trình bày các trường hợp còn lại, bao gồm khi nào việc cấu hình notify là đáng công.

Các rule restart phải báo lỗi rõ ràng

Restart=on-failure sẽ restart khi tiến trình thoát với mã khác 0 hoặc nhận signal fatal, nhưng giữ nguyên unit sau khi tiến trình thoát bình thường. Đây là behavior phù hợp cho một bản preview. Nếu dsh thoát với mã 0 vì đã đọc một config mà nó không chấp nhận, unit sẽ dừng và tiếp tục ở trạng thái stopped. systemctl status dsh hiển thị inactive (dead) để bạn kiểm tra sự việc này. Restart=always biến cùng sự việc đó thành một vòng lặp restart, khiến unit trông như vẫn hoạt động nếu chỉ quan sát từ xa.

Rate limit là phần thường bị bỏ qua. Mặc định của systemd là cho phép năm lần start trong mười giây. Với RestartSec=5s, unit không bao giờ đạt năm lần start trong cửa sổ mười giây. Vì vậy, một unit crash khi startup sẽ restart vô hạn và chỉ journal mới ghi nhận được điều đó. StartLimitIntervalSec=300 cùng với StartLimitBurst=5 có nghĩa là năm lần fail trong năm phút là đủ để systemd dừng thử. systemd sẽ bỏ cuộc và đưa unit vào trạng thái failed, đồng thời ghi log Start request repeated too quickly.. Sau khi sửa nguyên nhân, dùng sudo systemctl reset-failed dsh để xóa trạng thái đó. Cả hai setting phải nằm trong [Unit], không phải [Service]. systemd sẽ âm thầm bỏ qua chúng nếu đặt trong section sai.

Khởi động rồi kiểm tra

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

enable --now thực hiện 2 việc. enable là thiết lập để service khởi động lại sau reboot, còn --now khởi động service trong lần boot hiện tại. Chạy riêng systemctl start sẽ mất hiệu lực sau lần reboot tiếp theo, mà các bản cập nhật kernel thường yêu cầu reboot.

systemctl status dsh phải hiển thị Active: active (running), một dòng Main PID và một dòng Memory:. Sau đó xác nhận service đang listening ở đâu:

sudo ss -lntp | grep 3080

Bạn cần thấy 127.0.0.1:3080. Nếu thấy 0.0.0.0:3080, có thứ gì đó đã sửa bind address và agent của bạn đang public trên Internet. Tên process trong output đó là node, không phải dsh, vì binary dsh là một Node script nên pgrep -x dsh không tìm thấy gì. Thay vào đó, dùng systemctl show -p MainPID dsh.

Sau đó reboot một lần. Một service chưa từng hoạt động qua reboot thì chưa thể xem là service hoàn chỉnh.

sudo reboot

Kết nối lại và chạy systemctl is-active dsh. Lệnh này in ra active.

Đọc log bằng journalctl

Mọi thứ dsh ghi vào stdout và stderr đều được lưu trong journal dưới tên unit.

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

-f theo dõi các dòng mới, -n hiển thị N dòng cuối, còn -p err lọc theo priority. SyslogIdentifier=dsh trong unit khiến các dòng đó được gắn nhãn dsh thay vì node. Điều này quan trọng khi bạn đọc journal output lần đầu mà không lọc theo unit.

Hãy kiểm tra journal có được giữ lại sau khi reboot trước khi bạn cần dùng đến nó:

journalctl -u dsh -b -1

Nếu lệnh in ra Specifying boot ID or boot offset has no effect, no persistent journal was found, journal đang được lưu trong /run và sẽ bị xóa sau mỗi lần reboot. Tạo directory này rồi restart daemon:

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

Truy cập UI qua SSH tunnel, không mở port public

dsh phục vụ web UI (giao diện người dùng) trên 127.0.0.1:3080 và từ chối phục vụ ở mọi địa chỉ khác. Nếu yêu cầu --host 0.0.0.0, dsh sẽ dừng với lỗi sau:

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

Đây không phải là giới hạn cần tìm cách vượt qua. Web API (application programming interface) điều khiển agent, còn agent chạy shell command. Vì vậy, một port có thể truy cập được cũng tương đương với một shell trên VPS của bạn đối với bất kỳ ai tìm thấy nó. Maintainer nêu lý do bind cố định vào loopback là remote authentication chưa được xây dựng. Bạn nên đọc Ý nghĩa thực sự của dòng 127.0.0.1:3080 trong output khởi động trước khi tìm cách thay đổi nó. Thay vào đó, hãy forward port từ máy của bạn:

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

-L 3080:127.0.0.1:3080 mở port 3080 trên laptop và gửi mọi dữ liệu đến đó tới 127.0.0.1:3080, với địa chỉ được resolve trên VPS. -N nghĩa là không chạy remote command nào, nên session chỉ giữ tunnel mở. Để session chạy và mở http://127.0.0.1:3080/ trong trình duyệt. Đây là nơi bạn nhập DeepSeek API key, trong Settings rồi Models, và chọn thư mục workspace. Trỏ workspace tới /var/lib/dsh/workspace, là thư mục do service user sở hữu. Nếu không, các file tool của agent sẽ fail với EACCES: permission denied.

Nếu port 3080 đang được dùng trên laptop, ssh sẽ báo lỗi:

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

Chọn một local port khác bằng ssh -N -L 3081:127.0.0.1:3080 you@203.0.113.10, rồi truy cập http://127.0.0.1:3081/ trong trình duyệt. Lưu lệnh này vào ~/.ssh/config trên máy của bạn:

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

Sau đó, ssh -N dsh-vps là toàn bộ command cần chạy. Tunnel này hiện là lối truy cập duy nhất đến agent, nên SSH daemon là thành phần bảo vệ nó: chỉ dùng key, không dùng password authentication. Các biện pháp còn lại trong hardening SSH trên VPS vì thế càng quan trọng hơn bình thường. Nếu VPS này phát triển thành một private network nhỏ, có database hoặc staging box phía sau, quảng bá các địa chỉ đó vào tailnet bằng subnet router sẽ giúp bạn không phải forward từng service. Tuy nhiên, do dsh bind vào loopback, UI vẫn phải truy cập qua tunnel.

Không đặt key trong unit file. Các giá trị Environment= được systemctl show dsh -p Environment in ra; bất kỳ user nào trên máy cũng có thể chạy lệnh đó. Nếu plugin bạn cài cần key trong environment, hãy đặt key vào /etc/dsh.env, đặt mode 600 và owner là root, rồi tham chiếu file đó bằng EnvironmentFile=/etc/dsh.env. systemd đọc file này với quyền root tại thời điểm exec, còn systemctl show không in nội dung của file. Mỗi setting thực sự được lưu vào file nào trên disk, và dữ liệu nào rời khỏi máy khi bạn trỏ dsh tới local Ollama endpoint thay vì DeepSeek API, sẽ được trình bày trong cấu hình key, model và endpoint cho dsh.

Chi phí vận hành

Inference diễn ra trên API của DeepSeek, không phải trên VPS của bạn. Máy chủ của bạn chỉ phải chạy tiến trình Node, UI mà tiến trình này phục vụ và mọi lệnh mà agent quyết định chạy. Hai phần đầu có mức sử dụng ổn định và thấp. Phần thứ ba không bị giới hạn bởi bất kỳ thiết lập nào trong unit file này.

Hãy tự đo mức sử dụng cơ bản trên máy chủ của bạn thay vì tin vào con số từ máy của người khác:

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

MemoryCurrent có đơn vị là byte. Hãy theo dõi giá trị này khi agent đang làm việc, không phải khi agent đang ở trạng thái idle.

Các tool call là tiến trình con của service, nên chúng thuộc cùng control group và chịu cùng các giới hạn. Agent chạy npm install hoặc một test suite bên trong workspace có thể dùng nhiều memory hơn chính harness với mức chênh lệch rất lớn. Trên VPS 1 GB, đây là lúc sự cố xảy ra: kernel chọn một tiến trình và kill nó, còn journalctl -k | grep -i "out of memory" hiển thị dòng Out of memory: Killed process nêu tên tiến trình được chọn. Tiến trình đó thường không phải là tiến trình gây ra vấn đề.

Cách xử lý là chủ động đặt giới hạn. MemoryMax=CPUQuota= trong section [Service] giữ tác động của sự cố bên trong unit, để một build chạy mất kiểm soát bị kill thay vì khiến toàn bộ máy chủ bị treo. Giới hạn memory và CPU bằng systemd trình bày các giá trị giới hạn và cách hệ thống xử lý khi vượt giới hạn. Disk cũng tăng do lịch sử session trong DSH_HOME và do mọi dữ liệu agent ghi vào workspace, vì vậy hãy đưa du -sh /var/lib/dsh vào công cụ bạn đang dùng để theo dõi disk.

Nếu bạn cần một agent tương tác mà bạn có thể attach và detach, service không phải mô hình phù hợp; chạy agent trong session tmux persistent sẽ phù hợp hơn. Hãy chạy dsh dưới dạng unit khi bạn muốn nó luôn hoạt động và có thể truy cập qua tunnel.

Các lỗi thường gặp và chuỗi thông báo bạn sẽ thấy

status=203/EXEC. systemd không thể chạy file và ghi log Failed to locate executable /usr/local/bin/dsh: No such file or directory. Đường dẫn trong ExecStart= không khớp với nội dung command -v dsh đã in ra. Đây là lỗi mà Type=exec báo tại thời điểm systemctl start thay vì che giấu.

status=217/USER. Account trong User= không tồn tại. Xác nhận bằng id dsh.

status=200/CHDIR. WorkingDirectory= bị thiếu hoặc service user không thể truy cập vào đó. sudo -u dsh ls /var/lib/dsh/workspace tái hiện trực tiếp lỗi này.

Error: listen EADDRINUSE: address already in use 127.0.0.1:3080. Một tiến trình khác đã chiếm port, thường là tiến trình npx vẫn còn chạy trong một terminal khác. sudo ss -lntp | grep 3080 cho biết process đó.

EACCES: permission denied kèm theo một path. Quyền sở hữu trong /var/lib/dsh không đúng, thường vì lần chạy đầu tiên được thực hiện bằng root hoặc với HOME sai. sudo chown -R dsh:dsh /var/lib/dsh sẽ sửa lỗi này.

Start request repeated too quickly. Unit đã chạm rate limit khởi động và dừng thử lại. Lỗi thực tế nằm ở các dòng phía trên. Chạy sudo systemctl reset-failed dsh trước khi thử lại.

Unit là active (running) nhưng browser không hiển thị gì. Chạy lệnh kiểm tra trên VPS: nếu tại đó curl -fsS http://127.0.0.1:3080/ -o /dev/null && echo up in ra up thì service vẫn hoạt động bình thường và vấn đề nằm ở port forward.

Nâng cấp có chủ đích

Pin version có nghĩa là bạn chủ động thực hiện nâng cấp, thay vì để hệ thống tự nâng cấp. Hãy đọc release notes trước, vì cảnh báo của upstream về các thay đổi có thể phá vỡ tính tương thích chính là lý do cần pin version. Sao lưu thư mục lưu trạng thái, rồi thay thế version:

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 cũng thực hiện cùng npm install -g với version cũ, đồng thời khôi phục tarball đã sao lưu. Cách này chỉ hoạt động nếu bạn đã sao lưu tarball đó. Preview-stage agent runtime chính là loại phần mềm có thể ghi lại định dạng cấu hình trong quá trình nâng cấp mà bạn không kịp chuẩn bị.

FAQ

Vì sao dsh dừng khi tôi đóng phiên SSH?

npx @deepseek-ai/dsh web là tiến trình foreground thuộc phiên đăng nhập của bạn, nên nó bị dừng khi phiên kết thúc. Một systemd unit thuộc init system, nên vẫn tiếp tục chạy sau khi bạn ngắt kết nối và khởi động lại sau reboot. sudo systemctl enable --now dsh là cặp bước giúp bạn có cả hai: enable cho lần reboot, --now cho lần boot này.

Tôi nên dùng Type=simple hay Type=exec cho dsh?

Type=exec. dsh chạy ở foreground và không bao giờ fork, nên cả hai đều hoạt động. Tuy nhiên, Type=exec khiến systemd chờ execve() thành công trước khi xác nhận việc start đã thành công. Nếu đường dẫn trong ExecStart= sai, systemctl start sẽ fail với status=203/EXEC ngay trước mắt bạn. Với Type=simple, cùng lỗi đó trả về success và bị che khuất trong journal. Type=forkingType=notify đều sai trong trường hợp này, và cả hai đều treo cho đến khi TimeoutStartSec hết hạn sau 90 giây.

Làm cách nào để mở web UI của dsh từ laptop?

Forward cổng qua SSH: ssh -N -L 3080:127.0.0.1:3080 you@your-vps, rồi mở http://127.0.0.1:3080/ trong trình duyệt. Không bind service vào địa chỉ public. dsh từ chối --host 0.0.0.0 với 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, vì web API có thể yêu cầu agent chạy shell command và không có cơ chế xác thực từ xa nào ở phía trước.

Tôi có thể chạy dsh bằng root để đơn giản hóa quyền không?

Không. Harness này dùng để chạy command và ghi file, nên service có quyền gì thì agent có đúng các quyền đó. Tạo một system account bằng useradd --system --shell /usr/sbin/nologin dsh, dùng account đó làm owner của /var/lib/dsh, rồi thêm NoNewPrivileges=true vào unit. Nếu sau đó gặp EACCES: permission denied, nguyên nhân thường là một lần chạy trước đó bằng root đã để lại các file thuộc root, và sudo chown -R dsh:dsh /var/lib/dsh sẽ xóa vấn đề này.

Tôi nên pin phiên bản dsh nào trong unit?

Dùng phiên bản mà npm view @deepseek-ai/dsh version báo khi bạn thiết lập service, cài bằng npm install -g @deepseek-ai/dsh@<that version> và ghi lại ở nơi bạn có thể tìm thấy. 0.1.0-rc.7 là phiên bản hiện tại vào ngày 18 August 2026. Vấn đề không nằm ở con số, mà ở chỗ npx không có version sẽ resolve package tại thời điểm start, nên một lần restart không giám sát có thể âm thầm chuyển bạn sang một build dùng format config khác.