Skip to content

Instantly share code, notes, and snippets.

@vindard
Last active March 15, 2019 15:19
Show Gist options
  • Save vindard/8bbe7ed66d3d9f30646c4ec73e383a4d to your computer and use it in GitHub Desktop.
Save vindard/8bbe7ed66d3d9f30646c4ec73e383a4d to your computer and use it in GitHub Desktop.
A script designed for the "Raspibolt" configuration that creates a backup of the `.lnd` folder, packages it into a `.tar` archive and then uploads it to Dropbox
# NOTE: At 17 revision after ~2 weeks I think it's fair to say that this
# gist has grown into its own little project.
#
# Given this, I've moved the project across to its own proper repo now
# to facilitate better tracking & development. All further development
# can now be found at: https://github.com/vindard/lnd-backup
#
#---------------------------------------------------------------------------
#!/bin/bash
#==============================
# OPTIONAL USER HARDCODED VARIABLES
#==============================
# SET DROPBOX API KEY FOR UPLOADS
DROPBOX_APITOKEN=""
# SET GPG KEY FOR ENCRYPTING WITH (COMPRESSES AS WELL)
GPG=""
# SET A DEVICE NAME TO BE USED FOR BACKUPS, DEFAULTS TO /etc/hostname
DEVICE=""
#==============================
# SETUP REQUIRED ENVIRONMENT VARIABLES
#==============================
# if true, stop lnd and dump data if possible for backup
STOP_LND=false
# if true, backup whether state change or not
STATE_IGNORE=false
# Flags
for f in $@
do
case $f in
"-f") STATE_IGNORE=true ;;
"-s") STOP_LND=true ;;
esac
done
# Arguments
while getopts :d: opt; do
case $opt in
d) DROPBOX_APITOKEN=$OPTARG ;;
?) ;;
esac
done
# lnd/bitcoind paths
lnd_dir="/home/bitcoin/.lnd"
bitcoin_dir="/home/bitcoin/.bitcoin"
# Fetches the user whose home folder the directories will be stored under
ADMINUSER=( $(ls /home | grep -v bitcoin) )
DATE=$(date +%Y%m%d)
TIME=$(date +%Hh%Mm)
if [ -z "$DEVICE" ] ; then
DEVICE=$(echo $(cat /etc/hostname))
fi
DEVICE=$(echo $DEVICE | awk '{print tolower($0)}' | sed -e 's/ /-/g')
# Setup folders and filenames
DATADIR=/home/bitcoin/.lnd
WORKINGDIR=/home/$ADMINUSER/data-backups
BACKUPFOLDER=.lndbackup-$DEVICE
BACKUPFILE=lndbackup-$DEVICE--$DATE-$TIME.tar
CHANSTATEFILE=.chan_state.txt
# Make sure necessary folders exist
if [[ ! -e ${WORKINGDIR} ]]; then
mkdir -p ${WORKINGDIR}
fi
cd ${WORKINGDIR}
if [[ ! -e ${BACKUPFOLDER} ]]; then
mkdir -p ${BACKUPFOLDER}
fi
#==================
# CHECK CHANNEL STATE TO DETERMINE IF TO CONTINUE WITH BACKUP
#==================
# Function to check lnd status
function check_lnd_status {
systemctl -q is-active lnd
#kill -0 $(pidof lnd)
if [[ $? -eq 0 ]]; then
LNDSTOPPED=false
else
LNDSTOPPED=true
fi
}
# Function to stop lnd
function stop_lnd {
if [ ! $STOP_LND = false ] ; then
systemctl stop lnd
echo
echo "Stopping lnd..."
/bin/sleep 5s
check_lnd_status
fi
}
# Function to fetch channel state
function fetch_channel_state {
ROUTED=$(lncli $"${lncli_creds[@]}" fwdinghistory --start_time 1 --end_time 2000000000 | jq -r .last_offset_index)
INVOICES=$(lncli $"${lncli_creds[@]}" listinvoices | jq -r .last_index_offset)
PAYMENTS=$(lncli $"${lncli_creds[@]}" listpayments | jq '.payments | length')
CHAN_STATE=$(($ROUTED + $INVOICES + $PAYMENTS))
}
# SETUP LNCLI COMMAND FOR ROOT USER
chain="$(bitcoin-cli -datadir=${bitcoin_dir} getblockchaininfo | jq -r '.chain')"
if [[ $chain = "test" ]] ; then
macaroon_path="${lnd_dir}/data/chain/bitcoin/testnet/readonly.macaroon"
else
macaroon_path="${lnd_dir}/data/chain/bitcoin/mainnet/readonly.macaroon"
fi
lncli_creds=( --macaroonpath=${macaroon_path} --tlscertpath=${lnd_dir}/tls.cert)
# GET PRIOR BACKUP'S LND CHANNEL STATE, IF IT EXISTS
if [[ ! -e ${CHANSTATEFILE} ]]; then
LAST_STATE=0
elif [ ! $STOP_LND = false ] ; then
LAST_STATE=$(tail -n 100 ${CHANSTATEFILE} | grep stopped | jq -r .stopped | tail -n 1)
else
LAST_STATE=$(tail -n 1 ${CHANSTATEFILE})
fi
# EXECUTE CHANNEL-STATE-CHANGE CHECKS AND LOGGING
check_lnd_status
if [ ! $LNDSTOPPED = true ] ; then
fetch_channel_state
echo "---" >> $CHANSTATEFILE
echo "$? $(date)" >> $CHANSTATEFILE
if [ ! $STOP_LND = false ] ; then
echo "{\"stopped\": \""$CHAN_STATE"\"}" >> $CHANSTATEFILE
fi
echo $CHAN_STATE >> $CHANSTATEFILE
BACKUPFILE=${BACKUPFILE::-4}"--state-"$(printf "%04d\n" $((${CHAN_STATE}))).tar
STATE_CHANGE=$(("$LAST_STATE" - "$CHAN_STATE"))
else
STATE_CHANGE=-1
fi
# TERMINATE SCRIPT IF NO CHANGES DETECTED OR CHANGES IGNORED
echo "State change: "$STATE_CHANGE
if [[ $STATE_CHANGE -eq 0 && $STATE_IGNORE = false ]] ; then
echo "No channel state change detected"
/bin/sleep 0.5
echo "exiting..."
/bin/sleep 1
exit
else
stop_lnd
fi
#==================
# ...exits above if no state change detected
#==================
# ENSURE LND WAS SUCCESSFULLY STOPPED
max_tries=4
count=0
while [[ ! $LNDSTOPPED = true && $count -lt $(($max_tries - 1)) && ! $STOP_LND = false ]] ; do
stop_lnd
count=$(($count+1))
echo "Lnd stop, attempt#: "$(( $count + 1 ))" of "$max_tries
done
# SIGNAL IF LND WAS STOPPED
if [ $LNDSTOPPED = true ] ; then
BACKUPFILE="[stopped]-"$BACKUPFILE
echo "lnd successfully stopped!"
echo
else
BACKUPFILE="[inflight]-"$BACKUPFILE
if [ $STOP_LND = false ] ; then
echo "Running in-flight backup!"
else
echo "Sorry, lnd could not be stopped."
fi
echo
fi
# COPY DATA FOR BACKUP
echo "------------"
echo "Starting rsync..."
rsync -avh --delete --progress ${DATADIR}/ ${BACKUPFOLDER}/
# RESTART LND (AFTER COPY)
if [ $LNDSTOPPED = true ] ; then
systemctl start lnd
echo "Restarted lnd!"
fi
# CREATE ARCHIVE OF DATA TO BE UPLOADED
echo "------------"
echo "Creating tar archive of files..."
tar cvf ${BACKUPFILE} ${BACKUPFOLDER}/
chown -R ${ADMINUSER}:${ADMINUSER} ${BACKUPFOLDER} ${BACKUPFILE}
# GPG ENCRYPT ARCHIVE
function encrypt_backup {
GPGNOTFOUND=$(gpg -k ${GPG} 2>&1 >/dev/null | grep -c error)
if [ $GPGNOTFOUND -gt 0 ]; then
gpg --recv-keys ${GPG}
fi
gpg --trust-model always -r ${GPG} -e ${BACKUPFILE}
rm ${BACKUPFILE}
BACKUPFILE=$BACKUPFILE.gpg
}
if [ ! -z $GPG ] ; then
echo "------------"
echo "Running gpg encryption..."
encrypt_backup
echo "Encrypted!"
echo
fi
#==============================
# The archive file can be backed up via rsync or a cloud service now.
#==============================
function online_check {
wget -q --tries=10 --timeout=20 --spider http://google.com
if [[ $? -eq 0 ]]; then
ONLINE=true
else
ONLINE=false
fi
echo
echo "Online: "$ONLINE
echo "-----"
}
#==============================
# BACKUP VIA DROPBOX
#==============================
function dropbox_api_check {
curl -s -X POST https://api.dropboxapi.com/2/users/get_current_account \
--header "Authorization: Bearer "$DROPBOX_APITOKEN | grep rror
if [[ ! $? -eq 0 ]] ; then
VALID_DROPBOX_APITOKEN=true
else
VALID_DROPBOX_APITOKEN=false
fi
}
function upload_to_dropbox {
echo
echo "Starting Dropbox upload..."
SESSIONID=$(curl -s -X POST https://content.dropboxapi.com/2/files/upload_session/start \
--header "Authorization: Bearer "${DROPBOX_APITOKEN}"" \
--header "Dropbox-API-Arg: {\"close\": false}" \
--header "Content-Type: application/octet-stream" | jq -r .session_id)
echo "--> Session ID: "${SESSIONID}
echo
echo "Uploading "${BACKUPFILE}"..."
FINISH=$(curl -X POST https://content.dropboxapi.com/2/files/upload_session/finish \
--header "Authorization: Bearer "${DROPBOX_APITOKEN}"" \
--header "Dropbox-API-Arg: {\"cursor\": {\"session_id\": \""${SESSIONID}"\",\"offset\": 0},\"commit\": {\"path\": \"/"${BACKUPFOLDER}"/"${BACKUPFILE}"\",\"mode\": \"add\",\"autorename\": true,\"mute\": false,\"strict_conflict\": false}}" \
--header "Content-Type: application/octet-stream" \
--data-binary @$BACKUPFILE)
echo $FINISH | jq
}
# EXECUTE BACKUP VIA DROPBOX
online_check
dropbox_api_check
if [ $ONLINE = true -a -e ${BACKUPFILE} -a $VALID_DROPBOX_APITOKEN = true ] ; then
upload_to_dropbox
rm ${BACKUPFILE}
else
echo "Please check that the internet is connected and try again."
fi
# FINISH DROPBOX BACKUP
#=================================================================
#====================================
# BACKUP VIA RSYNC TO REMOTE SERVER
#====================================
# Consider adding this as a backup method
#====================================
# BACKUP VIA GOOGLE CLOUD
#====================================
# Consider adding this as a backup method
#----------------------------------------------
# FINISH
echo
echo "======="
echo " Done!"
echo "======="
@openoms
Copy link

openoms commented Mar 11, 2019

This is awesome. Did not test to recover yet, but the backup process works great on my RaspiBlitz.
The way I have run after login (would make sense to include in the description or script):

wget https://gist.github.com/vindard/8bbe7ed66d3d9f30646c4ec73e383a4d/raw/6dff2a1af6841a4163bafec7950452fffab31952/do-lndbackup.sh
nano do-lndbackup.sh 

filled in the DROPBOX-API-KEY and admin-user

sudo chmod +x do-lndbackup.sh 
sudo ./do-lndbackup.sh

Comments:
The admin-user is admin by default in both the RasbiBolt and RaspiBlitz
The script could ask for the API key if it is not filled it, something like:

if [ $APITOKEN="DROPBOX-API-KEY" ] ; then
  echo "Type or insert Dropbox API key followed by [ENTER]"
  read APITOKEN
fi

It would be great to see this part of the RaspiBlitz service menu and the RaspiBolt guide, are you planning to submit?

@vindard
Copy link
Author

vindard commented Mar 14, 2019

Hey thanks for the feedback @openoms!

Yup I'm planning to do install/run instructions when I do the PRs into those projects. For now I'm just trying to get the script logic right first (which I think I have now).

I like that suggestion for the Dropbox API key prompt, but since I have the final implementation planned to be done over a CRON job, I'd prefer people to hardcode their API and GPG keys.

I've also added flags so that folks will have some runtime options when setting up their cron jobs.

PRs hopefully submitted in to those repos you recommended later this week. Thanks again for trying out!

@vindard
Copy link
Author

vindard commented Mar 15, 2019

At 17 revision after ~2 weeks I think it's fair to say that this gist has grown into its own little project. Given this, I've moved the project across to its own proper repo now to facilitate better tracking & development.

All further development can now be found at: https://github.com/vindard/lnd-backup

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment