Files
lpra/README.md
T
2026-10-20 11:20:05 +00:00

240 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](https://s2.x-grk.com/api/vertex/docs)), модели сами, по смыслу разговора и без плашки, рисуют картинки
и правят фото - то, что пользователь прикрепил к сообщению, или последнюю созданную картинку
(блоки ```` ```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** - установщик агента, отдаётся сервером.
## Сборка
```sh
./build.sh
```
Получаются `bin/lpra-server*` и `dist/agent-linux-{amd64,arm64}`.
Версия задаётся в одном месте - `VERSION.txt`, она вшивается в сервер и агент.
## Установка сервера одной командой
На Linux-сервере (amd64 или arm64, systemd):
```sh
wget -qO- https://gitlab.com/itzredtea/lpra/-/raw/main/server-install.sh | sudo sh
```
Скрипт `server-install.sh`:
- качает бинарник сервера под архитектуру машины из [последнего релиза](https://gitlab.com/itzredtea/lpra/-/releases/permalink/latest)
и папку `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):
```sh
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` - мастер
создаст администратора.
## Запуск сервера
```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`);
- установщик берёт адреса с того домена, с которого его скачали: с основного - агент работает через основной
с резервом на прямой, с прямого - наоборот (и бинарник качается с прямого); если первый адрес не отвечает,
бинарник качается со второго. В «Полных вариантах» есть короткая команда и через прямой адрес;
- в «Изменить → Связь с сервером» у клиента, подключённого через прямой адрес, после статуса стоит «(direct)»;
- если хост туннеля не задан, агентам отдаётся хост прямого адреса;
- ключи доступа основного домена работают и на его поддомене.
### 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` (в панели - 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`.
## Скриншоты
<img width="1256" height="709" alt="image" src="docs/screenshots/1.png" />
<img width="1254" height="532" alt="image" src="docs/screenshots/2.png" />
<img width="1255" height="526" alt="image" src="docs/screenshots/3.png" />
<img width="1260" height="654" alt="image" src="docs/screenshots/4.png" />
*Автор иконки репозитория - * [grafixpoint](https://www.flaticon.com/authors/grafixpoint)