Browse Source

Update external Umbrel installation when booting from a newer Umbrel OS version. (#134)

check-external-filesystem
Luke Childs 4 years ago
committed by GitHub
parent
commit
756b788605
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 70
      events/triggers/update
  2. 58
      scripts/umbrel-os/external-storage/update-from-sdcard
  3. 521
      scripts/umbrel-os/semver
  4. 2
      scripts/umbrel-os/services/umbrel-external-storage.service
  5. 2
      scripts/update/00-run.sh
  6. 2
      scripts/update/01-run.sh
  7. 2
      scripts/update/02-run.sh
  8. 2
      scripts/update/03-run.sh
  9. 109
      scripts/update/update

70
events/triggers/update

@ -1,71 +1,5 @@
#!/usr/bin/env bash
check_dependencies () {
for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "This script requires \"${cmd}\" to be installed"
exit 1
fi
done
}
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
check_dependencies jq wget git rsync
# UMBREL_ROOT=/home/umbrel
UMBREL_ROOT=$(dirname $(readlink -f "${BASH_SOURCE[0]}"))/../..
RELEASE=$(cat "$UMBREL_ROOT"/statuses/update-status.json | jq .updateTo -r)
echo
echo "======================================="
echo "============= OTA UPDATE =============="
echo "======================================="
echo "========== Stage: Download ============"
echo "======================================="
echo
# Make sure an update is not in progres
if [[ -f "$UMBREL_ROOT/statuses/update-in-progress" ]]; then
echo "An update is already in progress. Exiting now."
exit 2
fi
echo "Creating lock"
touch "$UMBREL_ROOT"/statuses/update-in-progress
# Cleanup just in case there's temp stuff lying around from previous update
echo "Cleaning up any previous mess"
[[ -d "$UMBREL_ROOT"/.umbrel-"$RELEASE" ]] && rm -rf "$UMBREL_ROOT"/.umbrel-"$RELEASE"
# Update status file
cat <<EOF > "$UMBREL_ROOT"/statuses/update-status.json
{"state": "installing", "progress": 10, "description": "Downloading Umbrel $RELEASE", "updateTo": "$RELEASE"}
EOF
# Clone new release
echo "Downloading Umbrel $RELEASE"
mkdir -p "$UMBREL_ROOT"/.umbrel-"$RELEASE"
cd "$UMBREL_ROOT"/.umbrel-"$RELEASE"
curl -L "https://github.com/getumbrel/umbrel/archive/$RELEASE.tar.gz" | tar -xz --strip-components=1
# Run update scripts
echo "Running update install scripts of the new release"
cd scripts/update
UPDATE_INSTALL_SCRIPTS=$(ls *-run.sh)
for script in $UPDATE_INSTALL_SCRIPTS; do
if [[ -x $script ]]; then
echo
echo "== Begin Update Script $script =="
./"$script" "$RELEASE" "$UMBREL_ROOT"
echo "== End Update Script $script =="
echo
fi
done
# Delete cloned repo
echo "Deleting cloned repository"
[[ -d "$UMBREL_ROOT"/.umbrel-"$RELEASE" ]] && rm -rf "$UMBREL_ROOT"/.umbrel-"$RELEASE"
echo "Removing lock"
rm -f "$UMBREL_ROOT"/statuses/update-in-progress
exit 0
"${UMBREL_ROOT}/scripts/update/update" --ota

58
scripts/umbrel-os/external-storage/update-from-sdcard

@ -0,0 +1,58 @@
#!/usr/bin/env bash
set -euo pipefail
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../..)"
SD_MOUNT_POINT="/sd-root"
SD_UMBREL_ROOT="${SD_MOUNT_POINT}${UMBREL_ROOT}"
check_root () {
if [[ $UID != 0 ]]; then
echo "This script must be run as root"
exit 1
fi
}
check_dependencies () {
for cmd in "$@"; do
if ! command -v $cmd >/dev/null 2>&1; then
echo "This script requires \"${cmd}\" to be installed"
exit 1
fi
done
}
check_semver_range () {
local range="${1}"
local version="${2}"
"${UMBREL_ROOT}/scripts/umbrel-os/semver" -r "${range}" "${version}" | grep --quiet "^${version}$"
}
main () {
check_root
check_dependencies jq
echo "Checking if SD card Umbrel is newer than external storage..."
local external_version=$(cat "${UMBREL_ROOT}/info.json" | jq -r .version | cut -d "-" -f "1")
local sd_version=$(cat "${SD_UMBREL_ROOT}/info.json" | jq -r .version | cut -d "-" -f "1")
if ! check_semver_range ">${external_version}" "${sd_version}"; then
echo "No, SD version is not newer, exiting."
exit 0
fi
echo "Yes, SD version is newer."
# This will fail if we ever have multiple ranges with pre-release tags.
# e.g `1.2.3-beta <2.0.0` will become `1.2.3` due to striping the `-`
local update_requirement=$(cat "${SD_UMBREL_ROOT}/info.json" | jq -r .requires | cut -d "-" -f "1")
echo "Checking if the external storage version \"${external_version}\" satisfies update requirement \"${update_requirement}\"..."
if ! check_semver_range "${update_requirement}" "${external_version}"; then
echo "No, we can't do an automatic update, exiting."
exit 0
fi
echo "Yes, it does, attempting an automatic update..."
"${UMBREL_ROOT}/scripts/update/update" --path "${SD_UMBREL_ROOT}"
}
main

521
scripts/umbrel-os/semver

@ -0,0 +1,521 @@
#!/usr/bin/env bash
# https://github.com/qzb/sh-semver
# Commit: 2ac2437
_num_part='([0-9]|[1-9][0-9]*)'
_lab_part='([0-9]|[1-9][0-9]*|[0-9]*[a-zA-Z-][a-zA-Z0-9-]*)'
_met_part='([0-9A-Za-z-]+)'
RE_NUM="$_num_part(\.$_num_part)*"
RE_LAB="$_lab_part(\.$_lab_part)*"
RE_MET="$_met_part(\.$_met_part)*"
RE_VER="[ \t]*$RE_NUM(-$RE_LAB)?(\+$RE_MET)?"
BRE_DIGIT='[0-9]\{1,\}'
BRE_ALNUM='[0-9a-zA-Z-]\{1,\}'
BRE_IDENT="$BRE_ALNUM\(\.$BRE_ALNUM\)*"
BRE_MAJOR="$BRE_DIGIT"
BRE_MINOR="\(\.$BRE_DIGIT\)\{0,1\}"
BRE_PATCH="\(\.$BRE_DIGIT\)\{0,1\}"
BRE_PRERE="\(-$BRE_IDENT\)\{0,1\}"
BRE_BUILD="\(+$BRE_IDENT\)\{0,1\}"
BRE_VERSION="${BRE_MAJOR}${BRE_MINOR}${BRE_PATCH}${BRE_PRERE}${BRE_BUILD}"
filter()
{
local text="$1"
local regex="$2"
shift 2
echo "$text" | grep -E "$@" "$regex"
}
# Gets number part from normalized version
get_number()
{
echo "${1%%-*}"
}
# Gets prerelase part from normalized version
get_prerelease()
{
local pre_and_meta=${1%+*}
local pre=${pre_and_meta#*-}
if [ "$pre" = "$1" ]; then
echo
else
echo "$pre"
fi
}
# Gets major number from normalized version
get_major()
{
echo "${1%%.*}"
}
# Gets minor number from normalized version
get_minor()
{
local minor_major_bug=${1%%-*}
local minor_major=${minor_major_bug%.*}
local minor=${minor_major#*.}
if [ "$minor" = "$minor_major" ]; then
echo
else
echo "$minor"
fi
}
get_bugfix()
{
local minor_major_bug=${1%%-*}
local bugfix=${minor_major_bug##*.*.}
if [ "$bugfix" = "$minor_major_bug" ]; then
echo
else
echo "$bugfix"
fi
}
strip_metadata()
{
echo "${1%+*}"
}
semver_eq()
{
local ver1 ver2 part1 part2
ver1=$(get_number "$1")
ver2=$(get_number "$2")
local count=1
while true; do
part1=$(echo "$ver1"'.' | cut -d '.' -f $count)
part2=$(echo "$ver2"'.' | cut -d '.' -f $count)
if [ -z "$part1" ] || [ -z "$part2" ]; then
break
fi
if [ "$part1" != "$part2" ]; then
return 1
fi
local count=$(( count + 1 ))
done
if [ "$(get_prerelease "$1")" = "$(get_prerelease "$2")" ]; then
return 0
else
return 1
fi
}
semver_lt()
{
local number_a number_b prerelease_a prerelease_b
number_a=$(get_number "$1")
number_b=$(get_number "$2")
prerelease_a=$(get_prerelease "$1")
prerelease_b=$(get_prerelease "$2")
local head_a=''
local head_b=''
local rest_a=$number_a.
local rest_b=$number_b.
while [ -n "$rest_a" ] || [ -n "$rest_b" ]; do
head_a=${rest_a%%.*}
head_b=${rest_b%%.*}
rest_a=${rest_a#*.}
rest_b=${rest_b#*.}
if [ -z "$head_a" ] || [ -z "$head_b" ]; then
return 1
fi
if [ "$head_a" -eq "$head_b" ]; then
continue
fi
if [ "$head_a" -lt "$head_b" ]; then
return 0
else
return 1
fi
done
if [ -n "$prerelease_a" ] && [ -z "$prerelease_b" ]; then
return 0
elif [ -z "$prerelease_a" ] && [ -n "$prerelease_b" ]; then
return 1
fi
local head_a=''
local head_b=''
local rest_a=$prerelease_a.
local rest_b=$prerelease_b.
while [ -n "$rest_a" ] || [ -n "$rest_b" ]; do
head_a=${rest_a%%.*}
head_b=${rest_b%%.*}
rest_a=${rest_a#*.}
rest_b=${rest_b#*.}
if [ -z "$head_a" ] && [ -n "$head_b" ]; then
return 0
elif [ -n "$head_a" ] && [ -z "$head_b" ]; then
return 1
fi
if [ "$head_a" = "$head_b" ]; then
continue
fi
# If both are numbers then compare numerically
if [ "$head_a" = "${head_a%[!0-9]*}" ] && [ "$head_b" = "${head_b%[!0-9]*}" ]; then
[ "$head_a" -lt "$head_b" ] && return 0 || return 1
# If only a is a number then return true (number has lower precedence than strings)
elif [ "$head_a" = "${head_a%[!0-9]*}" ]; then
return 0
# If only b is a number then return false
elif [ "$head_b" = "${head_b%[!0-9]*}" ]; then
return 1
# Finally if of identifiers is a number compare them lexically
else
test "$head_a" \< "$head_b" && return 0 || return 1
fi
done
return 1
}
semver_gt()
{
if semver_lt "$1" "$2" || semver_eq "$1" "$2"; then
return 1
else
return 0
fi
}
semver_le()
{
semver_gt "$1" "$2" && return 1 || return 0
}
semver_ge()
{
semver_lt "$1" "$2" && return 1 || return 0
}
semver_sort()
{
if [ $# -le 1 ]; then
echo "$1"
return
fi
local pivot=$1
local args_a=()
local args_b=()
shift 1
for ver in "$@"; do
if semver_le "$ver" "$pivot"; then
args_a=( "${args_a[@]}" "$ver" )
else
args_b=( "$ver" "${args_b[@]}" )
fi
done
args_a=( $(semver_sort "${args_a[@]}") )
args_b=( $(semver_sort "${args_b[@]}") )
echo "${args_a[@]}" "$pivot" "${args_b[@]}"
}
regex_match()
{
local string="$1 "
local regexp="$2"
local match
match="$(eval "echo '$string' | grep -E -o '^[ \t]*($regexp)[ \t]+'")";
for i in $(seq 0 9); do
unset "MATCHED_VER_$i"
unset "MATCHED_NUM_$i"
done
unset REST
if [ -z "$match" ]; then
return 1
fi
local match_len=${#match}
REST="${string:$match_len}"
local part
local i=1
for part in $string; do
local ver num
ver="$(eval "echo '$part' | grep -E -o '$RE_VER' | head -n 1 | sed 's/ \t//g'")";
num=$(get_number "$ver")
if [ -n "$ver" ]; then
eval "MATCHED_VER_$i='$ver'"
eval "MATCHED_NUM_$i='$num'"
i=$(( i + 1 ))
fi
done
return 0
}
# Normalizes rules string
#
# * replaces chains of whitespaces with single spaces
# * replaces whitespaces around hyphen operator with "_"
# * removes wildcards from version numbers (1.2.* -> 1.2)
# * replaces "x" with "*"
# * removes whitespace between operators and version numbers
# * removes leading "v" from version numbers
# * removes leading and trailing spaces
normalize_rules()
{
echo " $1" \
| sed 's/\\t/ /g' \
| sed 's/ / /g' \
| sed 's/ \{2,\}/ /g' \
| sed 's/ - /_-_/g' \
| sed 's/\([~^<>=]\) /\1/g' \
| sed 's/\([ _~^<>=]\)v/\1/g' \
| sed 's/\.[xX*]//g' \
| sed 's/[xX]/*/g' \
| sed 's/^ //g' \
| sed 's/ $//g'
}
# Reads rule from provided string
resolve_rule()
{
local rule operator operands
rule="$1"
operator="$( echo "$rule" | sed "s/$BRE_VERSION/#/g" )"
operands=( $( echo "$rule" | grep -o "$BRE_VERSION") )
case "$operator" in
'*') echo "all" ;;
'#') echo "eq ${operands[0]}" ;;
'=#') echo "eq ${operands[0]}" ;;
'<#') echo "lt ${operands[0]}" ;;
'>#') echo "gt ${operands[0]}" ;;
'<=#') echo "le ${operands[0]}" ;;
'>=#') echo "ge ${operands[0]}" ;;
'#_-_#') echo "ge ${operands[0]}"
echo "le ${operands[1]}" ;;
'~#') echo "tilde ${operands[0]}" ;;
'^#') echo "caret ${operands[0]}" ;;
*) return 1
esac
}
resolve_rules()
{
local rules
rules="$(normalize_rules "$1")"
IFS=' ' read -ra rules <<< "${rules:-all}"
for rule in "${rules[@]}"; do
resolve_rule "$rule"
done
}
rule_eq()
{
local rule_ver="$1"
local tested_ver="$2"
semver_eq "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_le()
{
local rule_ver="$1"
local tested_ver="$2"
semver_le "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_lt()
{
local rule_ver="$1"
local tested_ver="$2"
semver_lt "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_ge()
{
local rule_ver="$1"
local tested_ver="$2"
semver_ge "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_gt()
{
local rule_ver="$1"
local tested_ver="$2"
semver_gt "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_tilde()
{
local rule_ver="$1"
local tested_ver="$2"
if rule_ge "$rule_ver" "$tested_ver"; then
local rule_major rule_minor
rule_major=$(get_major "$rule_ver")
rule_minor=$(get_minor "$rule_ver")
if [ -n "$rule_minor" ] && rule_eq "$rule_major.$rule_minor" "$(get_number "$tested_ver")"; then
return 0
fi
if [ -z "$rule_minor" ] && rule_eq "$rule_major" "$(get_number "$tested_ver")"; then
return 0
fi
fi
return 1
}
rule_caret()
{
local rule_ver="$1"
local tested_ver="$2"
if rule_ge "$rule_ver" "$tested_ver"; then
local rule_major
rule_major="$(get_major "$rule_ver")"
if [ "$rule_major" != "0" ] && rule_eq "$rule_major" "$(get_number "$tested_ver")"; then
return 0
fi
if [ "$rule_major" = "0" ] && rule_eq "$rule_ver" "$(get_number "$tested_ver")"; then
return 0
fi
fi
return 1
}
rule_all()
{
return 0
}
apply_rules()
{
local rules_string="$1"
shift
local versions=( "$@" )
# Loop over sets of rules (sets of rules are separated with ||)
for ver in "${versions[@]}"; do
rules_tail="$rules_string";
while [ -n "$rules_tail" ]; do
head="${rules_tail%%||*}"
if [ "$head" = "$rules_tail" ]; then
rules_string=""
else
rules_tail="${rules_tail#*||}"
fi
#if [ -z "$head" ] || [ -n "$(echo "$head" | grep -E -x '[ \t]*')" ]; then
#group=$(( $group + 1 ))
#continue
#fi
rules="$(resolve_rules "$head")"
# If specified rule cannot be recognised - end with error
if [ $? -eq 1 ]; then
exit 1
fi
if ! echo "$ver" | grep -q -E -x "[v=]?[ \t]*$RE_VER"; then
continue
fi
ver=$(echo "$ver" | grep -E -x "$RE_VER")
success=true
allow_prerel=false
if $FORCE_ALLOW_PREREL; then
allow_prerel=true
fi
while read -r rule; do
comparator="${rule%% *}"
operand="${rule#* }"
if [ -n "$(get_prerelease "$operand")" ] && semver_eq "$(get_number "$operand")" "$(get_number "$ver")" || [ "$rule" = "all" ]; then
allow_prerel=true
fi
"rule_$comparator" "$operand" "$ver"
if [ $? -eq 1 ]; then
success=false
break
fi
done <<< "$rules"
if $success; then
if [ -z "$(get_prerelease "$ver")" ] || $allow_prerel; then
echo "$ver"
break;
fi
fi
done
group=$(( group + 1 ))
done
}
FORCE_ALLOW_PREREL=false
USAGE="Usage: $0 [-r <rule>] [<version>... ]
Omitting <version>s reads them from STDIN.
Omitting -r <rule> simply sorts the versions according to semver ordering."
while getopts ar:h o; do
case "$o" in
a) FORCE_ALLOW_PREREL=true ;;
r) RULES_STRING="$OPTARG||";;
h) echo "$USAGE" && exit ;;
?) echo "$USAGE" && exit 1;;
esac
done
shift $(( OPTIND-1 ))
VERSIONS=( ${@:-$(cat -)} )
# Sort versions
VERSIONS=( $(semver_sort "${VERSIONS[@]}") )
if [ -z "$RULES_STRING" ]; then
printf '%s\n' "${VERSIONS[@]}"
else
apply_rules "$RULES_STRING" "${VERSIONS[@]}"
fi

2
scripts/umbrel-os/services/umbrel-external-storage.service

@ -8,6 +8,8 @@ Description=External Storage Mounter
Type=oneshot
Restart=no
ExecStart=/home/umbrel/umbrel/scripts/umbrel-os/external-storage/mount
ExecStartPost=/home/umbrel/umbrel/scripts/umbrel-os/external-storage/update-from-sdcard
TimeoutStartSec=45min
User=root
Group=root
StandardOutput=syslog

2
scripts/update/00-run.sh

@ -6,7 +6,7 @@ UMBREL_ROOT=$2
echo
echo "======================================="
echo "============= OTA UPDATE =============="
echo "=============== UPDATE ================"
echo "======================================="
echo "========= Stage: Pre-update ==========="
echo "======================================="

2
scripts/update/01-run.sh

@ -9,7 +9,7 @@ SD_CARD_UMBREL_ROOT="/sd-root${UMBREL_ROOT}"
echo
echo "======================================="
echo "============= OTA UPDATE =============="
echo "=============== UPDATE ================"
echo "======================================="
echo "=========== Stage: Install ============"
echo "======================================="

2
scripts/update/02-run.sh

@ -6,7 +6,7 @@ UMBREL_ROOT=$2
echo
echo "======================================="
echo "============= OTA UPDATE =============="
echo "=============== UPDATE ================"
echo "======================================="
echo "========= Stage: Post-update =========="
echo "======================================="

2
scripts/update/03-run.sh

@ -6,7 +6,7 @@ UMBREL_ROOT=$2
echo
echo "======================================="
echo "============= OTA UPDATE =============="
echo "=============== UPDATE ================"
echo "======================================="
echo "=========== Stage: Success ============"
echo "======================================="

109
scripts/update/update

@ -0,0 +1,109 @@
#!/usr/bin/env bash
if [[ $UID != 0 ]]; then
echo "Update script must be run as root"
exit 1
fi
check_dependencies () {
for cmd in "$@"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "This script requires \"${cmd}\" to be installed"
exit 1
fi
done
}
check_dependencies jq wget git rsync
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
update_type=""
if [[ "${1}" == "--ota" ]]; then
update_type="ota"
elif [[ "${1}" == "--path" ]]; then
update_type="path"
update_path="${2}"
else
echo "Update requires \"--ota\" or \"--path <path>\" option."
exit 1
fi
if [[ "${update_type}" == "path" ]] && [[ ! -f "${update_path}/.umbrel" ]]; then
echo "Update path doesn't seem to be an Umbrel install."
exit 1
fi
if [[ "${update_type}" == "ota" ]]; then
RELEASE=$(cat "$UMBREL_ROOT"/statuses/update-status.json | jq .updateTo -r)
elif [[ "${update_type}" == "path" ]]; then
RELEASE=$(cat "$update_path"/info.json | jq .version -r)
fi
echo
echo "======================================="
echo "=============== UPDATE ================"
echo "======================================="
echo "========== Stage: Download ============"
echo "======================================="
echo
# Make sure an update is not in progres
if [[ -f "$UMBREL_ROOT/statuses/update-in-progress" ]]; then
echo "An update is already in progress. Exiting now."
exit 2
fi
echo "Creating lock"
touch "$UMBREL_ROOT"/statuses/update-in-progress
# Cleanup just in case there's temp stuff lying around from previous update
echo "Cleaning up any previous mess"
[[ -d "$UMBREL_ROOT"/.umbrel-"$RELEASE" ]] && rm -rf "$UMBREL_ROOT"/.umbrel-"$RELEASE"
# Update status file
cat <<EOF > "$UMBREL_ROOT"/statuses/update-status.json
{"state": "installing", "progress": 10, "description": "Downloading Umbrel $RELEASE", "updateTo": "$RELEASE"}
EOF
# Download new release
if [[ "${update_type}" == "ota" ]]; then
echo "Downloading Umbrel ${RELEASE}"
mkdir -p "${UMBREL_ROOT}/.umbrel-${RELEASE}"
cd "${UMBREL_ROOT}/.umbrel-${RELEASE}"
curl -L "https://github.com/getumbrel/umbrel/archive/$RELEASE.tar.gz" | tar -xz --strip-components=1
fi
# Copy over new release from path
if [[ "${update_type}" == "path" ]]; then
echo "Copying Umbrel ${RELEASE} from ${update_path}"
mkdir -p "${UMBREL_ROOT}/.umbrel-${RELEASE}"
cp --recursive \
--archive \
--no-target-directory \
"${update_path}" "${UMBREL_ROOT}/.umbrel-${RELEASE}"
cd "${UMBREL_ROOT}/.umbrel-${RELEASE}"
fi
# Run update scripts
echo "Running update install scripts of the new release"
cd scripts/update
UPDATE_INSTALL_SCRIPTS=$(ls *-run.sh)
for script in $UPDATE_INSTALL_SCRIPTS; do
if [[ -x $script ]]; then
echo
echo "== Begin Update Script $script =="
./"$script" "$RELEASE" "$UMBREL_ROOT"
echo "== End Update Script $script =="
echo
fi
done
# Delete cloned repo
echo "Deleting cloned repository"
[[ -d "$UMBREL_ROOT"/.umbrel-"$RELEASE" ]] && rm -rf "$UMBREL_ROOT"/.umbrel-"$RELEASE"
echo "Removing lock"
rm -f "$UMBREL_ROOT"/statuses/update-in-progress
exit 0
Loading…
Cancel
Save