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
- bitcoind
- bitcoin-utils
- electrs
- Letsencrypt Zertifikat mit lego
- nginx
- Fritz!Box Freigabe
- BitBox auf eigene Full Node einstellen
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.



