dsh headless draaien als systemd service op een VPS
Voer dsh stabiel uit op uw VPS met een systemd unitbestand. Leer hoe u een dedicated gebruiker instelt, versies vastzet en logs beheert via journalctl voor uw DeepSeek Harness.
Dsh headless uitvoeren op een VPS, buiten een terminal om
Het headless uitvoeren van dsh op een VPS vereist slechts één systemd-unitbestand en een toegewezen gebruiker die het proces beheert. dsh is de command-line launcher voor DeepSeek Harness, de agent-runtime van DeepSeek, die in augustus 2026 als developer preview onder de MIT-licentie is uitgebracht. De quickstart adviseert u om npx @deepseek-ai/dsh web te typen; dit is correct, maar het proces wordt beëindigd zodra u uw SSH-sessie (secure shell) sluit.
Een unitbestand lost direct vier zaken op. De service start automatisch na een reboot. De output wordt naar de journal geschreven in plaats van dat deze voorbij scrolt. Het proces draait onder een account dat niet root is. Bovendien draait u de versie die u zelf heeft gekozen, wat hier belangrijker is dan gebruikelijk, aangezien de upstream-partij dit met hoofdletters aangeeft:
DeepSeek Harness bevindt zich momenteel in developer preview en wordt in hoog tempo doorontwikkeld. ER ZULLEN WIJZIGINGEN PLAATSVINDEN DIE DE COMPATIBILITEIT VERBREKEN.
Deze handleiding gaat ervan uit dat dsh handmatig al voor u werkt. Als dit niet het geval is, begin dan bij DeepSeek Harness installeren op een VPS en keer terug zodra npx @deepseek-ai/dsh web een pagina serveert.
Node eerst, omdat npm u niet zal waarschuwen
node -vHet eigen pakket van Ubuntu 24.04 is Node 18 (18.19.1 per augustus 2026), wat verouderd is voor een pakket dat dit jaar is uitgebracht. @deepseek-ai/dsh publiceert geen engines-veld, dus npm geeft geen EBADENGINE-waarschuwing wanneer uw Node-versie te oud is. De fout treedt pas op tijdens runtime, als een syntaxfout of een ontbrekende ingebouwde functie; dat is een veel ongunstiger moment om dit te ontdekken. Installeer een actuele long term support (LTS)-release via 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 -vnode -v zou nu een v22-versie moeten weergeven. De less-regel is aanwezig omdat het direct doorsturen (piping) van een extern script naar bash code uitvoert die u nooit heeft gelezen.
Controleer of het proces draait voordat u een unit schrijft
npx @deepseek-ai/dsh@0.1.0-rc.7 webLaat dit proces draaien. Open een tweede SSH-sessie:
curl -fsS http://127.0.0.1:3080/ -o /dev/null && echo upup betekent dat het webprofiel luistert op de loopback-interface, wat de standaardinstelling is voor binding. curl: (7) Failed to connect to 127.0.0.1 port 3080: Connection refused betekent dat dit niet het geval is; de eerste terminal geeft aan waarom. Stop de handmatige uitvoering met Ctrl+C voordat u verdergaat: een unit die probeert een poort te binden die al in gebruik is, faalt met Error: listen EADDRINUSE: address already in use 127.0.0.1:3080.
0.1.0-rc.7 was de gepubliceerde versie op 18 augustus 2026. Controleer wat de huidige versie is met npm view @deepseek-ai/dsh version en zet vervolgens de versie vast die u wilt gebruiken.
Installeer de versie die u heeft vastgezet, globaal
npx is het verkeerde hulpmiddel binnen een unit-bestand. Het bepaalt de pakketversie op het moment dat het proces start, waardoor een herstart over drie maanden een andere build van een preview-agent kan laden zonder dat u iets heeft aangepast. Bovendien moet de npm-registry bereikbaar zijn tijdens het opstarten, wat een werkende machine in een defecte unit verandert op de dag dat de registry traag is. Installeer het eenmalig, op de versie die u heeft genoteerd:
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 voert /usr/bin/dsh uit wanneer npm afkomstig is van NodeSource, en /usr/local/bin/dsh wanneer het afkomstig is van het eigen pakket van Ubuntu. Gebruik het pad dat daadwerkelijk werd getoond in het unit-bestand. npm ls -g toont de exacte versie; dit is het antwoord dat u over zes weken nodig heeft wanneer het gedrag verandert en u zich niet meer kunt herinneren wat u heeft geïnstalleerd.
Een gebruiker die eigenaar is van de service en niets anders
De agent voert shell-commando's uit. Dat is de taak van de agent. Als u deze als root uitvoert, wordt elke tool-aanroep een root-aanroep. Wijs daarom een eigen account toe zonder 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 wordt DSH_HOME, de map waarin dsh profielen bewaart. Een profiel is een benoemde stack van plugin-bundels met uw eigen patch-laag daarbovenop. De profielen web en headless bouwen zichzelf bij de eerste keer opstarten op basis van meegeleverde templates. Deze eerste opstart schrijft bestanden en kan bundels ophalen, dus voer dit handmatig uit zodat u het proces kunt controleren.
sudo -u dsh env HOME=/var/lib/dsh DSH_HOME=/var/lib/dsh/harness /usr/bin/dsh --profile webStel HOME expliciet in in plaats van te vertrouwen op wat sudo ermee doet. Of sudo het bestand HOME herschrijft voor een niet-login-commando hangt namelijk af van de instelling set_home in /etc/sudoers. Als dit onjuist is ingesteld, plaatst de eerste run cache-mappen in uw home-directory met dsh als eigenaar, waardoor de service later zijn eigen status niet kan vinden. Stop het proces met Ctrl+C zodra de curl-controle up retourneert.
Het unit-bestand
Schrijf /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= gebruikt het absolute pad dat u heeft verkregen via command -v dsh. systemd doorzoekt een vaste lijst met paden voor een kale opdrachtnaam, maar die lijst is niet de PATH van uw shell, dus een absoluut pad voorkomt giswerk.
WorkingDirectory= is de locatie waar relatieve paden worden opgelost en het is het startpunt voor een tool-aanroep die ls uitvoert zonder argumenten. Wijs dit naar de werkmap die u aan de agent geeft. Als de map ontbreekt of de servicegebruiker deze niet kan betreden, faalt de unit met status=200/CHDIR nog voordat dsh wordt uitgevoerd.
ProtectHome=true verbergt /home en /root voor het proces. Dit is hier veilig omdat alles wat de service aanraakt onder /var/lib/dsh leeft. Wijs de werkmap naar een pad onder /home en de agent zal rapporteren dat de map niet bestaat; dit is verwarrend totdat u zich deze regel herinnert. ProtectSystem=full maakt /usr, /boot en /etc alleen-lezen, wat de service nooit hoeft te beschrijven.
Verder gaan is verleidelijk en meestal onjuist. ProtectSystem=strict maakt het gehele bestandssysteem alleen-lezen, afgezien van de pseudo-bestandssystemen van de kernel, waardoor de eerste tool-aanroep die een bestand schrijft faalt met EROFS: read-only file system. Als u dat niveau wenst, voeg dan ReadWritePaths=/var/lib/dsh toe in dezelfde bewerking.
Welk Type= hoort hier
Type=exec, omdat dsh op de voorgrond blijft en nooit een fork uitvoert. Het voordeel ten opzichte van de standaardinstelling is een daadwerkelijke foutmelding. Bij Type=simple markeert systemd de start als succesvol zodra het proces is geforkt, nog voordat bekend is of het binaire bestand überhaupt bestaat. Hierdoor keert systemctl start dsh zonder fouten terug en is de mislukking alleen in het journal zichtbaar. Bij Type=exec wacht systemd tot execve() succesvol is afgerond, waardoor een typefout in ExecStart= direct leidt tot een foutmelding in het commando dat u zojuist heeft ingevoerd, waar u deze direct kunt zien.
De twee onjuiste antwoorden leiden beide tot een hangend proces. Type=forking instrueert systemd om te wachten tot een ouderproces wordt afgesloten, maar dsh sluit nooit af. De start blokkeert daarom totdat TimeoutStartSec verloopt (standaard 90 seconden), waarna de melding Job for dsh.service failed because a timeout was exceeded. volgt. Type=notify wacht op een READY=1-bericht via sd_notify; een Node-proces dat dit nooit verstuurt, loopt op dezelfde wijze vast. De volledige vergelijking van systemd service types behandelt de overige opties, inclusief wanneer het de moeite waard is om notify te configureren.
Restart-regels die luidruchtig falen
Restart=on-failure herstart bij een exit-code die niet nul is of bij een fataal signaal, en laat de unit met rust na een correcte afsluiting. Dit is het gedrag dat u wenst voor een preview-build. Als dsh ooit afsluit met 0 omdat het een configuratie heeft gelezen die niet correct is, stopt de unit en blijft deze gestopt, en systemctl status dsh toont inactive (dead) waar u dit kunt inzien. Restart=always verandert diezelfde gebeurtenis in een herstartlus die van een afstand gezond lijkt.
De rate limit is het onderdeel dat mensen vaak vergeten. De standaardinstellingen van systemd zijn vijf starts binnen tien seconden, en met RestartSec=5s bereikt u nooit vijf starts binnen een venster van tien seconden, waardoor een unit die crasht bij het opstarten eeuwig blijft herstarten en alleen het journal dit weet. StartLimitIntervalSec=300 met StartLimitBurst=5 betekent dat vijf fouten binnen vijf minuten voldoende zijn: systemd geeft het op en parkeert de unit in failed, waarbij Start request repeated too quickly. wordt gelogd. Wis die status met sudo systemctl reset-failed dsh zodra u de oorzaak heeft verholpen. Beide instellingen horen thuis in [Unit], niet in [Service], en systemd negeert ze geruisloos als ze in de verkeerde sectie staan.
Start de service en controleer de status
sudo systemctl daemon-reload
sudo systemctl enable --now dsh
systemctl status dshenable --now voert twee taken uit. enable zorgt ervoor dat de service na een herstart weer actief wordt, en --now start de service in de huidige sessie. Een kale systemctl start is na de volgende herstart verdwenen, en kernel-updates vereisen herstarts.
systemctl status dsh hoort Active: active (running), een Main PID en een Memory:-regel te tonen. Controleer vervolgens waar de service luistert:
sudo ss -lntp | grep 3080U wilt 127.0.0.1:3080 zien. Als u 0.0.0.0:3080 ziet, heeft iets het bind-adres aangepast en is uw agent bereikbaar via het publieke internet. De procesnaam in die uitvoer is node, niet dsh, omdat het dsh-binary een Node-script is; daarom vindt pgrep -x dsh niets. Gebruik in plaats daarvan systemctl show -p MainPID dsh.
Herstart daarna de server één keer. Een service die een herstart niet heeft overleefd, is nog geen volwaardige service.
sudo rebootMaak opnieuw verbinding en voer systemctl is-active dsh uit. Dit geeft active weer.
Logs lezen met journalctl
Alles wat dsh naar stdout en stderr schrijft, komt in de journal terecht onder de naam van de unit.
journalctl -u dsh -f
journalctl -u dsh -n 200 --no-pager
journalctl -u dsh --since "10 min ago" -p err-f volgt nieuwe regels, -n toont de laatste N regels, -p err filtert op prioriteit. SyslogIdentifier=dsh in de unit is de reden waarom deze regels worden getagd als dsh in plaats van node. Dit is van belang wanneer u journal-output leest die niet op unit is gefilterd.
Controleer of de journal reboots overleeft voordat u deze nodig heeft:
journalctl -u dsh -b -1Als dit Specifying boot ID or boot offset has no effect, no persistent journal was found weergeeft, bevindt de journal zich in /run en wordt deze bij elke reboot gewist. Maak de directory aan en herstart de daemon:
sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journaldBereik de UI via een SSH-tunnel in plaats van een publieke poort
dsh serveert de web-UI (user interface) op 127.0.0.1:3080 en weigert deze elders aan te bieden. Vraagt u om --host 0.0.0.0, dan stopt het programma met de volgende melding:
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 insteadDit is geen beperking om te omzeilen. De web-API (application programming interface) stuurt de agent aan, en de agent voert shell-commando's uit. Een bereikbare poort betekent dus een shell op uw VPS voor iedereen die deze vindt. De ontwikkelaars geven aan dat het ontbreken van externe authenticatie de reden is dat de bind vaststaat op loopback. Forward de poort daarom vanaf uw eigen machine:
ssh -N -L 3080:127.0.0.1:3080 you@203.0.113.10-L 3080:127.0.0.1:3080 opent poort 3080 op uw laptop en stuurt al het verkeer dat daar aankomt door naar 127.0.0.1:3080, zoals opgelost op de VPS. -N betekent dat er geen extern commando wordt uitgevoerd; de sessie houdt enkel de tunnel open. Laat dit draaien en open http://127.0.0.1:3080/ in uw browser. Hier voert u de DeepSeek API-sleutel in onder Settings en vervolgens Models, en hier kiest u de workspace-directory. Wijs de workspace aan op /var/lib/dsh/workspace, de directory waarvan de service-gebruiker de eigenaar is, anders falen de bestandstools van de agent met EACCES: permission denied.
Als poort 3080 bezet is op uw laptop, meldt ssh dit:
bind [127.0.0.1]:3080: Address already in use
channel_setup_fwd_listener_tcpip: cannot listen to port: 3080Kies een andere lokale poort met ssh -N -L 3081:127.0.0.1:3080 you@203.0.113.10 en navigeer vervolgens naar http://127.0.0.1:3081/. Bespaar uzelf het typewerk in ~/.ssh/config op uw eigen machine:
Host dsh-vps
HostName 203.0.113.10
User you
LocalForward 3080 127.0.0.1:3080Daarna is ssh -N dsh-vps het volledige commando. Deze tunnel is nu de enige toegangspoort tot uw agent, waardoor de SSH-daemon de beveiliging vormt: gebruik alleen sleutels, geen wachtwoordauthenticatie, en de rest van het beveiligen van SSH op uw VPS is hier nog belangrijker dan normaal.
De sleutel hoort niet thuis in het unit-bestand. Environment=-waarden worden getoond door systemctl show dsh -p Environment, wat elke gebruiker op de server kan uitvoeren. Als een plugin die u installeert een sleutel in de omgeving nodig heeft, plaats deze dan in /etc/dsh.env met modus 600, eigendom van root, en verwijs ernaar met EnvironmentFile=/etc/dsh.env. systemd leest dat bestand als root tijdens de uitvoering, en systemctl show toont de inhoud ervan niet.
Wat de kosten zijn voor het draaien
Inference vindt plaats via de API van DeepSeek, niet op uw VPS. Uw server betaalt voor het Node-proces, de UI die het serveert en elk commando dat de agent besluit uit te voeren. De eerste twee zijn constant en klein. De derde wordt door niets in dit unit-bestand begrensd.
Meet de ondergrens op uw eigen server in plaats van te vertrouwen op cijfers van iemand anders:
systemctl show dsh -p MemoryCurrent
systemd-cgtop -1 --depth 2MemoryCurrent is in bytes. Observeer dit terwijl de agent werkt, niet terwijl deze inactief is.
Tool-aanroepen zijn onderliggende processen van de service, dus ze vallen in dezelfde control group en tellen mee voor dezelfde limieten. Een agent die npm install of een testsuite binnen de werkruimte uitvoert, kan aanzienlijk meer geheugen verbruiken dan de harness zelf. Op een 1 GB VPS is dat waar het misgaat: de kernel kiest een proces en beëindigt dit, en journalctl -k | grep -i "out of memory" toont de Out of memory: Killed process-regel die aangeeft welk proces werd gekozen. Dat proces is vaak niet degene die het probleem veroorzaakte.
De oplossing is een limiet die u bewust instelt. MemoryMax= en CPUQuota= in de [Service]-sectie houden de schade binnen de unit, zodat een op hol geslagen build wordt beëindigd in plaats van dat de hele server vastloopt. Geheugen en CPU beperken met systemd behandelt de getallen en het foutgedrag. De schijfruimte groeit ook, door sessiegeschiedenis onder DSH_HOME en door alles wat de agent in de werkruimte schrijft, dus neem du -sh /var/lib/dsh op in de tools die u al gebruikt om schijfgebruik te monitoren.
Als u een interactieve agent wilt waar u aan kunt koppelen en loskoppelen, dan is een service niet de juiste vorm, en past een agent draaien in een persistente tmux-sessie beter. Draai dsh als een unit wanneer u wilt dat deze altijd actief is en bereikbaar via een tunnel.
Foutmodi en de meldingen die u zult zien
status=203/EXEC. systemd kon het bestand niet uitvoeren en logt Failed to locate executable /usr/local/bin/dsh: No such file or directory. Het pad in ExecStart= komt niet overeen met wat command -v dsh heeft weergegeven. Dit is de fout die Type=exec rapporteert op het moment van systemctl start in plaats van deze te verbergen.
status=217/USER. Het account in User= bestaat niet. Bevestig dit met id dsh.
status=200/CHDIR. WorkingDirectory= ontbreekt, of de servicegebruiker heeft geen toegang tot deze map. sudo -u dsh ls /var/lib/dsh/workspace reproduceert dit direct.
Error: listen EADDRINUSE: address already in use 127.0.0.1:3080. Iets anders gebruikt de poort al, meestal is dit de npx die in een andere terminal open is blijven staan. sudo ss -lntp | grep 3080 toont de naam van het proces.
EACCES: permission denied gevolgd door een pad. Het eigenaarschap onder /var/lib/dsh is onjuist, meestal omdat een eerste uitvoering plaatsvond als root of met de verkeerde HOME. sudo chown -R dsh:dsh /var/lib/dsh herstelt dit.
Start request repeated too quickly. De unit heeft de startlimiet bereikt en is gestopt met proberen. De werkelijke fout staat in de regels daarboven. Voer sudo systemctl reset-failed dsh uit voordat u het opnieuw probeert.
Unit is active (running) maar de browser toont niets. Voer de controle uit op de VPS: als curl -fsS http://127.0.0.1:3080/ -o /dev/null && echo up daar up weergeeft, is de service in orde en ligt het probleem bij de port forward.
Upgraden met opzet
Pinning betekent dat een upgrade iets is wat u doet, niet iets wat u overkomt. Lees eerst de release notes, aangezien de waarschuwing van de upstream-partij over wijzigingen die compatibiliteit verbreken de enige reden is voor de pin. Maak een back-up van de state-directory en wissel vervolgens de versie:
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-pagerRollback is hetzelfde proces npm install -g met de oude versie, plus het terugzetten van die tarball, wat alleen werkt als u deze heeft gemaakt. Een agent-runtime in de preview-fase is precies de software waarbij een upgrade het configuratieformaat onder uw handen herschrijft.
FAQ
Waarom stopt dsh wanneer ik mijn SSH-sessie sluit?
Omdat npx @deepseek-ai/dsh web een voorgrondproces is dat eigendom is van uw inlogsessie; het wordt beëindigd zodra de sessie eindigt. Een systemd-unit is eigendom van het init-systeem, waardoor deze actief blijft na het verbreken van de verbinding en automatisch start na een reboot. sudo systemctl enable --now dsh is de combinatie van stappen die beide biedt: enable voor de reboot, --now voor de huidige sessie.
Moet ik Type=simple of Type=exec gebruiken voor dsh?
Type=exec. dsh draait op de voorgrond en voert nooit een fork uit, dus beide werken. Echter, Type=exec zorgt ervoor dat systemd wacht tot execve() succesvol is voltooid voordat de start als geslaagd wordt gemarkeerd. Een onjuist pad in ExecStart= zorgt er dan voor dat systemctl start faalt met status=203/EXEC direct in beeld. Bij Type=simple resulteert dezelfde fout in een succesmelding en blijft deze verborgen in de journal. Type=forking en Type=notify zijn hier beide onjuist; ze blijven hangen totdat TimeoutStartSec na 90 seconden verloopt.
Hoe open ik de dsh web-UI vanaf mijn laptop?
Forward de poort via SSH: ssh -N -L 3080:127.0.0.1:3080 you@your-vps, en open vervolgens http://127.0.0.1:3080/ in uw browser. Probeer de service niet te binden aan een publiek adres. dsh weigert --host 0.0.0.0 met 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, omdat de web-API de agent shell-commando's kan laten uitvoeren en er geen externe authenticatie voor staat.
Kan ik dsh als root draaien om de rechten eenvoudig te houden?
Nee. De harness is ontworpen om commando's uit te voeren en bestanden te schrijven; de privileges van de service zijn dus de privileges van de agent. Maak een systeemaccount aan met useradd --system --shell /usr/sbin/nologin dsh, wijs het eigendom van /var/lib/dsh toe aan dit account en voeg NoNewPrivileges=true toe aan de unit. Als u daarna EACCES: permission denied tegenkomt, is de gebruikelijke oorzaak een eerdere uitvoering als root waarbij bestanden met root-rechten zijn achtergebleven; sudo chown -R dsh:dsh /var/lib/dsh lost dit op.
Welke versie van dsh moet ik vastzetten in de unit?
De versie die npm view @deepseek-ai/dsh version rapporteert wanneer u de service instelt, geïnstalleerd met npm install -g @deepseek-ai/dsh@<that version> en ergens genoteerd waar u deze terug kunt vinden. 0.1.0-rc.7 was actueel op 18 augustus 2026. Het gaat niet om het nummer, maar om het feit dat npx zonder versie het pakket pas bij het opstarten resolveert. Hierdoor kan een onbeheerde herstart u ongemerkt overzetten naar een build met een afwijkend configuratieformaat.