Created
January 15, 2016 09:07
-
-
Save haraldh/f48df9dbd661496caac1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# Author: Harald Hoyer <harald@redhat.com> | |
# Copyright 2015 Red Hat, Inc. All rights reserved. | |
# | |
# This program is free software; you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation; either version 2 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |
# | |
set -e | |
set -o pipefail | |
unset KEEP | |
usage() | |
{ | |
{ | |
echo "Usage: ${0##*/} [options] [<path to usr>]" | |
echo | |
echo "-h, --help print a help message and exit." | |
echo "--keep keep the working subvolume." | |
echo "--add-cmdline <options> add <options> to the kernel cmdline." | |
echo "--cmdline <options> set <options> as the kernel cmdline." | |
echo "--cleanup cleanup old subvolumes." | |
echo "--shell get a chroot shell." | |
echo "--update update the system." | |
echo | |
} >&2 | |
} | |
TEMP=$(unset POSIXLY_CORRECT; \ | |
getopt \ | |
--options "h" \ | |
--longoptions help,shell,keep,cleanup,update,cmdline:,add-cmdline: \ | |
-- "$@") | |
if (( $? != 0 )); then | |
usage | |
exit 1 | |
fi | |
eval set -- "$TEMP" | |
while (($# > 0)); do | |
case $1 in | |
--cleanup) CLEANUP=1;; | |
--update) CMD_UPDATE=1;; | |
--keep) KEEP=1;; | |
--shell) CHROOT_SHELL=1;; | |
--add-cmdline) CMDLINE_ADD="$2"; shift;; | |
--cmdline) CMDLINE="$2"; shift;; | |
--) shift; break;; | |
-h|--help) usage; exit 0;; | |
*) usage; exit 1;; | |
esac | |
shift | |
done | |
readonly TMPDIR="$(mktemp --tmpdir="/var/tmp/" -d -t updateusr.XXXXXX)" | |
mkdir -p "$TMPDIR/root" | |
readonly BTRFSROOT="$TMPDIR/btrfsroot" | |
mkdir -p "$TMPDIR/sysroot" | |
readonly SYSROOT="$TMPDIR/sysroot" | |
readonly USRDIR=$(readlink -f ${1:-/usr}) | |
trap ' | |
ret=$?; | |
set +e | |
cd / | |
if [[ -d "$SYSROOT" ]]; then | |
if [[ -d "$SYSROOT/srv" ]]; then | |
rm -fr "$SYSROOT/srv" || btrfs subvolume delete -c "$SYSROOT/srv" ||: | |
fi | |
if [[ -d "$SYSROOT/var/lib/machines" ]]; then | |
rm -fr "$SYSROOT/var/lib/machines" || btrfs subvolume delete -c "$SYSROOT/var/lib/machines" ||: | |
fi | |
for d in etc/resolv.conf usr proc dev sys run tmp; do | |
umount "$SYSROOT/$d" | |
done | |
rm -fr "$SYSROOT" | |
fi | |
if [[ $SNAPSHOT_WORK_NAME ]] && [[ $KEEP != 1 ]]; then | |
btrfs subvolume delete -c "$BTRFSROOT/$SNAPSHOT_WORK_NAME" | |
fi | |
mountpoint -q "$BTRFSROOT" && umount "$BTRFSROOT" | |
rmdir "$BTRFSROOT" | |
rm -fr "$TMPDIR" | |
exit $ret; | |
' EXIT | |
# clean up after ourselves no matter how we die. | |
trap 'exit 1;' SIGINT | |
mntp="$USRDIR" | |
while ! DEV=$(findmnt -e -v -n -o 'SOURCE' --target "$mntp"); do | |
mntp=${mntp%/*} | |
done | |
mkdir -p "$BTRFSROOT" | |
mount "$DEV" -o subvol=/ "$BTRFSROOT" | |
vercmp() { | |
local _n1=(${1//./ }) _op=$2 _n2=(${3//./ }) _i _res | |
for ((_i=0; ; _i++)); do | |
if [[ ! ${_n1[_i]}${_n2[_i]} ]]; then _res=0 | |
elif ((${_n1[_i]:-0} > ${_n2[_i]:-0})); then _res=1 | |
elif ((${_n1[_i]:-0} < ${_n2[_i]:-0})); then _res=2 | |
else continue | |
fi | |
break | |
done | |
case $_op in | |
gt) ((_res == 1));; | |
ge) ((_res != 2));; | |
eq) ((_res == 0));; | |
le) ((_res != 1));; | |
lt) ((_res == 2));; | |
ne) ((_res != 0));; | |
esac | |
} | |
btrfs_subvolume_name() { | |
local key value where="$1" | |
while read key value; do | |
[[ "$key" != "Name:" ]] && continue | |
printf -- "%s" "$value" | |
return 0 | |
done < <(btrfs subvolume show "$where") | |
return 1 | |
} | |
btrfs_find_usr_os() { | |
local id gen level path where="$1" what="$2" | |
while read id gen level path; do | |
[[ "$level" != 5 ]] && continue | |
[[ "$path" != usr:$what:* ]] && continue | |
printf -- "%s\n" "${path##\<FS_TREE\>/}" | |
done < <(btrfs subvolume list -at "$where") | |
return 0 | |
} | |
btrfs_find_newest() { | |
declare -a vols | |
readarray -t vols < <(btrfs_find_usr_os "$1" "$2") | |
maxversion=0 | |
maxindex=-1 | |
for (( i=0; i < ${#vols[@]}; i++)); do | |
IFS=: read -r name os arch version <<<"${vols[$i]}" | |
if vercmp $version gt $maxversion; then | |
maxindex="$i" | |
maxversion="$version" | |
fi | |
done | |
if (( $maxindex == -1 )); then | |
printf -- "btrfs subvolume for $1 not found\n" >&2 | |
exit 1 | |
fi | |
printf -- "${vols[$maxindex]}\n" | |
} | |
currentsubvol=$(btrfs_subvolume_name "$USRDIR") | |
USRVOL=${currentsubvol##usr:} | |
USRVOL=${USRVOL%:*} | |
[[ -d "$BTRFSROOT/$currentsubvol" ]] || exit 0 | |
readonly SNAPSHOT_VERSION="$(date -u +'%Y%m%d%H%M%S')" | |
readonly SNAPSHOT_NAME="usr:$USRVOL:$SNAPSHOT_VERSION" | |
readonly SNAPSHOT_WORK_NAME="usr-work:$USRVOL" | |
usrsubvol="$SNAPSHOT_NAME" | |
clean_vols() | |
{ | |
declare -a vols | |
readarray -t vols < <(btrfs_find_usr_os "$BTRFSROOT" "$USRVOL") | |
for (( i=0; i < ${#vols[@]}; i++)); do | |
[[ "${vols[$i]}" == "$usrsubvol" ]] && continue | |
[[ "${vols[$i]}" == "$currentsubvol" ]] && continue | |
printf -- "Removing old volume: %s\n" "${vols[$i]}" | |
for kdir in "$BTRFSROOT"/"${vols[$i]}"/lib/modules/*; do | |
[[ -d $kdir ]] || continue | |
( | |
cd "$kdir" | |
if [[ -f linux.efi ]]; then | |
EFI_NAME="${vols[$i]}-${kdir##*/}" | |
EFI_NAME="${EFI_NAME#usr:}" | |
EFI_NAME="${EFI_NAME//:/_}" | |
EFI_NAME="${EFI_NAME//./_}" | |
rm -f "/boot/EFI/Linux/$EFI_NAME.efi" | |
fi | |
for b in bootloader*.conf; do | |
[[ -f "$b" ]] || continue | |
# copy over the kernel and initrds | |
while read key val; do | |
case "$key" in | |
linux|initrd) | |
# replace \ with / | |
p=${val//\\//} | |
# create the base directory | |
rm -f "/boot/$p" | |
rmdir "/boot/${p%/*}" &>/dev/null || : | |
;; | |
esac | |
done < "$b" | |
rm -f /boot/loader/entries/"$b" | |
done | |
) | |
done | |
btrfs subvolume delete "$BTRFSROOT"/"${vols[$i]}" | |
done | |
} | |
setup_bootoptions() | |
{ | |
local _SNAME="$1" | |
local -a BOOT_OPTIONS | |
local HASUSRFSFLAGS | |
local -a USRFSOPTS | |
local -a line | |
if ! [[ "${BOOT_OPTIONS[@]}" ]]; then | |
if [[ "$CMDLINE" ]]; then | |
line=( $CMDLINE ) | |
else | |
read -r -d '' -a line < /proc/cmdline || : | |
fi | |
for i in "${line[@]}"; do | |
[[ "${i#initrd=*}" != "$i" ]] && continue | |
[[ "${i#rd.break*}" != "$i" ]] && continue | |
BOOT_OPTIONS+=("$i") | |
done | |
fi | |
[[ "$CMDLINE_ADD" ]] && BOOT_OPTIONS+=( $CMDLINE_ADD ) | |
for ((i=0; i < ${#BOOT_OPTIONS[@]}; i++)); do | |
case "${BOOT_OPTIONS[$i]}" in | |
mount.usrflags=*) | |
_opts="${BOOT_OPTIONS[$i]#mount.usrflags=}" | |
IFS=, read -r -a USRFSOPTS <<<"$_opts" || : | |
for ((j=0; j < ${#USRFSOPTS[@]}; j++)); do | |
case ${USRFSOPTS[$j]} in | |
subvol=*) | |
USRFSOPTS[$j]="subvol=$_SNAME" | |
;; | |
esac | |
done | |
BOOT_OPTIONS[$i]="mount.usrflags=$(IFS=,; echo "${USRFSOPTS[*]}")" | |
HASUSRFSFLAGS=1 | |
;; | |
esac | |
done | |
if ! [[ $HASUSRFSFLAGS = 1 ]]; then | |
BOOT_OPTIONS+=( "mount.usrflags=ro,subvol=$_SNAME" ) | |
fi | |
printf -- "%s " "${BOOT_OPTIONS[@]}" | |
} | |
cd "$BTRFSROOT" | |
if ! [[ -d $SNAPSHOT_WORK_NAME ]]; then | |
btrfs subvolume snapshot "$currentsubvol" "$SNAPSHOT_WORK_NAME" | |
fi | |
cd "$SYSROOT" | |
mkdir -p "$SYSROOT"/{proc,dev,sys,run,usr,home,tmp,boot,home} | |
mount --bind "$BTRFSROOT/$SNAPSHOT_WORK_NAME" usr | |
for d in proc dev sys run tmp; do | |
mount --bind "/$d" "$SYSROOT/$d" | |
done | |
for d in var etc; do | |
mkdir -p "$SYSROOT/usr/share/factory/$d" | |
# FIXME | |
# better mount bind, but rpm file conflicts currently | |
cp -a --reflink=auto usr/share/factory/etc "$SYSROOT/" | |
cp -a --reflink=auto usr/share/factory/var "$SYSROOT/" | |
done | |
if [[ -e /etc/resolv.conf ]]; then | |
[[ -e etc/resolv.conf ]] || > "$SYSROOT/etc/resolv.conf" | |
mount --bind /etc/resolv.conf "$SYSROOT/etc/resolv.conf" | |
fi | |
for d in bin sbin lib lib64; do | |
ln -s "usr/$d" "$SYSROOT/$d" | |
done | |
[[ -e var/run ]] || ln -s ../run "$SYSROOT/var/run" | |
[[ -e var/lock ]] || ln -s ../run/lock "$SYSROOT/var/lock" | |
if [[ "$CMD_UPDATE" ]]; then | |
chroot . dnf update -y | |
fi | |
if [[ $CHROOT_SHELL == "1" ]]; then | |
chroot . | |
fi | |
[[ $KEEP == "1" ]] && exit 0 | |
( | |
while read -r line || [[ $line ]]; do | |
case "$line" in | |
VERSION=*) | |
eval "$line" | |
line="VERSION=\"${VERSION%:*}:${SNAPSHOT_VERSION}\"" | |
;; | |
VERSION_ID=*) | |
eval "$line" | |
line="VERSION_ID=\"${VERSION_ID%:*}:${SNAPSHOT_VERSION}\"" | |
;; | |
PRETTY_NAME=*) | |
eval "$line" | |
line="PRETTY_NAME=\"${PRETTY_NAME%:*}:${SNAPSHOT_VERSION}\"" | |
;; | |
esac | |
echo "$line" | |
done < "$SYSROOT/usr/lib/os-release" | |
) > "$SYSROOT/os-release" && { | |
rm -f "$SYSROOT/usr/lib/os-release" | |
mv "$SYSROOT/os-release" "$SYSROOT/usr/lib/os-release" | |
} | |
BOOT_OPTIONS="$(setup_bootoptions "$SNAPSHOT_NAME")" | |
# cleanup /var | |
rm -fr "$SYSROOT/var/cache" | |
rm -fr "$SYSROOT/var/log" | |
# FIXME | |
cp -a --reflink=auto etc "$SYSROOT/usr/share/factory/" | |
cp -a --reflink=auto var "$SYSROOT/usr/share/factory/" | |
mkdir -p "$SYSROOT/usr/etc" | |
if [ -d "$SYSROOT/usr/share/factory/etc/ld.so.conf.d" ]; then | |
rm -fr "$SYSROOT/usr/etc/ld.so.conf.d" | |
mv "$SYSROOT/usr/share/factory/etc/ld.so.conf.d" "$SYSROOT/usr/etc/ld.so.conf.d" | |
fi | |
cd "$SYSROOT/lib/modules" | |
declare -a kvers | |
kvers=(*/vmlinuz) | |
numkernels=$((-${#kvers[@]})) | |
numkernels=-1 | |
for (( i=-1; i >= $numkernels; i-- )); do | |
v=${kvers[$i]]%/vmlinuz} | |
if [[ -d $v ]]; then | |
rm -f "$SYSROOT/etc/os-release" | |
( | |
while read -d '' -r line || [[ $line ]]; do | |
case "$line" in | |
VERSION=*) | |
eval "$line" | |
line="VERSION=\"${VERSION%:*}:${SNAPSHOT_VERSION}_${v//:/_}\"" | |
;; | |
VERSION_ID=*) | |
eval "$line" | |
line="VERSION_ID=\"${VERSION_ID%:*}:${SNAPSHOT_VERSION}_${v//:/_}\"" | |
;; | |
PRETTY_NAME=*) | |
eval "$line" | |
line="PRETTY_NAME=\"${PRETTY_NAME%:*}:${SNAPSHOT_VERSION}_${v//:/_}\"" | |
;; | |
esac | |
echo "$line" | |
done < "$SYSROOT/usr/lib/os-release" | |
) > "$SYSROOT/etc/os-release" | |
chroot "$SYSROOT" \ | |
dracut --force --uefi \ | |
--kernel-image /lib/modules/$v/vmlinuz \ | |
--kernel-cmdline "$BOOT_OPTIONS" \ | |
-N --no-hostonly-cmdline --reproducible \ | |
/lib/modules/$v/linux.efi "$v" | |
[[ -f $v/linux.efi ]] | |
mkdir -p /boot/EFI/Linux | |
EFI_NAME="${SNAPSHOT_NAME}-${v}" | |
EFI_NAME="${EFI_NAME#usr:}" | |
EFI_NAME="${EFI_NAME//:/_}" | |
EFI_NAME="${EFI_NAME//./_}" | |
cp $v/linux.efi "/boot/EFI/Linux/$EFI_NAME.efi" | |
fi | |
done | |
cd "$BTRFSROOT" | |
btrfs subvolume snapshot -r "$SNAPSHOT_WORK_NAME" "$SNAPSHOT_NAME" | |
[[ $CLEANUP == "1" ]] && clean_vols | |
exit 0 | |
sync |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment