# 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) ``` ## Возможности - **Терминал** - вкладки на нескольких машинах сразу, до четырёх терминалов в одной вкладке (перетаскиванием, с изменяемыми размерами), переименование вкладок. На телефоне - панель клавиш (Esc, Ctrl, Alt, стрелки и т.д.). - **SSH-терминал** - в меню «+»: агент открывает SSH-соединение к другому хосту (по паролю, ключу - можно загрузить файл - или своими ключами из `~/.ssh`), шелл открывается во вкладке, а шаги подключения видны в статусе. Ключ хоста запоминается при первом входе (`ssh_known_hosts` рядом с `state.json` агента); если он сменится, вход откажет. - **Файлы** - просмотр, редактор с подсветкой, hex-редактор, предпросмотр картинок, видео, PDF, таблиц SQLite; загрузка и скачивание до 512 МБ кусками по 4 МБ (проходит через Cloudflare); свойства (chmod/chown), zip/unzip. - **Перемещение и копирование** - внутри машины или, кнопкой «сеть» в том же окне, **на другую машину или на SSH-хост**: папка уходит потоком tar без промежуточных файлов, с прогрессом, скоростью и отменой. «Переместить» удаляет исходник после успешной отправки. Правила для существующего: сообщить / заменить / оставить как есть. - **Проброс портов** - `http` через панель, `tcp`/`udp` через прямой туннель. - **Статус** - нагрузка, память, диски, сеть, тест скорости канала до сервера. - **Вход** - пароль и/или ключи доступа (passkeys); пароль можно выключить, когда есть ключ. - **PIN-код** (необязательный, 4 цифры) - спрашивается в каждой новой вкладке браузера, но не при перезагрузке. На устройстве можно не спрашивать его 5/15/30/60 минут после ввода. Пять неверных попыток - выход из сессии. ## Пользователи и роли - **Администратор** видит всё: все машины, настройки, пользователей, ufw. - **Пользователь** видит только выданные ему машины (терминал, файлы, порты, статус, «Изменить» без удаления); настроек, других пользователей и чужих машин для него нет. Роль и машины задаются в «Настройки → Пользователи» (квадратная кнопка у пользователя открывает список машин). Там же галочка «Пароль» включает или выключает вход по паролю (выключить можно, когда у человека есть ключ доступа), а «Закрепить» не даёт ему поменять это самому. Последнего администратора нельзя удалить или понизить. ## Компоненты - **Сервер** (`cmd/server`) - панель и точка встречи для агентов. Ставится за nginx (TLS). - **Агент** (`cmd/agent`) - бинарь для Linux (amd64/arm64), systemd-сервис от root. - **install.sh** - установщик агента, отдаётся сервером. ## Сборка ```sh ./build.sh ``` Получаются `bin/lpra-server*` и `dist/agent-linux-{amd64,arm64}`. Версия задаётся в одном месте - `VERSION.txt`, она вшивается в сервер и агент. ## Запуск сервера ```sh ./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`); - установщик (`FALLBACK_HOST`) качает агента с прямого, если основной не отвечает; в «Полных вариантах» есть короткая команда и через прямой адрес; - если хост туннеля не задан, агентам отдаётся хост прямого адреса; - ключи доступа основного домена работают и на его поддомене. ### Cloudflare Работает за проксирующим Cloudflare: WebSocket он пропускает, файлы идут кусками меньше лимита на запрос, статика с хешем в имени кэшируется. Чтобы панель видела настоящие IP (ограничение попыток входа и кода установки считается по адресу), включите в nginx real IP: ```nginx 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 букв и цифр, кнопка «Новый код» меняет его): ```sh curl -fsSL https://panel.example/i/<код> | sudo sh ``` Или полной: ```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` идёт через адрес панели, `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