#!/bin/sh set -eu export BITMESSAGE_API_USER="${BITMESSAGE_API_USER:-bitmessage_api_user}" export BITMESSAGE_API_PASSWORD="${BITMESSAGE_API_PASSWORD:-bitmessage_api_password}" export BITMESSAGE_SEED_ADDRESSES="${BITMESSAGE_SEED_ADDRESSES:-0}" export BITMESSAGE_API_PORT="${BITMESSAGE_API_PORT:-8442}" export BITMESSAGE_TTL="${BITMESSAGE_TTL:-172800}" export BITMESSAGE_STOPRESENDINGAFTERXDAYS="${BITMESSAGE_STOPRESENDINGAFTERXDAYS:-30}" export BITMESSAGE_APIVARIANT="${BITMESSAGE_APIVARIANT:-legacy}" export BITMESSAGE_MAXTOTALCONNECTIONS="${BITMESSAGE_MAXTOTALCONNECTIONS:-200}" export BITMESSAGE_TRUSTED_PEER="${BITMESSAGE_TRUSTED_PEER:-}" export BITMESSAGE_SEND_OUTGOING="${BITMESSAGE_SEND_OUTGOING:-True}" export BITMESSAGE_KNOWN_NODES="${BITMESSAGE_KNOWN_NODES:-}" # Reject anything but a plain number: this value is written into keys.dat, and # unlike the credentials below it has no business containing characters that # esc() would have to neutralise. A typo here would otherwise land in the config # as a key the daemon silently ignores. case "$BITMESSAGE_MAXTOTALCONNECTIONS" in '' | *[!0-9]*) echo "BITMESSAGE_MAXTOTALCONNECTIONS must be a positive integer" >&2 exit 1 ;; esac # sendoutgoingconnections is read with safeGetBoolean, which would take "yes" or # "1" too; keys.dat is written by hand often enough that it is worth keeping one # spelling in it. Anything else is a typo, and a typo here reads as False -- # a node that quietly never dials out. case "$BITMESSAGE_SEND_OUTGOING" in [Tt]rue) BITMESSAGE_SEND_OUTGOING=True ;; [Ff]alse) BITMESSAGE_SEND_OUTGOING=False ;; *) echo "BITMESSAGE_SEND_OUTGOING must be True or False" >&2 exit 1 ;; esac # host:port with a numeric port -- the form both consumers need. PyBitmessage # does check trustedpeer itself, but by sys.exit() from a constructor deep in # the network thread: the container dies with the reason buried in the daemon # log. Fail here, where the message is the first thing in `docker logs`. check_peer() { case "$1" in *:*) ;; *) return 1 ;; esac [ -n "${1%:*}" ] || return 1 case "${1##*:}" in '' | *[!0-9]*) return 1 ;; esac } if [ -n "$BITMESSAGE_TRUSTED_PEER" ] && ! check_peer "$BITMESSAGE_TRUSTED_PEER" then echo "BITMESSAGE_TRUSTED_PEER must be host:port" >&2 exit 1 fi if [ -z "${BITMESSAGE_SEED_PHRASE:-}" ] then BITMESSAGE_SEED_PHRASE="$(cat /dev/random | tr -dc "a-z" | head -c32)" export BITMESSAGE_SEED_PHRASE fi # Escape a value for use on the right-hand side of the sed expressions below. # There, a backslash starts an escape, "&" stands for the whole match, and "|" # ends the replacement because it is the delimiter. Unescaped, a password # containing "&" was silently rewritten into something else and one containing # "|" made sed fail outright. esc() { printf '%s' "$1" | sed -e 's/[\\&|]/\\&/g' } # this command must be run as root (for bind mounts to container) if [ -f keys.dat ] then chown bitmessage:bitmessage keys.dat chmod 600 keys.dat fi # maxtotalconnections is the only brake on a node whose P2P port (8444) is # published: it caps inbound sockets at the total minus maxoutboundconnections. # The substitution below is a no-op when the key is missing, which would ship a # node that looks capped and is not -- and the PyBitmessage clone in the # Dockerfile is unpinned, so the stock config is whatever upstream generates # today. Add the key rather than trust the substitution alone; line 1 is the # [bitmessagesettings] header the daemon reads it from. if ! grep -q "^maxtotalconnections = " keys.dat then gosu bitmessage sed -i "1a maxtotalconnections = $BITMESSAGE_MAXTOTALCONNECTIONS" keys.dat fi # trustedpeer is absent from the stock keys.dat entirely, so the substitution # below is a no-op until the key exists -- same trap as maxtotalconnections. # The key is added even when the value is empty, which is how it can be taken # back off a node that was pinned before: safeGet returns "" and connectionpool # falls back to chooseConnection. That empty case is also why the anchors here # stop at "=" instead of "= ": with nothing to the right there is no trailing # space to match, and the substitution would never fire again. if ! grep -q "^trustedpeer =" keys.dat then gosu bitmessage sed -i "1a trustedpeer = $(esc "$BITMESSAGE_TRUSTED_PEER")" keys.dat fi # Set config values. Every expression is anchored to the start of the line and # names its key in the replacement, so no backreference is involved and nothing # in another section can match. With set -e a failure here now stops the # container instead of leaving the daemon on its previous settings unnoticed -- # including the case of a bind mount with no keys.dat at all. gosu bitmessage sed -i \ -e "s|^apiinterface = .*|apiinterface = 0.0.0.0|" \ -e "s|^apivariant = .*|apivariant = $(esc "$BITMESSAGE_APIVARIANT")|" \ -e "s|^apiusername = .*|apiusername = $(esc "$BITMESSAGE_API_USER")|" \ -e "s|^apipassword = .*|apipassword = $(esc "$BITMESSAGE_API_PASSWORD")|" \ -e "s|^apiport = .*|apiport = $(esc "$BITMESSAGE_API_PORT")|" \ -e "s|^apienabled = .*|apienabled = True|" \ -e "s|^ttl = .*|ttl = $(esc "$BITMESSAGE_TTL")|" \ -e "s|^stopresendingafterxdays = .*|stopresendingafterxdays = $(esc "$BITMESSAGE_STOPRESENDINGAFTERXDAYS")|" \ -e "s|^maxtotalconnections = .*|maxtotalconnections = $BITMESSAGE_MAXTOTALCONNECTIONS|" \ -e "s|^trustedpeer =.*|trustedpeer = $(esc "$BITMESSAGE_TRUSTED_PEER")|" \ -e "s|^sendoutgoingconnections = .*|sendoutgoingconnections = $BITMESSAGE_SEND_OUTGOING|" \ -e "s|^udp = .*|udp = False|" keys.dat # BITMESSAGE_KNOWN_NODES pins the peers the daemon starts from, and is rewritten # on every start: in a private contour the seed *is* the topology, and a file # left over from an earlier run names nodes that may no longer exist. Seeding it # also switches off the DNS bootstrap -- json_deserialize_knownnodes raises # knownNodesActual for any peer that is neither DEFAULT_NODES nor "self", and # connectionpool calls startBootstrappers only while that flag is down, so the # node never reaches bootstrap8080.bitmessage.org. # # Writing it "only when the file is missing" would have been a permanent no-op: # the image ships a knownnodes.dat, produced by the `pybitmessage -t` run in the # Dockerfile, and a named volume inherits it on first use. if [ -n "$BITMESSAGE_KNOWN_NODES" ] then now="$(date +%s)" nodes="" oldifs="$IFS" IFS="," for peer in $BITMESSAGE_KNOWN_NODES do IFS="$oldifs" if ! check_peer "$peer" then echo "BITMESSAGE_KNOWN_NODES entry '$peer' must be host:port" >&2 exit 1 fi [ -z "$nodes" ] || nodes="$nodes," nodes="$nodes {\"stream\": 1, \"peer\": {\"host\": \"${peer%:*}\", \"port\": ${peer##*:}}, \"info\": {\"lastseen\": $now, \"rating\": 0, \"self\": false}}" IFS="," done IFS="$oldifs" printf '[%s\n]\n' "$nodes" > knownnodes.dat chown bitmessage:bitmessage knownnodes.dat chmod 600 knownnodes.dat fi # generate address from seed if [ "$BITMESSAGE_SEED_ADDRESSES" -gt 0 ] then # Four attempts, not a bash {1..4}: this runs under dash, where brace # expansion is literal and the loop would have run once. The call is # idempotent (createDeterministicAddresses returns nothing for an address # that already exists), so these are retries while the API comes up. for i in 1 2 3 4 do sleep 15 gosu bitmessage /usr/bin/python /usr/local/bin/seed_addr_gen.py done & fi exec gosu bitmessage pybitmessage -d