[TECH][BACKUP] Lifeboat.sh, retaining access to backups

Posted on Sat 24 December 2022 in Tech, Hardware, Setup

The more time passes, the more backups I keep and the more afraid I am to lose them (or access to them), however those backup beeing encrypted and protected by many layers of passphrases, authentication, I must now make sure that I can access them from anywhere, at anytime, otherwise they are not as useful as they could be.

To overcome this issue I decided to create what I call my "setup's lifeboat". It contains the bare minimum requirement to get me started on any new system.

The idea is to only store the following pieces:

  • gnupg directory, I need it to access my password (pass) manager
  • pass directory
  • ssh keys (need them to access most of my remote machines)

Amongst many others, my password manager contains a SAMBA and restic passphrase that I can use to access the content of my local (i.e most up to date) backup, located in the NAS next room.

I have also decided to make a tool that I can use easily and that is self-documented (I do not plan to use it often, but when I will need it, I don't want to spend hours trying to remember how it was supposed to work).

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

#/ Usage: ./lifeboat.sh
#/ Description: Create a self contained encrypted archive with the most important data
#/ Options:
#/   --help: Display this help message
usage() { grep '^#/' "$0" | cut -c4- ; exit 0 ; }
expr "$*" : ".*--help" > /dev/null && usage

readonly LOG_FILE="/tmp/$(basename "$0").log"
info()    { echo "[INFO]    $@" | tee -a "$LOG_FILE" >&2 ; }
warning() { echo "[WARNING] $@" | tee -a "$LOG_FILE" >&2 ; }
error()   { echo "[ERROR]   $@" | tee -a "$LOG_FILE" >&2 ; }
fatal()   { echo "[FATAL]   $@" | tee -a "$LOG_FILE" >&2 ; exit 1 ; }

ORIG_DIR="$(pwd)"
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";

BACKUP_PASS="$HOME/.resticcredentials"
CIFS_PASS="$HOME/.smbcredentials"
GNUPG="$HOME/.gnupg"
PASS="$HOME/.password-store"
SSH="$HOME/.ssh"

ARCHIVE_NAME="lifeboat.tar.gz.gpg"

cleanup() {
  rm -rf $tmp
  popd &>/dev/null || :
}

if [[ "${BASH_SOURCE[0]}" = "$0" ]]; then
  trap cleanup EXIT

  tmp="$(mktemp --directory)"
  tmp2="$ORIG_DIR/$(mktemp --directory -p ./)"

  info "====== Lifeboat creation script ======"
  info "Creating temporary directoy $tmp"
  pushd $tmp &>/dev/null

  info "Backup in progress"
  # User backup
  cp -r $GNUPG ./
  cp -r $PASS ./
  cp -r $SSH ./
  cp $BACKUP_PASS ./
  cp $CIFS_PASS ./

  # Tool's instructions
  cp "$SCRIPT_DIR/wrapper.sh" ./
  cp "$SCRIPT_DIR/instructions.md" ./

  info "Encryption started, please provide a key"
  cd ..
  tar czf - -C "$tmp/" . | gpg --symmetric --no-symkey-cache > $ARCHIVE_NAME

  info "Preparing final instructions in $tmp2"
  mv $ARCHIVE_NAME "$tmp2"
  cd "$tmp2"
  cp "$SCRIPT_DIR/extract.sh" ./
  cp "$SCRIPT_DIR/readme.md" ./
fi

This will create an archive called lifeboat.tar.gz.gpg along with an extract.sh script and a readme (outside of the encrypted archive). The content of the extract.sh script is pretty straightforward, it is only here to avoid the burden of remembering gpg and tar commands to extract the backup (the readme file is not really required, its only purpose is to remember what this archive is and how it should be used, my future self might like that):

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

usage() { grep '^#/' "$0" | cut -c4- ; exit 0 ; }
expr "$*" : ".*--help" > /dev/null && usage

readonly LOG_FILE="/tmp/$(basename "$0").log"
info()    { echo "[INFO]    $@" | tee -a "$LOG_FILE" >&2 ; }
warning() { echo "[WARNING] $@" | tee -a "$LOG_FILE" >&2 ; }
error()   { echo "[ERROR]   $@" | tee -a "$LOG_FILE" >&2 ; }
fatal()   { echo "[FATAL]   $@" | tee -a "$LOG_FILE" >&2 ; exit 1 ; }

cleanup() {
  popd
}

if [[ "${BASH_SOURCE[0]}" = "$0" ]]; then
  trap cleanup EXIT

  info "===== Lifeboat extraction script ====="
  tmp="$(mktemp --directory -p ./)"
  info "Extracting lifeboat into $tmp"
  pushd $tmp &>/dev/null

  info "Decryption started, please provide passphrase"
  gpg -d ../lifeboat.tar.gz.gpg | tar xzf - || :

  info "Extraction done, go to $tmp and follow the instructions"
fi

The last piece of this tool is the wrapper.sh tool that is bundled inside the encrypted archive and contains a few useful wrapper scripts to use the content of the lifeboat directly from where it has been extracted (usb stick or tmpfs for instance). It includes helpers to:

  • install the dependencies required (cifs, restic, pass) on debian/ubuntu systems
  • access pass from a non-standard location
  • mount and explore a local restic backup
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

#/ Description: Wraps and documents the usage of pass and restic
#/ Options:
#/   --help: Display this help message
#/   -i/--install: Install the required dependencies
#/   -m/--mount: Mount the restic backup for local exploration
#/   -p/--pass: Setup environment to read pass
usage() { grep '^#/' "$0" | cut -c4- ; exit 0 ; }
expr "$*" : ".*--help" > /dev/null && usage

readonly LOG_FILE="/tmp/$(basename "$0").log"
info()    { echo "[INFO]    $@" | tee -a "$LOG_FILE" >&2 ; }
warning() { echo "[WARNING] $@" | tee -a "$LOG_FILE" >&2 ; }
error()   { echo "[ERROR]   $@" | tee -a "$LOG_FILE" >&2 ; }
fatal()   { echo "[FATAL]   $@" | tee -a "$LOG_FILE" >&2 ; exit 1 ; }

CIFS_IP="XXX.XXX.XXX.XXX"

cleanup() { 
  :
}

install_deps() {
  info "Installing all dependencies requried for this script (debian/ubuntu)"
  if ! command -v apt-get &> /dev/null
  then
    warning "apt was not found, please install cifs, restic and pass to properly use that script"
  else
    sudo apt install cifs-utils restic pass
  fi
}

mount_restic_backup() {
  if ! command -v restic &>/dev/null
  then
    error "Unable to find restic on this system"
    info "To manually inspect the backup, you need to mount the volume on which the backup is present"
    info "and then mount the backup itself using restic:"
    info "restic -r ./<RESTIC_ROOT>  -p ./.resticcredentials mount ./mnt/restic"
    info "Where RESTIC_ROOT is the location of the backup. You should be able to explore the file in mnt/restic"


    info "For more information about the other restic backup, mount the pass repository and read the instruction using"
    info "pass restic.wasabi"
  else
    warning "You can't run this script from a USB device, restic will not allow to mount a fuse device. If you wish to use"
    warning "this script, please copy it to your machine"
    mkdir -p mnt/restic-root
    mkdir -p mnt/restic

    ID=$(id -u)
    GID=$(id -g)
    info "Mounting remote filesystem, you probably need to update the IP address"
    info "The following command will ask you for your root password"
    sudo mount -t cifs //$CIFS_IP/restic mnt/restic-root -o gid=$GID,uid=$ID -o iocharset=utf8 -o credentials=$PWD/.smbcredentials -o vers=3.1.1
    info "Mounting backup into mnt/restic"
    restic  -r ./mnt/restic-root  -p .resticcredentials mount mnt/restic || :
    info "Unmounting backup and remote fs"
    sudo umount mnt/restic-root
  fi
}

pass_setup() {
  info "To use pass, please copy the following lines:"
  echo "export GNUPGHOME=\"$PWD/.gnupg\""
  echo "export PASSWORD_STORE_DIR=\"$PWD/.password-store\""

  info "Do not forget to unset those variable after you are done"
}

if [[ "${BASH_SOURCE[0]}" = "$0" ]]; then
  trap cleanup EXIT
  info "====== Lifeboat script ======"

  [ $# -eq 0 ] && usage && exit 1

  while [[ $# -gt 0 ]]
  do
  key="$1"

  case $key in
      -i|--install)
      shift
      install_deps
      ;;
      -m|--mount)
      shift
      mount_restic_backup
      ;;
      -p|--pass)
      shift
      pass_setup
      ;;
      *)    # unknown option
      error "Unknown argument \"$1\""
      shift # past argument
      ;;
  esac
  done
fi

This should now give me the smallest backup that I could think of, allowing me to access almost everything I need to restore my computer and accesses. It is encrypted, self-documented and easy to use. It obvioulsy has flaws, as I still need to remember the password that I used to create the archive and will probably be outdated pretty quickly, however I do not rotate my password that often since they are generated and pretty long anyway and I could still retrieve a more up to date version of the password store from a backup once I retrieve the restic key from the lifeboat's archive.

Stay safe and test your backups !