Кастомные балансеры
Кастомные балансеры позволяют подключать собственные источники контента без изменения основного кода сервера. Каждый балансер — standalone Go-программа, которая запускается как subprocess.
Как это работает
Lampa (клиент)
|
lampac-go (порт 888)
|
+-- /lite/source/rezka --> встроенный балансер
+-- /lite/source/collaps --> встроенный балансер
+-- /lite/source/mybalancer --> reverse proxy --> 127.0.0.1:50001 (subprocess)Сервер при старте:
- Сканирует
custom_balancers/на наличие поддиректорий сconfig.json - Компилирует и запускает балансеры с
auto_start: true - Проксирует запросы
/lite/source/{name}на порт subprocess-а - Мониторит состояние (перезапуск при падении)
Создание балансера
Структура файлов
custom_balancers/
mybalancer/
main.go <-- исходный код
config.json <-- конфигурация
go.mod <-- создаётся автоматически
mybalancer <-- бинарник (после компиляции)config.json
{
"name": "mybalancer",
"display_name": "My Balancer",
"port": 0,
"quality_badge": "FHD",
"content_type": "both",
"host": "https://example-source.com",
"auto_start": true
}| Поле | Тип | Описание |
|---|---|---|
name | string | Уникальный ID (a-zA-Z0-9_-, макс. 64 символа) |
display_name | string | Название в интерфейсе |
port | int | TCP-порт (0 = автоматически, начиная с 50000) |
quality_badge | string | Бейдж: SD, HD, FHD, 4K |
content_type | string | movie, serial или both |
host | string | URL источника (передаётся флагом -host) |
auto_start | bool | Запускать при старте сервера |
main.go — минимальный шаблон
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
)
func main() {
port := flag.Int("port", 50100, "listen port")
mainHost := flag.String("main-host", "", "main server address")
host := flag.String("host", "", "upstream source URL")
token := flag.String("token", "", "API token")
flag.Parse()
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
// Проверка доступности (для кросс-поиска)
if q.Get("checksearch") == "true" {
json.NewEncoder(w).Encode(map[string]any{"rch": true})
return
}
// Основной поиск
handleSearch(w, r, *host, *mainHost)
})
addr := fmt.Sprintf("127.0.0.1:%d", *port)
log.Printf("mybalancer: listening on %s", addr)
log.Fatal(http.ListenAndServe(addr, mux))
}Флаги запуска
Сервер автоматически передаёт subprocess-у:
| Флаг | Описание |
|---|---|
-port | TCP-порт для прослушивания |
-main-host | Адрес основного сервера (для проксирования потоков) |
-host | Upstream URL из config.json |
-token | API-токен (опционально) |
HTTP API
Балансер реализует единственный endpoint GET / с query-параметрами.
Входные параметры
| Параметр | Описание |
|---|---|
checksearch | true — быстрая проверка (кросс-поиск) |
kinopoisk_id | ID Кинопоиска |
imdb_id | IMDB ID |
title | Русское название |
original_title | Оригинальное название |
year | Год выхода |
serial | 1 — сериал |
s | Номер сезона (-1 = список сезонов) |
e | Номер серии |
t | Озвучка |
href | URL конкретного результата |
rjson | true — всегда JSON |
checksearch
Быстрая проверка — есть ли контент в этом источнике.
GET /?checksearch=true&kinopoisk_id=123&title=Movie{"rch": true}Форматы ответов
Все ответы — JSON с полем type:
{
"type": "movie",
"data": [{
"method": "play",
"url": "https://cdn.example.com/stream.m3u8",
"stream": "https://cdn.example.com/stream.m3u8",
"name": "Название",
"title": "Название / Original Title"
}]
}{
"type": "similar",
"data": [{
"method": "link",
"url": "/lite/source/mybalancer?kinopoisk_id=123&href=...",
"name": "Вариант 1",
"year": 2024,
"img": "https://poster.jpg"
}]
}{
"type": "season",
"data": [{
"method": "link",
"url": "/lite/source/mybalancer?kinopoisk_id=123&serial=1&s=1",
"name": "1 сезон"
}]
}{
"type": "episode",
"data": [{
"method": "play",
"url": "https://cdn.example.com/s01e01.m3u8",
"s": 1, "e": 1,
"name": "Серия 1",
"title": "Название / Original"
}]
}{"type": "empty", "data": []}Методы в data
| method | Описание |
|---|---|
play | Прямая ссылка на поток — запуск плеера |
call | URL для повторного запроса (lazy-load) |
link | Навигационная ссылка (выбор сезона/варианта) |
Проксирование потоков
Потоковые URL нужно оборачивать через proxystream основного сервера — для CORS и шифрования ссылок:
func buildStreamUrl(link, mainHost string, r *http.Request) string {
if mainHost == "" { return link }
body, _ := json.Marshal(map[string]string{
"url": link,
"plugin": "mybalancer",
})
req, _ := http.NewRequest("POST", mainHost+"/api/proxystream",
bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
// Пробросить заголовки для корректного proxy URL
for _, h := range []string{"X-Forwarded-For", "X-Forwarded-Host",
"X-Forwarded-Proto"} {
if v := r.Header.Get(h); v != "" {
req.Header.Set(h, v)
}
}
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != 200 { return link }
defer resp.Body.Close()
var result struct{ ProxyURL string `json:"proxy_url"` }
json.NewDecoder(resp.Body).Decode(&result)
if result.ProxyURL != "" { return result.ProxyURL }
return link
}Деплой
Через админ-панель
На вкладке «Конструктор» можно:
- Загрузить Go-код балансера
- Указать config (имя, хост, качество)
- Нажать «Deploy» — код скомпилируется и запустится автоматически
Через API
# Deploy (создать + скомпилировать + запустить)
curl -X POST "https://server/{admin}/api/custbal" \
-H "Content-Type: application/json" \
-d '{
"action": "deploy",
"name": "mybalancer",
"code": "package main...",
"config": {"display_name": "My Balancer", "quality_badge": "FHD", "auto_start": true}
}'
# Перекомпилировать
curl -X POST ... -d '{"action": "compile", "name": "mybalancer"}'
# Запуск / Стоп / Рестарт
curl -X POST ... -d '{"action": "start", "name": "mybalancer"}'
curl -X POST ... -d '{"action": "stop", "name": "mybalancer"}'
curl -X POST ... -d '{"action": "restart", "name": "mybalancer"}'
# Удалить
curl -X POST ... -d '{"action": "remove", "name": "mybalancer"}'Вручную
mkdir -p custom_balancers/mybalancer
# Положить main.go и config.json
# Перезапустить сервер — или через API: compile + startЖизненный цикл
stopped --start--> starting --TCP ready--> running
^ |
| crash/stop
+---------------------------------------------+
error- starting — subprocess запущен, ожидание TCP (15 сек)
- running — порт отвечает, трафик проксируется
- error — subprocess упал или не ответил
- stopped — остановлен (SIGINT, 5 сек, затем SIGKILL)
Конвертер C# -> Go
Через вкладку «Конструктор» можно конвертировать C# балансеры из старого Lampac:
- Вставить C# код
- Нажать «Портировать»
- LLM конвертирует в Go и автоматически деплоит
Для работы конвертера нужно настроить секцию
[llm] в config.toml — endpoint и API-ключ.Рекомендации
- Кэшируйте ответы upstream-а в памяти (TTL 10-30 мин)
- checksearch должен работать быстро (< 5 сек)
- Используйте
proxystreamдля потоковых URL - Балансер должен быть standalone Go-программой без CGO
- Рабочий пример:
custom_balancers/uaflix/main.go