#!/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.2 [ "$SFIC" ] && SFIC="$SFIC -i d" # globals [ -z "$LBU_DBDIR" ] && LBU_DBDIR="${APK_DATA:-$ROOT/var/lib/apk}" CURRENT_TDB="$LBU_DBDIR/current.tdb" TMPCURRENT_TDB="$LBU_DBDIR/tmp-current.tdb" COMMITED_TDB="$LBU_DBDIR/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 UMOUNT_LIST= 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) list-backup (lb) revert Common options: -h Show help for subcommand. -q Quiet mode. -v Verbose mode. " exit 1 } cleanup() { local i rm -f "$CURRENT_TDB" rm -f "$TMPCURRENT_TDB" for i in $UMOUNT_LIST; do umount $i done } exit_clean() { cleanup exit 1 } mount_once() { if ! grep $1 /proc/mounts >/dev/null; then mount $1 && UMOUNT_LIST="$1 $UMOUNT_LIST" fi } # create backupfile backup_apkovl() { local outfile="$1" local d=$( date -u -r "$outfile" "+%Y%m%d%H%m%S" ) local backup=$(echo "$outfile" | sed "s/\.apkovl\.tar\.gz/.$d.tar.gz/") vecho "Creating backup $backup" if [ -z "$DRYRUN" ]; then mv "$outfile" "$backup" fi } # 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" mkdir -p "$(dirname \"$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 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 media="${1:-$LBU_MEDIA}" [ -z "$media" ] && usage_commit # mount media unles its already mounted mnt=/media/$media [ -d "$mnt" ] || usage mount_once "$mnt" || die "failed to mount $mnt" # 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 [ "$lines" = "$outfile" ]; then backup_apkovl "$outfile" elif [ -n "$lines" ]; then # More then one apkovl, this is a security concern cleanup 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 cleanup die "Problems creating archive. aborting" fi # delete old backups if needed # poor mans 'head -n -N' done with awk. ls "$mnt"/$(hostname).[0-9][0-9][0-9][0-9]*[0-9].tar.gz \ | awk '{ a[++i] = $0; } END { print a[0]; while (i-- > '"${BACKUP_LIMIT:-0}"') { print a[++j] } }' | xargs rm 2>/dev/null # 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 sync [ "$media" = "floppy" ] && sleep 1 # 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_listbackup() { cat <] EOF exit 1 } cmd_listbackup() { local media=${1:-"$LBU_MEDIA"} local mnt="/media/$media" [ -z "$media" ] && usage_listbackup mount_once "$mnt" || die "failed to mount $mnt" ls -1 "$mnt"/*.[0-9][0-9]*[0-9][0-9].tar.gz* 2>/dev/null | sed 's:.*/::' } #--------------------------------------------------------------------------- # lbu_revert - revert to old config usage_revert() { cat < [] The revision should be one of the files listed by 'lbu list-backup'. EOF } cmd_revert() { local media=${2:-"$LBU_MEDIA"} [ -z "$media" ] && usage_revert local mnt="/media/$media" local revertto="$mnt/$1" local current="$mnt/$(hostname).apkovl.tar.gz" if [ -n "$ENCRYPTION" ]; then current="$current.$ENCRYPTION" fi mount_once "$mnt" || die "failed to mount $mnt" [ -f "$revertto" ] || die "file not found: $revertto" backup_apkovl "$current" vecho "Reverting to $1" [ -z "$DRYRUN" ] && mv "$revertto" "$current" } #--------------------------------------------------------------------------- # 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 local retcode=$? if [ "$VERBOSE" ] ; then echo "" show_include echo "" show_exclude fi return $retcode } #------------------------------------------------ # 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";; list-backup|lb) SUBCMD="listbackup";; revert) SUBCMD="revert";; *) 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` trap exit_clean SIGINT SIGTERM cmd_$SUBCMD "$@" retcode=$? cleanup exit $retcode