#!/bin/sh # lbu - utility to create local backups. # Copyright (c) 2006 Natanael Copa # May be distributed under GPL2 PREFIX= . $PREFIX/lib/libalpine.sh # this one is from apk-tools . $PREFIX/lib/apk/libutil.sh VERSION=1.1 [ "$SFIC" ] && SFIC="$SFIC -i d" # globals CURRENT_TDB="$APK_DATA/current.tdb" TMPCURRENT_TDB="$APK_DATA/tmp-current.tdb" COMMITED_TDB="$APK_DATA/commited.tdb" EXCLUDE_LIST=/etc/lbu/exclude INCLUDE_LIST=/etc/lbu/include PACKAGES_LIST=/etc/lbu/packages.list DEFAULT_CIPHER="aes-256-cbc" MASK="Npugsh" LBUDIRS=`echo "$APK_LBUDIRS" | sed 's/:/ /g'` LBU_CONF=/etc/lbu/lbu.conf if [ -f "$LBU_CONF" ]; then . "$LBU_CONF" fi retcode=0 usage() { echo "$PROGRAM $VERSION" echo "usage: $PROGRAM [options] [args] Available subcommands: commit (ci) exclude (ex, delete) include (inc, add) list (ls) package (pkg) status (stat, st) update (up) Common options: -h Show help for subcommand. -q Quiet mode. -v Verbose mode. " exit 1 } # verify we have openssl if we want to encrypt check_openssl() { [ -z "$ENCRYPTION" ] && return 0 OPENSSL=$(which openssl 2>/dev/null) || die "openssl was not found" $OPENSSL list-cipher-commands | grep "^$ENCRYPTION$" > /dev/null \ || die "Cipher $ENCRYPTION is not supported" } gen_current_tdb() { # generate current tdb rm -f "$CURRENT_TDB" $SFIC -R -t --mask "$MASK" --old "$APK_DEFAULT_TDB" $LBUDIRS \ | grep -v ^D | awk '{print $2}' | $SFIC --add $CURRENT_TDB --file - } # list_add(char *listfile, char* file...) list_add() { local list="$1" shift mkdir -p `dirname "$list"` while [ $# -gt 0 ] ; do filename=`echo "$1" | sed 's:^/\+::'` if grep "^$filename$" "$list" >/dev/null 2>&1 ; then vecho "$filename is already in $list." else vecho "Adding $filename to $list." echo "$filename" >> "$list" fi shift done } # list_delete(char *listfile, char *file...) list_delete() { local list="$1" local tmp="$list.old" shift [ -f "$list" ] || return 1 while [ $# -gt 0 ] ; do filename=`echo "$1" | sed 's:^/\+::'` mv "$list" "$tmp" vecho "Removing $filename from list." grep -v "^$filename$" "$tmp" > "$list" rm "$tmp" shift done } # # lbu_include - add/remove files to include list # usage_include() { echo "$PROGRAM $VERSION Add filename(s) to include list (/etc/lbu/include) usage: $PROGRAM include|inc|add [-rv] ... $PROGRAM include|inc|add [-v] -l Options: -l List contents of include list. -r Remove specified file(s) from include list instead of adding. -v Verbose mode. " exit 1 } cmd_include() { if [ "$LIST" ] ; then [ $# -gt 0 ] && usage_include show_include return fi [ $# -lt 1 ] && usage_include if [ "$REMOVE" ] ; then list_delete "$INCLUDE_LIST" "$@" else list_add "$INCLUDE_LIST" "$@" list_delete "$EXCLUDE_LIST" "$@" fi } show_include() { if [ -f "$INCLUDE_LIST" ] ; then vecho "Include files:" cat "$INCLUDE_LIST" fi } # # lbu_package - create a package # usage_package() { echo "$PROGRAM $VERSION Create backup package. usage: $PROGRAM package|pkg -v [|] Options: -v Verbose mode. If is a directory, a package named .apkovl.tar.gz will be created in the specified directory. If is specified, and is not a direcotry, a package with the specified name willbe created. If nor is not specified, a package named .apkovl.tar.gz will be created in current work directory. " exit 1 } cmd_package() { local pkg="$1" local rc=0 local owd="$PWD" local suff="apkovl.tar.gz" local tmpdir tmppkg check_openssl init_tmpdir tmpdir [ -n "$ENCRYPTION" ] && suff="$suff.$ENCRYPTION" # find filename if [ -d "$pkg" ] ; then pkg="$pkg/$(hostname).$suff" elif [ -z "$pkg" ]; then pkg="$PWD/$(hostname).$suff" fi tmppkg="$tmpdir/$(basename $pkg)" # generate the packages.list vecho "Generating $PACKAGES_LIST" cd "$ROOT" mkdir -p $(dirname $PACKAGES_LIST) echo "#This file is generated by 'lbu package' (lbu $VERSION)" \ > $PACKAGES_LIST for i in $(apk_glob '*') ; do reqby=$(apk_info -qr $i); # only add the packages who has no REQUIRED_BY [ -z "$reqby" ] && echo $i done | sed 's/-[0-9].*//' >> $PACKAGES_LIST # Automatically add list and modified files currentlist=`VERBOSE="" USE_DEFAULT="-a" cmd_status -a | grep -v ^D | awk '{print $2}'` # we generate a tmpcurrent before we commit to avoid race condition rm -f "$CURRENT_TDB" $SFIC --add "$CURRENT_TDB" $currentlist # create tar archive [ -f "$EXCLUDE_LIST" ] && excl="-X $EXCLUDE_LIST" [ -f "$INCLUDE_LIST" ] && incl="-T $INCLUDE_LIST" if [ -n "$VERBOSE" ]; then echo "Archiving the following files:" >&2 # we dont want to mess the tar output with the # password prompt. Lets get the tar output first. tar $excl $incl -c -v $currentlist > /dev/null rc=$? fi if [ $rc -eq 0 ]; then if [ -z "$ENCRYPTION" ]; then tar $excl $incl -c $currentlist | gzip -c >"$tmppkg" rc=$? else set -- enc "-$ENCRYPTION" -salt [ -n "$PASSWORD" ] && set -- "$@" -pass pass:"$PASSWORD" tar $excl $incl -c $currentlist | gzip -c \ | $OPENSSL "$@" > "$tmppkg" rc=$? fi fi cd "$owd" # actually commit unless dryrun mode if [ $rc -eq 0 ]; then [ -z "$DRYRUN" ] && cp "$tmppkg" "$pkg" vecho "Created $pkg" else rm -f "$CURRENT_TDB" fi return $rc } # # lbu list - list files that would go to archive # usage_list() { echo "$PROGRAM $VERSION Lists files that would go to tar package. Same as: 'lbu package -v /dev/null' usage: $PROGRAM list|ls " exit 1 } cmd_list() { VERBOSE="-v" cmd_package /dev/null } # # lbu_commit - commit config files to writeable media # usage_commit() { echo "$PROGRAM $VERSION Create a backup of config to writeable media. usage: $PROGRAM commit|ci [-nv] [] Options: -d Remove old apk overlay files. -e Protect configuration with a password. -n Don't commit, just show what would have been commited. -p Give encryption password on the command-line -v Verbose mode. The following values for is supported: floppy usb If is not specified, the environment variable LBU_MEDIA will be used. Password protection will use $DEFAULT_CIPHER encryption. Other ciphers can be used by setting the DEFAULT_CIPHER or ENCRYPTION environment variables. For possible ciphers, try: openssl -v The password used to encrypt the file, can either be specified with the -p option or using the PASSWORD environment variable. The environment varialbes can also be set in $LBU_CONF " exit 1 } cmd_commit() { local media mnt was_mounted statuslist tmplist currentlist local incl excl outfile ovls lines check_openssl # turn on verbose mode if dryrun [ -n "$DRYRUN" ] && VERBOSE="-v" # find what media to use if [ "$1" ] ; then media="$1" else media="$LBU_MEDIA" fi [ -z "$media" ] && usage_commit # mount media unles its already mounted mnt=/media/$media [ -d "$mnt" ] || usage was_mounted=`grep $mnt /proc/mounts` if [ -z "$was_mounted" ]; then mount $mnt || die "failed to mount $mnt." fi # find the outfile outfile="$mnt/$(hostname).apkovl.tar.gz" if [ -n "$ENCRYPTION" ]; then outfile="$outfile.$ENCRYPTION" fi # remove old config files if [ -n "$DELETEOLDCONFIGS" ] ; then local rmfiles=$(ls "$mnt/"*.apkovl.tar.gz* 2>/dev/null) if [ -n "$rmfiles" ] ; then if [ -n "$VERBOSE" ]; then echo "Removing old apk overlay files:" >&2 echo "$rmfiles" echo "" >&2 fi [ -z "$DRYRUN" ] && rm "$mnt/"*.apkovl.tar.gz* fi else lines=$(ls -1 "$mnt"/*.apkovl.tar.gz* 2>/dev/null) if [ -n "$lines" ] && [ "$lines" != "$outfile" ]; then # More then one apkovl, this is a security concern [ -z "$was_mounted" ] && umount "$mnt" eecho "The following apkovl file(s) were found:" eecho "$lines" eecho "" die "Please use -d to replace." fi fi # create package if ! cmd_package "$outfile"; then [ -n "$was_mounted" ] && umount "$mnt" rm -f "$CURRENT_TDB" die "Problems creating archive. aborting" fi # remove obsolete file. some older version of alpine needs this # to be ble to upgrade if [ -z "$DRYRUN" ] && [ -f $mnt/packages.list ]; then echo "Note: Removing packages.list from $(basename $mnt)." echo " $PACKAGES_LIST will be used." rm -f $mnt/packages.list fi # make sure data is written and unmount the media sync sleep 1 [ -z "$was_mounted" ] && umount "$mnt" # move current to commited. [ "$DRYRUN" ] || mv "$CURRENT_TDB" "$COMMITED_TDB" vecho "Successfully saved apk overlay files" } #--------------------------------------------------------------------------- # lbu_exclude - add remove file(s) from exclude list usage_exclude() { echo "$PROGRAM $VERSION Add filename(s) to exclude list (/etc/lbu/exclude) usage: $PROGRAM exclude|ex|delete [-rv] ... $PROGRAM exclude|ex|delete [-v] -l Options: -l List contents of exclude list. -r Remove specified file(s) from exclude list instead of adding. -v Verbose mode. " exit 1 } cmd_exclude() { if [ "$LIST" ] ; then [ $# -gt 0 ] && usage_exclude show_exclude return fi [ $# -lt 1 ] && usage_exclude if [ "$REMOVE" ] ; then list_delete "$EXCLUDE_LIST" "$@" else list_delete "$INCLUDE_LIST" "$@" list_add "$EXCLUDE_LIST" "$@" fi } show_exclude() { if [ -f "$EXCLUDE_LIST" ] ; then vecho "Exclude files:" cat "$EXCLUDE_LIST" fi } #--------------------------------------------------------------------------- # lbu_status - check what files have been changed since last save usage_status() { echo "$PROGRAM $VERSION Check what files have been changed since last commit. usage: $PROGRAM status|st [-M ] [-av] Options: -M Use a different mask for comparing. (see sfic -h) -a Compare all files, not just since last commit. -v Also show include and exclude lists. " exit 1 } cmd_status() { cd "$ROOT" [ "$SFIC" ] || die "Need sfic." [ -f "$APK_DEFAULT_TDB" ] || die "$APK_DEFAULT_TDB not found." # genereate temp tdb #gen_temp_tdb if [ -f "$COMMITED_TDB" ] && [ -z "$USE_DEFAULT" ]; then # generate current tdb gen_current_tdb OLD="$COMMITED_TDB" NEW="--new $CURRENT_TDB" else OLD="$APK_DEFAULT_TDB" NEW="-R $LBUDIRS" fi $SFIC $QUIET $VERBOSE --mask "$MASK" -t --old "$OLD" $NEW retcode=$? if [ "$VERBOSE" ] ; then echo "" show_include echo "" show_exclude fi } #------------------------------------------------ # lbu_update - Update the database wihtout commit to media. usage_update() { echo "$PROGRAM $VERSION Update the commited database without commit to media. usage: $PROGRAM update|up [-v] ... Options: -v Verbose mode. " exit 1 } cmd_update() { [ $# -lt 1 ] && usage_update cd "$ROOT" for i in "$@" ; do echo $i | sed 's:^/::' done | lbu_filter | $SFIC $VERBOSE --add "$COMMITED_TDB" --file - } #----------------------------------------------------------- # Main cmd=`echo "$PROGRAM" | cut -s -d_ -f2` PROGRAM=`echo "$PROGRAM" | cut -d_ -f1` if [ -z "$cmd" ] ; then cmd="$1" [ -z "$cmd" ] && usage shift fi # check for valid sub command case "$cmd" in include|inc|add) SUBCMD="include";; commit|ci) SUBCMD="commit";; exclude|ex|delete) SUBCMD="exclude";; list|ls) SUBCMD="list";; package|pkg) SUBCMD="package";; status|stat|st) SUBCMD="status";; update|up) SUBCMD="update";; *) usage;; esac # parse common args while getopts "adehlM:np:qrv" opt ; do case "$opt" in a) [ $SUBCMD = status ] || usage_$SUBCMD USE_DEFAULT="-a" ;; d) DELETEOLDCONFIGS="yes" ;; e) [ -z "$ENCRYPTION" ] && ENCRYPTION="$DEFAULT_CIPHER" ;; h) usage_$SUBCMD ;; l) LIST="-l" ;; M) [ $SUBCMD = status ] || usage_$SUBCMD MASK="$OPTARG" ;; n) [ $SUBCMD = commit ] || usage_$SUBCMD DRYRUN="-n" ;; p) PASSWORD="$OPTARG" ;; q) QUIET="$QUIET -q" ;; r) REMOVE="-r" ;; v) VERBOSE="$VERBOSE -v" ;; esac done shift `expr $OPTIND - 1` cmd_$SUBCMD "$@" # cleanup rm -f "$CURRENT_TDB" exit $retcode