VPS에서 dsh를 systemd 서비스로 실행하는 방법
VPS에서 dsh를 헤드리스로 구동하기 위한 systemd 유닛 파일 설정법을 안내합니다. 전용 계정 생성, 재시작 규칙, journalctl 로그 확인 및 SSH 터널링을 통해 안정적인 서비스 운영 환경을 구축하는 방법을 단계별로 설명합니다.
터미널이 아닌 VPS에서 dsh를 헤드리스로 실행하기
VPS에서 dsh를 헤드리스로 실행하려면 systemd 유닛 파일 하나와 이를 소유할 전용 계정이 필요합니다. dsh는 2026년 8월 개발자 프리뷰로 MIT 라이선스 하에 공개된 DeepSeek의 에이전트 런타임인 DeepSeek Harness의 명령줄 실행 도구입니다. 퀵스타트 가이드에서는 npx @deepseek-ai/dsh web을 입력하라고 안내하는데, 이는 올바른 방법이지만 SSH(secure shell) 세션을 종료하는 즉시 프로세스가 중단됩니다.
유닛 파일은 다음 네 가지 문제를 한 번에 해결합니다. 서비스는 재부팅 후에도 자동으로 다시 시작됩니다. 출력 내용은 화면을 지나쳐 사라지는 대신 저널(journal)에 기록됩니다. root가 아닌 별도의 계정으로 실행됩니다. 또한, 사용자가 선택한 버전으로 실행되는데, 이는 업스트림에서 다음과 같이 강조하듯 매우 중요한 사항입니다.
DeepSeek Harness는 현재 개발자 프리뷰 단계이며 빠르게 반복 개선되고 있습니다. 호환성을 깨뜨리는 변경 사항이 발생할 것입니다.
이 가이드는 dsh가 이미 수동으로 정상 작동함을 전제로 합니다. 만약 작동하지 않는다면 VPS에 DeepSeek Harness 설치하기를 먼저 수행한 뒤, npx @deepseek-ai/dsh web에서 페이지가 정상적으로 표시되면 다시 돌아오십시오.
Node를 먼저 설치하십시오. npm은 경고를 보내지 않습니다.
node -vUbuntu 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 upup는 웹 프로파일이 루프백 인터페이스에서 수신 대기 중임을 의미하며, 이는 기본적으로 바인딩되는 위치입니다. 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/dshcommand -v dsh는 npm이 NodeSource에서 설치되었을 때 /usr/bin/dsh을 출력하며, Ubuntu 자체 패키지에서 설치되었을 때는 /usr/local/bin/dsh을 출력합니다. unit 파일에는 실제로 출력된 경로를 사용하십시오. npm ls -g은 정확한 버전을 출력하며, 이는 6주 뒤 동작이 변경되었을 때 무엇을 설치했는지 기억나지 않는 상황에서 필요한 정보입니다.
서비스를 소유하고 다른 권한은 없는 사용자
에이전트는 셸 명령을 실행하는 것이 주 업무입니다. 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가 프로필을 보관하는 디렉터리인 DSH_HOME이 됩니다. 프로필은 사용자의 패치 계층이 상단에 적용된 플러그인 번들 스택을 의미하며, 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를 눌러 중단하십시오.
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.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은 커널 의사 파일 시스템(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회 시작에 도달하지 않으므로, 시작 시 충돌하는 유닛은 영원히 재시작하며 저널에만 기록이 남습니다. StartLimitIntervalSec=300와 StartLimitBurst=5을 함께 설정하면 5분 이내에 5회 실패할 경우 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 3080127.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(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에서 셸을 획득할 수 있습니다. 유지보수 담당자는 원격 인증 기능이 구현되지 않았기 때문에 바인딩을 루프백으로 고정했다고 설명합니다. 대신 본인의 컴퓨터에서 포트를 포워딩하십시오.
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 키를 입력하고 작업 공간 디렉터리를 선택할 수 있습니다. 작업 공간을 서비스 사용자가 소유한 /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 데몬이 이를 보호하게 됩니다. 키 기반 인증만 사용하고 비밀번호 인증은 비활성화하십시오. VPS의 SSH 강화에 관한 나머지 지침이 평소보다 더 강력하게 적용됩니다.
API 키를 유닛 파일에 넣어서는 안 됩니다. Environment= 값은 서버의 모든 사용자가 실행할 수 있는 systemctl show dsh -p Environment 명령으로 출력됩니다. 설치한 플러그인이 환경 변수로 키를 요구한다면, root 소유의 600 권한을 가진 /etc/dsh.env 파일에 키를 저장하고 EnvironmentFile=/etc/dsh.env으로 참조하십시오. systemd는 실행 시점에 root 권한으로 해당 파일을 읽으며, systemctl show은 파일 내용을 출력하지 않습니다.
운영 비용
추론은 VPS가 아닌 DeepSeek API에서 수행됩니다. 사용자의 서버는 Node 프로세스, 서비스되는 UI, 그리고 에이전트가 실행하기로 결정한 모든 명령에 대한 비용을 부담합니다. 앞의 두 가지는 일정하고 작지만, 세 번째는 이 유닛 파일에서 아무런 제한을 받지 않습니다.
다른 사람의 수치를 신뢰하기보다 자신의 서버에서 직접 최소 비용을 측정하십시오.
systemctl show dsh -p MemoryCurrent
systemd-cgtop -1 --depth 2MemoryCurrent은 바이트 단위입니다. 에이전트가 유휴 상태일 때가 아니라 작업 중일 때 모니터링하십시오.
도구 호출은 서비스의 자식 프로세스이므로 동일한 제어 그룹(control group)에 속하며 동일한 제한을 적용받습니다. npm install를 실행하거나 작업 공간 내에서 테스트 스위트를 실행하는 에이전트는 하네스 자체보다 훨씬 더 많은 메모리를 사용할 수 있습니다. 1 GB VPS에서는 이 지점에서 문제가 발생합니다. 커널이 프로세스를 선택하여 종료시키며, 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 세션에서 에이전트 실행하기가 더 적합합니다. 항상 실행 상태를 유지하고 터널을 통해 접근해야 할 때만 dsh를 유닛으로 실행하십시오.
실패 유형 및 표시되는 문자열
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. 유닛이 시작 속도 제한(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을 출력한다면 서비스는 정상이며, 문제는 포트 포워딩 설정에 있는 것입니다.
의도적인 업그레이드
버전 고정(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을 사용하면 시작 시점에 패키지를 다시 확인한다는 점입니다. 이 경우 자동 재시작 과정에서 설정 형식이 다른 빌드로 예고 없이 업데이트될 수 있습니다.