Files
2026-10-20 14:21:10 +03:00

24 KiB
Raw Permalink Blame History

LPRA - LongPolling Remote Access

Лёгкий self-hosted сервис для доступа к Linux-машинам без белого IP (за NAT). Агент на клиенте сам исходяще подключается к серверу - по WebSocket, а где он не проходит, по HTTP long-polling. В веб-панели видно список машин, и по каждой есть терминал в браузере, файловый менеджер, проброс портов и статус.

браузер ──HTTPS/WS──> [Cloudflare] ──> [nginx TLS] ──> [Go server 127.0.0.1:8091] <──WS / long-poll── агент (за NAT)
                                                              │         ▲
                                                            SQLite      └──── прямой TCP/UDP-туннель :8446 (проброс tcp/udp)

Возможности

  • Одна страница - Машины, WorkSpace (терминалы и файлы), Настройки; переходы без перезагрузки. Вкладки WorkSpace живут, пока открыта вкладка браузера: уход на другие страницы их не закрывает, перезагрузка сбрасывает (браузер сначала спросит). «Порты», «Статус» и «Изменить» машины открываются окном поверх текущей страницы - из списка машин или правым кликом по заголовку вкладки или панели.
  • WorkSpace - вкладки на нескольких машинах сразу, до четырёх панелей в одной вкладке - терминал или файловый менеджер (перетаскиванием, с изменяемыми размерами), переименование вкладок. На телефоне - панель клавиш (Esc, Ctrl, Alt, стрелки и т.д.).
  • SSH-терминал - в меню «+»: агент открывает SSH-соединение к другому хосту (по паролю, ключу - можно загрузить файл - или своими ключами из ~/.ssh), шелл открывается во вкладке, а шаги подключения видны в статусе. Ключ хоста запоминается при первом входе (ssh_known_hosts рядом с state.json агента); если он сменится, вход откажет.
  • Файлы - просмотр, редактор с подсветкой, hex-редактор, предпросмотр картинок, видео, PDF, баз SQLite с правкой (значение по клику, добавление и удаление строк, вкладка SQL; агент меняет базу через сам SQLite, так что работающая база не ломается); загрузка и скачивание до 512 МБ кусками по 4 МБ (проходит через Cloudflare); свойства (chmod/chown), zip/unzip.
  • Файлы по SSH - в меню «+» › «Файловый менеджер» › «SSH-хост…»: машина подключается к хосту и работает с файлами командами по SSH, SFTP не нужен. Есть всё, кроме редактора SQLite; zip и распаковка - если на хосте установлены zip и unzip. Оборвалось подключение - панель предложит переподключиться.
  • Перетаскивание файлов - строку (или все отмеченные) в другую файловую панель или в папку в ней: окно «Переместить / Копировать» у места броска, между машинами и SSH-хостами - с прогрессом. В терминал той же машины или того же SSH-хоста - вставляется путь в кавычках, в терминал другой - нельзя, с подсказкой почему. Задержать над заголовком вкладки - она откроется. Только на компьютере.
  • Перемещение и копирование - внутри машины или, кнопкой «сеть» в том же окне, на другую машину или на SSH-хост: папка уходит потоком tar без промежуточных файлов, с прогрессом, скоростью и отменой. «Переместить» удаляет исходник после успешной отправки. Правила для существующего: сообщить / заменить / оставить как есть.
  • ИИ-ассистент - круглая кнопка чата (на компьютере её и окно можно перетаскивать); каждый включает себе сам в профиле. Видит вкладки, активный терминал или папку и список ваших машин, отвечает потоком. Действия - кнопками: выполнить команду в терминале нужной машины, прочитать, показать или записать файл - всё с правами пользователя. Плашка «Агент» - делает шаги сам, спрашивая перед каждой командой и записью; «Думать» - сначала рассуждает (свёрнуто над ответом). Модели задаёт администратор в «Настройки › ИИ-ассистент»: их может быть несколько, у каждой своё название в чате (и короткое, до 12 символов, - для кнопки выбора), тип - Gradio (по умолчанию - прокси к Qwen Omni), OpenAI-совместимый API или Anthropic, адрес, модель и ключ (хранится на сервере, в браузер не уходит). Если моделей несколько, в чате рядом с плашками появляется выбор, первая - по умолчанию; выбор запоминается за пользователем. В «Аккаунт › Вид» можно показывать выбор всегда. Плашка «Поиск» даёт ассистенту искать в интернете; поисковик выбирается там же: DuckDuckGo (по умолчанию, без настройки), свой SearXNG (нужен search.formats: [json]), Brave Search API или Tavily (по ключу). Если выбранный не ответил, поиск идёт через DuckDuckGo. Картинки: если администратор задал API-ключ в «Настройки › ИИ-ассистент › Генерация изображений» (Vertex API), модели сами, по смыслу разговора и без плашки, рисуют картинки и правят фото - то, что пользователь прикрепил к сообщению, или последнюю созданную картинку (блоки ```image и ```image edit, формат и качество - в первой строке; улучшение описания можно отключить: noenhance). Картинка показывается в чате, описание в карточке сворачивается. Название прикреплённого файла открывает его (или скачивает). Удаление чата стирает его содержимое, в том числе текст вложений, из файлов базы; картинки хранятся на сервисе Vertex и оттуда не удаляются.
  • Сохранённые SSH-хосты - в окне SSH-подключения галочка «Сохранить»: хост и вход (пароль или ключ) хранятся на сервере, у каждого пользователя свои, зашифрованы AES-256-GCM ключом сервера. Ключ берётся из LPRA_SECRET_KEY (32 байта, hex или base64) или из файла <база>.key рядом с базой, который создаётся сам - храните его вместе с бэкапом базы, без него сохранённые входы не расшифровать.
  • Проброс портов - http через панель, tcp/udp через прямой туннель.
  • Статус - нагрузка, память, диски, сеть, тест скорости канала до сервера.
  • Автообновление («Настройки › Автообновление», обе части выключены, пока не включены): клиентов - агенты раз в N минут/часов/суток сверяют свою версию с версией сервера и, если у сервера новее, обновляются сами (как по кнопке «Обновить»; нужен root или sudo без пароля; агентам до 0.2.5 один раз обновиться придётся вручную); сервера - сервер раз в N сверяет себя с последним релизом на GitLab, и когда вышел новый, администратору при входе (после PIN) показывается окно «Обновить / Отказаться» - отказ запоминается для этой версии. «Обновить» запускает server-install.sh отдельной службой systemd (нужны root и systemd); там же кнопки «Проверить сейчас» и «Обновить сейчас».
  • Вход - пароль и/или ключи доступа (passkeys); пароль можно выключить, когда есть ключ.
  • PIN-код (необязательный, 4 цифры) - спрашивается в каждой новой вкладке браузера, но не при перезагрузке. На устройстве можно не спрашивать его 5/15/30/60 минут после ввода. Пять неверных попыток - выход из сессии.

Пользователи и роли

  • Администратор видит всё: все машины, настройки, пользователей, ufw.
  • Пользователь видит только выданные ему машины (терминал, файлы, порты, статус, «Изменить» без удаления); настроек, других пользователей и чужих машин для него нет.

Роль и машины задаются в «Настройки → Пользователи» (квадратная кнопка у пользователя открывает список машин). Там же галочка «Пароль» включает или выключает вход по паролю (выключить можно, когда у человека есть ключ доступа), а «Закрепить» не даёт ему поменять это самому. Последнего администратора нельзя удалить или понизить.

База данных

По умолчанию - SQLite (файл из -db, по умолчанию lpra.db). В «Настройки › База данных» можно выбрать PostgreSQL: хост, порт, база, пользователь, пароль, SSL; кнопка «Проверить и сохранить» подключается и записывает выбор в <база>.backend.json рядом с SQLite-файлом (права 0600), «Перезапустить сервер» завершает процесс - systemd (Restart=always) поднимает его уже на PostgreSQL. При первом запуске на пустой PostgreSQL все данные копируются из SQLite-файла, сам файл остаётся как был (к нему можно вернуться тем же выбором). История ИИ-ассистента тоже хранится в базе - у каждого пользователя последние 200 сообщений.

Компоненты

  • Сервер (cmd/server) - панель и точка встречи для агентов. Ставится за nginx (TLS).
  • Агент (cmd/agent) - бинарь для Linux (amd64/arm64), systemd-сервис от root.
  • install.sh - установщик агента, отдаётся сервером.

Сборка

./build.sh

Получаются bin/lpra-server* и dist/agent-linux-{amd64,arm64}. Версия задаётся в одном месте - VERSION.txt, она вшивается в сервер и агент.

Установка сервера одной командой

На Linux-сервере (amd64 или arm64, systemd):

wget -qO- https://gitlab.com/itzredtea/lpra/-/raw/main/server-install.sh | sudo sh

Скрипт server-install.sh:

  • качает бинарник сервера под архитектуру машины из последнего релиза и папку dist/ (бинарники агентов) из ветки main, кладёт всё в /opt/lpra;
  • ставит службу systemd lpra-server (порт спросит, по умолчанию 8091);
  • по желанию настраивает nginx: спросит домен, создаст сайт на порт 80 (при необходимости поставит nginx), затем certbot --nginx - если certbot с плагином nginx не установлен, этот шаг пропускается;
  • если включён ufw - предложит открыть 80, 443 и туннель 8446 tcp/udp;
  • запускает службу, перезагружает nginx и проверяет, что сервер отвечает.

Вопросы задаются в терминале, даже когда скрипт идёт через | sudo sh. Без терминала ответы берутся из переменных: LPRA_PORT, LPRA_NGINX (y/n), LPRA_DOMAIN, LPRA_EMAIL, LPRA_CERTBOT (y/n), LPRA_UFW (y/n):

wget -qO- https://gitlab.com/itzredtea/lpra/-/raw/main/server-install.sh | sudo LPRA_NGINX=y LPRA_DOMAIN=lpra.example.com sh

Повторный запуск той же командой - обновление: бинарник и dist/ заменяются, база (/opt/lpra/lpra.db и её ключ lpra.db.key), настройки службы и nginx остаются. После установки откройте /setup - мастер создаст администратора.

Запуск сервера

./bin/lpra-server -addr 127.0.0.1:8091 -dist ./dist -db ./lpra.db
Флаг Переменная По умолчанию
-addr LPRA_ADDR 127.0.0.1:8091 адрес HTTP (за nginx)
-db LPRA_DB lpra.db база SQLite
-dist LPRA_DIST dist бинарники агента для /download/
-tunnel LPRA_TUNNEL_ADDR :8446 прямой туннель TCP+UDP (пусто - выключен)
-insecure-cookies LPRA_INSECURE_COOKIES=1 куки без Secure - только для локального запуска без TLS

Пример юнита - deploy/lpra-server.service.

Первый запуск: откройте /setup - мастер создаст администратора. Дальше в /settings: адрес хоста, enroll-секрет, короткий код установки и готовая команда.

nginx

Конфиг - deploy/nginx.conf.example. Обязательно:

  • заголовки Upgrade / Connection (через map $http_upgrade $connection_upgrade) - без них WebSocket не пройдёт, и агенты и терминалы останутся на long-polling (работает, но медленнее на больших выводах);
  • proxy_read_timeout больше 25 с (удержание long-poll), proxy_buffering off.

Прямой адрес (без Cloudflare)

В «Настройки → Адрес хоста» можно задать второй, прямой адрес - например https://direct.lpra.example.com, DNS-запись без проксирования. Тогда:

  • агенты, не достучавшись до основного адреса 30 секунд, переходят на прямой и каждые 5 минут проверяют, не ожил ли основной; уже установленные агенты получают прямой адрес с сервера сами (он хранится в state.json);
  • установщик берёт адреса с того домена, с которого его скачали: с основного - агент работает через основной с резервом на прямой, с прямого - наоборот (и бинарник качается с прямого); если первый адрес не отвечает, бинарник качается со второго. В «Полных вариантах» есть короткая команда и через прямой адрес;
  • в «Изменить → Связь с сервером» у клиента, подключённого через прямой адрес, после статуса стоит «(direct)»;
  • если хост туннеля не задан, агентам отдаётся хост прямого адреса;
  • ключи доступа основного домена работают и на его поддомене.

Cloudflare

Работает за проксирующим Cloudflare: WebSocket он пропускает, файлы идут кусками меньше лимита на запрос, статика с хешем в имени кэшируется. Чтобы панель видела настоящие IP (ограничение попыток входа и кода установки считается по адресу), включите в nginx real IP:

real_ip_header CF-Connecting-IP;
set_real_ip_from 173.245.48.0/20;   # и остальные диапазоны с https://www.cloudflare.com/ips/

Прямой туннель (:8446) - это не HTTP, через проксирующий Cloudflare он не идёт: в «Настройки → Прямой туннель» укажите для агентов IP хоста или DNS-имя без проксирования.

Установка агента

Короткой командой из /settings (код из 3-24 букв и цифр, кнопка «Новый код» меняет его):

curl -fsSL https://panel.example/i/<код> | sudo sh

Или полной:

curl -fsSL https://panel.example/install.sh | sudo HOST=https://panel.example ENROLL_SECRET=<секрет> sh

Без параметров установщик спросит секрет, а если агент уже стоит - предложит удалить. Обновить агента можно кнопкой в панели («Изменить» → обновить).

Связь агента с сервером

В «Изменить» → «Связь с сервером» для каждой машины:

  • auto (по умолчанию) - WebSocket; если он не подключается, long-polling ровно минуту, потом снова попытка WebSocket;
  • ws - только WebSocket;
  • lp - только long-polling.

Проброс портов и прямой туннель

Правила создаются на странице машины «Порты». Режим http (в панели - http-lp) идёт через соединение агента с панелью: по WebSocket, а если он недоступен - по long-polling; tcp/udp - через прямой туннель: агент держит исходящее соединение к порту туннеля хоста, по нему мультиплексируются все соединения.

Порт туннеля и хост для агентов меняются в /settings → «Прямой туннель»; агенты забирают их сами, переустановка не нужна. Локальные TUNNEL_ADDR / TUNNEL_PORT в окружении агента имеют приоритет (TUNNEL_ADDR=off отключает туннель).

Если на хосте активен ufw и сервер запущен от root, панель сама открывает порты правил и порт туннеля, а при удалении правила закрывает. При ProtectSystem=strict в юните нужно ReadWritePaths=/etc/ufw /run.

Пользователь терминала

По умолчанию терминал открывается от пользователя агента (root). В «Изменить машину» можно выбрать другого пользователя или создать нового (с паролем, по желанию с sudo) - если агент запущен от root. Файловый менеджер работает от пользователя агента.

Безопасность

  • Пароли админов - bcrypt; сессии в HttpOnly/Secure/SameSite куках; CSRF на формах; ограничение попыток входа, PIN и кода установки.
  • Ключи доступа (WebAuthn) - вход без пароля; пароль можно выключить совсем.
  • Агенты: общий enroll-секрет → индивидуальный токен (хранится хешем, сверка constant-time); туннель авторизуется HMAC от токена.
  • SSH-пароли и ключи передаются агенту один раз для подключения и нигде не сохраняются.
  • Открытие терминала и SSH, перенос файлов на другие машины, смена пользователя пишутся в audit.

Скриншоты

image image image image

*Автор иконки репозитория - * grafixpoint