#!/bin/bash
#Description: create pfs module from changes in chroot
#Author : Alexandr Betkher (betcher) <http://magos-linux.ru>
#VERSION 4.4
#190807 sfs 


if [ -f $(dirname $0)/pfs ] ;  then
    . $(dirname $0)/pfs
else 
    . $(which pfs) || exit 13 
fi    
 
CHROOT="chroot"
TRIM="--trim" ##sfs
WH=""
buildDir=$SYSMNT/$$-tmp
filter="??*" # нужен для пересборки сабмодуля из контейнера, чтоб не собирать все что в контейнере,  нужно приделать ключ. Пока не важно.
fast_alg=""

HLP () {
echo "Usage: 	$(basename $0) <$(basename $0) keys> <--command>  - command which will be execute in  chroot"
echo "				$(basename $0) <$(basename $0) keys> <--script> - script which will be copied to chroot and run" 
echo "				$(basename $0) module.$EXT  - rebuild module" 
echo "$(basename $0) keys:"
echo "-h | --help - this help"
echo "--command  	- команда для выполнения в chroot, параметр должен быть последним в строке"
echo "Запуск программы с другой локалью : --command LANG=ru_RU.UTF-8 program. Работает только с \"-c chroot\" "
echo "--script 	- скрипт, который будет перенесен в chroot и запущен в нем"
echo "-m | --mask 	- маска для системных бандлов из которых будет создана aufs для chroot"
echo "--mlist 	- список модулей и папок для создания aufs, разделитель в списке - \",\" "
echo "В списке можно применять \"#\" для блокирования. Пример : --mlist 1.pfs,#2.pfs,3.pfs "
echo "--flist 	- список модулей и папок из файла. В файле можно применять \"#\" для блокирования"
echo "Создать список из модулей в каталоге dir: find  /pth/dir -name *.$EXT |sort |awk '{print \"#\"$0}' > flist.lst"
echo "-f | --fast 	- создать модуль c быстрым типом компрессии"
echo "-n | -o | --name 	- имя создаваемого модуля или каталога (если без расширения $EXT)"
echo "-c | --contaner 	- тип контейнера:  \"chroot\" или \"nspawn\" (systemd-nspawn нужен и на хост-системе и на гостевой)" 
echo "--boot - только для -c nspawn. См. systemd-nspawn -b" 
echo "--notrim 	- не удалять заведомо не нужные в модуле файлы (кэш и т.п.)"
echo "--wh 	- не удалять специальные файлы aufs (тени)"
echo "--bind 	- список каталогов для монтирования в chroot в формате /SOURCE/DIR1::/TARGET/DIR1,/SOURCE/DIR2::/TARGET/DIR2"
echo "--nomod 	- не создавать модуль с изменениями"
echo "-X 	- разрешить подключение к текущей X сессии. Для запуска в chroot приложений с GUI. Рекомендуется использовать с \"-c nspawn\""
echo "Ядро хост-системы должно быть одинаковой архитектуры (i686 или x86_64) c гостевой. Сама хост-система может быть любой"
echo
echo "ПРИМЕРЫ"
echo "======="
echo "Сборка модуля mc в каталог ModuleDIR:"
echo "	chroot2pfs -o ModuleDIR  --flist /tmp/module.list --command apt install mc"
echo "Запуск программы pcmanfm из гостевой системы, собранной из модулей 1 и 3 (1 в верхнем слое AUFS, т.е. перекрывает 2):"
echo "	chroot2pfs --nomod -X -c chroot --mlist /pth/1.$EXT,\\
#2.\\
3.$EXT --command pcmanfm"
echo
exit 
}

umount_all () {
for tm in {sys,proc,dev}; do
    while (grep -q $root_br/$tm /proc/mounts ) ; do
        umount $root_br/$tm
    done
done
}

get_layers () {
layers=""
local layers
if  [ "$MASK" -o "$MLIST" -o "$FLIST" ] ; then
	[ "$MASK" ] && layers=$(aufs-n --hidetop  --reverse --raw '$bname_source $bundle' |egrep "$MASK" | awk '{print $2}')
	[ "$MLIST" ] && layers="$layers $(echo "$MLIST" | sed 's/#[^,]\+,\?//g; s/,$//' |sed 's/[\,]/ /g')"
	[ "$FLIST" ] && layers="$layers $(grep -v '^#' $FLIST)"
else
	layers=$(aufs-n --hidetop --reverse --raw '$bundle')
fi
echo $layers |grep [[:alnum:]] && return
for item in $layers ; do 
	[ -e $item ] && echo $item 
done
}

argslist="$@"
if  echo $argslist |grep -q "\-*command.*" ; then
    command="$(echo $@ |sed 's/^.*\-*command//')"
    argslist="$(echo $@ |sed 's/\-*command.*$//')"
fi

for arg in $argslist
do
  case "${arg}" in
    "-h" | "--help" ) HLP;;
    "-n" | "-o" | "--name") name="yes";;
    "-f" | "--fast") fast_alg="-f" ;;
    "-s" | "--script" ) script="yes" ;;
    "-m" | "--mask") mask="yes" ;;
    "--builddir") bdir="yes" ;; # for rebuild only
    "-b" | "--bind") bind="yes" ;;
    "--mlist" ) mlist="yes" ;;
    "--flist" ) flist="yes" ;;
    "--showlayers" ) SHOWLAYERS=yes ;;
    "-c" | "--contaner" ) contaner=yes;;
    "--notrim" ) TRIM="";;
    "-w" | "--wh" ) WH="--wh";;
    "-X" ) X="yes";;
    "--nomod" ) NOMOD="yes";;
    "--boot" ) BOOT="-b";;
    "-"*[A-Za-z]*) echo "$(basename "$0"): invalid option -- '$(echo ${arg} | tr -d '-')'" >&2; exit 1;;
    *) if [ "${name}" = "yes" ]; then NAME="${arg}"
		elif [ "${mlist}" = "yes" ]; then MLIST="${arg}"
		elif [ "${flist}" = "yes" ]; then FLIST="${arg}"
		elif [ "${script}" = "yes" ]; then SCRIPT="${arg}"
		elif [ "${bdir}" = "yes" ]; then   buildDir="$(readlink -e ${arg})/$$-tmp"
		elif [ "${contaner}" = "yes" ]; then CONTANER="${arg}" 
        elif [ "${mask}" = "yes" ]; then MASK="${arg}"
        elif [ "${bind}" = "yes" ]; then BIND="${arg}"
        else modules="${arg} ${modules}" ; fi
        mlist="no"; flist="no"; name="no";  mask="no"; bdir="no"; bind="no"; script="no"; contaner="no";;
  esac
done

# rebuild modules
if  $(file $(echo $modules |awk '{print $1}') 2>/dev/null | grep -q quashfs); then
    for rmod in $modules ; do
    if ! unsquashfs -l $rmod | grep -q .*/var/lib/chroot2pfs/.  ; then
			exitmsg "Module $rmod was made by another program" 1 noexit
			continue
	fi
    mkdir -p $(dirname $buildDir)
    echo "rebuilding $rmod in $buildDir"
    u2pfs_current=$(realpath $0)
    unsquashfs -d $buildDir "$rmod" -e /var/lib/chroot2pfs -n >/dev/null
    mv $buildDir/var/lib/chroot2pfs/* $buildDir  # || exitmsg "Module $rmod was made by another program" 7
    rm -fr $buildDir/var
    CPWD=$(pwd)
    cd $buildDir
    for mod in $(find ./ -type d -name "??*"); do
		SCRIPT=$(readlink -e $(ls -1 ./$mod/*.sh 2>/dev/null ) 2>/dev/null ) ; [ $SCRIPT ] && SCRIPT="--script $SCRIPT"
        $u2pfs_current $(echo $(cat $mod/cmdline) $SCRIPT )  | tee ./chroot2pfs.out
        module=$(cat ./chroot2pfs.out | tail -n1)
        mv $CPWD/$(basename $module) $CPWD/$(basename $module).old 2>/dev/null
        mv $module ${CPWD}/
    done
    cd $CPWD
    rm -rf $buildDir
    newmods="$(echo $CPWD/$(basename $module)) $newmods"
    done
    echo "$newmods"
    exit
fi

allow_only_root

[ $CONTANER ] &&  CHROOT="$CONTANER"

if [ $SHOWLAYERS ] ; then 
	get_layers
	exit
fi

echo "Using:  $CHROOT"
#Имя обязательно, так как зацепиться не за что
[ $NOMOD ] && NAME=nomod
[ $NAME ] || read -p "Please enter name for module:      " NAME  
[ -f "${NAME}" ] && exitmsg "Output file '$NAME' already exist" 6
[ -d "${NAME}" ] && exitmsg "Output diectory '$NAME' already exist" 6


#make root aufs
root_br="$(mkaufs || exitmsg "mkaufs error" 2)"
nn="$(echo "$root_br" | sed -n 's/^.*\([0-9]\)$/\1/p')"
[ -d "$root_br" ] || exitmsg "error mounting aufs" 3
echo "aufs number: $nn"

#add sources as aufs layers
layers=$(get_layers)
echo $layers |grep -q [[:alnum:]] || exitmsg "Empty layers list" 7
for  i in $layers ;do
    eval addlayer "$nn" "$i" "$devnull" || exitmsg "can't insert layer to aufs $nn" 5
done 

if [ $CHROOT = "chroot" ] ; then
mkdir -p $root_br/{dev,proc,sys,tmp}
for tm in {dev,proc,sys}; do
    [ /$tm ] && mount -o bind /$tm $root_br/$tm
done
fi

#сохраняем в модуль $@ для последующих пересборок
#надо подумать что еще полезно сохранить
#cp /etc/resolv.conf $root_br/etc/resolv.conf # у Антона было, у меня без этого работает
mkdir -p $root_br/var/lib/chroot2pfs/$(basename "${NAME%.$EXT}")
date > $root_br/var/lib/chroot2pfs/$(basename "${NAME%.$EXT}")/date
echo $@ > $root_br/var/lib/chroot2pfs/$(basename "${NAME%.$EXT}")/cmdline
if [ "$SCRIPT" ] ; then
	[ -f  "$root_br/var/lib/chroot2pfs/$(basename "${NAME%.$EXT}")/$(basename $SCRIPT)" ]  || cp  $SCRIPT  $root_br/var/lib/chroot2pfs/$(basename "${NAME%.$EXT}")/
	chmod +x $root_br/var/lib/chroot2pfs/$(basename "${NAME%.$EXT}")/$(basename $SCRIPT) 
fi

if [ "$BIND" ] ; then
bind_dirs=''
binds="$(echo "$BIND" |sed 's/[\,]/ /g')"
for bnd in $binds ; do
	extdir=$(echo $bnd |sed 's/::/#/' |cut -d "#" -f1)
	indir="$root_br/$(echo $bnd |sed 's/::/#/' |cut -d "#" -f2)"
	mkdir -p "$indir"
	mount -o bind "$extdir" "$indir"
	bind_dirs="$bind_dirs $indir"
done
fi

if [ "$X" = "yes" ] ; then
	if ! xhost |grep -q "LOCAL:" ; then
		xhost +local:
		remove_X_perm=yes
	fi
fi

if [ $CHROOT = "nspawn" ] ; then
md5sum /bin/bash | awk '{print $1}' > $root_br/etc/machine-id #  nspawn do not works without machine-id
[ "$SCRIPT" ] || systemd-nspawn $BOOT -D $root_br   -M ${NAME%.$EXT} --setenv=DISPLAY="$DISPLAY" --bind=/tmp/.X11-unix:/tmp/.X11-unix $command 
[ "$SCRIPT" ] && systemd-nspawn $BOOT -D $root_br   -M ${NAME%.$EXT} --setenv=DISPLAY="$DISPLAY" --bind=/tmp/.X11-unix /var/lib/chroot2pfs/$(basename "${NAME%.$EXT}")/$(basename $SCRIPT)
elif [ $CHROOT = "chroot" ] ; then
[ "$SCRIPT" ] || chroot $root_br env -i DISPLAY="$DISPLAY" $command
[ "$SCRIPT" ] && chroot $root_br env -i DISPLAY="$DISPLAY" /var/lib/chroot2pfs/$(basename "${NAME%.$EXT}")/$(basename $SCRIPT)
else 
NOMOD=yes 
echo "Wrong contaner type, must be \"chroot\" or \"nspawn\""
fi

[ $remove_X_perm ] && xhost -local: 
umount_all

mod_br=$SYSMNT/changes$nn
rm -rf $mod_br/tmp >/dev/null
rm -rf $mod_br/{dev,proc,sys,tmp} >/dev/null
rmdir "$mod_br"/{dev,proc,sys,lost+found} "$mod_br/var/cache" "$mod_br/var/lib" "$mod_br/var" "$mod_br/etc" 2>/dev/null

[ $NOMOD ] || mkpfs $mod_br -l $fast_alg  $WH  $TRIM   -o $NAME && MODULE="$(readlink -e ${NAME})" 
[ "$bind_dirs" ] && for dir in $bind_dirs ; do
	umount "$dir"
done 
echo "delaufs $nn"
delaufs $nn

# по этой строке имя модуля получается при пересборке.
echo $MODULE 
