137 lines
11 KiB
Markdown
137 lines
11 KiB
Markdown
# 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 минут после ввода.
|
||
Пять неверных попыток - выход из сессии.
|
||
|
||
## Компоненты
|
||
- **Сервер** (`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
|
||
Работает за проксирующим 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`.
|
||
|
||
## Скриншоты
|
||
<img width="1256" height="709" alt="image" src="https://github.com/user-attachments/assets/5f0b30f1-d5e3-4b62-817e-4041641df6e3" />
|
||
|
||
<img width="1254" height="532" alt="image" src="https://github.com/user-attachments/assets/0207a7f0-2b59-40ee-abd1-d8673059cc24" />
|
||
|
||
<img width="1255" height="526" alt="image" src="https://github.com/user-attachments/assets/4dcbe907-33f5-493e-a56f-01cd8bf0fea1" />
|
||
|
||
<img width="1260" height="654" alt="image" src="https://github.com/user-attachments/assets/fdf59733-8e5d-46e3-b232-235ddfe1be73" />
|