SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-27

VPS에서 dsh를 systemd 서비스로 실행하는 방법

VPS에서 dsh를 헤드리스로 실행하기 위한 systemd 유닛 파일 설정법을 안내합니다. 전용 계정 생성, 자동 재시작 규칙, journalctl 로그 확인 및 SSH 터널링을 통해 DeepSeek Harness를 안정적으로 운영하는 방법을 확인하십시오.

터미널이 아닌 VPS에서 dsh를 헤드리스로 실행하기

VPS에서 dsh를 헤드리스로 실행하려면 systemd 유닛 파일 하나와 이를 소유할 전용 계정이 필요합니다. dsh는 2026년 8월 개발자 프리뷰로 MIT 라이선스 하에 공개된 DeepSeek의 에이전트 런타임인 DeepSeek Harness의 명령줄 실행 도구입니다. 하네스(harness)는 모델 자체가 아니라 모델을 둘러싼 프로그램을 의미하므로, systemd로 관리하는 대상은 DeepSeek의 추론 엔진이 아니라 루프, 도구 및 권한입니다. 퀵스타트 가이드에서는 npx @deepseek-ai/dsh web을 입력하라고 안내하는데, 이는 올바른 방법이지만 SSH(secure shell) 세션을 종료하는 즉시 프로세스가 종료됩니다.

유닛 파일은 다음 네 가지 문제를 한 번에 해결합니다. 서비스가 재부팅 후 자동으로 다시 시작됩니다. 출력 내용이 화면을 지나쳐 사라지지 않고 저널(journal)에 기록됩니다. root가 아닌 별도의 계정으로 실행됩니다. 또한 선택한 특정 버전으로 실행되는데, 이는 업스트림에서 다음과 같이 강조하듯 매우 중요합니다.

DeepSeek Harness는 현재 개발자 프리뷰 단계이며 빠르게 반복 개선되고 있습니다. 호환성을 깨뜨리는 변경 사항이 발생할 것입니다.

이 가이드는 dsh가 이미 수동으로 정상 작동함을 전제로 합니다. 만약 작동하지 않는다면 VPS에 DeepSeek Harness 설치하기를 먼저 수행한 뒤 npx @deepseek-ai/dsh web이 페이지를 정상적으로 제공하는지 확인하고 돌아오십시오.

Node를 먼저 설치하십시오. npm은 경고를 보내지 않습니다.

node -v

Ubuntu 24.04의 기본 패키지는 Node 18(2026년 8월 기준 18.19.1)이며, 이는 올해 배포된 패키지치고는 구버전입니다. @deepseek-ai/dsh는 engines 필드를 제공하지 않으므로, Node 버전이 너무 낮아도 npm은 EBADENGINE 경고를 출력하지 않습니다. 대신 런타임에 구문 오류나 내장 모듈 누락으로 실패하게 되는데, 이는 문제를 발견하기에 훨씬 좋지 않은 시점입니다. NodeSource에서 현재 지원되는 장기 지원(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 -v

이제 node -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 up

up는 웹 프로파일이 루프백 인터페이스에서 리스닝 중임을 의미하며, 이는 기본 바인딩 위치입니다. 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는 2026년 8월 18일에 게시된 버전입니다. npm view @deepseek-ai/dsh version을 사용하여 현재 버전을 확인한 다음, 실행할 버전을 고정하십시오.

고정한 버전을 전역으로 설치하기

npx는 unit 파일 내부에서 사용하기에 적절하지 않은 도구입니다. 이 명령은 프로세스가 시작될 때 패키지 버전을 결정하므로, 3개월 뒤에 재시작하면 사용자의 의도와 상관없이 프리뷰 단계 에이전트의 다른 빌드가 실행될 수 있습니다. 또한 부팅 시점에 npm 레지스트리에 접근할 수 있어야 하므로, 레지스트리 응답이 느린 날에는 정상적으로 작동하던 시스템의 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 파일에는 실제로 출력된 경로를 사용하십시오. npm ls -g은 정확한 버전을 출력하며, 이는 6주 뒤 동작이 변경되어 설치한 버전을 기억하지 못할 때 필요한 정보입니다. 설치에 실패하거나, command -v dsh가 아무것도 출력하지 않거나, 의도한 버전과 다른 버전이 반환된다면 unit 파일을 작성하기 전에 일반적인 dsh 설치 및 버전 오류 해결 방법을 먼저 수행하십시오.

서비스 소유권만 가진 전용 사용자

에이전트는 셸 명령을 실행하는 것이 주 업무입니다. root 권한으로 실행하면 모든 도구 호출이 root 권한으로 수행되므로, 로그인 셸이 없는 전용 계정을 생성하여 할당하십시오.

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 web

sudo가 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.target

ExecStart=에는 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는 커널 의사 파일 시스템(pseudo-filesystem)을 제외한 전체 파일 시스템을 읽기 전용으로 만듭니다. 따라서 파일을 쓰려는 첫 번째 도구 호출은 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은 sd_notify을 통해 READY=1 메시지를 기다리는데, 이를 보내지 않는 Node 프로세스는 동일한 방식으로 멈춰 버립니다. systemd 서비스 타입 전체 비교 문서에서 notify를 구성할 가치가 있는 경우를 포함한 나머지 내용을 다룹니다.

실패를 명확히 알리는 재시작 규칙

Restart=on-failure는 0이 아닌 종료 코드나 치명적인 시그널이 발생할 때 재시작하며, 정상 종료 시에는 유닛을 그대로 둡니다. 이는 프리뷰 빌드에서 권장되는 동작입니다. 만약 dsh가 잘못된 설정을 읽어 0을 반환하며 종료된다면, 유닛은 정지 상태로 유지되며 systemctl status dsh에서 inactive (dead)을 통해 이를 확인할 수 있습니다. Restart=always을 사용하면 동일한 이벤트가 재시작 루프를 유발하여 멀리서 보기에는 정상적으로 작동하는 것처럼 보일 수 있습니다.

많은 사용자가 속도 제한(rate limit) 설정을 간과합니다. systemd의 기본값은 10초 이내에 5회 시작하는 것이며, RestartSec=5s를 사용하면 10초라는 시간 내에 5회 시작에 도달하지 않으므로, 시작 시 충돌하는 유닛은 영원히 재시작을 반복하며 저널에만 기록이 남게 됩니다. StartLimitBurst=5과 함께 StartLimitIntervalSec=300을 설정하면 5분 이내에 5회 실패할 경우 systemd가 포기하고 유닛을 failed 상태로 전환하며 Start request repeated too quickly.을 기록합니다. 원인을 해결한 후에는 sudo systemctl reset-failed dsh를 사용하여 해당 상태를 초기화하십시오. 두 설정 모두 [Service]이 아닌 [Unit] 섹션에 포함되어야 하며, 잘못된 섹션에 작성할 경우 systemd는 이를 아무런 경고 없이 무시합니다.

서비스 시작 및 확인

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

enable --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이 보인다면, 바인딩 주소가 변경되어 에이전트가 공용 인터넷에 노출된 상태입니다. 해당 출력에서 프로세스 이름은 dsh이 아니라 node로 나타납니다. dsh 바이너리는 Node 스크립트이므로 pgrep -x dsh으로는 아무것도 찾을 수 없습니다. 대신 systemctl show -p MainPID dsh을 사용하십시오.

그런 다음 한 번 재부팅하십시오. 재부팅 후에도 정상적으로 동작하지 않는 서비스는 아직 서비스로서 완성된 것이 아닙니다.

sudo reboot

다시 접속하여 systemctl is-active dsh를 실행하십시오. active이 출력됩니다.

journalctl로 로그 읽기

dsh가 stdout과 stderr로 출력하는 모든 내용은 해당 유닛 이름으로 저널에 기록됩니다.

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 설정 때문에 해당 줄들이 node가 아닌 dsh로 태그가 지정됩니다. 이는 유닛별로 필터링되지 않은 저널 출력을 처음 읽을 때 중요합니다.

저널이 필요한 상황이 오기 전에 재부팅 후에도 저널이 유지되는지 확인하십시오.

journalctl -u dsh -b -1

만약 출력 결과가 Specifying boot ID or boot offset has no effect, no persistent journal was found이라면, 저널은 /run에 위치하며 재부팅 시마다 삭제됩니다. 디렉터리를 생성하고 데몬을 재시작하십시오.

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

공개 포트 대신 SSH 터널을 통해 UI에 접속하기

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(애플리케이션 프로그래밍 인터페이스)가 에이전트를 구동하고, 에이전트는 셸 명령을 실행하므로, 외부에서 접근 가능한 포트는 곧 발견하는 누구에게나 열린 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 포트를 열고, 그곳으로 들어오는 모든 데이터를 127.0.0.1:3080(VPS에서 해석된 주소)로 전송합니다. -N은 원격 명령을 실행하지 않겠다는 의미이므로, 세션은 터널을 유지하는 역할만 합니다. 이 상태를 유지하고 브라우저에서 http://127.0.0.1:3080/을 여십시오. 이곳의 Settings 내 Models 항목에서 DeepSeek API 키를 입력하고 작업 디렉터리를 선택합니다. 작업 디렉터리는 서비스 사용자가 소유한 /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만 입력하면 됩니다. 이제 이 터널이 에이전트로 향하는 유일한 통로이므로, SSH 데몬이 이를 보호하게 됩니다. 키 기반 인증만 사용하고 비밀번호 인증은 비활성화하십시오. VPS의 SSH 보안 강화에 관한 나머지 지침이 평소보다 더 강력하게 적용됩니다. 만약 해당 VPS가 데이터베이스나 스테이징 서버를 포함한 작은 사설 네트워크로 확장된다면, 서브넷 라우터로 해당 주소를 tailnet에 알리는 것이 서비스별 포워딩을 줄여주지만, dsh의 루프백 바인딩 특성상 UI 자체는 여전히 터널을 통해 접속해야 합니다.

키는 유닛 파일에 포함해서는 안 됩니다. Environment= 값은 systemctl show dsh -p Environment에 의해 출력되는데, 이는 서버의 모든 사용자가 실행할 수 있습니다. 설치한 플러그인이 환경 변수에 키를 필요로 한다면, root 소유의 600 모드로 설정된 /etc/dsh.env에 키를 저장하고 EnvironmentFile=/etc/dsh.env로 참조하십시오. systemd는 실행 시점에 해당 파일을 root 권한으로 읽으며, systemctl show은 그 내용을 출력하지 않습니다. 각 설정이 디스크의 어떤 파일에 저장되는지, 그리고 dsh를 DeepSeek API 대신 로컬 Ollama 엔드포인트로 지정할 때 어떤 데이터가 서버를 나가는지에 대해서는 dsh의 키, 모델 및 엔드포인트 설정하기에서 다룹니다.

운영 비용

추론은 VPS가 아닌 DeepSeek API에서 수행됩니다. 사용자의 서버는 Node 프로세스, 제공되는 UI, 그리고 에이전트가 실행하기로 결정한 모든 명령에 대한 비용을 부담합니다. 앞의 두 가지는 일정하고 적은 양이지만, 세 번째는 이 unit 파일에서 아무런 제한을 받지 않습니다.

다른 사람의 수치를 신뢰하기보다 본인의 서버에서 직접 최소 사용량을 측정하십시오.

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

MemoryCurrent는 바이트 단위입니다. 에이전트가 유휴 상태일 때가 아니라 작업 중일 때 모니터링하십시오.

도구 호출은 서비스의 자식 프로세스이므로 동일한 제어 그룹(control group)에 속하며 동일한 제한 사항의 적용을 받습니다. npm install을 실행하거나 작업 공간 내에서 테스트 스위트를 실행하는 에이전트는 하네스(harness) 자체보다 훨씬 더 많은 메모리를 사용할 수 있습니다. 1 GB VPS에서는 이 지점에서 문제가 발생합니다. 커널이 프로세스를 선택하여 종료시키며, journalctl -k | grep -i "out of memory"은 선택된 프로세스를 지목하는 Out of memory: Killed process 라인을 보여줍니다. 해당 프로세스는 종종 문제를 일으킨 원인이 아닙니다.

해결책은 의도적으로 제한을 설정하는 것입니다. [Service] 섹션의 MemoryMax=과 CPUQuota=는 피해를 unit 내부로 한정하므로, 제어할 수 없는 빌드가 발생해도 전체 서버가 멈추는 대신 해당 프로세스만 종료됩니다. systemd로 메모리 및 CPU 제한하기에서 수치와 실패 동작을 다룹니다. 디스크 또한 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. 유닛이 시작 속도 제한에 도달하여 실행을 포기했습니다. 실제 오류는 해당 메시지 상단에 나타납니다. 다시 시도하기 전에 sudo systemctl reset-failed dsh을 실행하십시오.

유닛 상태는 active (running)이지만 브라우저에 아무것도 나타나지 않음. VPS에서 확인을 수행하십시오. curl -fsS http://127.0.0.1:3080/ -o /dev/null && echo up 명령을 실행했을 때 up이 출력된다면 서비스는 정상이며, 문제는 포트 포워딩 설정에 있는 것입니다.

의도적인 업그레이드

버전 고정(pinning)을 사용하면 업그레이드는 자동으로 일어나는 일이 아니라 사용자가 직접 수행하는 작업이 됩니다. 업그레이드 전 반드시 릴리스 노트를 읽어야 합니다. 업스트림에서 경고하는 호환성 파괴 변경 사항이 바로 버전 고정을 사용하는 이유이기 때문입니다. 상태 디렉터리를 백업한 뒤 버전을 교체하십시오.

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은 로그인 세션이 소유한 포그라운드 프로세스이므로 세션이 종료되면 함께 정리되기 때문입니다. 반면 systemd 유닛은 init 시스템이 소유하므로 연결을 끊어도 계속 실행되며 재부팅 후에도 다시 시작됩니다. sudo systemctl enable --now dsh는 이 두 가지를 모두 보장하는 단계입니다. 재부팅 시 자동 시작을 위해 enable를, 현재 부팅 세션에서 즉시 시작하기 위해 --now을 사용하십시오.

dsh에 Type=simple과 Type=exec 중 무엇을 사용해야 합니까?

Type=exec을 권장합니다. dsh는 포그라운드에서 실행되며 포크(fork)하지 않으므로 둘 다 작동은 하지만, Type=exec을 사용하면 systemd가 execve()가 성공할 때까지 기다린 뒤 시작 성공 여부를 판단합니다. ExecStart=에 잘못된 경로를 입력하면 systemctl start이 실패하며 status=203/EXEC가 표시됩니다. Type=simple을 사용하면 같은 실수를 해도 성공으로 간주되어 저널에만 기록되므로 확인이 어렵습니다. Type=forking와 Type=notify는 이 경우에 적합하지 않으며, TimeoutStartSec이 90초 후에 만료될 때까지 프로세스가 멈춘 상태가 됩니다.

노트북에서 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를 통해 에이전트가 셸 명령을 실행할 수 있는데, 그 앞에 원격 인증 절차가 없기 때문입니다.

권한 관리를 단순하게 하려고 dsh를 root로 실행해도 됩니까?

아니요. 이 하네스는 명령을 실행하고 파일을 작성하는 역할을 하므로, 서비스가 가진 권한은 곧 에이전트의 권한이 됩니다. useradd --system --shell /usr/sbin/nologin dsh로 시스템 계정을 생성하고, /var/lib/dsh의 소유권을 해당 계정으로 변경한 뒤, 유닛 파일에 NoNewPrivileges=true을 추가하십시오. 이후 EACCES: permission denied 오류가 발생한다면, 이전에 root로 실행하여 root 소유의 파일이 남아있는 경우가 대부분입니다. sudo chown -R dsh:dsh /var/lib/dsh를 사용하여 이를 정리하십시오.

유닛 파일에는 어떤 버전의 dsh를 고정해야 합니까?

서비스를 설정할 때 npm view @deepseek-ai/dsh version이 보고하는 버전을 사용하십시오. npm install -g @deepseek-ai/dsh@<that version>로 설치하고 나중에 확인할 수 있는 곳에 기록해 두어야 합니다. 2026년 8월 18일 기준으로는 0.1.0-rc.7이 최신 버전이었습니다. 중요한 것은 특정 숫자가 아니라, 버전을 명시하지 않은 npx는 시작 시점에 패키지를 결정한다는 점입니다. 이 경우 자동 재시작 시 설정 형식이 다른 빌드로 예고 없이 업데이트될 수 있습니다.