diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..340f44af7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +# Start by ignoring everything +* + +# Include certain files and directories; mostly the build system, and some of the config. when run, those are bind-mounted in. +!/VERSION +!/LICENSE +!/compile.sh +!/lib +!/extensions +!/config/sources +!/config/templates + + +# Ignore unnecessary files inside include directories +# This should go after the include directories +**/*~ +**/*.log +**/.DS_Store diff --git a/.gitignore b/.gitignore index f599d5086..8b94ca453 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,6 @@ /.vagrant/ ubuntu-*-cloudimg-console.log -### to ignore changes in a working copy -.ignore_changes - ### compile configurations added by users /config-*.conf ### but not default (supplied) files @@ -15,8 +12,6 @@ ubuntu-*-cloudimg-console.log /.tmp/ /output/ /cache/ -/*userpatches*/ -/userpatches ### General annoyances ### .DS_Store diff --git a/.ignore_changes b/.ignore_changes new file mode 100644 index 000000000..e69de29bb diff --git a/btcpay-build.sh b/btcpay-build.sh new file mode 100755 index 000000000..95774b4f8 --- /dev/null +++ b/btcpay-build.sh @@ -0,0 +1,186 @@ +#!/bin/bash + +set -e + +HELP=true +DEPLOY=false +BUILD=false +UPDATE=false +DEPLOY_ON="" +BOARD="" +PROD=false +while (( "$#" )); do + case "$1" in + deploy) + HELP=false + DEPLOY=true + shift 1 + ;; + build) + BUILD=true + HELP=false + shift 1 + ;; + update) + BUILD=true + UPDATE=true + HELP=false + shift 1 + ;; + --production) + PROD=true + shift 1 + ;; + --help) + HELP=true + shift 1 + ;; + --deploy-on) + DEPLOY_ON="$2" + shift 2 + ;; + --board) + BOARD="$2" + shift 2 + ;; + --) # end argument parsing + shift + break + ;; + -*|--*=) # unsupported flags + echo "Error: Unsupported flag $1" >&2 + exit 1 + ;; + *) # preserve positional arguments + PARAMS="$PARAMS $1" + shift + ;; + esac +done + + +if $HELP; then cat <<-END +Usage: +------ + +Build, update or deploy the armbian image + + build: Build the kernel, u-boot and create the hack0 image + update: Create the hack0 image without rebuilding the kernel and u-book + deploy: Deploy the last built image on the --deploy-device + --deploy-on /dev/sda: Flash the image on the device /dev/sda + --board rock64: Create an image for rock64 (Available: rockpro64, rock64) + --production: Create a production image (will ignore build-local.conf) + --help: Show this help +END +fi + +if $BUILD; then + if ! [[ "$BOARD" ]]; then + echo "The board should be specified with --board (See --help)" + exit 1 + fi + BUILD_ARGS=" + BOARD=${BOARD} + BUILD_ONLY=no + KERNEL_CONFIGURE=no + RELEASE=jammy + BRANCH=current + BUILD_DESKTOP=no + WIREGUARD=no + BUILD_MINIMAL=yes + FORCE_USE_RAMDISK=no + KERNEL_GIT=shallow" + if $UPDATE; then + BUILD_ARGS=" + ${BUILD_ARGS} + CLEAN_LEVEL=oldcache + PROGRESS_LOG_TO_FILE=yes" + fi + pushd . 2>/dev/null + rm -f "userpatches/config-docker.conf" "userpatches/Dockerfile" + cd "userpatches/overlay" + OVERLAY_DIRECTORY="$(pwd)" + cd "$OVERLAY_DIRECTORY" + source build.conf + + if $PROD; then + touch .production + echo "Building production image..." + else + echo "Building debug image..." + rm -rf .production + [ -f "build-local.conf" ] && source build-local.conf && echo "build-local.conf loaded" + fi + ! [ -d "btcpayserver-docker" ] && git clone "$BTCPAY_REPOSITORY" + cd btcpayserver-docker + git checkout "$BTCPAY_BRANCH" + git fetch origin + if ! git diff --quiet remotes/origin/HEAD || ! [ -f ../docker-images.tar ]; then + git pull + rm -f ../docker-images.tar + . ./build.sh -i + cd Generated + export BTCPAY_DOCKER_PULL_FLAGS="--platform arm64" + # https://github.com/docker/docker-ce/blob/master/components/cli/experimental/README.md + # Edit /etc/docker/daemon.json with "experimental": true + ./pull-images.sh + ./save-images.sh ../../docker-images.tar + # Do not mess up the build environment + export BTCPAY_DOCKER_PULL_FLAGS="" + ./pull-images.sh + else + echo "docker-images.tar is up to date" + fi + cd "$OVERLAY_DIRECTORY" + if ! [ -f "utxo-snapshot-bitcoin-mainnet-820852.tar" ]; then + set +e + rm utxo-snapshot-*.tar &> /dev/null + set -e + wget "https://eu2.contabostorage.com/1f50a74c9dc14888a8664415dad3d020:utxosets/utxo-snapshot-bitcoin-mainnet-820852.tar" -c -q --show-progress + fi + popd + + # Make sure built images are deleted + mkdir -p output/images + rm -rf output/images + + # WSL2 include some windows path in PATH, making the build fail. This remove those. + export PATH=$(/usr/bin/printenv PATH | /usr/bin/perl -ne 'print join(":", grep { !/\/mnt\/[a-z]/ } split(/:/));') + time ./compile.sh ${BUILD_ARGS} +fi + + +if $DEPLOY; then + IMAGE="$(echo output/images/*.img)" + IMAGE_SHA="$(echo output/images/*.img.sha)" + if ! [[ "$IMAGE" ]] || ! [ -f "$IMAGE" ]; then + echo "No image were found in output/images" + exit 1 + fi + if ! [[ "$DEPLOY_ON" ]]; then + echo "The deployment device target should be specified with --deploy-on (See --help)" + exit 1 + fi + + if ! lsblk "$DEPLOY_ON" 2>/dev/null; then + echo "Device $DEPLOY_ON is not available" + exit 1 + fi + + echo "Writing image" "$DEPLOY_ON" "info" + ifsha=$(cat $IMAGE_SHA | awk '{print $1}') + + [[ -x "$(command -v pv)" ]] || apt-get install -y pv + + pv -p -b -r -c -N "[ .... ] dd" $IMAGE | dd of=$DEPLOY_ON bs=1M iflag=fullblock oflag=direct status=none + echo "Verifying. Please wait!" + ofsha=$(dd if=$DEPLOY_ON count=$(du -b $IMAGE | cut -f1) status=none iflag=count_bytes oflag=direct | sha256sum | awk '{print $1}') + + if [[ $ifsha == $ofsha ]]; then + echo "Writing succeeded" "$IMAGE" "info" + else + echo "Writing failed" "$IMAGE" "err" + exit 1 + fi +fi diff --git a/userpatches/.gitignore b/userpatches/.gitignore new file mode 100644 index 000000000..e1adf7924 --- /dev/null +++ b/userpatches/.gitignore @@ -0,0 +1,7 @@ +Dockerfile +README +Vagrantfile +config-default.conf +config-docker.conf +config-example.conf +config-vagrant.conf diff --git a/userpatches/README.md b/userpatches/README.md new file mode 100644 index 000000000..de8bec307 --- /dev/null +++ b/userpatches/README.md @@ -0,0 +1,88 @@ +# Image customization + +## Pre requisite + +You need to have docker installed with experimental features enabled: + +In your `/etc/docker/daemon.json`, make sure you have + +```json +{ + "experimental": true +} +``` + +Then reload docker with ```systemctl restart docker```. + +## Common workflow + +The common development workflow is the following: + +1. The first time you build an image for a board, run ```./btcpay-build.sh build --board rockpro64```, this will build the kernel, u-boot and the hack0 image for rockpro64. +2. Then, if you modify any document in this folder (`userpatches`), you can recreate an image without re-building the kernel and u-boot with ```./btcpay-build.sh update --board rockpro64``` +3. You can also customize the environment variables documented in [BTCPay Server](https://github.com/btcpayserver/btcpayserver-docker) locally by adding them to `overlay/build-local.conf`, this will override [overlay/build.conf](overlay/build.conf) settings. Note that the `build-local.conf` file is ignored when building a production image. +4. Once you are satisfied with the image you can deploy to the device, for example, assuming your SD card is on `/dev/sdd` you would run ```./btcpay-build.sh deploy --deploy-on /dev/sdd```. + +Note that you can run several actions at the same time, for example this will update the image and deploy: ```./btcpay-build.sh update deploy --board rockpro64 --deploy-on /dev/sdd```. + +During the first start, hack0 is in `setup mode`, the setup mode will: + +1. Format any attached SSD or NVMe disk +2. Load docker images +3. Set mount bind so bitcoin's data directory is saved on the SSD/NVMe disk +4. Deploy a UTXO set snapshot in this directory +5. Start BTCPay Server, and test if the connectivity works correctly +6. Stop BTCPay Server and delete data created during the setup mode + +For 10 minutes, you will see the red light on and the white light blinking. +When the red light is off, and the white light stopped blinking and stays on, the setup ran successfully. Unplug the hack0, and the unit is ready to be shipped. The next boot will not run in setup mode. + +If the red light does not get off, something failed and the hack0 could not be properly configured and you need to flash again the image on the SD Card. + +## Architecture + +hack0-armbian is a fork of [armbian](https://github.com/armbian/build) with patches specific to hack0. + +Here are what our patches do: +1. Format any attached SSD or NVMe disk +2. Load docker images +3. Set mount bind so bitcoin's data directory is saved on the SSD/NVMe disk +4. Deploy a UTXO set snapshot in this directory +5. Setup fan to cool down the processor if it becomes too hot +6. Setup the btcpay-test which signal when the hack0 is ready to be used. +7. Setup mDNS so the local domain name `hack0.local` can be used to find your hack0 + +`btcpay-test` controls two leds (red and white) on the rock64. When starting, the red light is on, and the white led is blinking. Once hack0 is ready to be used, the red light is off and the white led stays on. + +## Pre built images + +> :warning: When you first boot a prebuilt images, the hack0 will be in `setup mode`, which will wipe all data in the SSD drive to the board. Please read `Common workflow` section above. + +### Version 0.6 + +Image: https://hack0-image.s3.amazonaws.com/hack0-rockpro64-0.8.img + +sha256sum: 5fac50f8083f3349a95b4d38534c223c26f47507a3d30f106d1efe570254cedd + +Release date: 8 January 2023 + +## FAQ + +### How can I change the local domain name? + +By default the hack0 will be named `hack0.local` on your network. +If you want to change to `example.local`, add `HACK0_HOSTNAME=example` to `overlay/build-local.conf`. + +### How can I configure the image to allow SSH connection with my public key? + +1. Copy your ssh public key in a `overlay/authorized_keys`. +2. In `overlay/build-local.conf`, add `HACK0_LOAD_AUTHORIZED_KEYS=true`. + +### How can I create a production image? + +A production image will ignore `overlay/build-local.conf`, just run `./btcpay-build.sh build --production`. + +### How to customize the BTCPay Server install + +We are setting up BTCPay Server thanks to the [docker install](https://github.com/btcpayserver/btcpayserver-docker). +You can customize the environment variables documented in [the repository](https://github.com/btcpayserver/btcpayserver-docker) and add them to `overlay/build-local.conf` to customize your installation. diff --git a/userpatches/customize-image.sh b/userpatches/customize-image.sh new file mode 100755 index 000000000..3063008de --- /dev/null +++ b/userpatches/customize-image.sh @@ -0,0 +1,98 @@ +#!/bin/bash + +set -e + +echo "Running BTCPayServer armbian customization script..." + +# Disable ssh password auth +echo root:root | chpasswd +sed -i '/PASSWORDAUTHENTICATION/Ic\PasswordAuthentication no' /etc/ssh/sshd_config + +export LANG=C LC_ALL="en_US.UTF-8" +export DEBIAN_FRONTEND=noninteractive +export APT_LISTCHANGES_FRONTEND=none + +OVERLAY="/tmp/overlay" +DESTINATION="/root" + +source "$OVERLAY/build.conf" +! [ -f "$OVERLAY/.production" ] && [ -f "$OVERLAY/build-local.conf" ] && source "$OVERLAY/build-local.conf" + +$SETUP_MODE && touch "$DESTINATION/.setup-mode" +$SETUP_MODE && $SETUP_CLEANUP && echo "clean" > "$DESTINATION/.setup-mode" + +if $HACK0_LOAD_AUTHORIZED_KEYS && [ -f "$OVERLAY/authorized_keys" ]; then + mkdir -p "/root/.ssh" + cp "$OVERLAY/authorized_keys" "/root/.ssh/authorized_keys" + echo "SSH keys copied" +fi + +apt update +apt upgrade -y +apt install -y git vim + +# Customize the Motd with hack0 header +echo "MOTD_DISABLE='header'" >> /etc/default/armbian-motd +cp -af "$OVERLAY/10-hack0-header" /etc/update-motd.d/ + +####### Setup BTCPayServer +# Note that we can't install here because we can't use docker in the chroot +# Instead, we copy the images in a tar, and we will load them up +# during the first run. +cp -af "$OVERLAY/docker-images.tar" "$DESTINATION/docker-images.tar" +cp -af $OVERLAY/utxo-snapshot-*.tar "$DESTINATION/" +git clone "$OVERLAY/btcpayserver-docker" "$DESTINATION/btcpayserver-docker" +cd "$DESTINATION/btcpayserver-docker" +git remote set-url origin "$BTCPAY_REPOSITORY" +git checkout "$BTCPAY_BRANCH" +git pull +echo "$HACK0_HOSTNAME" > /etc/hostname +hostname -F /etc/hostname +BTCPAY_HOST="$HACK0_HOSTNAME.local" +REVERSEPROXY_DEFAULT_HOST="$BTCPAY_HOST" +source btcpay-setup.sh --docker-unavailable --install-only --no-startup-register +# Register btcpay-init +mkdir -p /opt/btcpay +cp -af "$OVERLAY/btcpay-init.sh" "/opt/btcpay/" +cp -af "$OVERLAY/btcpay-init.service" "/etc/systemd/system/" +systemctl --no-reload enable btcpay-init.service + +cp -af "$OVERLAY/btcpay-setup-external-drive.sh" "/opt/btcpay/" +cp -af "$OVERLAY/btcpay-setup-external-drive.service" "/etc/systemd/system/" +systemctl --no-reload enable btcpay-setup-external-drive + +cp -af "$OVERLAY/fancontrol.sh" "/opt/btcpay/" +cp -af "$OVERLAY/fancontrol.service" "/etc/systemd/system/" +systemctl --no-reload enable fancontrol +echo 'KERNEL=="thermal_zone0", SUBSYSTEM=="thermal", ATTR{mode}="disabled"' > /etc/udev/rules.d/10-thermalmode.rules + +cp -af "$OVERLAY/btcpay-test.sh" "/opt/btcpay/" +cp -af "$OVERLAY/btcpay-test.service" "/etc/systemd/system/" +systemctl --no-reload enable btcpay-test + +cp -af "$OVERLAY/btcpay-common.sh" "/opt/btcpay/btcpay-common.sh" +############ + +####### Setup WIFI (if supported by the board) +if [[ "$WIFI_SSID" ]]; then + rm -f /boot/armbian_first_run.txt.template + echo "FR_net_change_defaults=1 +FR_net_wifi_enabled=1 +FR_net_wifi_ssid='$WIFI_SSID' +FR_net_wifi_key='$WIFI_PW' +FR_general_delete_this_file_after_completion=1" > /boot/armbian_first_run.txt +fi +####### + +#### Without this, port 53 (DNS) is taken by the OS and pihole can't work + +sed -r -i 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf + +####### Setup mdns +adduser --system --group --disabled-login --home /var/run/avahi-daemon avahi +apt install -y openssl net-tools fio libnss-mdns \ + avahi-daemon avahi-discover avahi-utils \ + fail2ban acl ifmetric +sed -i '/PUBLISH-WORKSTATION/Ic\publish-workstation=yes' /etc/avahi/avahi-daemon.conf +####### + diff --git a/userpatches/overlay/.gitignore b/userpatches/overlay/.gitignore new file mode 100644 index 000000000..4d3c6b295 --- /dev/null +++ b/userpatches/overlay/.gitignore @@ -0,0 +1,6 @@ +authorized_keys +build-local.conf +docker-images.tar +utxo-snapshot-*.tar +btcpayserver-docker/ +.production \ No newline at end of file diff --git a/userpatches/overlay/10-hack0-header b/userpatches/overlay/10-hack0-header new file mode 100755 index 000000000..b76b01b02 --- /dev/null +++ b/userpatches/overlay/10-hack0-header @@ -0,0 +1,8 @@ +#!/bin/bash + +. /etc/os-release +. /etc/armbian-release + +KERNELID=$(uname -r) +TERM=linux toilet -f standard -F border hack0 +echo -e "Welcome to $(echo $NAME | cut -d' ' -f1) $(if [[ $ID == debian ]]; then echo ${PRETTY_NAME##*\(} | rev | cut -c2- | rev | sed 's/.*/\u&/'; else echo -n ${VERSION_CODENAME^};fi) with \e[0;91mArmbian Linux\x1B[0m $KERNELID" \ No newline at end of file diff --git a/userpatches/overlay/btcpay-common.sh b/userpatches/overlay/btcpay-common.sh new file mode 100755 index 000000000..f8881213b --- /dev/null +++ b/userpatches/overlay/btcpay-common.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +DEVICE_NAME="" +PARTITION_NAME="" +if lsblk /dev/sda1 &> /dev/null; then + DEVICE_NAME=/dev/sda + PARTITION_NAME=/dev/sda1 +elif lsblk /dev/nvme0n1 &> /dev/null; then + DEVICE_NAME=/dev/nvme0n1 + PARTITION_NAME=/dev/nvme0n1p1 +fi +MOUNT_DIR="/mnt/external" +MOUNT_UNIT="mnt-external.mount" +DOCKER_VOLUMES="/var/lib/docker/volumes" +SSHKEYFILE="/root/.ssh/id_rsa_btcpay" +SETUP_CLEANUP=true +if [ -f "/root/.setup-mode" ]; then + SETUP_MODE=true + SETUP_CLEANUP=$([[ "$(cat "/root/.setup-mode")" == "clean" ]]) +else + SETUP_MODE=false +fi \ No newline at end of file diff --git a/userpatches/overlay/btcpay-init.service b/userpatches/overlay/btcpay-init.service new file mode 100644 index 000000000..6ad0c6996 --- /dev/null +++ b/userpatches/overlay/btcpay-init.service @@ -0,0 +1,11 @@ +[Unit] +Description=BTCPay Server initialization +After=docker.service +Requires=docker.service + +[Service] +Type=simple +ExecStart=/opt/btcpay/btcpay-init.sh + +[Install] +WantedBy=multi-user.target diff --git a/userpatches/overlay/btcpay-init.sh b/userpatches/overlay/btcpay-init.sh new file mode 100755 index 000000000..82b41975e --- /dev/null +++ b/userpatches/overlay/btcpay-init.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +set -e + +source /opt/btcpay/btcpay-common.sh + +while ! [ -f /etc/ssh/ssh_host_rsa_key ]; do + if dpkg-reconfigure openssh-server; then + systemctl restart ssh + echo -e "[ \e[32mOK\e[0m ] SSH Host keys generated" + else + sleep 5 + fi +done + +if ! mountpoint -q "$MOUNT_DIR"; then + echo -e "[ \e[31mFailed\e[0m ] The mount directory $MOUNT_DIR does not exists, is the external drive plugged?" + exit 1 +fi + +cd /root + +$SETUP_MODE && rm -rf /root/.not_logged_in_yet +if $SETUP_MODE && [ -f "docker-images.tar" ]; then + echo "Loading docker images..." + docker load < "docker-images.tar" + echo -e "[ \e[32mOK\e[0m ] Docker images loaded." + $SETUP_CLEANUP && rm -f "docker-images.tar" +fi + +if $SETUP_MODE && [ -f utxo-snapshot-*.tar ]; then + BITCOIN_DATA_DIR=/var/lib/docker/volumes/generated_bitcoin_datadir/_data + rm -rf "$BITCOIN_DATA_DIR" + source /etc/profile.d/btcpay-env.sh + echo "Loading UTXO set" + SNAPSHOT_TAR="$(readlink -f utxo-snapshot-*.tar)" + pushd . &> /dev/null + cd btcpayserver-docker/contrib/FastSync + ./load-utxo-set.sh $SNAPSHOT_TAR + popd + echo -e "[ \e[32mOK\e[0m ] UTXO Set preloaded." + $SETUP_CLEANUP && rm -f "$SNAPSHOT_TAR" +fi + +if $SETUP_MODE; then + source /etc/profile.d/btcpay-env.sh + . btcpay-setup.sh -i --no-systemd-reload +fi \ No newline at end of file diff --git a/userpatches/overlay/btcpay-setup-external-drive.service b/userpatches/overlay/btcpay-setup-external-drive.service new file mode 100644 index 000000000..81f4f233f --- /dev/null +++ b/userpatches/overlay/btcpay-setup-external-drive.service @@ -0,0 +1,11 @@ +[Unit] +Description=BTCPay Server external drive initialization +Before=docker.service +After=mnt-external.mount + +[Service] +Type=oneshot +ExecStart=/opt/btcpay/btcpay-setup-external-drive.sh + +[Install] +WantedBy=multi-user.target diff --git a/userpatches/overlay/btcpay-setup-external-drive.sh b/userpatches/overlay/btcpay-setup-external-drive.sh new file mode 100755 index 000000000..ec78a0e5a --- /dev/null +++ b/userpatches/overlay/btcpay-setup-external-drive.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +set -e + +source /opt/btcpay/btcpay-common.sh + +if ! [[ "$DEVICE_NAME" ]]; then + echo -e "[ \e[31mFailed\e[0m ] The external device is not found" +else + if lsblk $PARTITION_NAME &> /dev/null; then + echo -e "[ \e[32mOK\e[0m ] Partitioning of external drive $DEVICE_NAME skipped: The disk is already partitioned" + else + echo "Partitioning the external drive $DEVICE_NAME..." + ### DANGER ZONE ### + ( + echo o # Create a new empty DOS partition table + echo n # Add a new partition + echo p # Primary partition + echo 1 # Partition number + echo # First sector (Accept default: 1) + echo # Last sector (Accept default: varies) + echo w # Write changes + ) | fdisk ${DEVICE_NAME} + partprobe ${DEVICE_NAME} + while ! lsblk $PARTITION_NAME &> /dev/null; do + sleep 1 + done + fi + if $SETUP_MODE || ! blkid -t "TYPE=ext4" "$PARTITION_NAME" &> /dev/null; then + if mountpoint -q "$MOUNT_DIR"; then + umount "$MOUNT_DIR" + fi + if mountpoint -q "$DOCKER_VOLUMES"; then + umount "$DOCKER_VOLUMES" + fi + mkfs.ext4 -F "$PARTITION_NAME" + fi + mkdir -p "$MOUNT_DIR" + if mountpoint -q "$MOUNT_DIR"; then + echo -e "[ \e[32mOK\e[0m ] The partition $PARTITION_NAME is already mounted on $MOUNT_DIR" + else + echo "Mounting $PARTITION_NAME on $MOUNT_DIR" + mount -o defaults,noatime "$PARTITION_NAME" "$MOUNT_DIR" + if grep -qF "$MOUNT_DIR" /etc/fstab; then + echo -e "[ \e[32mOK\e[0m ] /etc/fstab is up-to-date" + else + echo "$PARTITION_NAME $MOUNT_DIR ext4 defaults,noatime,nofail 0 2" >> /etc/fstab + echo -e "[ \e[32mOK\e[0m ] Updated /etc/fstab" + fi + fi + + # We need to use mount bind instead of symbolic link because docker would complain when running `docker volume rm` + if mountpoint -q "$DOCKER_VOLUMES"; then + echo -e "[ \e[32mOK\e[0m ] $DOCKER_VOLUMES is already a mount point" + else + echo "Creating a mount point on the docker volumes directory $DOCKER_VOLUMES to the external drive $MOUNT_DIR..." + rm -rf "$DOCKER_VOLUMES" + mkdir -p "$DOCKER_VOLUMES" + mount --bind "$MOUNT_DIR" "$DOCKER_VOLUMES" + if grep -qF "$DOCKER_VOLUMES" /etc/fstab; then + echo -e "[ \e[32mOK\e[0m ] /etc/fstab is up-to-date" + else + echo "$MOUNT_DIR $DOCKER_VOLUMES none bind,nobootwait 0 2" >> /etc/fstab + echo -e "[ \e[32mOK\e[0m ] Updated /etc/fstab for the mount point" + fi + fi + + docker_service="/lib/systemd/system/docker.service" + if ! grep -qF "After=$MOUNT_UNIT" "$docker_service"; then + sed -i "s/After=/After=$MOUNT_UNIT /g" "$docker_service" + echo -e "[ \e[32mOK\e[0m ] Update $docker_service: Docker needs now to start after $MOUNT_UNIT" + fi + if ! grep -qF "Requires=$MOUNT_UNIT" "$docker_service"; then + sed -i "s/Requires=/Requires=$MOUNT_UNIT /g" "$docker_service" + echo -e "[ \e[32mOK\e[0m ] Update $docker_service: Docker must now requires $MOUNT_UNIT to start" + fi +fi \ No newline at end of file diff --git a/userpatches/overlay/btcpay-test.service b/userpatches/overlay/btcpay-test.service new file mode 100644 index 000000000..f4d3fd3c1 --- /dev/null +++ b/userpatches/overlay/btcpay-test.service @@ -0,0 +1,9 @@ +[Unit] +Description=BTCPay Server self-test service + +[Service] +Type=oneshot +ExecStart=/opt/btcpay/btcpay-test.sh + +[Install] +WantedBy=multi-user.target diff --git a/userpatches/overlay/btcpay-test.sh b/userpatches/overlay/btcpay-test.sh new file mode 100755 index 000000000..154837c5e --- /dev/null +++ b/userpatches/overlay/btcpay-test.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +set -e + +source /opt/btcpay/btcpay-common.sh + +if [ -f /sys/devices/platform/leds/leds/diy-led/brightness ]; then + red_led=/sys/devices/platform/leds/leds/diy-led/brightness + red_led_on=255 + red_led_off=0 +elif [ -f /sys/devices/platform/leds/leds/diy/brightness ]; then + red_led=/sys/devices/platform/leds/leds/diy/brightness + red_led_on=255 + red_led_off=0 +else + red_led=/sys/devices/platform/leds/leds/standby-led/brightness + red_led_on=0 + red_led_off=255 +fi + +if [ -f /sys/devices/platform/leds/leds/power-led/brightness ]; then + white_led=/sys/devices/platform/leds/leds/power-led/brightness +elif [ -f /sys/devices/platform/leds/leds/work/brightness ]; then + white_led=/sys/devices/platform/leds/leds/work/brightness + echo "none" > /sys/devices/platform/leds/leds/work/trigger +else + white_led=/sys/devices/platform/leds/leds/work-led/brightness +fi + +echo '255' > $white_led +echo "$red_led_on" > $red_led + +white_led_value=255 +drive_mounted=false +docker_running=false +container_started=false +btcpay_init_exited=false +btcpayserver_pinged=false +fancontrol_running=false + +blink_speed=1 +timeout=$(((60*20))) + +total_wait=0 +success=false +while true; do + echo "$white_led_value" > $white_led + if ! $drive_mounted && [ "$(systemctl show -p SubState --value mnt-external.mount)" == "mounted" ]; then + drive_mounted=true + echo -e "[ \e[32mOK\e[0m ] The external drive is correctly mounted" + fi + + if ! $docker_running && [ "$(systemctl show -p SubState --value docker.service)" == "running" ]; then + docker_running=true + echo -e "[ \e[32mOK\e[0m ] Docker started" + fi + + if ! $container_started && [ "$(docker ps -aq -f status=running -f name=generated_btcpayserver_1)" ]; then + container_started=true + echo -e "[ \e[32mOK\e[0m ] BTCPayServer container is started" + fi + + if ! $btcpayserver_pinged && [ "$(curl -sL -w "%{http_code}\\n" "http://localhost/" -o /dev/null)" == "200" ]; then + btcpayserver_pinged=true + echo -e "[ \e[32mOK\e[0m ] BTCPayServer is online" + fi + + if ! $btcpay_init_exited && [ "$(systemctl show -p SubState --value btcpay-init)" == "dead" ]; then + btcpay_init_exited=true + echo -e "[ \e[32mOK\e[0m ] BTCPay-Init exited" + fi + + if ! $fancontrol_running && [ "$(systemctl show -p SubState --value fancontrol.service)" == "running" ]; then + fancontrol_running=true + echo -e "[ \e[32mOK\e[0m ] The Fan control service is running" + elif $fancontrol_running && [ "$(systemctl show -p SubState --value fancontrol.service)" != "running" ]; then + fancontrol_running=false + echo -e "[ \e[31mFailed\e[0m ] The Fan control service is not running" + fi + + if $drive_mounted && $docker_running && $container_started && $btcpayserver_pinged && $fancontrol_running && $btcpay_init_exited; then + echo -e "[ \e[32mOK\e[0m ] All tests passed" + success=true + break + elif [ $total_wait -ge $timeout ]; then + success=false + break + fi + + white_led_value=$(((white_led_value==0?255:0))) + sleep $blink_speed + total_wait=$(((total_wait+blink_speed))) +done + +if $success; then + if $SETUP_MODE; then + echo "0" > $white_led + rm -rf /root/.setup-mode + + source /etc/profile.d/btcpay-env.sh + docker-compose -f $BTCPAY_DOCKER_COMPOSE down -t 20 # calling btcpay-down.sh hangs + docker volume ls -q | while read -r line ; do + if [ "$line" != "generated_bitcoin_datadir" ]; then + docker volume rm -f "$line" + fi + done + + rm -rf "$MOUNT_DIR/generated_bitcoin_datadir/_data/debug.log" + echo "Deleted all volumes that are not related to bitcoin" + + rm -rf "$SSHKEYFILE" "$SSHKEYFILE.pub" + sed -i '/btcpay$/d' /root/.ssh/authorized_keys + + echo "Deleted BTCPay SSH key to access SSH host" + + rm -f /etc/ssh/ssh_host* + echo "Deleted SSH host keys" + fi + echo "255" > $white_led + echo "$red_led_off" > $red_led + exit 0 +else + echo "0" > $white_led + echo "$red_led_on" > $red_led + echo -e "[ \e[31mFailed\e[0m ] Some tests did not passed in less than $timeout seconds" + exit 1 +fi \ No newline at end of file diff --git a/userpatches/overlay/build.conf b/userpatches/overlay/build.conf new file mode 100755 index 000000000..6334ca6a2 --- /dev/null +++ b/userpatches/overlay/build.conf @@ -0,0 +1,23 @@ +#!/bin/bash + +# If true, will deploy authorized_keys in the overlay directory on the device for activating SSH access +HACK0_LOAD_AUTHORIZED_KEYS=false + +# Can access hack0 device on the local network via hack0.local (mDNS) +HACK0_HOSTNAME="hack0" + +# If true, hack0 will erase any data on the deployed device during the first boot +SETUP_MODE=true + +# Will remove files not needed after setup +SETUP_CLEANUP=true + +# See https://github.com/btcpayserver/btcpayserver-docker +BTCPAY_REPOSITORY="https://github.com/btcpayserver/btcpayserver-docker" +BTCPAY_BRANCH="master" +NBITCOIN_NETWORK="mainnet" +BTCPAYGEN_CRYPTO1="btc" +BTCPAYGEN_REVERSEPROXY="nginx" +BTCPAYGEN_LIGHTNING="lnd" +BTCPAYGEN_ADDITIONAL_FRAGMENTS="opt-save-storage" +BTCPAY_ENABLE_SSH=true diff --git a/userpatches/overlay/fancontrol.service b/userpatches/overlay/fancontrol.service new file mode 100644 index 000000000..5c4cc1e53 --- /dev/null +++ b/userpatches/overlay/fancontrol.service @@ -0,0 +1,16 @@ +[Unit] +Description=BTCPay Server fancontrol +ConditionPathExists=/sys/class/thermal/thermal_zone0/temp + +[Service] +Type=simple +Environment="TEMP_MIN=60" +Environment="TEMP_MAX=75" +Environment="TEMP_COOLDOWN=55" +Environment="TEMP_FILE=/sys/class/thermal/thermal_zone0/temp" +ExecStart=/opt/btcpay/fancontrol.sh +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/userpatches/overlay/fancontrol.sh b/userpatches/overlay/fancontrol.sh new file mode 100755 index 000000000..7c5b351a2 --- /dev/null +++ b/userpatches/overlay/fancontrol.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +set -e + +: "${TEMP_MIN:=45}" +: "${TEMP_MAX:=60}" +: "${TEMP_FILE:=/sys/class/thermal/thermal_zone0/temp}" +: "${TEMP_COOLDOWN:=40}" +: "${FAN_MIN:=120}" +: "${FAN_MAX:=255}" +: "${FAN_KICKSTART:=0}" +: "${CYCLE:=10}" +: "${VERBOSE:=false}" + +if [[ -z "${FAN_FILE}" ]]; then + FAN_FILE=$(ls /sys/devices/platform/pwm-fan/hwmon/*/pwm1) + echo "Fan detected at $FAN_FILE" +fi + +ECHO="true || " +if "$VERBOSE"; then + ECHO="echo" +fi + +if [ "$TEMP_COOLDOWN" -gt "$TEMP_MIN" ] || [ "$TEMP_MIN" -gt "$TEMP_MAX" ]; then + echo "Inconsistent temperature range" + exit 1 +fi + +cooldown=false +while true; do + temp=$(<$TEMP_FILE) + temp=$((temp/1000)) + $ECHO "Current temperature: $temp" + fanpwm=$(($FAN_MIN + ( ( $FAN_MAX - $FAN_MIN ) / ( $TEMP_MAX - $TEMP_MIN ) ) * ( $temp - $TEMP_MIN ) )) + if [ "$fanpwm" -gt "$FAN_MAX" ]; then + fanpwm=$FAN_MAX + elif [ "$fanpwm" -lt "$FAN_MIN" ]; then + fanpwm=$FAN_MIN + fi + if $cooldown; then + if [ "$fanpwm" -lt "$FAN_MIN" ]; then + fanpwm=$FAN_MIN + fi + if [ "$temp" -lt "$TEMP_COOLDOWN" ]; then + fanpwm=0 + cooldown=false + $ECHO "Fan turned off" + else + $ECHO "The temperature ($temp c) is still more or equal to the cooldown temperature ($TEMP_COOLDOWN), keep the fan turned on." + fi + elif [ "$temp" -ge "$TEMP_MIN" ]; then + cooldown=true + $ECHO "The temperature ($temp c) is more or equal to the desired temperature ($TEMP_MIN c), turning on the fan." + if [ "$FAN_KICKSTART" -ne "0" ]; then + $ECHO "Kickstart for $FAN_KICKSTART seconds." + echo "$FAN_MAX" > "$FAN_FILE" + sleep "$FAN_KICKSTART" + fi + else + $ECHO "The temperature ($temp c) is less than the desired temperature ($TEMP_MIN c), turn off the fan." + fanpwm=0 + fi + + $ECHO "FAN PWM: $fanpwm" + echo "$fanpwm" > "$FAN_FILE" + sleep "$CYCLE" +done