
Протокол SAVVA всегда был разрешительным ончейн — любой мог публиковать, любой мог читать, любой мог запустить домен. Реализация, на практике, была другой историей: до сих пор вам нужен был доступ к исходному коду и инструментарий Go для сборки бэкенда, а также приходилось писать длинный YAML-файл конфигурации с нуля и выяснять, какие из его десятков полей вам действительно нужно установить. Это барьер, который мы предпочли бы не ставить между людьми и их собственным суверенитетом над уровнем контента.
Поэтому мы поставляем бэкенд в виде публичного образа Docker. Один образ, один файл конфигурации с несколькими значениями для заполнения и одна команда docker compose up -d. Если у вас установлен Docker и есть час свободного времени в субботу днем, вы можете запустить узел SAVVA на своем собственном домене — доступ к исходному коду не требуется.
Почему это важно
SAVVA — это мультидоменная платформа — тот же протокол, тот же ончейн-реестр контента, обслуживаемый любым количеством независимых доменов. Любой может запустить домен. Каждый домен — это свой собственный бренд, свое сообщество, своя политика модерации. Протокол это не волнует.
Но разрешительный протокол с трудноустанавливаемой эталонной реализацией является разрешительным только в теории. Цель этого релиза — привести практику в соответствие с теорией: вы должны иметь возможность запустить свой собственный сайт по протоколу SAVVA по цене небольшого VPS, и вам не нужно быть Go-разработчиком для этого.
Что вам понадобится, прежде чем начать
Пять вещей. Ни одна из них не требует каких-либо знаний, специфичных для SAVVA:
-
Сервер Linux или Mac с Docker (24+) и плагином Compose. Любой небольшой VPS подойдет — вам не нужна мощная машина. Однако вам понадобится дисковое пространство для хранилища данных встроенного узла IPFS (см. Об IPFS-хранилище ниже).
-
База данных Postgres, до которой может достучаться бэкенд. Любая версия 14+. Вы можете запустить ее на той же машине, на управляемом сервисе (RDS, Supabase, Neon и т. д.) или в любом другом месте.
-
URL RPC блокчейна. SAVVA работает на Monad. Стандартный публичный RPC основной сети —
https://rpc.monad.xyzи работает "из коробки" — без регистрации, без API-ключа, просто вставьте его в.env.Подвох: публичные RPC ограничены по скорости и являются общими. Они подходят для небольшого личного узла или для проверки установки, но при реальной нагрузке вы столкнетесь с дросселированием, периодическими тайм-аутами и более медленной синхронизацией блоков. Для узла, который вы собираетесь поддерживать — особенно того, который обслуживает публичный домен — планируйте либо:
- Запустить свой собственный узел Monad (полный контроль, без ограничений скорости, но нетривиальная системная административная работа и дисковое пространство), либо
- Арендовать приватный RPC у провайдера (QuickNode, Alchemy, Ankr и т. д. — они продают выделенные конечные точки с гораздо более высокими ограничениями скорости и лучшими гарантиями бесперебойной работы, чем публичные RPC).
В любом случае URL попадает в
BLOCKCHAIN_RPCв.env. Вы можете начать с публичного RPC и переключиться позже — миграция не требуется, достаточно отредактировать одну строку. -
Адрес административного кошелька. Идентификатор кошелька, которому будет разрешено администрировать домен. (Необязательно, но рекомендуется позже: отдельный кошелек процессора — ключ подписи, который бэкенд использует для обработки платного / зашифрованного контента. Вы можете запустить узел без него и добавить его, когда будете готовы.)
-
Один — в идеале два — аккаунта службы закрепления IPFS. Это единственная внешняя регистрация, которая вам нужна. Встроенный узел IPFS хранит контент локально, но один узел — это единая точка отказа; служба закрепления реплицирует все, что вы закрепляете, в долговечное внешнее хранилище и предоставляет вам публичный URL шлюза, чтобы любой в Интернете мог получить ваш контент, даже когда ваш собственный узел отключен.
Мы рекомендуем Pinata в качестве основного. Большинство служб закрепления позволяют вам просить их получить CID из публичной сети IPFS только после того, как он уже был опубликован — это означает, что ваш контент должен распространиться по рою, прежде чем он будет надежно закреплен, иногда это минуты недоступности для недавно опубликованного файла. API Pinata предоставляет прямую конечную точку для загрузки: бэкенд передает файл непосредственно Pinata одновременно с его добавлением локально, поэтому контент надежно закрепляется и становится доступным через шлюз немедленно.
Примечание о тарифных планах шлюза Pinata. Бесплатный план использует общий
gateway.pinata.cloud(ограничен по скорости и является общим для всех бесплатных пользователей — работает для персональных узлов с низким трафиком, рискованно для всего, что обращено к публике). Выделенный шлюз на поддомене, который вы контролируете (yourname.mypinata.cloud), требует платного плана Pinata. Если ваш узел будет обслуживать реальный трафик, планируйте обновление — это разница между "ваш контент доступен" и "ваш контент доступен до тех пор, пока общая инфраструктура работает". Другие сервисы (web3.storage, Filebase, 4everland) имеют аналогичные разделения на общие/выделенные уровни.И добавьте вторую службу наряду с Pinata. Одна служба закрепления — это безотказность работы одной компании, биллинговые отношения и политические решения, отделяющие от полной потери контента. Две независимые службы эффективно устраняют этот риск. Пакет поддерживает до десяти — установите
PIN_SERVICE_2_URL/_API_KEY/_GATEWAY(и_3_,_4_и т. д.) в.env. Обычная пара — Pinata в качестве быстрой/долговечной основной службы иweb3.storageилиFilebaseв качестве второй, более дешевой резервной.От каждой службы вам понадобятся три строки: URL конечной точки API, API-ключ (обычно JWT) и URL публичного шлюза службы.
Пакет поставляется со своим собственным узлом IPFS — вам не нужно предоставлять его отдельно. (Если вы уже используете узел IPFS и хотите указать на него, см. примечание о переопределении в шаге 1.)
Вот и все. Никакой регистрации со стороны SAVVA, никаких API-ключей, кроме как для службы закрепления.
Установка за пять минут
1. Создайте каталог развертывания и два файла
mkdir savva && cd savva
Создайте docker-compose.yml со следующим содержимым:
services:
ipfs:
image: ipfs/kubo:latest
container_name: savva-ipfs
restart: unless-stopped
environment:
- IPFS_PROFILE=server
volumes:
# Переопределите IPFS_DATA_PATH в .env, чтобы разместить хранилище данных на
# другом диске. По умолчанию это ./ipfs-data рядом с этим файлом.
- ${IPFS_DATA_PATH:-./ipfs-data}:/data/ipfs
ports:
# Порт Swarm — должен быть доступен из публичного интернета (или,
# по крайней мере, NAT-traversable), чтобы узел мог участвовать в репликации
# закреплений. Привязка как TCP, так и UDP.
- "4001:4001"
- "4001:4001/udp"
healthcheck:
test: ["CMD-SHELL", "ipfs --api=/ip4/127.0.0.1/tcp/5001 id >/dev/null 2>&1 || exit 1"]
interval: 5s
timeout: 3s
retries: 12
start_period: 5s
savva-backend:
image: ghcr.io/alexna-holdings/savva-backend:${SAVVA_VERSION:-latest}
container_name: savva-backend
restart: unless-stopped
env_file: .env
depends_on:
ipfs:
condition: service_healthy
ports:
- "${PORT:-8080}:8080"
volumes:
- ./data:/data
# Опционально: смонтируйте файл приватного ключа и установите PROCESSOR_KEY_FILE в .env,
# чтобы указать на этот путь внутри контейнера.
- ./secrets:/run/secrets:ro
Создайте .env со следующим содержимым (вы заполните значения на шаге 2):
# ----------------------------------------------------------------------
# ОБЯЗАТЕЛЬНО — заполните их перед `docker compose up`.
# ----------------------------------------------------------------------
# Публичное имя хоста, который обслуживает этот экземпляр (без схемы, без пути).
DOMAIN=mysavva.example.com
# Адрес(а) кошелька(ов), которые администрируют домен (EIP-55 checksummed).
# Чтобы перечислить нескольких администраторов, разделите их запятыми: 0xAaa...,0xBbb...
ADMIN_ADDRESS=0xYourAdminWalletAddress
# Строка подключения к Postgres. База данных должна уже существовать; см. шаг 3.
DB_CONNECTION_STRING=postgres://savva:[email protected]:5432/savva?sslmode=disable
# Конечная точка API IPFS. По умолчанию она указывает на службу `ipfs`,
# включенную в docker-compose.yml выше. Переопределяйте только в том случае, если вы хотите
# указать на узел IPFS, который вы используете в другом месте.
# IPFS_URL=http://ipfs:5001
# URL RPC блокчейна. Публичный RPC основной сети Monad работает "из коробки";
# замените на приватную конечную точку, если вам нужна более высокая пропускная способность / надежность.
BLOCKCHAIN_RPC=https://rpc.monad.xyz
# Основная служба закрепления IPFS. Обязательно — см. шаг 5 в предварительных условиях.
# PIN_SERVICE_URL: конечная точка API службы закрепления IPFS
# PIN_SERVICE_API_KEY: JWT / bearer-токен из вашей учетной записи
# PIN_SERVICE_GATEWAY: URL публичного шлюза службы
PIN_SERVICE_URL=https://api.pinata.cloud/psa
PIN_SERVICE_API_KEY=
PIN_SERVICE_GATEWAY=https://gateway.pinata.cloud/ipfs/
# Настоятельно рекомендуется: ВТОРАЯ служба закрепления для избыточности.
# Пакет поддерживает до десяти (PIN_SERVICE_2_*, PIN_SERVICE_3_*, ...).
# PIN_SERVICE_2_URL=https://api.web3.storage/pins
# PIN_SERVICE_2_API_KEY=
# PIN_SERVICE_2_GATEWAY=https://w3s.link/ipfs/
# Ключ подписи процессора. НЕОБЯЗАТЕЛЬНО — оставьте пустым, чтобы запустить узел без
# возможностей процессора. Установите позже, когда захотите обрабатывать платный /
# зашифрованный контент. ЛИБО вставьте необработанный шестнадцатеричный ключ сюда, ЛИБО смонтируйте
# файл по пути ./secrets/processor.key и установите PROCESSOR_KEY_FILE ниже.
PROCESSOR_KEY=
# PROCESSOR_KEY_FILE=/run/secrets/processor.key
# ----------------------------------------------------------------------
# НЕОБЯЗАТЕЛЬНО — разумные значения по умолчанию уже встроены. Раскомментируйте для переопределения.
# ----------------------------------------------------------------------
# Контракт On-chain Config. По умолчанию Monad mainnet; измените для других цепей.
# CONFIG_CONTRACT=0xEeDf3fd85b8C955160CBee10FB45e02add055e39
# Где встроенный узел IPFS хранит свои данные на хосте. По умолчанию
# ./ipfs-data рядом с этим файлом. Для производственных развертываний укажите
# на другой диск — хранилище данных растет по мере добавления закрепленного контента.
# IPFS_DATA_PATH=./ipfs-data
# Telegram-бот для домена (необязательно). Установите и TOKEN, и NAME для
# включения; оставьте любое поле пустым для отключения. TOKEN берется из BotFather,
# NAME — это @-имя пользователя бота без @. ID бота автоматически
# выводится из префикса токена "<id>:<secret>".
# TELEGRAM_BOT_TOKEN=123456789:ABCdef-the-rest-of-your-token
# TELEGRAM_BOT_NAME=YourSavvaBot
# Версия образа для извлечения (соответствует тегу выпуска).
# SAVVA_VERSION=latest
# Хостовый порт, открываемый docker compose. Контейнер всегда прослушивает
# порт 8080 внутри; это изменяет только порт, к которому привязывается ваш хост.
# PORT=8080
# Подробность: trace, debug, info, warn, error.
# VERBOSITY=info
# Блок, с которого начинать индексацию в новой БД.
# INITIAL_BLOCK=0
# Ограничения размера.
# MAX_FILE_SIZE=50MB
# MAX_POST_SIZE=10MB
# MAX_USER_DISK_SPACE=1GB
# URL публичного веб-сайта для домена (по умолчанию https://${DOMAIN}).
# DOMAIN_WEBSITE=https://mysavva.example.com
Это весь установочный пакет — два файла в одном каталоге.
2. Заполните .env
Откройте .env и замените значения-заполнители. Семь полей являются обязательными:
DOMAIN,ADMIN_ADDRESS,DB_CONNECTION_STRING,BLOCKCHAIN_RPCPIN_SERVICE_URL,PIN_SERVICE_API_KEY,PIN_SERVICE_GATEWAY(из вашей учетной записи службы закрепления — см. пункт 5 предварительных условий)
PROCESSOR_KEY является необязательным и может быть добавлен позже, а IPFS_URL автоматически установлен по умолчанию на встроенную службу IPFS. Все, что находится ниже разделителя OPTIONAL, имеет разумные значения по умолчанию и может оставаться закомментированным.
О порте. Контейнер всегда прослушивает порт 8080 внутри — это жестко закодировано в образе. Сопоставление docker compose ${PORT:-8080}:8080 по умолчанию публикует его на хосте по порту 8080, поэтому curl http://localhost:8080/info работает "из коробки". Установите PORT= в .env только в том случае, если вы хотите использовать другой хостовый порт (например, PORT=9000, если 8080 уже занят на вашей машине). Ваш обратный прокси в любом случае общается с 8080 контейнера.
Если вы не хотите вставлять приватный ключ в файл, смонтируйте его как секрет:
mkdir -p secrets
echo "0xYourProcessorPrivateKey" > secrets/processor.key
chmod 600 secrets/processor.key
…а в .env:
PROCESSOR_KEY=
PROCESSOR_KEY_FILE=/run/secrets/processor.key
Папка secrets/ монтируется в контейнер только для чтения с помощью docker-compose.yml по умолчанию. Контейнер считывает ключ с диска при запуске; значение никогда не отображается в docker inspect или списках процессов.
3. Загрузите базу данных
У вас есть два способа заполнить базу данных. Настоятельно рекомендуется восстановление из снимка.
Вариант A (рекомендуется) — восстановление из публичного снимка
SAVVA публикует ежедневные снимки Postgres по адресу savva.app/public_files/, по одному для каждой цепочки, названные следующим образом:
savva-db-backup-monad-2026-05-03.sql.gz
savva-db-backup-pls-2026-05-03.sql.gz
Выберите цепочку, которую вы индексируете (monad используется по умолчанию в этом руководстве), и последнюю дату. Дамп представляет собой обычный gzipped SQL — восстановите его с помощью psql:
# Выберите последний снимок для вашей цепочки.
SNAP=https://savva.app/public_files/savva-db-backup-monad-2026-05-03.sql.gz
# Пустая целевая база данных должна уже существовать и соответствовать $DB_CONNECTION_STRING.
curl -L "$SNAP" | gunzip -c | psql "$DB_CONNECTION_STRING"
Когда бэкенд запустится, он продолжит работу с того места, на котором остановился снимок — обычно на несколько часов отставая от текущего блока — и завершит синхронизацию за считанные минуты, а не часы.
Вариант B — инициализация пустой схемы и повторная синхронизация с генезиса
Полезно, если вы работаете в пользовательской цепочке, хотите независимой верификации или просто хотите посмотреть, как работает индексатор:
docker compose run --rm savva-backend -initdb
Это создает все таблицы, необходимые бэкенду, и устанавливает версию схемы. Первый docker compose up -d после этого начнет индексацию с настроенного INITIAL_BLOCK вперед — ожидайте длительной начальной синхронизации.
4. Запустите
docker compose up -d
Контейнер извлекает образ (≈100 МБ), считывает .env, генерирует свой собственный YAML-файл конфигурации и начинает индексировать блокчейн. Следите за логами:
docker compose logs -f savva-backend
Успешный запуск выглядит примерно так:
INF Config: Blockchain RPC configured
INF Config: Processor key configured
INF Connected to DB
INF SAVVA Backend. v:1.0.25
...затем следуют строки о том, как слушатель блокчейна догоняет. Если вы видите ошибки, обратитесь к разделу "Устранение неполадок" ниже.
5. Проверьте
Бэкенд прослушивает порт 8080. С той же машины:
curl http://localhost:8080/info
Вы должны получить JSON-ответ, описывающий систему: адреса контрактов, ваш домен, версию, шлюзы IPFS и так далее.
Это работающий узел SAVVA.
Размещение в публичном интернете
Образ не завершает TLS — это сделано намеренно. Различные операторы хотят разных вещей (Cloudflare, Caddy, nginx, Traefik, Tailscale Funnel), и мы не хотим выбирать за вас. Минимум — это что-то, что:
- Прослушивает
:443, завершает TLS, проксирует на:8080контейнера. - Перенаправляет обновление WebSocket для конечной точки
/ws. - Маршрутизирует
/api/*и URL-адреса для обнаружения SEO (/robots.txt,/sitemap*.xml) в бэкенд.
Caddy с reverse_proxy 127.0.0.1:8080 — это разумный выбор из двух строк, если у вас нет предпочтений. Для полной конфигурации nginx производственного уровня см. раздел Для администраторов объявления о SEO — это та же конфигурация, которую вы использовали бы для любого сайта на платформе SAVVA.
Настройка активов вашего домена (пакет UI)
Сам по себе бэкенд SAVVA не поставляет пользовательский интерфейс — он обслуживает API и ожидает, что ваш обратный прокси будет обслуживать веб-клиент SolidJS из пакета, размещенного на IPFS. После запуска бэкенда:
- Соберите (или форкните) проект savva-ui-solidjs, закрепите результат сборки в IPFS и получите соответствующий CID.
- Из клиента SAVVA, подписанного вашим административным кошельком, вызовите команду администратора
setDomainAssetsCIDс CID. Бэкенд загрузит пакет, сохранит его вdata/domain_assets/и будет обслуживать его оттуда.
CID не является частью YAML-конфигурации — он устанавливается во время выполнения и сохраняется в базе данных. Это означает, что вы можете менять пользовательские интерфейсы, не перезапуская бэкенд.
Обновление до новой версии
Релизы публикуются как помеченные образы Docker. Для обновления:
# Закрепите определенную версию (рекомендуется для продакшена):
echo "SAVVA_VERSION=1.0.26" >> .env
docker compose pull
docker compose up -d
# Или просто отслеживайте последнюю версию:
docker compose pull && docker compose up -d
Миграции схемы применяются автоматически при запуске. Следите за примечаниями к выпуску для любой версии, которая изменяет схему, на случай, если потребуется ручной шаг.
Устранение неполадок
ERROR: required env var X is not set — вы пропустили обязательное поле в .env. Ошибка указывает имя переменной.
dial tcp: connection refused для БД — контейнер не может подключиться к Postgres. Если ваша БД работает на том же хосте, что и Docker, используйте host.docker.internal (Mac/Windows) или LAN-IP вашей машины, а не localhost. localhost внутри контейнера означает сам контейнер.
http: server gave HTTP response to HTTPS client для URL IPFS — у вас http:// для конечной точки IPFS, которая на самом деле HTTPS, или наоборот. Проверьте схему.
В логах постоянно появляется RPC error — ваш URL RPC неверен, ограничен по скорости или ID цепочки не совпадает. Адрес контракта конфигурации по умолчанию предназначен для Monad; если вы подключаетесь к другой цепочке, установите CONFIG_CONTRACT в .env на правильный адрес для этой цепочки.
Контейнер запускается, но долго ничего не происходит — это нормально, если вы выбрали вариант B на шаге 3 (пустая схема). Бэкенд синхронизирует историю блокчейна, начиная с INITIAL_BLOCK, что может занять часы в цепочке с длинной историей. Следите за docker compose logs -f — вы увидите, как номера блоков растут. Если вы не хотите ждать, остановите контейнер, удалите базу данных и вместо этого восстановите ее из публичного снимка (вариант A).
Если вы столкнулись с проблемой, которая здесь не описана, обратитесь в каналы поддержки SAVVA с выводом ваших docker compose logs и очищенным .env (скройте ключ процессора).
Об IPFS-хранилище
В установке SAVVA используются два уровня закрепления:
- Встроенный узел Kubo (работающий в службе
ipfs:compose) хранит каждый загруженный файл локально. Это быстро, бесплатно и немедленно доступно — но это единая точка отказа. Если этот диск выйдет из строя, локальная копия будет утеряна. - Ваша внешняя служба закрепления (настраиваемая через
PIN_SERVICE_*в.env) также делает копию. Бэкенд просит службу закрепления закрепить каждый новый CID сразу после его добавления в локальный узел, чтобы контент вашего сообщества был надежно реплицирован и оставался доступным через публичный шлюз службы, даже когда ваш собственный узел отключен.
Сочетание "быстро локально + надежно внешне" — вот почему существуют обе половины. Не пропускайте внешнюю службу закрепления, если только вы не запускаете одноразовый тестовый узел — потеря закрепления необратима.
Кроме того, встроенное хранилище данных IPFS заслуживает такого же обращения, как и любой другой растущий каталог состояний. В отличие от базы данных Postgres (которая имеет фиксированную схему и растет только при добавлении доменов), хранилище данных IPFS растет пропорционально контенту вашего сообщества. И поскольку пакет поставляется с process-all-domains: true, установленным в сгенерированной конфигурации, ваш узел индексирует и закрепляет посты со всех доменов в сети, а не только с вашего — это сделано намеренно (это сохраняет контент доступным, даже когда отдельные операторы доменов отключаются), но это означает, что рост хранилища данных отслеживает всю платформу, а не только ваше сообщество. Планируйте это так же, как и любую другую нагрузку на хранилище закреплений:
- Разместите хранилище данных на диске, который вы готовы увеличивать. Настройка
IPFS_DATA_PATH=в.envуправляет путем на хосте. По умолчанию это./ipfs-dataрядом с файлом compose; для продакшена укажите на выделенный диск или том (/mnt/data1/ipfs, подключенный том EBS и т. д.). - Мониторьте использование диска. Сигнал тревоги не сработает, если диск заполнится. Следите за
du -sh ipfs-data/(или куда вы его указали) и общим предупреждением об использовании диска. - Создавайте резервные копии, как и для любого другого каталога состояний. Остановка службы
ipfsи синхронизация папки с данными с помощью rsync — самый простой путь. - Откройте порт 4001 (TCP и UDP). Это порт IPFS swarm. Если он заблокирован брандмауэром, контент все равно закрепляется локально, но не реплицируется в более широкую сеть IPFS — остальной мир будет получать ваш контент напрямую через ваш узел, но медленно. Большинство облачных провайдеров требуют явного открытия этого порта в группе безопасности / брандмауэре VPC.
- Kubo по умолчанию не имеет ограничения
MaxStorage. Если вы хотите жесткий потолок с автоматической сборкой мусора, отредактируйтеipfs-data/configпосле первого запуска и установитеDatastore.StorageMaxна размер, например"100GB".
Если вы уже используете узел IPFS и предпочитаете использовать его, установите IPFS_URL= в .env, чтобы указать на него, и удалите блок службы ipfs: из docker-compose.yml. Бэкенду все равно.
Чего намеренно нет в образе
Сам образ запускает только бэкенд. Стек compose добавляет службу IPFS, но Postgres, TLS и веб-клиент по-прежнему находятся в вашей ответственности:
- Postgres — у операторов есть сильные мнения о резервном копировании, репликах и управляемых/самостоятельно размещаемых решениях. Объединение их в один пакет затруднило бы все это.
- TLS — выбор обратного прокси остается за вами.
- Веб-клиент — распространяется через IPFS и закрепляется администратором, а не встроен в образ бэкенда.
Если вам нужна установка "все в одном" с Postgres, Caddy и пользовательским интерфейсом, это отдельный файл compose, который мы можем опубликовать позже для обычного / любительского использования. Текущий пакет предназначен для людей, которые собираются поддерживать что-то, что они намерены сохранить.
Что вы можете сделать сейчас
- Попробуйте. Даже если вы не планируете запускать публичный домен, установка за пять минут, описанная выше, даст вам рабочий узел SAVVA на локальном хосте. Это полезный способ увидеть, что на самом деле делает платформа "под капотом".
- Форкните домен. Если вы когда-либо хотели сайт SAVVA для конкретного сообщества — вашего DAO, вашего журнала, ваших друзей — у вас теперь есть проект на выходные, а не на месяц.
- Сообщите нам, чего не хватает. Это первый публичный выпуск пакета для самостоятельного хостинга. Если что-то в вашей среде не работает, мы хотим знать.
Протокол всегда был разрешительным. Теперь реализация наконец-то достаточно проста, чтобы эта разрешительность стала реальностью.