# Общие сведения > English version: [README.md](README.md) [PyBitmessage](https://bitmessage.org/) — клиент P2P-протокола обмена сообщениями Bitmessage, служащий для отправки шифрованных сообщений как одному адресату, так и множеству подписчиков. Клиент PyBitmessage, работающий демоном в docker-контейнере с включённым XML-RPC API. Репозиторий описывает только развёртывание в docker. # Использование Контейнер создаёт детерминированные адреса Bitmessage на основе переменной `BITMESSAGE_SEED_PHRASE`. Ниже — примеры, с которых удобно начать создание контейнера. У контейнера два порта, и они не взаимозаменяемы. **8442** — XML-RPC API: он полностью управляет демоном и не имеет TLS, поэтому задайте свои учётные данные и оставьте его на loopback. **8444** — P2P-порт Bitmessage: опубликуйте его, чтобы к узлу могли подключаться другие, или не публикуйте, и тогда узел работает только на исходящих. Внутри контейнера демон слушает оба в любом случае. ## docker-compose ```yaml services: pybitmessage: build: context: https://git.bitdeals.org/private/bitmessage.git dockerfile: ./docker/Dockerfile image: registry.bitdeals.org/bitmessage environment: - BITMESSAGE_API_USER=CHANGE_ME - BITMESSAGE_API_PASSWORD=CHANGE_ME - BITMESSAGE_SEED_PHRASE=bitmessage_seed_phrase - BITMESSAGE_SEED_ADDRESSES=1 - BITMESSAGE_TTL=172800 - BITMESSAGE_STOPRESENDINGAFTERXDAYS=60 - BITMESSAGE_MAXTOTALCONNECTIONS=40 ports: - 127.0.0.1:8442:8442 # API — только loopback - 8444:8444 # P2P — уберите строку, чтобы остаться на исходящих volumes: - bitmessage:/home/bitmessage volumes: bitmessage: ``` ## docker cli ```sh docker run -d \ -e BITMESSAGE_API_USER=CHANGE_ME \ -e BITMESSAGE_API_PASSWORD=CHANGE_ME \ -e BITMESSAGE_SEED_PHRASE=bitmessage_seed_phrase \ -e BITMESSAGE_SEED_ADDRESSES=1 \ -e BITMESSAGE_TTL=172800 \ -e BITMESSAGE_STOPRESENDINGAFTERXDAYS=60 \ -e BITMESSAGE_MAXTOTALCONNECTIONS=40 \ -p 127.0.0.1:8442:8442 \ -p 8444:8444 \ -v bitmessage:/home/bitmessage \ registry.bitdeals.org/bitmessage ``` # Параметры Образы контейнера настраиваются параметрами, передаваемыми при запуске. |Параметр|Назначение| |:--------|:-------| |-p 127.0.0.1:8442|Порт API. Внутри контейнера демон всегда слушает `0.0.0.0`, поэтому доступность определяет то, что опубликовано| |-p 8444|P2P-порт Bitmessage. Необязательный: без него узел всё равно подключается к пирам сам, просто к нему подключиться нельзя. Публиковать только как `8444:8444` — см. «Замечания»| |-v /home/bitmessage|Каталог данных: `keys.dat` (личность, настройки) и `messages.dat`. Без него после каждого обновления это новый узел| |-e BITMESSAGE_API_USER|Пользователь XML-RPC API. По умолчанию: `bitmessage_api_user` — измените| |-e BITMESSAGE_API_PASSWORD|Пароль XML-RPC API. По умолчанию: `bitmessage_api_password` — измените, см. «Замечания»| |-e BITMESSAGE_SEED_PHRASE|Парольная фраза для создания детерминированных адресов. По умолчанию: генерируется заново при каждом старте, то есть адреса каждый раз другие. Используется только при `BITMESSAGE_SEED_ADDRESSES` больше `0`| |-e BITMESSAGE_SEED_ADDRESSES|Количество создаваемых детерминированных адресов. По умолчанию: `0`| |-e BITMESSAGE_TTL|Срок жизни вновь отправляемых сообщений, в секундах. По умолчанию: `172800`| |-e BITMESSAGE_STOPRESENDINGAFTERXDAYS|Прекратить повторную отправку недоставленного сообщения через X дней. По умолчанию: `30`| |-e BITMESSAGE_APIVARIANT|Предоставляемый API: xml или json-RPC. По умолчанию: `legacy`| |-e BITMESSAGE_MAXTOTALCONNECTIONS|Предел одновременных соединений, входящих и исходящих вместе (`maxoutboundconnections` равен 8, то есть это значение минус 8 — запас на входящие). По умолчанию: `200`, штатное значение PyBitmessage — снижайте, если P2P-порт опубликован| |-e BITMESSAGE_TRUSTED_PEER|`host:port` единственного пира, к которому узел подключается наружу; больше ни к кому. По умолчанию: пусто — узел выбирает пиров сам. См. «Замечания»| |-e BITMESSAGE_SEND_OUTGOING|Подключается ли узел наружу вообще: `True` или `False`. По умолчанию: `True`. При `False` получается узел, который только принимает входящие, — центр приватного контура| |-e BITMESSAGE_KNOWN_NODES|Список `host:port` через запятую; записывается в `knownnodes.dat` при каждом старте вместо того, что там было. По умолчанию: пусто — файл не трогается. Заодно выключает бутстрап по DNS, см. «Замечания»| |-e BITMESSAGE_WATCHDOG|Перезапускать ли демона, потерявшего всех пиров: `True` или `False`. По умолчанию: `True`. См. «Замечания»| |-e BITMESSAGE_WATCHDOG_PERIOD|Секунд между проверками числа пиров. По умолчанию: `60`| |-e BITMESSAGE_WATCHDOG_AFTER|Сколько проверок подряд без пиров нужно, чтобы вмешаться. По умолчанию: `5` — то есть пять минут при периоде по умолчанию| |-e BITMESSAGE_WATCHDOG_COOLDOWN|Минимальный промежуток между двумя перезапусками, в секундах. По умолчанию: `900`. `0` снимает ограничение| # Замечания - `%` в пароле API использовать нельзя: он ломает собственный чтец конфигурации PyBitmessage, и любой вызов API отвечает `500`, притом что `keys.dat` выглядит правильным. Остальные символы допустимы, контейнер их экранирует. - Клиенты обязаны кодировать учётные данные — они попадают в `http://user:password@host:port/`, где `@`, `#`, `/` и `:` меняют разбор URL. Скрипты этого образа кодируют, ваш клиент должен тоже. - Контейнер становится здоровым, когда у демона появилось сетевое соединение, — на новом узле это несколько минут. Начальный период ожидания заодно покрывает стартовый `VACUUM` файла `messages.dat`. - **Демона без пиров перезапускаем, молчащего — нет.** Демон, потерявший всех пиров, сам обратно дорогу не находит: он может простоять на нуле соединений сколько угодно, — а только что запущенный ищет пиров напористо и находит. Поэтому контейнер присматривает за собственным демоном: раз в `BITMESSAGE_WATCHDOG_PERIOD` спрашивает у API число соединений и после `BITMESSAGE_WATCHDOG_AFTER` нулей подряд останавливает демона через его же API и поднимает заново. *Контейнер* при этом не пересоздаётся и никто снаружи не участвует, так что соседний узел сохраняет адреса и видит лишь моргнувший API. Демона, у которого API не отвечает вовсе, сторож не трогает намеренно. Это узел, застрявший на стартовом `VACUUM`, которого он не может закончить; перезапуск его не лечит, а лишь обрывает следующий `VACUUM` на середине. Проверка здоровья показывает `unhealthy` в обоих случаях, но вмешиваться стоит только в первый, а во втором нужен человек. **Задайте контейнеру `stop_grace_period: 90s`** (compose) или `--stop-timeout 90` (`docker run`). Корректное закрытие `messages.dat` не укладывается в десять секунд, отпущенные Docker по умолчанию, — замерено около тринадцати на небольшой базе, и растёт вместе с файлом, — так что без этого демона убьёт SIGKILL на середине записи, ровно то, ради предотвращения чего супервизор и заведён. Сам образ это задать не может, только вызывающая сторона. `BITMESSAGE_WATCHDOG=False` выключает правило о пирах. Супервизор остаётся в любом случае: он же корректно останавливает демона по `docker stop` и поднимает того, кто умер сам, вместо контейнера, стоящего вокруг трупа. - **P2P-порт публикуется только как `8444:8444`.** Пирам демон сообщает порт из собственной конфигурации (`port` в `keys.dat`), а не тот, в который вы его отобразили, поэтому `8555:8444` объявляет сети порт, где никого нет. Для другого порта на хосте нужен `extport` в `keys.dat`, а его этот контейнер не подставляет. - **Свой адрес нигде не указывается.** В version-сообщении демон шлёт захардкоженный `127.0.0.1`, который каждый пир отбрасывает и берёт вместо него IP, увиденный на сокете, а порт — из того же сообщения. Поэтому узел с опубликованным 8444 сеть находит сама, как только он подключится наружу: ни IP, ни DNS-имя хоста задавать негде. Единственное исключение — скрытый сервис Tor, которому нужен явный `onionhostname`. - **Приватному контуру нужны назначенные пиры и звезда, в которую их назначать.** PyBitmessage отвергает кандидата, чья сетевая группа (для IPv4 — /16) уже представлена среди его исходящих соединений. Все контейнеры одного compose-проекта живут в одной /16, поэтому каждый узел удерживает ровно одно исходящее соединение — со случайно выбранным пиром, и контур чаще всего распадается на компоненты. Задайте одному узлу `BITMESSAGE_SEND_OUTGOING=False`, и он станет хабом, который только принимает (проверка смотрит лишь на исходящие, входящие она не ограничивает), остальные направьте на него через `BITMESSAGE_TRUSTED_PEER=:8444` — объекты пойдут спица → хаб → спицы. Адреса задавайте IP, а не именами сервисов: проверка разбирает хост как IP, да и обмен `addr` между узлами оперирует IP. - **`BITMESSAGE_KNOWN_NODES` — то, что делает приватный контур приватным.** Узел, у которого в `knownnodes.dat` есть пир не из встроенного списка PyBitmessage, перестаёт спрашивать адреса у `bootstrap8080.bitmessage.org`; без этого даже узел с назначенным пиром при каждом старте резолвит публичный бутстрап-хост. Файл переписывается на каждом старте, поэтому во что узел верит при загрузке, определяет переменная, а не история контейнера. - **Публикация 8444 — осознанный компромисс по безопасности.** Демон работает на Python 2 и уже разбирает недоверенные данные от своих исходящих пиров, так что открытый порт эту поверхность не создаёт — он меняет то, *кто* может подключиться, *когда* и *сколько их*. Разбор до рукопожатия становится доступен кому угодно, а реальный риск — исчерпание ресурсов, а не выполнение кода. Держите `BITMESSAGE_MAXTOTALCONNECTIONS` низким и ограничьте контейнер по памяти и CPU. - **У демона нет собственных прав, а контейнер снимает с себя остальное.** `run.sh` нужен root ровно один раз, на старте: `keys.dat` может приехать из bind-монтирования с чужим владельцем, поэтому сначала ему меняют владельца, ставят режим 600 и читают. Сразу после этого скрипт перезапускает сам себя с bounding set из четырёх capability — `SETUID` и `SETGID`, чтобы запускать демона под его собственным пользователем, `KILL` для запасной остановки и `SETPCAP`, чтобы снять остальные, — а каждый запуск демона идёт через `drop_privs.py`: uid 2000, все четыре набора capability пустые, `no_new_privs` включён. Ни на одном файле образа нет бита setuid или setgid, так что захваченному демону не по чему подниматься. Три вещи может задать только вызывающая сторона, и все три стоит задать. `cap_drop: ALL` с возвращёнными `CHOWN`, `DAC_OVERRIDE`, `FOWNER`, `SETUID`, `SETGID`, `KILL` и `SETPCAP`: эти семь нужны описанному выше старту, а сузить набор, с которым работают healthcheck и `docker exec`, контейнер сам не может — только это. `read_only: true` с `tmpfs` под `/tmp` и `/run`: демон пишет только в свой домашний каталог. И `pids_limit` — рядом с ограничениями по памяти и CPU, о которых просит предыдущее замечание.