Bitcoin Full Node mit FreeBSD 14.2 auf einem Mac mini i7 Server (Late 2012 Macmini6,2)

Ich habe eine Full Node mit einem Raspberry Pi und Umbrel laufen. Im lokalen Netz funktioniert das ganz gut, aber: Der Umbrel-Server kann keine SSL- , sondern nur unverschlüsselte (TOR)Verbindungen zu Wallets aufbauen. Das wollte ich anders. Ich baue eine Bitcoin Full Node, die hinter einer Fritz!Box über eine Domain via SSL erreichbar ist.

Beim Stöbern im Dachboden fand ich einen alten Mac mini i7 Server (Late 2012 Macmini6,2 2.6 GHz), der von Apple keine Updates mehr bekommt und zwei SATA SSDs mit jeweils 1TB Speicher. Die beiden HDDs des Mac mini habe ich ausgebaut und durch die zwei SSDs ersetzt.

Inhalt

 

FreeBSD 14.2 installieren

Quelle: https://download.freebsd.org/releases/ISO-IMAGES/14.2/

Mit Belana Edger das Image FreeBSD-14.2-RELEASE-amd64-memstick.img auf einen USB-Stick kopieren, den Stick in den Mac mini stecken und mit gedrückter option Taste starten. Den Installationsprozess von FreeBSD durchlaufen, USB-Stick abziehen und neu starten. Wenn bei der Installation das Zusammenlegen der zwei SSDs (wie von mir :) übersehen wurde, kann dies jetzt nachträglich geschehen.

Die zwei SSDs im zpool zusammenlegen

Die Laufwerke anzeigen.

❯ camcontrol devlist
<Samsung SSD 840 EVO 1TB EXT0CB6Q>  at scbus0 target 0 lun 0 (pass0,ada0)
<Samsung SSD 840 EVO 1TB EXT0CB6Q>  at scbus1 target 0 lun 0 (pass1,ada1)
<AHCI SGPIO Enclosure 2.00 0001>   at scbus2 target 0 lun 0 (ses0,pass2)

Die Partitiionen anzeigen, FreeBSD wurde auf ada1 installiert. Auf ada0 befinden sich noch alte Daten.

❯ gpart show
=>        40  1953525088  ada1  GPT  (932G)
          40      532480     1  efi  (260M)
      532520        1024     2  freebsd-boot  (512K)
      533544         984        - free -  (492K)
      534528     4194304     3  freebsd-swap  (2.0G)
     4728832  1948794880     4  freebsd-zfs  (929G)
  1953523712        1416        - free -  (708K)

=>        34  1953525101  ada0  GPT  (932G)
          34        2014        - free -  (1.0M)
        2048       65536     1  efi  (32M)
       67584       49152     2  linux-data  (24M)
      116736      524288     3  linux-data  (256M)
      641024       49152     4  linux-data  (24M)
      690176      524288     5  linux-data  (256M)
     1214464       16384     6  linux-data  (8.0M)
     1230848      196608     7  linux-data  (96M)
     1427456  1952097679     8  linux-data  (931G)

Die Partitiion von ada0 löschen.

❯ gpart destroy -F ada0
ada0 destroyed

❯ gpart show
=>        40  1953525088  ada1  GPT  (932G)
          40      532480     1  efi  (260M)
      532520        1024     2  freebsd-boot  (512K)
      533544         984        - free -  (492K)
      534528     4194304     3  freebsd-swap  (2.0G)
     4728832  1948794880     4  freebsd-zfs  (929G)
  1953523712        1416        - free -  (708K)

Neue Partition anlegen.

❯ gpart create -s GPT /dev/ada0
ada0 created

❯ gpart show
=>        40  1953525088  ada1  GPT  (932G)
          40      532480     1  efi  (260M)
      532520        1024     2  freebsd-boot  (512K)
      533544         984        - free -  (492K)
      534528     4194304     3  freebsd-swap  (2.0G)
     4728832  1948794880     4  freebsd-zfs  (929G)
  1953523712        1416        - free -  (708K)

=>        40  1953525088  ada0  GPT  (932G)
          40  1953525088        - free -  (932G)

❯ zpool status
  pool: zroot
 state: ONLINE
status: Some supported and requested features are not enabled on the pool.
        The pool can still be used, but some features are unavailable.
action: Enable all features using 'zpool upgrade'. Once this is done,
        the pool may no longer be accessible by software that does not support
        the features. See zpool-features(7) for details.
config:

        NAME        STATE     READ WRITE CKSUM
        zroot       ONLINE       0     0     0
          ada1p4    ONLINE       0     0     0

errors: No known data errors

❯ zpool list
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
zroot   928G  94.2G   834G        -         -     2%    10%  1.00x    ONLINE  -

Laufwerk zum Pool hinzufügen.

❯ zpool add zroot /dev/ada0

❯ zpool list
NAME    SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
zroot  1.81T  94.2G  1.72T        -         -     1%     5%  1.00x    ONLINE  -

❯ df -h
Filesystem            Size    Used   Avail Capacity  Mounted on
zroot/ROOT/default    1.7T    4.1G    1.7T     0%    /
devfs                 1.0K      0B    1.0K     0%    /dev
/dev/gpt/efiboot0     260M    1.8M    258M     1%    /boot/efi
zroot/var/mail        1.7T    152K    1.7T     0%    /var/mail
zroot                 1.7T     96K    1.7T     0%    /zroot
zroot/tmp             1.7T    244K    1.7T     0%    /tmp
zroot/backup          1.7T     66G    1.7T     4%    /backup
zroot/home            1.7T     96K    1.7T     0%    /home
zroot/var/audit       1.7T     96K    1.7T     0%    /var/audit
zroot/var/log         1.7T    1.7M    1.7T     0%    /var/log
zroot/var/tmp         1.7T    144K    1.7T     0%    /var/tmp
zroot/var/crash       1.7T     96K    1.7T     0%    /var/crash
zroot/usr/src         1.7T    861M    1.7T     0%    /usr/src
zroot/usr/ports       1.7T    2.4G    1.7T     0%    /usr/ports
zroot/home/gent       1.7T     26M    1.7T     0%    /home/gent

 

bitcoind installieren

portmaster net-p2p/bitcoin-daemon
...
===>>> net-p2p/bitcoin-daemon >> (12)

===>>> The following actions will be taken if you choose to proceed:
        Install net-p2p/bitcoin-daemon
        Install databases/db5
        Install databases/sqlite3
        Install lang/tcl86
        Install devel/boost-libs
        Install devel/boost-jam
        Install devel/icu
        Install devel/libevent
        Install net/libzmq4
        Install net/norm
        Install net/openpgm
        Install security/libsodium
        Install net/miniupnpc

===>>> Proceed? y/n [y]
...
===> Creating groups
Creating group 'bitcoin' with gid '779'
===> Creating users
Creating user 'bitcoin' with uid '779'

config

/etc/rc.conf

bitcoind_enable="YES"
bitcoind_user="bitcoin"
bitcoind_group="bitcoin"
bitcoind_conf="/usr/local/etc/bitcoin.conf"
bitcoind_data_dir="/var/db/bitcoin"

/usr/local/etc/bitcoin.conf

server=1
rest=1
listen=1
daemon=1

Nach dem Start von bitcoind fängt dieser an, die Blockchain zu laden. Das Laden hat bei meiner 500 Mbit Glasfaser ca. 1 Tag gedauert.

 

bitcoin-utils installieren

portmaster net-p2p/bitcoin-utils

config

# ~/.bitcoin/bitcoin.conf

datadir=/var/db/bitcoin
rpccookiefile=/var/db/bitcoin/.cookie

prüfen der Blockhöhe, wie weit bitcoind mit dem Sync ist

❯ bitcoin-cli getblockcount
884297

 

electrs installieren

Die Installation dauerte mehrere Stunden!

portmaster finance/electrs

config

cd /usr/local/etc
mkdir electrs
cd electrs

/usr/local/etc/electrs/config.toml

# DO NOT EDIT THIS FILE DIRECTLY - COPY IT FIRST!
# If you edit this, you will cry a lot during update and will not want to live anymore!

# This is an EXAMPLE of how configuration file should look like.
# Do NOT blindly copy this and expect it to work for you!
# If you don't know what you're doing consider using automated setup or ask an experienced friend.

# This example contains only the most important settings.
# See docs or electrs man page for advanced settings.

# File where bitcoind stores the cookie, usually file .cookie in its datadir
cookie_file = "/var/db/bitcoin/.cookie"

# The listening RPC address of bitcoind, port is usually 8332
daemon_rpc_addr = "127.0.0.1:8332"

# The listening P2P address of bitcoind, port is usually 8333
daemon_p2p_addr = "127.0.0.1:8333"

# Directory where the index should be stored. It should have at least 70GB of free space.
db_dir = "/var/db/electrs"

# bitcoin means mainnet. Don't set to anything else unless you're a developer.
network = "bitcoin"

# The address on which electrs should listen. Warning: 0.0.0.0 is probably a bad idea!
# Tunneling is the recommended way to access electrs remotely.
#electrum_rpc_addr = "127.0.0.1:50001"
electrum_rpc_addr = "10.0.0.31:50001"

# How much information about internal workings should electrs print. Increase before reporting a bug.
log_filters = "INFO"
cd /var/db
mkdir electrs
chown -R bitcoin:bitcoin electrs
echo 'electrs_enable="YES"' >> /etc/rc.conf

erster Start

sudo -u bitcoin electrs --conf-dir=/usr/local/etc/electrs

[2025-02-16T19:48:56.491Z INFO  electrs::index] indexing 2000 blocks: [170001..172000]
[2025-02-16T19:48:57.777Z INFO  electrs::chain] chain updated: tip=0000000000000837e82c3a4ebe35a1d1d943e056234dba7c629922c6d4052d4c, height=172000
...
...

Das Indexing der Blockchain wird gestartet, das kann einige STUNDEN dauern. Ich breche den Vorgang im Terminal wieder ab mit control + c und schreibe mir ein Script für den Daemon, der im Hintergrund laufen wird.

electrs nach dem booten als daemon laufen lassen

Der Port finance/electrs hat bei mir (Stand Feb. 2025) kein Script in rc.d installiert. Deshalb habe ich mir das Script von bitcoind kopiert und für electrs umgeschrieben.

/usr/local/etc/rc.d/electrs

#! /bin/sh

# PROVIDE: electrs
# REQUIRE: DAEMON
# KEYWORD: shutdown

#
# Add the following lines to /etc/rc.conf to enable electrs_daemon:
#
#electrs_enable="YES"

. /etc/rc.subr

name="electrs"
rcvar="electrs_enable"

start_precmd="electrs_precmd"
start_cmd="electrs_start"
restart_precmd="electrs_checkconfig"
reload_precmd="electrs_checkconfig"
configtest_cmd="electrs_checkconfig"
status_cmd="electrs_status"
stop_cmd="electrs_stop"
stop_postcmd="electrs_wait"
command="/usr/local/bin/electrs"
daemon_command="/usr/sbin/daemon"
pidfile="/var/run/${name}.pid"
extra_commands="configtest"

: ${electrs_enable:=NO}
: ${electrslimits_enable:="NO"}

load_rc_config $name

: ${electrs_user:=bitcoin}
: ${electrs_group:=bitcoin}
: ${electrs_data_dir:="/var/db/electrs"}
: ${electrs_config_file:="/usr/local/etc/electrs/config.toml"}
#: ${bitcoindlimits_args:="-e -U ${bitcoind_user}"}
: ${bitcoindlimits_args:=""}

# set up dependant variables
procname="${command}"
required_files="${electrs_config_file}"

electrs_checkconfig()
{
  echo "Performing sanity check on electrs configuration:"
  if [ ! -d "${electrs_data_dir}" ]
  then
    echo "Missing data directory: ${electrs_data_dir}"
    exit 1
  fi
  chown -R "${electrs_user}:${electrs_group}" "${electrs_data_dir}"

  if [ ! -f "${electrs_config_file}" ]
  then
    echo "Missing configuration file: ${electrs_config_file}"
    exit 1
  fi
  if [ ! -x "${command}" ]
  then
    echo "Missing executable: ${command}"
    exit 1
  fi
  return 0
}

electrs_cleanup()
{
  rm -f "${pidfile}"
}

electrs_precmd()
{
  electrs_checkconfig

  pid=$(check_pidfile "${pidfile}" "${procname}")
  if [ -z "${pid}" ]
  then
    echo "electrs is not running"
    rm -f "${pidfile}"
  fi

  if checkyesno electrslimits_enable
  then
    eval $(/usr/bin/limits ${electrslimits_args}) 2>/dev/null
  else
    return 0
  fi
}

electrs_status()
{
  local pid
  pid=$(check_pidfile "${pidfile}" "${procname}")
  if [ -z "${pid}" ]
  then
    echo "electrs is not running"
    return 1
  else
    echo "electrs running, pid: ${pid}"
  fi
}

electrs_start()
{
  echo "sleeping for 60 sec.."
  sleep 60
  echo "Starting electrs:"
  cd "${electrs_data_dir}" || return 1
  ${daemon_command} -u "${electrs_user}" -p "${pidfile}" -f \
    ${command} \
    --conf="${electrs_config_file}"
}

electrs_stop()
{
  echo "Stopping electrs:"
  pid=$(check_pidfile "${pidfile}" "${procname}")
  if [ -z "${pid}" ]
  then
    echo "electrs is not running"
    return 1
  else
    kill ${pid}
  fi
}

electrs_wait()
{
  local n=60
  echo "Waiting for electrs shutdown:"
  while :
  do
    printf '.'
    pid=$(check_pidfile "${pidfile}" "${procname}")
    if [ -z "${pid}" ]
    then
      printf '\n'
      break
    fi
    sleep 1
    n=$((${n} - 1))
    if [ ${n} -eq 0 -a -f "${pidfile}" ]
    then
      printf "\nForce shutdown"
      kill -9 $(cat "${pidfile}")
      for n in 1 2 3
      do
        printf '.'
        sleep 1
      done
      printf '\n'
      break
    fi
  done
  rm -f "${pidfile}"
  echo "Shutdown complete"
}

run_rc_command "$1"
chmod 755 /usr/local/etc/rc.d/electrs
service electrs start

 

Letsencrypt Zertifikat mit lego holen

Lego hat den Vorteil gg. certbot: es benötigt keinen offenen Port 80. Ich habe meine Domain bei Hetzner liegen, Hetzner bietet für den DNS auch eine API an. Außer Hetzner werden auch andere Provider von lego unterstützt, Informationen findet man in der Doku zu Lego:

Quellen: https://go-acme.github.io/lego/

lego installieren

portmaster security/lego

config

echo 'YOUR-API-KEY' >> /usr/local/etc/lego/hetzner_api_key.txt
echo 'node.comodin.com' >> /usr/local/etc/lego/domains.txt

cd /usr/local/etc/lego/
chown _lego *.txt
chmod 600 *.txt

ls -la
total 36
drwx------   2 _lego _lego    8B Feb  8 11:30 .
drwxr-xr-x  13 root  wheel   31B Feb  8 10:25 ..
-r-xr-xr-x   1 root  wheel  667B Jan 30 11:05 deploy.sh
-r-xr-xr-x   1 root  wheel  667B Jan 30 11:05 deploy.sh.sample
-rw-------   1 _lego _lego   14B Feb  8 11:25 domains.txt
-rw-------   1 _lego _lego   33B Feb  8 11:30 hetzner_api_key.txt
-rwx------   1 _lego _lego  857B Feb  8 11:25 lego.sh
-rwx------   1 _lego _lego  838B Jan 30 11:05 lego.sh.sample

Zertifikat holen


sudo -u _lego HETZNER_API_KEY_FILE=/usr/local/etc/lego/hetzner_api_key.txt /usr/local/bin/lego --email mail@comodin.com --path /usr/local/etc/lego --dns hetzner -d 'node.comodin.com' run

2025/02/16 12:48:18 [DEBUG] GET https://acme-v02.api.letsencrypt.org/directory
2025/02/16 12:48:19 [INFO] [node.comodin.com] acme: Obtaining bundled SAN certificate
2025/02/16 12:48:19 [DEBUG] HEAD https://acme-v02.api.letsencrypt.org/acme/new-nonce
2025/02/16 12:48:19 [DEBUG] POST https://acme-v02.api.letsencrypt.org/acme/new-order
2025/02/16 12:48:19 [DEBUG] POST https://acme-v02.api.letsencrypt.org/acme/authz/2232015615/476825680515
2025/02/16 12:48:20 [INFO] [node.comodin.com] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz/2232015615/476825680515
2025/02/16 12:48:20 [INFO] [node.comodin.com] acme: Could not find solver for: tls-alpn-01
2025/02/16 12:48:20 [INFO] [node.comodin.com] acme: Could not find solver for: http-01
2025/02/16 12:48:20 [INFO] [node.comodin.com] acme: use dns-01 solver
2025/02/16 12:48:20 [INFO] [node.comodin.com] acme: Preparing to solve DNS-01
2025/02/16 12:48:20 [INFO] [node.comodin.com] acme: Trying to solve DNS-01
2025/02/16 12:48:20 [INFO] [node.comodin.com] acme: Checking DNS record propagation. [nameservers=127.0.0.1:53]
2025/02/16 12:48:22 [INFO] Wait for propagation [timeout: 2m0s, interval: 2s]
2025/02/16 12:48:22 [INFO] [node.comodin.com] acme: Waiting for DNS record propagation.
2025/02/16 12:48:24 [INFO] [node.comodin.com] acme: Waiting for DNS record propagation.
2025/02/16 12:48:26 [INFO] [node.comodin.com] acme: Waiting for DNS record propagation.
2025/02/16 12:48:28 [INFO] [node.comodin.com] acme: Waiting for DNS record propagation.
2025/02/16 12:48:31 [INFO] [node.comodin.com] acme: Waiting for DNS record propagation.
2025/02/16 12:48:33 [INFO] [node.comodin.com] acme: Waiting for DNS record propagation.
2025/02/16 12:48:35 [DEBUG] POST https://acme-v02.api.letsencrypt.org/acme/chall/2232015615/476825680515/uHmEtw
2025/02/16 12:48:35 [DEBUG] POST https://acme-v02.api.letsencrypt.org/acme/authz/2232015615/476825680515
2025/02/16 12:48:39 [DEBUG] POST https://acme-v02.api.letsencrypt.org/acme/authz/2232015615/476825680515
2025/02/16 12:48:39 [INFO] [node.comodin.com] The server validated our request
2025/02/16 12:48:39 [INFO] [node.comodin.com] acme: Cleaning DNS-01 challenge
2025/02/16 12:48:40 [INFO] [node.comodin.com] acme: Validations succeeded; requesting certificates
2025/02/16 12:48:40 [DEBUG] POST https://acme-v02.api.letsencrypt.org/acme/finalize/2232015615/354971124435
2025/02/16 12:48:41 [DEBUG] POST https://acme-v02.api.letsencrypt.org/acme/cert/04076f25b51637c11673673728aa3373248c
2025/02/16 12:48:41 [DEBUG] POST https://acme-v02.api.letsencrypt.org/acme/cert/04076f25b51637c11673673728aa3373248c/1
2025/02/16 12:48:41 [INFO] [node.comodin.com] Server responded with a certificate.

Die Zertifikate liegen hier: /usr/local/etc/lego/certificates/

 

nginx installieren

Eingehende SSL-Verbindungen über Port 50002 werden von nginx an den electrs-Port 50001 gestreamt.

portmaster www/nginx

sudo sysrc nginx_enable=yes

sudo service nginx start

config

/usr/local/etc/nginx/nginx.conf

load_module /usr/local/libexec/nginx/ngx_stream_module.so;

stream {
        upstream electrs {
                server 10.0.0.31:50001;
        }

        server {
                listen 50002 ssl;
                server_name node.comodin.com;
                proxy_pass electrs;

                ssl_certificate /usr/local/etc/lego/certificates/node.comodin.com.crt;
                ssl_certificate_key /usr/local/etc/lego/certificates/node.comodin.com.key;
                ssl_session_cache shared:SSL:1m;
                ssl_session_timeout 4h;
                ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
                ssl_prefer_server_ciphers on;
        }
}
sudo service nginx restart

 

Fritz!Box Port Freigabe

Im DNS legt man (sofern keine feste IP vorhanden ist) einen CNAME auf die Ihre MyFRITZ!-Adresse an. Die Ihre MyFRITZ!-Adresse finden man in der Fritz!Box im Menü Internet -> MyFRITZ!-Konto. Der Hostname des Mac mini sollte die komplette Domain lauten.

❯ hostname
node.comodin.com

Dann kann in der Fritz!Box die Freigabe eingerichtet werden. Zum einfachen Testen, habe ich auch https Port 443 geöffnet und mit dem Webbrowser getestet ob die Webseite vom nginx abgerufen werden kann.

 

BitBox auf eigene Bitcoin Full Node einstellen

Jetzt richten wir in der BitBox den Zugang zur eigenen Bitcoin Full Node ein.