Как открыть веб-интерфейс DSH на VPS с локального ПК
Веб-интерфейс DSH по умолчанию привязывается к 127.0.0.1:3080, поэтому стандартная ссылка не работает на удаленном ПК. Изучите три безопасных способа настройки доступа.
Почему веб-интерфейс DSH не открывается с вашего ноутбука
Веб-интерфейс DSH привязывается к интерфейсу loopback, поэтому URL, выведенный в вашем терминале, работает только на той машине, которая его сгенерировала. npx @deepseek-ai/dsh web сообщает URL для запуска в формате http://127.0.0.1:3080/?token=..., а 127.0.0.1 означает «компьютер, считывающий этот адрес» для любого устройства, которое его обрабатывает. Ваш ноутбук считывает его, обращается к собственному интерфейсу loopback и обнаруживает, что там никто не ожидает соединений. Если сам адрес вызывает вопросы, почему DSH выводит http://127.0.0.1:3080 подробно разъясняет этот момент. Не меняйте параметры привязки DSH. Вместо этого создайте аутентифицированный канал от вашего ноутбука до адреса loopback на VPS.
Настройка по умолчанию для loopback верна, и все методы на этой странице сохраняют её. За портом 3080 скрывается агент, выполняющий shell-команды на вашем сервере, а веб-интерфейс не содержит страницы входа. DSH теперь аутентифицирует браузер с помощью токена запуска: каждый запуск процесса создает новый токен, напечатанный URL содержит его, а при первом посещении он обменивается на сессионную cookie. Этот токен является единственным bearer-учетным данным, передаваемым в URL, поэтому loopback остается наиболее важным уровнем защиты: чтобы вообще получить доступ к сокету, вы уже должны иметь shell-доступ к серверу.
К чему привязывается DSH и как это проверить
Согласно README для DeepSeek Harness, проверенному 11 октября 2026 года, npx @deepseek-ai/dsh web «по умолчанию запускает веб-интерфейс на http://127.0.0.1:3080». Справочник CLI в том же репозитории указывает, что порт по умолчанию остается 3080, что --host «должен указывать один конкретный локальный адрес, а не подстановочный знак», и что --host 0.0.0.0 завершает работу с ошибкой использования. DSH находится на стадии предварительного просмотра для разработчиков, и в README заглавными буквами указано, что возможны изменения, нарушающие обратную совместимость. Подтвердите оба значения на своей установке, а не доверяйте любой странице, включая эту.
dsh --profile web --dump-config
ss -ltnp | grep 3080--dump-config выводит итоговое дерево конфигурации без запуска агента, поэтому оно показывает хост и порт, которые остаются после применения всех уровней патчей. ss показывает, что именно прослушивается в данный момент. Корректная строка выглядит так:
LISTEN 0 511 127.0.0.1:3080 0.0.0.0:* users:(("node",pid=8123,fd=24))Смотрите только на адрес перед двоеточием. 127.0.0.1:3080 означает привязку только к loopback, что и требуется. Любой другой адрес означает, что DSH обслуживает сеть, к которой принадлежит этот адрес, что может быть публичной сетью. Если ss не выводит ни одной строки, значит, DSH не запущен, и никакой туннель не поможет, пока он не заработает. Процесс запуска описан в разделе установка и запуск DeepSeek Harness на VPS.
Флаги применяются к одному запуску. Чтобы сделать значение постоянным, отредактируйте уровень патча профиля. dsh --profile <name> загружает профиль по пути $DSH_HOME/profiles/<name>, а уровни накладываются в следующем порядке: патчи пакета, собственный cordis.patch.yml профиля, $DSH_HOME/cordis.patch.yml на уровне домашней директории, а затем любой оверлей --patch. Настройки прослушивания находятся в плагине @deepseek-ai/dsh-host-webserver как host и port. Перед редактированием прочитайте свое итоговое дерево конфигурации с помощью --dump-config, так как веб-профиль при первом запуске записывает себя из поставляемого шаблона, а в предварительной версии структура этого шаблона постоянно меняется.
Строка запуска также определяет, откуда берутся ваши учетные данные. DSH выводит корневой URL с токеном процесса в запросе token, и только GET /?token=... принимает его: не API-путь и не заголовок Authorization. Этот запрос устанавливает подписанный, привязанный к хосту файл cookie HttpOnly, SameSite=Strict и перенаправляет на чистый URL. Токен меняется при каждом перезапуске и нигде не сохраняется, а время жизни cookie по умолчанию ограничено 30 днями. Запрос без действительных учетных данных получает минимальный ответ 401. Скопируйте весь выведенный URL, включая токен, так как каждый метод, описанный ниже, заканчивается тем, что вы открываете его.
Второй барьер: проверка доверия /api
Доставка пакетов на порт 3080 — это лишь половина дела. В DSH предусмотрена вторая проверка, которая приводит к сбивающему с толку сбою: страница загружается, макет отображается, но ничего не работает.
Плагин @deepseek-ai/dsh-client-connection содержит настройку trustedHosts. В его README указано правило: заголовок Host должен содержать loopback-адрес, собственный адрес привязки (bind address) слушающего сокета на любом порту или соответствовать записи trustedHosts. Кроме того, когда браузер отправляет заголовок Origin, он должен указывать ту же authority. Этот барьер срабатывает перед аутентификацией для каждого запроса и отклоняет любой запрос /api, который не проходит проверку. Браузер сообщает об отказе следующим образом:
transport failure for /api/host.describe: HTTP 403Поэтому любой прокси, установленный перед DSH, должен объявлять то имя, которое вводит пользователь в браузере. CLI принимает флаг --trusted-host <authority>, который можно использовать несколько раз:
dsh web --trusted-host dsh.example.comБарьер сопоставляет authority (хост и порт) и нормализует порты по умолчанию для схемы слушателя. Запись без указания порта, например dsh.example.com, соответствует этому имени на любом порту, поэтому она также охватывает dsh.example.com:8443. Запись с указанием порта, например dsh.example.com:8443, соответствует только этому конкретному хосту и порту. Для loopback-адресов запись не требуется, поэтому localhost:3080 и 127.0.0.1:3080 проходят проверку успешно. Ошибка 403, которую вы не можете объяснить, почти всегда вызвана расхождением между именем в адресной строке браузера и именами в вашем списке доверенных узлов. Ошибка 401 — это другая проблема: барьер пропустил запрос, но у браузера еще нет сессионной cookie.
Этот барьер является проверкой источника (origin check). Это не аутентификация. Браузеру самим протоколом запрещено устанавливать заголовок Host, поэтому данная проверка действительно предотвращает ситуацию, когда страница на другом сайте управляет вашим агентом. Любой клиент, не являющийся браузером, может свободно записывать этот заголовок. Рассматривайте trustedHosts как настройку совместимости для прокси, а не как средство обеспечения безопасности.
Вариант 1: локальное перенаправление SSH
Используйте этот метод в первую очередь, так как он не требует внесения изменений в настройки сервера.
ssh -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps-L открывает порт для прослушивания на вашем ноутбуке и перенаправляет каждое соединение на 127.0.0.1:3080, как оно разрешается на VPS. -N означает «не выполнять удаленную команду», поэтому сессия поддерживает только перенаправление. Указание локальной стороны как 127.0.0.1:3080, а не просто 3080, сделано намеренно: это привязывает прослушиватель на стороне ноутбука к loopback, поэтому настройка GatewayPorts в конфигурации вашего SSH-клиента не сможет незаметно опубликовать агент в сети, к которой вы подключены.
Теперь откройте URL для запуска, который вывел DSH, вместе с токеном, в браузере на вашем ноутбуке. Поскольку перенаправление прослушивает 127.0.0.1:3080, выведенный http://127.0.0.1:3080/?token=... работает без изменений. Простого http://localhost:3080 недостаточно в новой сессии браузера: без cookie DSH ответит ошибкой 401. После первого посещения cookie будет поддерживать вашу авторизацию, но он привязан к хосту, поэтому продолжайте использовать имя, которое открыли первым, так как cookie, установленный для 127.0.0.1, не будет отправлен на localhost. Здесь бесплатно работают две вещи. Заголовок Host является loopback-авторитетом, поэтому проверка /api проходит без какой-либо конфигурации. Кроме того, браузеры рассматривают loopback-источники, такие как http://127.0.0.1 и http://localhost, как безопасные контексты, что важно, поскольку веб-приложение DSH вызывает crypto.randomUUID() во время загрузки, а браузеры предоставляют эту функцию только через HTTPS или на loopback-источнике.
Добавьте -f, чтобы ssh переходил в фоновый режим после установки перенаправления:
ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vpsЧестный компромисс. Ничего нового не прослушивает публичный адрес, и правила брандмауэра не меняются, поэтому это минимально возможное увеличение поверхности атаки. Весь ваш контроль доступа — это конфигурация SSH, что делает SSH только по ключам с отключенным входом по паролю обязательным условием, а не просто полезным дополнением. Стоимость этого решения в том, что туннель принадлежит одной клиентской сессии. Он разрывается при переходе ноутбука в спящий режим, вы перезапускаете его вручную, и он не предоставляет доступа с телефона.
Вариант 2: Использование Tailscale serve на VPS
Tailscale создает частную сеть между вашими устройствами. Установка Tailscale на VPS предоставляет этой машине адрес 100.x.y.z, к которому могут обращаться только устройства в вашей tailnet, а политики контроля доступа определяют, кто именно может подключиться. Предоставление доступа к машине распространяет эту возможность на устройства внешнего пользователя.
Простого подключения к сети недостаточно, и именно здесь часто возникают трудности. DSH не прослушивает адрес 100.x.y.z, так как по умолчанию привязан к loopback. Браузер при попытке обращения к http://100.x.y.z:3080 получит ошибку connection refused, поскольку на этом адресе нет открытого сокета. --host действительно принимает этот конкретный адрес, но привязка к внешнему интерфейсу переключает сервис на обычный HTTP и выводит предупреждение, что возвращает нас к проблеме безопасного контекста из Варианта 1.
tailscale serve — это компонент, который связывает эти части. Он запускается на VPS, принимает трафик из вашей частной сети и проксирует его на локальный адрес:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
sudo tailscale serve --bg --https=443 localhost:3080
tailscale serve statustailscale serve status выводит URL в формате https://<machine>.<tailnet>.ts.net/. Запустите DSH с доверием к этому имени, иначе /api вернет ошибку 403, как описано выше:
dsh web --public-url https://your-vps.your-tailnet.ts.net/ --trusted-host your-vps.your-tailnet.ts.net--public-url меняет только то, что выводит DSH, поэтому URL для запуска будет содержать имя tailnet и откроется на телефоне без изменений. Сам по себе этот параметр ничего не разрешает, поэтому --trusted-host по-прежнему необходим. Выведенный URL содержит активные учетные данные, поэтому вставляйте его только в браузеры на ваших собственных устройствах.
Это также устраняет проблему безопасного контекста. Tailscale терминирует реальный HTTPS с помощью сертификата, выданного для этого имени, поэтому crypto.randomUUID() становится доступным, и интерфейс загружается в мобильном браузере. Трафик не выходит в публичный интернет, так как serve публикует сервис только внутри вашей частной сети.
Не используйте здесь tailscale funnel. Это команда из того же семейства, но предназначенная для публичного интернета; она разместит агент с доступом к оболочке на имени хоста, которое может разрешить любой желающий. Прочтите как serve удерживает трафик внутри вашей tailnet, в то время как funnel публикует его наружу перед использованием любой из этих команд. Информацию о настройке для мобильных устройств см. в доступе к self-hosted агенту с телефона.
Компромиссом является зависимость от стороннего сервиса. Каждое устройство, которому нужен интерфейс, должно быть подключено к сети, а координационный сервер, который вы не контролируете, определяет состав участников. Если это неприемлемо, self-hosted сервер управления Headscale использует тот же протокол на вашем собственном оборудовании.
Вариант 3: аутентифицированный TLS reverse proxy
Используйте этот метод, если браузер не может подключиться к частной сети, например, при работе с компьютера, которым вы не управляете. Теперь вы публикуете имя хоста в Интернете, поэтому аутентификация должна быть настоящей и исходить от прокси. Launch token в DSH — это лишь bearer-учетные данные в URL, которыми пользуются все, у кого есть ссылка, поэтому этот механизм не считается полноценным уровнем защиты.
TLS означает transport layer security, шифрование, лежащее в основе https://. Направьте nginx на порт loopback и установите пароль перед ним. Блок map относится к контексту http, поэтому вынесите его в отдельный файл в /etc/nginx/conf.d/:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}Затем сам сайт:
server {
listen 443 ssl;
server_name dsh.example.com;
ssl_certificate /etc/letsencrypt/live/dsh.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dsh.example.com/privkey.pem;
auth_basic "dsh";
auth_basic_user_file /etc/nginx/dsh.htpasswd;
location / {
proxy_pass http://127.0.0.1:3080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cookie_flags ~ secure;
proxy_read_timeout 3600s;
proxy_buffering off;
}
}Создайте файл паролей, протестируйте конфигурацию и перезагрузите:
sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/dsh.htpasswd you
sudo nginx -t && sudo systemctl reload nginxnginx -t должен вывести syntax is ok и test is successful. Если вместо этого выводится ошибка, nginx продолжит использовать старую конфигурацию, так что ничего не сломается. Затем запустите DSH с доверенным публичным именем, так как proxy_set_header Host $host пересылает dsh.example.com, и в противном случае защитный механизм отклонит запрос. --public-url заставляет сгенерированный launch URL использовать это имя, чтобы вы могли открыть его напрямую:
dsh web --public-url https://dsh.example.com/ --trusted-host dsh.example.comТри директивы здесь критически важны. Заголовки Upgrade и Connection поддерживают долгоживущее соединение интерфейса открытым; без них nginx закроет его, и интерфейс будет постоянно переподключаться, отображая устаревшие данные. proxy_read_timeout 3600s не дает nginx прервать выполнение агента, если он «думает» дольше стандартных 60 секунд. proxy_cookie_flags ~ secure помечает сессионную cookie DSH Secure на этапе HTTPS, как того требует руководство по публичному развертыванию DSH, поскольку backend на обычном HTTP выдает cookie без этого флага. DSH также может обслуживать HTTPS самостоятельно с помощью --tls-cert и --tls-key, но он не умеет получать или обновлять сертификаты и не добавляет парольную защиту, поэтому не заменяет здесь прокси. Обоснование остальных параметров приведено в разборе конфигурации nginx reverse proxy по директивам, а выбор самого прокси — в сравнении nginx с Caddy и Traefik.
Четко осознавайте, что дает basic auth. Он защищает от автоматических сканеров и гораздо лучше, чем открытый порт. Однако это один общий пароль перед оболочкой, без второго фактора и возможности отозвать доступ у конкретного человека. Любой, кто узнает пароль и получит launch URL или сессионную cookie, сможет выполнять команды от имени пользователя, под которым запущен DSH. Если доступ нужен нескольким людям, лучше установите перед сервисом полноценный прокси идентификации и прочитайте правила безопасности при запуске агента для программирования на VPS перед тем, как расширять доступ.
Что произойдет при привязке к 0.0.0.0 и открытии порта
Очевидный путь — изменить привязку и открыть порт в межсетевом экране. DSH блокирует использование подстановочного адреса. --host отклоняет 0.0.0.0, а плагин @deepseek-ai/dsh-host-webserver отвергает неопределенный адрес в любом виде при загрузке конфигурации, включая 0.0.0.0, :: и формы IPv4-mapped. То, что host принимает — это один конкретный литерал адреса IPv4 или IPv6, поэтому указание вашего публичного IP является настройкой, которую DSH примет. Существуют руководства сообщества, предлагающие удалить проверку подстановочного адреса из установленного пакета. Они сопровождаются предупреждениями, и эти предупреждения обоснованы. Тот же уровень плагинов гораздо полезнее использовать в обратном направлении, где лимиты расходов и правила разрешений для инструментов ограничивают действия агента, а не расширяют область его прослушивания.
Вот что на самом деле дает публичная привязка. Порт 3080 отвечает на вашем публичном IP-адресе по обычному HTTP, и DSH выводит соответствующее предупреждение. Страницы входа нет. Единственной защитой является токен запуска, который передается через интернет в открытом виде внутри URL, как и сессионная cookie после него. Защита /api проверяет заголовок Host, который любой небраузерный клиент может задать самостоятельно, и в любом случае разрешает адрес привязки на любом порту, поэтому указание вашего адреса в trustedHosts ничего не меняет для злоумышленника, обладающего curl. Собственное руководство по развертыванию DSH гласит, что ни объявленный URL, ни защита не оберегают сам прослушиваемый порт. За этим портом находится агент, который выполняет shell-команды от имени вашего пользователя вместе с учетными данными из $DSH_HOME, где DSH хранит профили и API-ключи. Один перехваченный токен или cookie означает удаленное выполнение кода на вашем сервере с оплатой по вашему счету. Сканеры непрерывно проверяют все адресное пространство, поэтому скрытый IP-адрес не является мерой контроля. Любой секрет, который может прочитать агент, будет скомпрометирован вместе с доступом к shell.
Все описанные выше методы существуют для того, чтобы вам никогда не пришлось этого делать. SSH-проброс — стандартный вариант для одного пользователя с одним ноутбуком. Переходите на tailscale serve, если используется телефон или требуется настоящий сертификат, и используйте публичный reverse proxy в тех случаях, когда браузер, которым вы не управляете, должен получить доступ к UI, при условии наличия перед ним полноценной аутентификации.
FAQ
Почему http://127.0.0.1:3080 не открывается с моего ноутбука?
Потому что 127.0.0.1 означает «машина, с которой открыт этот адрес». DSH выводит его на VPS, где это корректно. Ваш ноутбук читает ту же строку и обращается к собственному loopback-интерфейсу, где ничего не запущено. Проверьте состояние сервера с помощью ss -ltnp | grep 3080 на VPS. Строка с 127.0.0.1:3080 означает, что DSH запущен и привязан к loopback намеренно. Вам нужен туннель или прокси, а не другой адрес привязки, а затем — полный URL запуска с токеном.
Можно ли запустить dsh web с --host 0.0.0.0?
Нет. Справочник CLI в репозитории, проверенный 11 октября 2026 года, гласит, что --host должен указывать на один конкретный локальный адрес, а не на wildcard, иначе --host 0.0.0.0 завершится с ошибкой использования. Плагин веб-сервера отклоняет этот же адрес при загрузке конфигурации. Конкретный адрес, например ваш публичный IP, принимается, но он работает по обычному HTTP, поэтому токен запуска, защищающий оболочку, передаётся по сети в открытом виде. Патчи от сообщества убирают проверку wildcard. Если вы примените такой патч, ответственность за межсетевой экран и транспортную безопасность, которую DSH не обеспечивает, ложится на вас.
Почему все вызовы /api возвращают HTTP 403 за моим reverse proxy?
Механизм доверия /api отклоняет любой запрос, если заголовок Host не является адресом loopback, адресом привязки слушателя или не указан в trustedHosts. За прокси этот заголовок содержит ваше публичное имя, поэтому механизм отклоняет его, а браузер записывает transport failure for /api/host.describe: HTTP 403. Запустите DSH с --trusted-host <your name> и укажите имя, соответствующее адресу в адресной строке вашего браузера. Запись без порта соответствует этому имени на любом порту, а запись с портом должна точно совпадать по хосту и порту. Если вызовы возвращают 401, значит, проверка пройдена, но у браузера ещё нет сессии — откройте выведенный URL запуска с токеном.
Почему интерфейс DSH загружается, но не завершает запуск по обычному HTTP?
Веб-приложение вызывает crypto.randomUUID() при загрузке, а браузеры предоставляют эту функцию только в безопасном контексте: HTTPS или origin типа loopback, например http://localhost. При передаче по обычному HTTP с голого IP-адреса функция не определена, поэтому зависящие от неё вызовы выдают ошибку, и интерфейс не заполняется. Использование HTTPS с tailscale serve или TLS reverse proxy решает проблему, как и доступ через SSH-форвард на http://localhost.
Какой метод следует использовать разработчику-одиночке?
SSH local forward. Он не требует изменений на сервере, не меняет правила межсетевого экрана и использует SSH-ключ, которому вы уже доверяете. Выполните ssh -f -N -L 127.0.0.1:3080:127.0.0.1:3080 you@your-vps, затем откройте URL запуска, который вывел DSH, http://127.0.0.1:3080/?token=..., сохранив токен. Переходите к tailscale serve, когда вам понадобится интерфейс на телефоне или доступ, который не прерывается при переходе ноутбука в спящий режим.