mirror of
https://github.com/getumbrel/umbrel.git
synced 2026-10-12 02:14:08 +00:00
277 lines
10 KiB
Bash
Executable File
277 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# This script is exposed via: curl -sL https://export.umbrel.sh | sudo bash
|
|
# Which resolves to this file at the 1.1.1 tag in this repo
|
|
|
|
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 |