#!/usr/bin/env bash

set -euo pipefail

# This script will:
# - Look for an internal Umbrel install
# - Ask the user to confirm the location or enter a new one
# - Exit and print error if no valid Umbrel install detected
# - Look for USB storage device
# - Ask the user to confirm the device or enter a new one
# - Exit and print error if no valid USB storage device detected
# - Check size of external storage is large enough for Umbrel install
# - Check we have write permissions on external drive
# - Stop Umbrel if it's running
# - Copy Umbrel install to external drive

# Bail if not running as root
check_root() {
  if [[ $UID != 0 ]]; then
    echo "This script must be run as root"
    exit 1
  fi
}

# Check depndencies are installed
check_dependencies () {
  for cmd in "$@"; do
    if ! command -v $cmd >/dev/null 2>&1; then
      echo "This script requires \"${cmd}\" to be installed"
      echo
      echo "You can try running: sudo apt-get install ${cmd}"
      exit 1
    fi
  done
}

# Interactively confirm a value with the user. The user can press enter to accept the default value
# or enter a new value.
confirm_value_with_user() {
    local prompt="${1}"
    local default_value="${2}"
    local user_input

    # Prompt the user and get input
    read -p "$prompt " user_input </dev/tty # We need to explicitly pipe /dev/tty in here because stdin might be the curl output

    # If input is empty, return the default value
    if [[ -z "$user_input" ]]; then
        echo "$default_value"
    else
        echo "$user_input"
    fi
}

# Grabs the Umbrel data directory from the systemd service file
find_umbrel_install() {
    local service_file_path="/etc/systemd/system/umbrel-startup.service"
    if [[ ! -f "${service_file_path}" ]]
    then
        return
    fi

    local umbrel_install=$(cat "${service_file_path}" | grep '^ExecStart=')
    umbrel_install="${umbrel_install#ExecStart=}"
    umbrel_install="${umbrel_install%/scripts/start}"
    echo "${umbrel_install}"
}

# Lists block devices for currently attached USB storage devices
# We only return unmounted devices
list_usb_storage_devices() {
    local devices=$(lsblk --output NAME,TRAN --json | jq -r '.blockdevices[] | select(.tran=="usb") | .name')
    for device in $devices
    do
        echo "/dev/${device}"
    done
}

# Returns the vendor and model name of a block device
get_block_device_model() {
  device="${1}"
  vendor=$(cat "/sys/block/${device}/device/vendor")
  model=$(cat "/sys/block/${device}/device/model")

  # We echo in a subshell without quotes to strip surrounding whitespace
  echo "$(echo $vendor) $(echo $model)"
}

# Reutns the block device size in bytes
get_block_device_size_bytes() {
    local block_device="${1}"
    lsblk --nodeps --noheadings --output SIZE --bytes "${block_device}"
}

# Converts bytes to GB
bytes_to_gb() {
    echo $1 | awk '{printf "%.1f", $1 / 1024 / 1024 / 1024}'
}

# Wipes a block device and reformats it with a single EXT4 partition
format_block_device () {
  device_path="${1}"
  partition_path="${device_path}1"
  wipefs -a "${device_path}"
  parted --script "${device_path}" mklabel gpt
  parted --script "${device_path}" mkpart primary ext4 0% 100%
  # We need to run sync here to make sure the filesystem is reflecting the
  # the latest changes in /dev/*
  sync
  mkfs.ext4 -F -L umbrel "${partition_path}"
}

main() {
    check_root

    check_dependencies jq rsync lsblk wipefs parted mkfs.ext4

    echo "Searching for Umbrel installations..."
    local umbrel_install=$(find_umbrel_install)
    if [[ ! -d "${umbrel_install}/app-data" ]]
    then
        echo "No Umbrel installation automatically found"
        umbrel_install=""
    fi
    echo
    echo "Please confirm your Umbrel installation directory."
    if [[ -d "${umbrel_install}/app-data" ]]
    then
        echo "  - If it is '${umbrel_install}', just press Enter."
        echo "  - If it is a different directory, type its full path and press Enter."
    else
        echo "  - Type it's full path and press Enter."
    fi
    echo
    umbrel_install=$(confirm_value_with_user "Your Umbrel installation directory:" "${umbrel_install}")

    if [[ -d "${umbrel_install}/app-data" ]]
    then
        echo
        echo "Exporting your Umbrel data from: ${umbrel_install}"
        local install_size=$(du --human --max-depth 0 "${umbrel_install}" | awk '{print $1}')
        echo "Your Umbrel data (${install_size}), including the apps listed below, is ready to be exported to a USB storage device:"
        echo "$(ls ${umbrel_install}/app-data)" || true
    else
        echo "Error: Umbrel installation not found"
        exit 1
    fi

    echo "Searching for USB storage devices..."
    local usb_storage_devices=$(list_usb_storage_devices)
    local largest_usb_size_bytes="0"
    local default_usb_device=""
    for block_device in $usb_storage_devices
    do
        local usb_name=$(get_block_device_model ${block_device#/dev/})
        local usb_size=$(lsblk --nodeps --noheadings --output SIZE ${block_device})
        local usb_size_bytes=$(get_block_device_size_bytes "${block_device}")
        echo "  - ${block_device} (${usb_name} ${usb_size})"
        if [[ $usb_size_bytes -gt $largest_usb_size_bytes ]]
        then
            largest_usb_size_bytes="${usb_size_bytes}"
            default_usb_device="${block_device}"
        fi
    done

    if [[ -z "${default_usb_device}" ]]
    then
        echo "No USB devices automatically found"
    fi
    echo
    echo "Please confirm the USB storage device where you want to export your Umbrel data."
     if [[ ! -z "${default_usb_device}" ]]
    then
        local usb_name=$(get_block_device_model ${block_device#/dev/})
        local usb_size=$(lsblk --nodeps --noheadings --output SIZE ${block_device})
        echo "  - If you'd like to use ${block_device} (${usb_name} ${usb_size}), just press Enter."
        echo "  - If you'd like to use a different USB storage device, type its path (eg. "/dev/sdb", without quotes) and press Enter."
    else
        echo "  - Type the full path of your USB storage device (eg. "/dev/sdb", without quotes) and press Enter."
    fi
    echo
    local usb_block_device=$(confirm_value_with_user "USB storage device:" "${default_usb_device}")

    if [[ ! -b "${usb_block_device}" ]]
    then
        echo "Error: \"${usb_block_device}\" ($(get_block_device_model ${usb_block_device#/dev/})) is not a valid storage device. Please make sure you've connected a compatible storage device like an external HDD or SSD."
        exit 1
    fi

    echo "Continuing with ${usb_block_device} ($(get_block_device_model ${usb_block_device#/dev/}))"

    local usb_size_bytes=$(get_block_device_size_bytes "${usb_block_device}")
    local umbrel_install_size_bytes=$(du --bytes --max-depth 0 "${umbrel_install}" | awk '{print $1}')
    local buffer_bytes=$(( 1024 * 1024 * 1024 )) # 1GB buffer
    if [[ $usb_size_bytes -lt $(( umbrel_install_size_bytes + buffer_bytes )) ]]
    then
        echo "Error: $(get_block_device_model ${usb_block_device#/dev/}) ($(bytes_to_gb $usb_size_bytes) GB) does not have enough space to store your Umbrel data ($(bytes_to_gb $umbrel_install_size_bytes) GB). Please connect a larger storage device and run this script again."
        exit 1
    fi

    echo
    echo "WARNING: Continuing will format the USB storage device $(get_block_device_model ${usb_block_device#/dev/}) and erase any existing data on it."

    local confirm_formatting=$(confirm_value_with_user "Type \"y\" (without quotes) and press Enter to continue:" "")
    if [[ "${confirm_formatting}" != "y" ]]
    then
        echo "Exiting now: did not receive \"y\" as the confirmation to continue."
        echo "To restart the process, simply re-run this script and select the correct USB storage device."
        exit 1
    fi

    echo "Formatting USB storage device $(get_block_device_model ${usb_block_device#/dev/})..."
    # Quickly attempt to unmount all partitions on the USB device
    # This will throw errors but we don't care
    umount "${usb_block_device}"* 2> /dev/null || true
    sync
    echo
    format_block_device "${usb_block_device}"
    echo

    local usb_partition="${usb_block_device}1"
    echo "Mounting ${usb_partition}..."
    local usb_mount_path=$(mktemp --directory --suffix -umbrel-usb-mount)
    mount "${usb_partition}" "${usb_mount_path}"

    # Make sure no matter what, this gets unmounted
    trap "umount ${usb_mount_path} 2> /dev/null || true" EXIT

    # Check we can write
    local temporary_copy_path="${usb_mount_path}/umbrel-data-export-temporary-${RANDOM}-$(date +%s)"
    mkdir "${temporary_copy_path}"
    if [[ ! -d "${temporary_copy_path}" ]]
    then
        echo "Error: Could not write to the USB storage device $(get_block_device_model ${usb_block_device#/dev/}). Please re-connect a compatible USB storage device and run this script again."
        exit 1
    fi

    # Stop Umbrel if it's running so we can safely copy data
    echo "Stopping Umbrel to prepare for data export..."
    echo
    "${umbrel_install}/scripts/stop" || {
        # If the stop script fails try heavy handedly stopping all Docker containers to ensure
        docker stop $(docker ps -aq) || {
            echo "Error: Could not stop Umbrel"
            exit 1
        }
    }
    echo

    # Copy data
    echo "Exporting your Umbrel data to the USB storage device $(get_block_device_model ${usb_block_device#/dev/}), this may take a while..."
    local final_path="${usb_mount_path}/umbrel"
    rsync --archive --delete "${umbrel_install}/" "${temporary_copy_path}"
    mv "${temporary_copy_path}" "${final_path}"

    # Ensure fs caches are flushed and unmount
    echo "Export complete, unmounting USB storage device..."
    sync
    umount ${usb_mount_path}

    echo
    echo "Done! Your Umbrel data has been exported to your external USB storage device $(get_block_device_model ${usb_block_device#/dev/})."
    echo
    echo "Next steps:"
    echo "  1. Shutdown your device."
    echo "  2. Flash umbrelOS 1.1.1 on its internal storage."
    echo "  3. Boot up with the USB storage device $(get_block_device_model ${usb_block_device#/dev/}) connected to your device."
    echo "  4. Open http://umbrel.local"
    echo
    echo "For detailed instructions, visit:"
    echo "  https://link.umbrel.com/linux-update"
}

main