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

Запуск узла

Узел запускается в три шага: поднять контейнер, зарегистрировать звезду в блокчейне, проверить, что всё подключилось. Дальше он работает и зарабатывает сам.

Открытая альфа работает в Arbitrum Sepolia. Chain id 421614, образ singulanetwork/star:alpha. ETH на газ можно взять в кране Arbitrum Sepolia. Настоящие деньги не нужны.

  • Linux-сервер с публичным IP.
  • Открытые входящие порты TCP 443, 8443, 2053 и 2083. Нужны все четыре, зачем — в разделе пресеты маскировки.
  • Кошелёк с небольшим количеством Sepolia ETH на одну транзакцию. Он станет владельцем звезды и будет получать выплаты. На сервер его ключ не попадает.

Быстрее всего — установщиком:

Окно терминала
curl -fsSL https://stargate.singula.network/install-node.sh | sudo bash

Он проверит, что есть Docker и свободны четыре порта, спросит код страны, поднимет контейнер и напечатает адрес подписанта, который понадобится на шаге 2. Если запустить его ещё раз, он обновит образ и сохранит личность узла.

Docker установщик не ставит и обратный прокси не настраивает. REALITY сам завершает TLS и должен видеть исходный ClientHello, поэтому входы узла занимают порты хоста напрямую.

Ниже — то же самое вручную.

Программа star одна для всех ролей и при запуске читает свою роль из StarRegistry. При первом запуске роли ещё нет: узел создаёт хранилище, печатает свою личность и ждёт регистрации.

Всё, что принадлежит узлу, лежит в одном каталоге: хранилище, ключ, которым оно зашифровано, и первый час после запуска — фраза восстановления. Монтируйте этот каталог целиком в /var/lib/star.

Сначала создайте мастер-секрет. Без него узел не запустится, а сам он его не придумывает: если бы он молча создал новый секрет, всё сохранённое под старым стало бы недоступно.

Окно терминала
mkdir -p ~/.singula
chmod 700 ~/.singula
openssl rand -hex 32 > ~/.singula/master_key
chmod 600 ~/.singula/master_key
services:
star-node:
image: singulanetwork/star:alpha
container_name: singula-star
restart: unless-stopped
network_mode: 'host'
# От вашего имени, чтобы файлы в монтировании остались вашими. Сам по себе
# образ переходит на собственного пользователя, а тот не может писать в
# чужой каталог — и узел падает на первом же действии, открытии хранилища.
user: "${UID}:${GID}"
volumes:
- ~/.singula:/var/lib/star
environment:
- MASTER_SECRET_FILE=/var/lib/star/master_key
- BLOCKCHAIN_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
- CHAIN_ID=421614
- NODE_COUNTRY_CODE=DE
# - REVERSE_PROXY=node-de.example.com:443
Окно терминала
UID="$(id -u)" GID="$(id -g)" docker compose up -d && docker compose logs -f

Root не нужен нигде, даже для порта 443: Docker выставляет net.ipv4.ip_unprivileged_port_start в 0, а у xray в образе есть файловая capability на привязку портов. Нужен только пользователь, которому можно работать с Docker: usermod -aG docker $USER.

network_mode: host — самый простой способ открыть четыре порта и правильно определить публичный IP.

Без Compose:

Окно терминала
docker run -d --name singula-star --network host \
--user "$(id -u):$(id -g)" \
-v ~/.singula:/var/lib/star \
-e MASTER_SECRET_FILE=/var/lib/star/master_key \
-e BLOCKCHAIN_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc \
-e CHAIN_ID=421614 -e NODE_COUNTRY_CODE=DE \
singulanetwork/star:alpha

Можно и без Docker — запустить бинарник под systemd с BADGER_DIR=/var/lib/star/badger и теми же переменными.

ПеременнаяПо умолчаниюЧто делает
NODE_COUNTRY_CODEXXСтрана узла. Учитывается, только когда клиент просит конкретную страну
NODE_MAX_SESSIONS100Сколько сессий узел держит одновременно
REVERSE_PROXY—Публичный host:port, по которому клиенты должны вас находить, если он отличается от адреса машины
METRICS_HTTP_PORT9101Порт /status (JSON) и /metrics (Prometheus); 0 выключает
METRICS_BIND127.0.0.1Где он слушает. Расширяйте только за файрволом или паролем — см. ниже
BADGER_DIR/var/lib/star/badgerЛокальное хранилище

В логах узел печатает баннер с адресом подписанта — это его горячий ключ:

STAR — MANUAL REGISTRATION REQUIRED
Signer address : 0x...

Если баннер уже уехал из лога, тот же адрес отдаёт локальная страница состояния:

Окно терминала
curl -s localhost:9101/status | jq -r .info.signer

В Stargate подключите кошелёк, который будет владельцем звезды, откройте Stars, нажмите + Register Star, вставьте адрес подписанта и подпишите транзакцию.

Это вызов StarRegistry.register(signer). Ни выбора роли, ни очереди: каждая новая звезда — узел. Один адрес подписанта может принадлежать только одной звезде.

Перезапускать ничего не нужно. Узел сам следит за блокчейном и подхватит свой starId, когда транзакция пройдёт.

На карточке звезды в Stargate должны быть роль Node, зелёный индикатор, публичный адрес и свежий heartbeat — узел шлёт его каждые 30 секунд.

В логах узла появится Registered with CP: он нашёл живой Control Plane в CPEndpointRegistry, держит с ним соединение и ждёт сессий.

Узел рассказывает о себе по двум адресам на одном порту: /status отдаёт JSON, /metrics — формат Prometheus. Цифры одни и те же: сколько сессий он везёт, есть ли связь с Control Plane, нашёл ли он себя в блокчейне, сколько работает, сколько ест процессора и памяти.

Рядом с цифрами лежит блок info: адрес подписанта, starId, страна, ключи REALITY, реестр и провайдер RPC. Ключ из адреса RPC вырезан. В Prometheus те же поля приходят метками singula_info.

Окно терминала
curl -s localhost:9101/status | jq

Страница слушает только localhost, и так лучше оставить. В ответе написано role: node, а метрики называются singula_node_*, так что по одному запросу к открытому порту видно, что здесь узел. А узлы как раз стараются не выдавать себя: четыре маскировочных пресета, имитация популярного сайта на 443, ни одного адреса узла в блокчейне.

Если нужно, расширьте доступ через METRICS_BIND, но только за файрволом, списком разрешённых адресов или обратным прокси с паролем. Узел напишет об этом в лог.

Узел всегда держит четыре входа REALITY, по одному на регион. Каждый слушает свой порт и выглядит как обращение к сайту, популярному в этом регионе. Набор зашит в программу, отключить пресет нельзя.

ПресетПортСайт-прикрытиеИмена серверов
default443www.adobe.comwww.adobe.com, helpx.adobe.com, assets.adobe.com
RU8443yandex.netyandex.net, yastatic.net, mc.yandex.ru
CN2053www.qq.comwww.qq.com, weixin.qq.com, www.gtimg.com
IR2083www.aparat.comwww.aparat.com, www.digikala.com, asredanesh.com

Пресет выбирает клиент. Если он ничего не попросил, узел берёт default: где находится клиент, в сети никто не выясняет.

Каждый пресет доступен ещё и по xHTTP на том же порту, по одному пути, постоянному для этого узла и пресета. Клиент, которому мешают по голому TCP, переключается на xHTTP без новой сессии.

Откройте все четыре порта. Закрытый порт не выключает пресет — просто клиенты из этого региона не смогут подключиться.

REALITY сам завершает TLS и должен видеть исходный ClientHello, поэтому прокси перед узлом может только маршрутизировать соединения, но не расшифровывать их. Используйте маршрутизацию по SNI со сквозным TLS:

Traefik
# TLS passthrough, SNI → нужный входящий
services:
star:
image: singulanetwork/star:alpha
restart: unless-stopped
user: "${UID}:${GID}"
networks: [public]
volumes:
- ~/.singula:/var/lib/star
environment:
- MASTER_SECRET_FILE=/var/lib/star/master_key
- BLOCKCHAIN_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
- CHAIN_ID=421614
- NODE_COUNTRY_CODE=DE
- REVERSE_PROXY=node.example.com:443
labels:
- "traefik.enable=true"
- "traefik.docker.network=public"
- "traefik.tcp.routers.star-default.rule=HostSNI(`www.adobe.com`) || HostSNI(`helpx.adobe.com`) || HostSNI(`assets.adobe.com`)"
- "traefik.tcp.routers.star-default.entrypoints=websecure"
- "traefik.tcp.routers.star-default.tls.passthrough=true"
- "traefik.tcp.routers.star-default.service=star-default"
- "traefik.tcp.services.star-default.loadbalancer.server.port=443"
- "traefik.tcp.routers.star-ru.rule=HostSNI(`yandex.net`) || HostSNI(`yastatic.net`) || HostSNI(`mc.yandex.ru`)"
- "traefik.tcp.routers.star-ru.entrypoints=websecure"
- "traefik.tcp.routers.star-ru.tls.passthrough=true"
- "traefik.tcp.routers.star-ru.service=star-ru"
- "traefik.tcp.services.star-ru.loadbalancer.server.port=8443"
- "traefik.tcp.routers.star-cn.rule=HostSNI(`www.qq.com`) || HostSNI(`weixin.qq.com`) || HostSNI(`www.gtimg.com`)"
- "traefik.tcp.routers.star-cn.entrypoints=websecure"
- "traefik.tcp.routers.star-cn.tls.passthrough=true"
- "traefik.tcp.routers.star-cn.service=star-cn"
- "traefik.tcp.services.star-cn.loadbalancer.server.port=2053"
- "traefik.tcp.routers.star-ir.rule=HostSNI(`www.aparat.com`) || HostSNI(`www.digikala.com`) || HostSNI(`asredanesh.com`)"
- "traefik.tcp.routers.star-ir.entrypoints=websecure"
- "traefik.tcp.routers.star-ir.tls.passthrough=true"
- "traefik.tcp.routers.star-ir.service=star-ir"
- "traefik.tcp.services.star-ir.loadbalancer.server.port=2083"
traefik:
image: traefik:v3.1
restart: unless-stopped
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.websecure.address=:443
ports: ["443:443"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks: [public]
networks:
public:
external: true
HAProxy
# Один публичный порт; HAProxy разводит по SNI, не терминируя TLS.
services:
star:
image: singulanetwork/star:alpha
restart: unless-stopped
user: "${UID}:${GID}"
networks: [edge]
volumes:
- ~/.singula:/var/lib/star
environment:
- MASTER_SECRET_FILE=/var/lib/star/master_key
- BLOCKCHAIN_RPC_URL=https://sepolia-rollup.arbitrum.io/rpc
- CHAIN_ID=421614
- NODE_COUNTRY_CODE=DE
expose: ["443", "8443", "2053", "2083"]
haproxy:
image: haproxy:2.9-alpine
restart: unless-stopped
depends_on: [star]
ports: ["443:443"]
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
networks: [edge]
networks:
edge: {}
# haproxy.cfg — без терминации TLS, REALITY должен видеть ClientHello
# global
# log stdout format raw local0
# defaults
# mode tcp
# timeout connect 5s
# timeout client 1h
# timeout server 1h
# frontend tls_in
# bind *:443
# tcp-request inspect-delay 5s
# tcp-request content accept if { req_ssl_hello_type 1 }
# acl is_ru req_ssl_sni -m end yandex.net yastatic.net mc.yandex.ru
# acl is_cn req_ssl_sni -m end qq.com gtimg.com
# acl is_ir req_ssl_sni -m end aparat.com digikala.com asredanesh.com
# use_backend ru if is_ru
# use_backend cn if is_cn
# use_backend ir if is_ir
# default_backend def
# backend def
# server star_def star:443
# backend ru
# server star_ru star:8443
# backend cn
# server star_cn star:2053
# backend ir
# server star_ir star:2083

Все ключи узла — подписанта, REALITY, сессий — выводятся из одного секрета, который создаётся при первом запуске. Рядом записывается фраза восстановления из 24 слов, /var/lib/star/recovery-phrase.txt, и через час она удаляется сама. Скопируйте её сразу: больше она нигде не хранится.

Чтобы восстановить узел, укажите файл с фразой в NODE_MNEMONIC_FILE. Можно и через NODE_MNEMONIC, но файл надёжнее: переменная видна в docker inspect и в compose-файлах, которые попадают в репозитории. Если у фразы есть пароль, добавьте NODE_MNEMONIC_PASSPHRASE; другой пароль даст другой узел.

Фраза восстанавливает те же ключи, поэтому звезду заново регистрировать не нужно: адрес подписанта совпадёт с тем, что уже записан в блокчейне.

Сами ключи хранятся зашифрованными, ключом из файла MASTER_SECRET_FILE — ~/.singula/master_key, если вы шли по шагам выше, или /run/secrets/singula_master_key, если ничего не задавали. Потерять этот файл не страшно, пока у вас есть фраза. Если пропали оба, узлу понадобится новая личность, а владелец перепривяжет подписанта вызовом updateSigner.

Control Plane начнёт направлять к вам сессии. Узел выбирается по загрузке, свежести heartbeat и случайной добавке — но не по тому, где клиент: этого Control Plane не знает. NODE_COUNTRY_CODE важен, только когда клиент сам попросил вашу страну.

Каждые четыре часа и ещё раз незадолго до конца эпохи Control Plane склеивает отработанные вами интервалы, просит узел подписать каждый и отправляет их в EpochAttestations. Платят за время, а не за объём, но время засчитывается не всегда: оно копится, пока с последнего засчитанного чекпоинта клиента не пройдёт 64 КиБ, а если за 10 минут столько не набралось, сгорает.

Когда эпоха закроется, claimNode выплатит USDC на кошелёк владельца. Вызвать его может кто угодно — получатель всё равно берётся из реестра, — так что ETH на кошельке держать не обязательно.

Секунда оплачивается по фиксированной ставке: сейчас около 0.2333 sUSDC за сутки работы с одной сессией. Подробнее — в экономике.

Если сессий нет, это нормально для тихой сети: значит, их сейчас мало во всей сети, а не что у вас неудачный регион.