2016-04-12 05:05:00 +02:00
#!/bin/bash
2016-07-10 05:45:54 +02:00
2019-08-27 07:13:11 +02:00
version = "v0.1.2"
2018-08-17 21:48:21 +02:00
2020-02-07 16:49:22 +01:00
CURRENT_DIR = " $( pwd ) "
2019-07-30 15:33:33 +02:00
SCRIPTNAME = " ${ 0 ##*/ } "
2020-02-07 16:49:22 +01:00
MYNAME = " ${ SCRIPTNAME %.* } "
LOGFILE = " ${ CURRENT_DIR } / ${ SCRIPTNAME %.* } .log "
2020-01-11 15:21:54 +01:00
REQUIRED_TOOLS = "parted losetup tune2fs md5sum e2fsck resize2fs"
2020-02-07 16:49:22 +01:00
ZIPTOOLS = ( "gzip xz" )
2020-02-07 17:30:38 +01:00
declare -A ZIP_PARALLEL_TOOL = ( [ gzip] = "pigz" [ xz] = "xz" ) # parallel zip tool to use in parallel mode
2020-02-07 16:49:22 +01:00
declare -A ZIP_PARALLEL_OPTIONS = ( [ gzip] = "-f9" [ xz] = "-T0" ) # options for zip tools in parallel mode
declare -A ZIPEXTENSIONS = ( [ gzip] = "gz" [ xz] = "xz" ) # extensions of zipped files
2018-08-17 21:48:21 +02:00
function info( ) {
2020-02-07 16:49:22 +01:00
echo " $SCRIPTNAME : $1 ... "
2019-07-30 15:33:33 +02:00
}
2018-08-17 21:48:21 +02:00
function error( ) {
2019-07-30 15:33:33 +02:00
echo -n " $SCRIPTNAME : ERROR occured in line $1 : "
2018-08-17 21:48:21 +02:00
shift
echo " $@ "
}
2018-02-14 20:05:03 +01:00
function cleanup( ) {
2019-08-27 07:11:27 +02:00
if losetup " $loopback " & >/dev/null; then
2018-08-17 21:48:21 +02:00
losetup -d " $loopback "
fi
if [ " $debug " = true ] ; then
local old_owner = $( stat -c %u:%g " $src " )
2019-08-27 07:11:27 +02:00
chown " $old_owner " " $LOGFILE "
2018-08-17 21:48:21 +02:00
fi
2018-02-14 20:05:03 +01:00
}
2018-08-17 21:48:21 +02:00
function logVariables( ) {
if [ " $debug " = true ] ; then
echo " Line $1 " >> " $LOGFILE "
shift
local v var
for var in " $@ " ; do
eval " v=\$ $var "
2019-08-27 07:13:11 +02:00
echo " $var : $v " >> " $LOGFILE "
2018-08-17 21:48:21 +02:00
done
fi
}
2019-07-30 15:33:33 +02:00
function checkFilesystem( ) {
info "Checking filesystem"
2019-08-27 07:13:11 +02:00
e2fsck -pf " $loopback "
( ( $? < 4 ) ) && return
2019-07-30 15:33:33 +02:00
2019-08-27 07:13:11 +02:00
info "Filesystem error detected!"
2019-07-30 15:33:33 +02:00
2019-08-27 07:13:11 +02:00
info "Trying to recover corrupted filesystem"
e2fsck -y " $loopback "
( ( $? < 4 ) ) && return
2019-07-30 15:33:33 +02:00
2019-08-27 07:13:11 +02:00
if [ [ $repair = = true ] ] ; then
info "Trying to recover corrupted filesystem - Phase 2"
e2fsck -fy -b 32768 " $loopback "
( ( $? < 4 ) ) && return
fi
error $LINENO "Filesystem recoveries failed. Giving up..."
2020-06-15 00:39:41 +02:00
exit 9
2019-07-30 15:33:33 +02:00
}
2020-04-27 12:17:07 +02:00
function set_autoexpand( ) {
#Make pi expand rootfs on next boot
mountdir = $( mktemp -d)
2020-06-13 22:19:04 +02:00
partprobe " $loopback "
2020-04-27 12:17:07 +02:00
mount " $loopback " " $mountdir "
if [ ! -d " $mountdir /etc " ] ; then
info "/etc not found, autoexpand will not be enabled"
umount " $mountdir "
return
fi
2020-06-13 21:08:43 +02:00
if [ [ -f " $mountdir /etc/rc.local " ] ] && [ [ " $( md5sum " $mountdir /etc/rc.local " | cut -d ' ' -f 1) " != "1c579c7d5b4292fd948399b6ece39009" ] ] ; then
echo "Creating new /etc/rc.local"
2020-04-27 12:17:07 +02:00
if [ -f " $mountdir /etc/rc.local " ] ; then
mv " $mountdir /etc/rc.local " " $mountdir /etc/rc.local.bak "
fi
#####Do not touch the following lines#####
cat <<\E OF1 > " $mountdir /etc/rc.local "
#!/bin/bash
do_expand_rootfs( ) {
ROOT_PART = $( mount | sed -n 's|^/dev/\(.*\) on / .*|\1|p' )
PART_NUM = ${ ROOT_PART #mmcblk0p }
if [ " $PART_NUM " = " $ROOT_PART " ] ; then
echo " $ROOT_PART is not an SD card. Don't know how to expand "
return 0
fi
# Get the starting offset of the root partition
PART_START = $( parted /dev/mmcblk0 -ms unit s p | grep " ^ ${ PART_NUM } " | cut -f 2 -d: | sed 's/[^0-9]//g' )
[ " $PART_START " ] || return 1
# Return value will likely be error for fdisk as it fails to reload the
# partition table because the root fs is mounted
fdisk /dev/mmcblk0 <<EOF
p
d
$PART_NUM
n
p
$PART_NUM
$PART_START
p
w
EOF
cat <<EOF > /etc/rc.local &&
#!/bin/sh
echo " Expanding /dev/ $ROOT_PART "
resize2fs /dev/$ROOT_PART
rm -f /etc/rc.local; cp -f /etc/rc.local.bak /etc/rc.local; /etc/rc.local
EOF
reboot
exit
}
raspi_config_expand( ) {
/usr/bin/env raspi-config --expand-rootfs
if [ [ $? != 0 ] ] ; then
return -1
else
rm -f /etc/rc.local; cp -f /etc/rc.local.bak /etc/rc.local; /etc/rc.local
reboot
exit
fi
}
raspi_config_expand
echo "WARNING: Using backup expand..."
sleep 5
do_expand_rootfs
echo "ERROR: Expanding failed..."
sleep 5
2020-06-13 21:08:43 +02:00
if [ [ -f /etc/rc.local.bak ] ] ; then
cp -f /etc/rc.local.bak /etc/rc.local
/etc/rc.local
fi
2020-04-27 12:17:07 +02:00
exit 0
EOF1
#####End no touch zone#####
chmod +x " $mountdir /etc/rc.local "
fi
umount " $mountdir "
}
2019-07-30 15:33:33 +02:00
help( ) {
local help
read -r -d '' help << EOM
2020-02-29 14:52:35 +01:00
Usage: $0 [ -adhrspvzZ] imagefile.img [ newimagefile.img]
2020-01-11 15:21:54 +01:00
-s Don' t expand filesystem when image is booted the first time
2020-06-16 01:00:26 +02:00
-v Be verbose
2020-02-29 14:52:35 +01:00
-r Use advanced filesystem repair option if the normal one fails
2020-02-07 17:30:38 +01:00
-z Compress image after shrinking with gzip
-Z Compress image after shrinking with xz
-a Compress image in parallel using multiple cores
2020-02-29 14:52:35 +01:00
-p Remove logs, apt archives, dhcp leases and ssh hostkeys
-d Write debug messages in a debug log file
2019-07-30 15:33:33 +02:00
EOM
2019-08-27 07:11:27 +02:00
echo " $help "
2020-06-15 00:39:41 +02:00
exit 1
2019-07-30 15:33:33 +02:00
}
2016-07-10 05:45:54 +02:00
should_skip_autoexpand = false
2018-08-17 21:48:21 +02:00
debug = false
2018-08-19 17:46:55 +02:00
repair = false
2020-02-07 16:49:22 +01:00
parallel = false
verbose = false
2020-01-03 18:49:25 +01:00
prep = false
2020-02-07 17:30:38 +01:00
ziptool = ""
2016-07-10 05:45:54 +02:00
2020-02-29 14:52:35 +01:00
while getopts ":adhprsvzZ" opt; do
2016-08-22 10:11:37 +02:00
case " ${ opt } " in
2020-02-07 16:49:22 +01:00
a) parallel = true; ;
2018-08-17 21:48:21 +02:00
d) debug = true; ;
2019-07-30 15:33:33 +02:00
h) help; ;
2020-01-03 18:49:25 +01:00
p) prep = true; ;
2020-02-07 16:49:22 +01:00
r) repair = true; ;
s) should_skip_autoexpand = true ; ;
2020-02-29 14:52:35 +01:00
v) verbose = true; ;
2020-02-07 17:30:38 +01:00
z) ziptool = "gzip" ; ;
Z) ziptool = "xz" ; ;
2020-02-07 16:49:22 +01:00
*) help; ;
2016-08-22 10:11:37 +02:00
esac
2016-07-10 05:45:54 +02:00
done
shift $(( OPTIND-1))
2018-08-17 21:48:21 +02:00
if [ " $debug " = true ] ; then
info " Creating log file $LOGFILE "
2019-08-27 07:11:27 +02:00
rm " $LOGFILE " & >/dev/null
2018-08-17 21:48:21 +02:00
exec 1> >( stdbuf -i0 -o0 -e0 tee -a " $LOGFILE " >& 1)
exec 2> >( stdbuf -i0 -o0 -e0 tee -a " $LOGFILE " >& 2)
fi
echo " ${ 0 ##*/ } $version "
2016-04-12 05:05:00 +02:00
#Args
2018-08-17 21:48:21 +02:00
src = " $1 "
2017-12-02 23:18:54 +01:00
img = " $1 "
2016-04-12 05:05:00 +02:00
#Usage checks
2017-12-02 23:18:54 +01:00
if [ [ -z " $img " ] ] ; then
2020-01-11 15:21:54 +01:00
help
2016-04-12 05:05:00 +02:00
fi
2020-01-11 15:21:54 +01:00
2018-01-21 23:08:43 +01:00
if [ [ ! -f " $img " ] ] ; then
2018-08-17 21:48:21 +02:00
error $LINENO " $img is not a file... "
2020-06-15 00:39:41 +02:00
exit 2
2016-04-12 05:05:00 +02:00
fi
if ( ( EUID != 0 ) ) ; then
2018-08-17 21:48:21 +02:00
error $LINENO "You need to be running as root."
2020-06-15 00:39:41 +02:00
exit 3
2016-04-12 05:05:00 +02:00
fi
2020-01-11 15:21:54 +01:00
# check selected compression tool is supported and installed
2020-02-07 17:30:38 +01:00
if [ [ -n $ziptool ] ] ; then
2020-06-13 22:12:39 +02:00
if [ [ ! " ${ ZIPTOOLS [@] } " = ~ $ziptool ] ] ; then
2020-01-11 15:21:54 +01:00
error $LINENO " $ziptool is an unsupported ziptool. "
2020-06-15 00:39:41 +02:00
exit 17
2020-01-11 15:21:54 +01:00
else
2020-05-23 11:11:49 +02:00
if [ [ $parallel = = true && $ziptool = = "gzip" ] ] ; then
2020-02-07 17:30:38 +01:00
REQUIRED_TOOLS = " $REQUIRED_TOOLS pigz "
else
REQUIRED_TOOLS = " $REQUIRED_TOOLS $ziptool "
fi
2020-01-11 15:21:54 +01:00
fi
fi
2016-04-26 18:03:43 +02:00
#Check that what we need is installed
2020-01-11 15:21:54 +01:00
for command in $REQUIRED_TOOLS ; do
2019-08-27 07:11:27 +02:00
command -v $command >/dev/null 2>& 1
2018-01-22 04:05:31 +01:00
if ( ( $? != 0 ) ) ; then
2018-08-17 21:48:21 +02:00
error $LINENO " $command is not installed. "
2020-06-15 00:39:41 +02:00
exit 4
2018-01-22 04:05:31 +01:00
fi
done
2016-04-26 18:03:43 +02:00
2016-04-28 07:35:12 +02:00
#Copy to new file if requested
if [ -n " $2 " ] ; then
2020-05-23 11:40:53 +02:00
f = " $2 "
2020-06-13 22:12:39 +02:00
if [ [ -n $ziptool && " ${ f ##*. } " = = " ${ ZIPEXTENSIONS [ $ziptool ] } " ] ] ; then # remove zip extension if zip requested because zip tool will complain about extension
2020-05-23 11:40:53 +02:00
f = " ${ f %.* } "
fi
info " Copying $1 to $f ... "
cp --reflink= auto --sparse= always " $1 " " $f "
2016-04-28 07:35:12 +02:00
if ( ( $? != 0 ) ) ; then
2018-08-17 21:48:21 +02:00
error $LINENO "Could not copy file..."
2020-06-15 00:39:41 +02:00
exit 5
2016-04-28 07:35:12 +02:00
fi
2018-01-22 04:41:45 +01:00
old_owner = $( stat -c %u:%g " $1 " )
2020-05-23 11:40:53 +02:00
chown " $old_owner " " $f "
img = " $f "
2016-04-28 07:35:12 +02:00
fi
2018-02-14 20:05:03 +01:00
# cleanup at script exit
2020-05-31 21:09:51 +02:00
trap cleanup EXIT
2018-02-14 20:05:03 +01:00
2016-04-12 05:05:00 +02:00
#Gather info
2020-01-03 18:49:25 +01:00
info "Gathering data"
2020-01-09 13:21:55 +01:00
beforesize = " $( ls -lh " $img " | cut -d ' ' -f 5) "
2020-04-20 14:58:25 +02:00
parted_output = " $( parted -ms " $img " unit B print) "
rc = $?
if ( ( $rc ) ) ; then
2020-04-09 18:17:47 +02:00
error $LINENO " parted failed with rc $rc "
2020-04-20 13:24:55 +02:00
info " Possibly invalid image. Run 'parted $img unit B print' manually to investigate "
2020-06-15 00:39:41 +02:00
exit 6
2020-04-09 18:17:47 +02:00
fi
2020-04-20 14:58:25 +02:00
partnum = " $( echo " $parted_output " | tail -n 1 | cut -d ':' -f 1) "
2020-04-20 13:24:55 +02:00
partstart = " $( echo " $parted_output " | tail -n 1 | cut -d ':' -f 2 | tr -d 'B' ) "
2020-06-13 23:10:19 +02:00
if [ -z " $( parted -s " $img " unit B print | grep " $partstart " | grep logical) " ] ; then
parttype = "primary"
else
parttype = "logical"
fi
2020-01-09 13:21:55 +01:00
loopback = " $( losetup -f --show -o " $partstart " " $img " ) "
tune2fs_output = " $( tune2fs -l " $loopback " ) "
2020-04-27 12:17:07 +02:00
rc = $?
if ( ( $rc ) ) ; then
echo " $tune2fs_output "
error $LINENO "tune2fs failed. Unable to shrink this type of image"
exit 7
fi
2020-01-09 13:21:55 +01:00
currentsize = " $( echo " $tune2fs_output " | grep '^Block count:' | tr -d ' ' | cut -d ':' -f 2) "
blocksize = " $( echo " $tune2fs_output " | grep '^Block size:' | tr -d ' ' | cut -d ':' -f 2) "
2016-04-12 05:05:00 +02:00
2020-06-13 23:10:19 +02:00
logVariables $LINENO beforesize parted_output partnum partstart parttype tune2fs_output currentsize blocksize
2018-08-17 21:48:21 +02:00
2016-07-10 05:45:54 +02:00
#Check if we should make pi expand rootfs on next boot
2020-06-13 23:10:19 +02:00
if [ " $parttype " = = "logical" ] ; then
echo "WARNING: PiShrink does not yet support autoexpanding of this type of image"
elif [ " $should_skip_autoexpand " = false ] ; then
set_autoexpand
2016-07-10 05:45:54 +02:00
else
2018-01-22 02:37:05 +01:00
echo "Skipping autoexpanding process..."
2016-04-19 10:21:22 +02:00
fi
2016-04-12 05:05:00 +02:00
2020-01-03 18:49:25 +01:00
if [ [ $prep = = true ] ] ; then
info "Syspreping: Removing logs, apt archives, dhcp leases and ssh hostkeys"
mountdir = $( mktemp -d)
mount " $loopback " " $mountdir "
2020-01-09 13:21:55 +01:00
rm -rf " $mountdir /var/cache/apt/archives/* " " $mountdir /var/lib/dhcpcd5/* " " $mountdir /var/log/* " " $mountdir /var/tmp/* " " $mountdir /tmp/* " " $mountdir /etc/ssh/*_host_* "
2020-01-03 18:49:25 +01:00
umount " $mountdir "
fi
2016-07-07 07:28:32 +02:00
#Make sure filesystem is ok
2019-08-27 07:13:11 +02:00
checkFilesystem
2018-08-17 21:48:21 +02:00
if ! minsize = $( resize2fs -P " $loopback " ) ; then
rc = $?
error $LINENO " resize2fs failed with rc $rc "
2020-06-15 00:39:41 +02:00
exit 10
2018-08-17 21:48:21 +02:00
fi
2019-08-27 07:11:27 +02:00
minsize = $( cut -d ':' -f 2 <<< " $minsize " | tr -d ' ' )
2020-01-09 13:21:55 +01:00
logVariables $LINENO currentsize minsize
2016-07-07 07:28:32 +02:00
if [ [ $currentsize -eq $minsize ] ] ; then
2018-08-17 21:48:21 +02:00
error $LINENO "Image already shrunk to smallest size"
2020-06-15 00:39:41 +02:00
exit 11
2016-07-07 07:28:32 +02:00
fi
#Add some free space to the end of the filesystem
2018-01-22 04:31:19 +01:00
extra_space = $(( $currentsize - $minsize ))
2018-08-17 21:48:21 +02:00
logVariables $LINENO extra_space
2018-01-22 04:31:19 +01:00
for space in 5000 1000 100; do
if [ [ $extra_space -gt $space ] ] ; then
minsize = $(( $minsize + $space ))
break
fi
done
2018-08-17 21:48:21 +02:00
logVariables $LINENO minsize
2016-07-07 07:28:32 +02:00
#Shrink filesystem
2018-08-17 21:48:21 +02:00
info "Shrinking filesystem"
2018-01-22 03:50:41 +01:00
resize2fs -p " $loopback " $minsize
2020-04-20 14:58:25 +02:00
rc = $?
if ( ( $rc ) ) ; then
error $LINENO " resize2fs failed with rc $rc "
2018-01-22 03:50:41 +01:00
mount " $loopback " " $mountdir "
mv " $mountdir /etc/rc.local.bak " " $mountdir /etc/rc.local "
umount " $mountdir "
losetup -d " $loopback "
2020-06-15 00:39:41 +02:00
exit 12
2016-04-19 10:21:22 +02:00
fi
2016-04-12 05:05:00 +02:00
sleep 1
#Shrink partition
2018-01-22 04:31:19 +01:00
partnewsize = $(( $minsize * $blocksize ))
newpartend = $(( $partstart + $partnewsize ))
2018-08-17 21:48:21 +02:00
logVariables $LINENO partnewsize newpartend
2020-04-20 14:58:25 +02:00
parted -s -a minimal " $img " rm " $partnum "
rc = $?
if ( ( $rc ) ) ; then
2018-08-17 21:48:21 +02:00
error $LINENO " parted failed with rc $rc "
2020-06-15 00:39:41 +02:00
exit 13
2018-08-17 21:48:21 +02:00
fi
2020-06-13 23:10:19 +02:00
parted -s " $img " unit B mkpart " $parttype " " $partstart " " $newpartend "
2020-04-20 14:58:25 +02:00
rc = $?
if ( ( $rc ) ) ; then
2018-08-17 21:48:21 +02:00
error $LINENO " parted failed with rc $rc "
2020-06-15 00:39:41 +02:00
exit 14
2018-08-17 21:48:21 +02:00
fi
2016-04-12 05:05:00 +02:00
#Truncate the file
2018-08-17 21:48:21 +02:00
info "Shrinking image"
2020-04-20 14:58:25 +02:00
endresult = $( parted -ms " $img " unit B print free)
rc = $?
if ( ( $rc ) ) ; then
2018-08-17 21:48:21 +02:00
error $LINENO " parted failed with rc $rc "
2020-06-15 00:39:41 +02:00
exit 15
2018-08-17 21:48:21 +02:00
fi
2019-08-23 15:46:45 +02:00
endresult = $( tail -1 <<< " $endresult " | cut -d ':' -f 2 | tr -d 'B' )
2018-08-17 21:48:21 +02:00
logVariables $LINENO endresult
2020-04-20 14:58:25 +02:00
truncate -s " $endresult " " $img "
rc = $?
if ( ( $rc ) ) ; then
2018-08-17 21:48:21 +02:00
error $LINENO " trunate failed with rc $rc "
2020-06-15 00:39:41 +02:00
exit 16
2018-08-19 22:05:28 +02:00
fi
2018-08-17 21:48:21 +02:00
2020-02-29 14:52:35 +01:00
# handle compression
2020-02-07 17:30:38 +01:00
if [ [ -n $ziptool ] ] ; then
2020-02-07 16:49:22 +01:00
options = ""
2020-02-29 14:52:35 +01:00
envVarname = " ${ MYNAME ^^ } _ ${ ziptool ^^ } " # PISHRINK_GZIP or PISHRINK_XZ environment variables allow to override all options for gzip or xz
2020-02-07 17:30:38 +01:00
[ [ $parallel = = true ] ] && options = " ${ ZIP_PARALLEL_OPTIONS [ $ziptool ] } "
[ [ -v $envVarname ] ] && options = " ${ !envVarname } " # if environment variable defined use these options
2020-06-16 00:56:06 +02:00
[ [ $verbose = = true ] ] && options = " $options -v " # add verbose flag if requested
2020-05-31 21:09:51 +02:00
2020-02-07 16:49:22 +01:00
if [ [ $parallel = = true ] ] ; then
2020-02-07 17:30:38 +01:00
parallel_tool = " ${ ZIP_PARALLEL_TOOL [ $ziptool ] } "
info " Using $parallel_tool on the shrunk image "
2020-06-16 00:41:42 +02:00
if ! $parallel_tool ${ options } " $img " ; then
2020-02-07 16:49:22 +01:00
rc = $?
2020-02-07 17:30:38 +01:00
error $LINENO " $parallel_tool failed with rc $rc "
2020-06-15 00:39:41 +02:00
exit 18
2020-02-07 16:49:22 +01:00
fi
2020-05-31 21:09:51 +02:00
2020-02-07 16:49:22 +01:00
else # sequential
info " Using $ziptool on the shrunk image "
2020-06-16 00:41:42 +02:00
if ! $ziptool ${ options } " $img " ; then
2020-02-07 16:49:22 +01:00
rc = $?
error $LINENO " $ziptool failed with rc $rc "
2020-06-15 00:39:41 +02:00
exit 19
2020-02-07 16:49:22 +01:00
fi
fi
img = $img .${ ZIPEXTENSIONS [ $ziptool ] }
2019-08-27 09:52:43 +02:00
fi
2018-01-22 03:39:33 +01:00
aftersize = $( ls -lh " $img " | cut -d ' ' -f 5)
2018-08-17 21:48:21 +02:00
logVariables $LINENO aftersize
2016-04-12 05:05:00 +02:00
2018-08-17 21:48:21 +02:00
info " Shrunk $img from $beforesize to $aftersize "