Перейти к содержимому

Signal Lab как сервер ​

signal-lab-server — это Signal Lab без окна: тот же движок, отдающий браузеру тот же интерфейс. Поставьте его на машину рядом с оборудованием — стоечный ПК, виртуальную машину шоу-контроля, общий лабораторный компьютер — и открывайте из Chrome, Firefox или Edge в любой точке сети: каждый экран работает, как в настольном приложении, а запуски, отчёты и экспорт скачиваются через браузер.

Скрипты и конвейеры работают с тем же сервером через его HTTP API, а signallab --server отправляет на него запуски.

От настольного приложения сервер отличается несколькими вещами:

  • Один сервер — один движок. Все вошедшие на него страницы видят одни и те же работающие задачи, одни и те же библиотеки сигналов и эмуляторов и одну и ту же банку cookie экрана HTTP (Хранить cookie). Кто делит сервер, делит и это.
  • Секреты принадлежат серверу: он читает их из своего окружения или из файлов, а из браузера задать их нельзя (см. Секреты).
  • Брандмауэр принадлежит хосту. Сервер его никогда не меняет; уведомление о брандмауэре из настольного приложения не появляется.
  • Он обновляется вместе со своим образом, а не через средство обновления приложения (см. Обновление).

На хосте с Linux — одной командой ​

На машине с Linux, у которой есть выход в интернет:

bash
curl -fsSL https://raw.githubusercontent.com/ProAnima/SignalLab/main/deploy/install.sh | sh

Скрипт:

  1. ставит Docker, если его нет, — предварительно спросив — штатным установщиком Docker (get.docker.com);
  2. записывает compose.yaml в /opt/signallab (в ~/signallab, если вы не root);
  3. скачивает образ и запускает сервер с сетью хоста, чтобы OSC, UDP, широковещание, multicast и обнаружение выходили в настоящую сеть;
  4. ждёт, пока сервер ответит на проверку работоспособности (до 90 секунд);
  5. печатает адреса, которые нужно открыть, токен доступа для входа и команды, чтобы обновить сервер, прочитать журналы и удалить его;
  6. если включён ufw или firewalld, предлагает открыть порт сервера (см. Брандмауэр хоста).

Параметры передают после sh -s --:

bash
curl -fsSL https://raw.githubusercontent.com/ProAnima/SignalLab/main/deploy/install.sh | sh -s -- --version 1.0.0 --port 8430
ПараметрЧто делаетПо умолчанию
--version X.Y.ZВерсия образа (latest или X.Y.Z; ведущая v отбрасывается).latest
--port NПорт, на который заходят браузеры.1430
--listen IP:PORTСлушать только один адрес.0.0.0.0:<port>
--dir DIRКуда положить файл compose./opt/<name> от root, иначе ~/<name>
--name NAMEИмя контейнера и его тома данных: строчные буквы, цифры, - и _. Второму серверу на том же хосте нужно собственное имя.signallab
--image NAMEДругой образ или реестр; полное NAME:TAG используется как есть.ghcr.io/proanima/signallab
--open-udp PORTSПри включённом брандмауэре пропустить также UDP на этих портах — для мониторов и ожиданий: 9000,9100:9110.
--no-firewallНикогда не менять ufw и firewalld.
--yes, -yОтвечать «да»: установить Docker, открыть брандмауэр, удалить данные с --purge.
--uninstallОстановить и удалить сервер; данные остаются.
--purgeВместе с --uninstall: удалить также данные и токен.
--help, -hВывести параметры.

Нужен плагин Docker Compose (пакет docker-compose-plugin), который приносит установщик Docker. Образ собран для x86_64 и arm64.

Чтобы обновить сервер, запустите команду снова: та же команда скачивает самый новый образ (или версию, которую вы задали через --version) и перезапускает сервер; данные и токен остаются. Если вы использовали --name, укажите то же имя.

Собственные настройки — секреты экспериментов, SIGNALLAB_ALLOWED_HOSTS, SIGNALLAB_SECURE_COOKIE за HTTPS — записывают в compose.override.yaml рядом с файлом compose. Docker Compose подмешивает его, а скрипт при каждом запуске переписывает compose.yaml, но override не трогает. compose.yaml, написанный не скриптом, сохраняется как compose.yaml.before-install.

yaml
# compose.override.yaml
services:
  signallab:
    environment:
      SIGNALLAB_ALLOWED_HOSTS: lab-pc.example.com,192.0.2.10
      SIGNALLAB_SECRET_API_TOKEN: ${API_TOKEN}

Чтобы удалить сервер:

bash
curl -fsSL https://raw.githubusercontent.com/ProAnima/SignalLab/main/deploy/install.sh | sh -s -- --uninstall

Данные остаются в томе Docker, и повторная установка возвращает их вместе с прежним токеном. --uninstall --purge удаляет и данные, и токен — после вопроса.

Брандмауэр хоста ​

С сетью хоста сервер слушает собственные порты хоста, поэтому о том, кто до него дотянется, решает брандмауэр хоста. Если включён ufw или firewalld, скрипт спрашивает, прежде чем открыть TCP-порт сервера и UDP-порты из --open-udp, записывает, что он открыл (.firewall рядом с файлом compose), и --uninstall закрывает ровно это. Если вы откажетесь, браузеры на других машинах попадут на сервер только после того, как это разрешит брандмауэр, а мониторы и ожидания услышат другие машины только на тех UDP-портах, которые он откроет.

Docker ​

Образ ​

ghcr.io/proanima/signallab для linux/amd64 и linux/arm64 публикуется с каждым выпуском:

ТегЧто это
X.Y.ZЭтот выпуск.
X.YСамый новый стабильный выпуск этой линейки.
latestСамый новый стабильный выпуск.

В нём лежат signal-lab-server, собранный интерфейс и командная строка signallab; сервер запускается с такими настройками:

ПеременнаяЗначение в образе
SIGNALLAB_LISTEN0.0.0.0:1430
SIGNALLAB_DATA_DIR/data
SIGNALLAB_UI_DIR/usr/share/signal-lab/ui
SIGNALLAB_GENERATE_TOKENtrue

Он работает от имени непривилегированного пользователя (uid и gid 10001), пишет только в /data (том), открывает порт 1430 и каждые 30 секунд проверяет собственную работоспособность.

Запуск ​

bash
docker run -d --name signallab --network host --restart unless-stopped \
  -v signallab-data:/data --read-only --cap-drop ALL --security-opt no-new-privileges \
  ghcr.io/proanima/signallab:1.0.0
docker logs signallab                    # при первом запуске: "Sign in with it:" и токен

Затем откройте http://<host>:1430 и войдите с этим токеном. Чтобы позже увидеть токен снова:

bash
docker exec signallab cat /data/token

--read-only, --cap-drop ALL и no-new-privileges необязательны и ничего не стоят: серверу не нужны привилегии, и пишет он только в /data.

Docker Compose ​

deploy/compose.yaml из репозитория — то же самое в виде файла Compose:

yaml
name: signallab

services:
  signallab:
    image: ${SIGNALLAB_IMAGE:-ghcr.io/proanima/signallab:latest}
    container_name: signallab
    network_mode: host
    environment:
      SIGNALLAB_GENERATE_TOKEN: "true"
    volumes:
      - signallab-data:/data
    read_only: true
    cap_drop: [ALL]
    security_opt: ["no-new-privileges:true"]
    restart: unless-stopped
    stop_grace_period: 15s

volumes:
  signallab-data:
bash
docker compose up -d
docker exec signallab cat /data/token

Задайте SIGNALLAB_IMAGE=ghcr.io/proanima/signallab:X.Y.Z, чтобы закрепить версию.

Сеть ​

Сеть DockerЧто работаетЧто не работает
--network host (network_mode: host), на хосте с LinuxВсё: OSC, UDP, TCP, HTTP, WebSocket и MQTT в локальную сеть, прослушиваемые порты, широковещание, multicast, обнаружение.—
Bridge (по умолчанию), с опубликованными портамиUnicast к хостам, до которых достаёт контейнер; слушатели на опубликованных портах (-p 1430:1430 -p 9000:9000/udp).Широковещание и multicast; ответы на порты, которые не опубликованы.
Docker Desktop в Windows или macOSUnicast и опубликованные слушатели.Сеть хоста до физической сети. В Windows используйте настольное приложение.

Том данных ​

/data хранит всё, что хранит сервер: эксперимент, библиотеки сигналов и эмуляторов, отчёты о запусках, экспорт и токен. Именованный том, как выше, изначально принадлежит пользователю образа. Папка хоста, смонтированная туда, должна быть доступна для записи uid 10001:

bash
sudo mkdir -p /srv/signallab && sudo chown 10001:10001 /srv/signallab
docker run -d --name signallab --network host -v /srv/signallab:/data ghcr.io/proanima/signallab:1.0.0

Без Docker ​

Сервер публикуется как образ. Чтобы запустить signal-lab-server напрямую, соберите его из исходников (см. Сборка):

bash
npm install
npm run build                            # интерфейс, в dist/
cargo run --release -p signal-lab-server

Он отдаёт dist/ по адресу http://127.0.0.1:1430, только для этой машины и без токена. Добавьте --listen и токен, чтобы открыть его для сети.

Параметры ​

У каждого параметра есть переменная окружения — для контейнеров. Параметры сильнее переменных.

ПараметрПеременнаяПо умолчаниюЧто делает
--listen IP:PORTSIGNALLAB_LISTEN127.0.0.1:1430Где слушать. Любому адресу, кроме loopback, нужен токен.
--token-file PATHSIGNALLAB_TOKEN_FILEФайл с токеном доступа (например, секрет Docker).
--token TOKENSIGNALLAB_TOKENСам токен доступа. Лучше файл: аргументы видны другим пользователям машины.
--generate-tokenSIGNALLAB_GENERATE_TOKENвыкл.Если токен не задан и адрес не loopback: использовать токен, хранящийся в <data folder>/token, создав его при первом запуске.
--data-dir PATHSIGNALLAB_DATA_DIRDocuments/SignalLab в домашней папке пользователяПапка данных.
--secrets-dir PATHSIGNALLAB_SECRETS_DIR/run/secrets/signallabПапка секретов только для чтения, по файлу на имя.
--ui-dir PATHSIGNALLAB_UI_DIRui рядом с программой, иначе ./distСобранный интерфейс. Без него отдаётся только API.
--allowed-host NAMESIGNALLAB_ALLOWED_HOSTSИмена хоста, по которым можно обращаться к серверу, через запятую. Они и имена loopback принимаются всегда, с токеном и без; если ни одного не задано, сервер с токеном отвечает на любое имя, а сервер без токена — только на имена loopback (см. Имена хоста).
--secure-cookieSIGNALLAB_SECURE_COOKIEвыкл.Отправлять cookie сессии только по HTTPS. Включайте за прокси с HTTPS.
--log FILTERSIGNALLAB_LOGinfoЧто писать в журнал: error, warn, info, debug или по модулям (signal_lab_server=debug).
--log-format text|jsonSIGNALLAB_LOG_FORMATtextСтроки журнала текстом или по одному объекту JSON на строку.

Переключатели принимают из своей переменной true или false: SIGNALLAB_GENERATE_TOKEN=true.

КомандаЧто делает
signal-lab-server tokenПечатает новый случайный токен: 64 шестнадцатеричных символа.
signal-lab-server healthcheckЗавершается с кодом 0, если на --listen отвечает сервер (проверка работоспособности образа).
signal-lab-server --versionПечатает версию.
signal-lab-server --helpПечатает все параметры.

Коды завершения: 0 — остановка по Ctrl+C или SIGTERM; 2 — настройки отклонены (нет токена на доступном адресе, слишком короткий токен, токен задан дважды, файл токена не читается, --generate-token без папки данных); 1 — сервер не может слушать на этом адресе или в папку данных нельзя писать. Причина печатается в stderr.

Токен доступа ​

Без токена сервер слушает только loopback и обслуживает только эту машину. На любом другом адресе токен нужен, и без него сервер не запускается. Задать его можно тремя способами:

СпособКогда использовать
--generate-token (в образе включён)Ничего настраивать не нужно: при первом запуске сервер создаёт токен, сохраняет его в <data folder>/token — читать его может только собственный пользователь сервера — и один раз печатает в журнал. При следующих запусках он используется снова, поэтому вошедшие браузеры и скрипты продолжают работать после перезапусков и обновлений. Нужна папка данных (--data-dir).
--token-file PATHСобственный токен в файле, например секрет Docker. Перевод строки в конце не считается частью токена.
SIGNALLAB_TOKENТокен в окружении.

В токене не меньше 24 символов, без пробелов и переводов строки; задавайте его только одним способом. Хороший токен делает signal-lab-server token:

bash
docker run --rm ghcr.io/proanima/signallab:1.0.0 token > signallab_token.txt

а Compose передаёт его как секрет (файл должен быть доступен для чтения uid 10001):

yaml
services:
  signallab:
    environment:
      SIGNALLAB_TOKEN_FILE: /run/secrets/signallab_token
    secrets:
      - signallab_token

secrets:
  signallab_token:
    file: ./signallab_token.txt

Токен, заданный явно, сильнее --generate-token, и тогда ничего не создаётся. Чтобы сменить созданный токен, остановите сервер, удалите <data folder>/token и запустите его снова: он создаст и напечатает новый. Повреждённый файл токена сервер сообщит, но не заменит.

Вход ​

Откройте http://<host>:1430. Сервер с токеном сначала направляет браузер на страницу входа — на языке браузера; вставьте токен один раз, и браузер остаётся вошедшим 7 дней. Кнопка Выйти в интерфейсе завершает сессию. Сессии хранятся в памяти сервера: перезапуск выводит из системы всех.

На loopback без токена входа нет.

Скрипты отправляют токен с каждым запросом, как Authorization: Bearer <token>:

bash
curl -fsS http://192.0.2.10:1430/api/invoke/app_info \
  -H "Authorization: Bearer $(cat signallab_token.txt)" \
  -H "Content-Type: application/json" -d 'null'

См. HTTP API и Безопасность сервера, где изложены правила, на которых всё это держится.

Папка данных ​

Свои файлы сервер хранит в папке данных: эксперимент, библиотеки сигналов и эмуляторов, отчёты о запусках, экспорт и созданный токен. Это --data-dir (SIGNALLAB_DATA_DIR), /data в образе, а в остальных случаях — Documents/SignalLab в домашней папке пользователя, от имени которого он работает. Папка данных, заданная через --data-dir, создаётся, если её нет, и проверяется при запуске: если сервер не может писать в неё, он останавливается и называет эту папку. См. Файлы.

Скачивание (отчёты, экспорт) идёт только изнутри этой папки.

Секреты ​

Эксперименты читают секреты как {{secret.NAME}}. На сервере они доступны только для чтения — из:

  1. переменной окружения SIGNALLAB_SECRET_NAME, иначе
  2. файла NAME в папке секретов (--secrets-dir, по умолчанию /run/secrets/signallab — раскладка секретов Docker).

Перевод строки в конце файла не входит в значение; пустое значение считается незаданным; значение — не больше 16 КиБ. Имена состоят из букв, цифр и _ и не начинаются с цифры. Интерфейс показывает, какие секреты заданы на сервере, но задать секрет из браузера нельзя — значения никогда не попадают туда, где защита слабее, и никогда не выходят обратно (см. Секреты).

В Compose — файл секрета на каждое имя:

yaml
services:
  signallab:
    secrets:
      - source: api_token
        target: /run/secrets/signallab/API_TOKEN

secrets:
  api_token:
    file: ./api_token.txt

Файл должен быть доступен для чтения uid 10001.

За прокси с HTTPS ​

Сервер говорит по обычному HTTP. Для HTTPS поставьте перед ним обратный прокси (Caddy, nginx, Traefik), который:

  • передаёт заголовок Host без изменений;
  • пропускает переключения на WebSocket (интерфейс держит одно такое соединение открытым — к /api/events);

и запустите сервер с --secure-cookie, чтобы cookie сессии ходила только по HTTPS, а --allowed-host задайте равным имени, которым пользуются люди.

Обновление ​

Сервер обновляется своим образом: средство обновления настольного приложения тут ни при чём.

  • Установлен скриптом: запустите ту же команду снова.
  • С Compose: docker compose pull && docker compose up -d.
  • С docker run: скачайте новый образ, затем удалите контейнер и запустите его снова с тем же томом.

Данные и токен лежат в томе, поэтому остаются на месте.

Проверка работоспособности ​

GET /api/health отвечает всем, без токена:

bash
curl -s http://127.0.0.1:1430/api/health
# {"auth":true,"status":"ok","version":"1.0.0"}

auth говорит, требует ли сервер токен. signal-lab-server healthcheck спрашивает то же на этой машине и завершается с кодом 0, если сервер ответил; образ запускает её каждые 30 секунд (тайм-аут 5 секунд, 3 попытки), поэтому docker ps показывает контейнер как исправный.

Журналы ​

Сервер пишет журнал в stdout: docker logs -f signallab. На уровне по умолчанию (info) он сообщает, где слушает и нужен ли токен, свои папки данных и интерфейса, каждую запущенную задачу — мониторы, генераторы, штормы, сканирования, запуски, эмуляторы — с адресом клиента, каждый вход, каждый вход с неверным токеном (как предупреждение) и случаи, когда страница отстаёт от событий. --log debug добавляет каждую команду. Строки раскрашиваются только в терминале (и никогда при заданной NO_COLOR); --log-format json пишет по одному объекту JSON на строку — для сборщика журналов.

Остановка ​

Ctrl+C, docker stop или SIGTERM закрывает соединения всех страниц, останавливает все задачи — идущий запуск завершается как остановленный — и завершает сервер с кодом 0. Файлы compose дают ему на это 15 секунд.