File: //var/chroot/var/chroot/sbin/lbu_exclude
#!/bin/sh
# lbu - utility to create local backups.
# Copyright (c) 2006-2022 Natanael Copa <ncopa@alpinelinux.org>
# May be distributed under GPL2 or MIT
VERSION=3.18.1-r1
sysconfdir=/etc/lbu
PREFIX=
: ${LIBDIR=$PREFIX/lib}
. "$LIBDIR/libalpine.sh"
LBU_LIST="${ROOT}etc/apk/protected_paths.d/lbu.list"
DEFAULT_CIPHER="aes-256-cbc"
LBU_CONF="${ROOT}$sysconfdir"/lbu.conf
LBU_PREPACKAGE="${ROOT}$sysconfdir"/pre-package.d
LBU_POSTPACKAGE="${ROOT}$sysconfdir"/post-package.d
if [ -f "$LBU_CONF" ]; then
. "$LBU_CONF"
fi
UMOUNT_LIST=
usage() {
cat <<-__EOF__
$PROGRAM $VERSION
usage: $PROGRAM <subcommand> [options] [args]
Available subcommands:
commit (ci)
diff
exclude (ex, delete)
include (inc, add)
list (ls)
list-backup (lb)
package (pkg)
revert
status (stat, st)
Common options:
-h Show help for subcommand.
-q Quiet mode.
-v Verbose mode.
__EOF__
exit $1
}
cleanup() {
local i
for i in $REMOUNT_RO_LIST; do
mount -o remount,ro $i
done
for i in $UMOUNT_LIST; do
umount $i
done
}
exit_clean() {
cleanup
exit 1
}
# check if given dir is not a mounted mountpoint
is_unmounted() {
awk "\$2 == \"$1\" {exit 1}" /proc/mounts
}
mount_once() {
if is_unmounted "$1"; then
mount $1 && UMOUNT_LIST="$1 $UMOUNT_LIST" || return 1
fi
}
# check if given dir is read-only
is_ro() {
local tmpfile="$(mktemp -p "$1" 2>/dev/null)"
[ -z "$tmpfile" ] && return 0
rm -f "$tmpfile"
return 1
}
mount_once_rw() {
mount_once "$1" || return 1
if is_ro "$1"; then
REMOUNT_RO_LIST="$1 $REMOUNT_RO_LIST"
mount -o remount,rw "$1"
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"
APKOVL_BACKUP="$backup"
fi
}
restore_apkovl() {
local outfile="$1"
if [ -n "$DRYRUN" ] || [ -z "$APKOVL_BACKUP" ]; then
return 0
fi
mv "$APKOVL_BACKUP" "$outfile"
}
# 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 -1 -cipher-commands | grep "^$ENCRYPTION$" > /dev/null \
|| die "Cipher $ENCRYPTION is not supported"
}
# grep and sed has issues with escaping '*' in lists so we rather do
# our own filter functions
list_has() {
local line=
[ -e "$LBU_LIST" ] || return 1
while read line; do
[ "$line" = "$1" ] && return 0
done < "$LBU_LIST"
return 1
}
list_filter_out() {
local line=
while read line; do
if [ "$line" != "$1" ]; then
echo "$line"
fi
done < "$LBU_LIST"
}
# list_add(char prefix, char *listfile, char* file...)
list_add() {
local prefix="$1"
shift
mkdir -p "${LBU_LIST%/*}"
while [ $# -gt 0 ] ; do
filename="$(echo "$1" | sed -E 's:^/+::')"
if list_has "${prefix}${filename}"; then
vecho "$filename is already in $LBU_LIST."
else
vecho "Adding $filename to $LBU_LIST."
echo "${prefix}${filename}" >> "$LBU_LIST"
fi
shift
done
}
# list_delete(char prefix, char *listfile, char *file...)
list_delete() {
local prefix="$1"
local tmp="$LBU_LIST.new"
shift
[ -f "$LBU_LIST" ] || return 1
while [ $# -gt 0 ] ; do
filename="$(echo "$1" | sed -E 's:^/+::')"
if list_has "${prefix}${filename}"; then
vecho "Removing $filename from $LBU_LIST."
list_filter_out "${prefix}${filename}" > "$tmp" \
&& mv "$tmp" "$LBU_LIST"
else
vecho "$filename is not in $LBU_LIST"
fi
shift
done
}
# unpack archive on LBU_MEDIA to given dir
unpack_apkovl() {
local f="$(hostname).apkovl.tar.gz"
local dest="$1"
local mnt="${LBU_BACKUPDIR:-/media/$LBU_MEDIA}"
local count=0
mkdir -p "$dest"
if [ -n "$LBU_MEDIA" ]; then
mount_once "$mnt"
fi
if [ -n "$ENCRYPTION" ]; then
f="$f.$ENCRYPTION"
fi
if [ ! -f "$mnt/$f" ]; then
return 1
fi
if [ -z "$ENCRYPTION" ]; then
tar -C "$dest" -zxf "$mnt/$f"
return
fi
check_openssl
while [ $count -lt 3 ]; do
$OPENSSL enc -d -$ENCRYPTION -in "$mnt/$f" ${PASSWORD:+-k "$PASSWORD"} \
| tar -C "$dest" -zx 2>/dev/null && return 0
count=$(( $count + 1 ))
done
cleanup
die "Failed to unpack $mnt/$f"
}
#
# lbu_include - add/remove files to include list
#
usage_include() {
cat <<-__EOF__
$PROGRAM $VERSION
Add filename(s) to include list ($LBU_LIST)
usage: $PROGRAM include|inc|add [-rv] <file> ...
$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.
__EOF__
exit $1
}
cmd_include() {
if [ "$LIST" ] ; then
[ $# -gt 0 ] && usage_include "1" >&2
show_include
return
fi
[ $# -lt 1 ] && usage_include "1" >&2
if [ "$REMOVE" ] ; then
list_delete + "$@"
else
list_add + "$@"
list_delete - "$@"
fi
}
show_include() {
if [ -f "$LBU_LIST" ] ; then
vecho "Include files:"
grep -- '^+' "$LBU_LIST" | sed 's/^+//'
fi
}
#
# lbu_package - create a package
#
usage_package() {
cat <<-__EOF__
$PROGRAM $VERSION
Create backup package.
usage: $PROGRAM package|pkg -v [<dirname>|<filename>]
Options:
-v Verbose mode.
If <dirname> is a directory, a package named <hostname>.apkovl.tar.gz will
be created in the specified directory.
If <filename> is specified, and is not a directory, a package with the
specified name willbe created.
If <dirname> nor <filename> is not specified, a package named
<hostname>.apkovl.tar.gz will be created in current work directory.
__EOF__
exit $1
}
_gen_filelist() {
apk audit --root ${ROOT:-/} --backup --quiet --recursive --check-permissions
}
cmd_package() {
local pkg="$1"
local rc=0
local owd="$PWD"
local suff="apkovl.tar.gz"
local tmpdir tmppkg
check_openssl
init_tmpdir tmpdir
if [ -d "$LBU_PREPACKAGE" ]; then
run-parts "$LBU_PREPACKAGE" >&2 || return 1
fi
[ -n "$ENCRYPTION" ] && [ "x$pkg" != "x-" ] && 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)"
local tar_create="tar -c --no-recursion -T -"
cd "${ROOT:-/}"
# remove old package.list
if [ -f etc/lbu/packages.list ] && [ -f var/lib/apk/world ]; then
echo "Note: Removing /etc/lbu/packages.list." >&2
echo " /var/lib/apk/world will be used." >&2
rm -f etc/lbu/packages.list
fi
# create tar archive
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.
_gen_filelist | $tar_create -v > /dev/null
rc=$?
fi
if [ $rc -eq 0 ]; then
if [ -z "$ENCRYPTION" ] || [ "x$pkg" = "x-" ]; then
_gen_filelist | $tar_create -z >"$tmppkg"
rc=$?
else
set -- enc "-$ENCRYPTION" -salt
[ -n "$PASSWORD" ] && set -- "$@" -pass pass:"$PASSWORD"
_gen_filelist | $tar_create -z \
| $OPENSSL "$@" > "$tmppkg"
rc=$?
fi
fi
cd "$owd"
# actually commit unless dryrun mode
if [ $rc -eq 0 ]; then
if [ -z "$DRYRUN" ]; then
if [ "x$pkg" = "x-" ]; then
cat "$tmppkg"
elif [ -b "$pkg" ] || [ -c "$pkg" ]; then
cat "$tmppkg" > "$pkg"
else
if cp "$tmppkg" "$pkg.new"; then
mv "$pkg.new" "$pkg"
rc=$?
else
rm -f "$pkg.new"
rc=1
fi
fi
fi
[ $rc -eq 0 ] && vecho "Created $pkg"
fi
if [ -d "$LBU_POSTPACKAGE" ]; then
run-parts "$LBU_POSTPACKAGE" >&2
fi
return $rc
}
#
# lbu list - list files that would go to archive
#
usage_list() {
cat <<-__EOF__
$PROGRAM $VERSION
Lists files that would go to tar package. Same as: 'lbu package -v /dev/null'
usage: $PROGRAM list|ls
__EOF__
exit $1
}
cmd_list() {
if [ "$1" = "-h" ]; then
usage_list 0
fi
_gen_filelist
}
#
# lbu_commit - commit config files to writeable media
#
usage_commit() {
cat <<-__EOF__
$PROGRAM $VERSION
Create a backup of config to writeable media.
usage: $PROGRAM commit|ci [-nv] [<media>]
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 <password> Give encryption password on the command-line
-v Verbose mode.
The following values for <media> is supported: floppy usb
If <media> is not specified, the environment variable LBU_BACKUPDIR or
LBU_MEDIA will be used. If LBU_BACKUPDIR is set, nothing will be mounted.
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 variables can also be set in $LBU_CONF
__EOF__
exit $1
}
cmd_commit() {
local media mnt statuslist tmplist
local incl excl outfile ovls lines
check_openssl
# turn on verbose mode if dryrun
[ -n "$DRYRUN" ] && VERBOSE="-v"
mnt="$LBU_BACKUPDIR"
if [ -z "$mnt" ]; then
# find what media to use
media="${1:-$LBU_MEDIA}"
[ -z "$media" ] && usage_commit "1" >&2
# mount media unless its already mounted
mnt=/media/$media
[ -d "$mnt" ] || usage "1" >&2
mount_once_rw "$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 [ "$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
restore_apkovl "$outfile"
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* 2>/dev/null \
| 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 able to upgrade
if [ -z "$DRYRUN" ] && [ -f $mnt/packages.list ]; then
echo "Note: Removing packages.list from $(basename $mnt)."
echo " /var/lib/apk/world will be used."
rm -f $mnt/packages.list
fi
# make sure data is written
sync
[ "$media" = "floppy" ] && sleep 1
# move current to commited.
vecho "Successfully saved apk overlay files"
}
#---------------------------------------------------------------------------
# lbu_exclude - add remove file(s) from exclude list
usage_exclude() {
cat <<-__EOF__
$PROGRAM $VERSION
Add filename(s) to exclude list ($LBU_LIST)
usage: $PROGRAM exclude|ex|delete [-rv] <file> ...
$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.
__EOF__
exit $1
}
cmd_exclude() {
if [ "$LIST" ] ; then
[ $# -gt 0 ] && usage_exclude "1" >&2
show_exclude
return
fi
[ $# -lt 1 ] && usage_exclude "1" >&2
if [ "$REMOVE" ] ; then
list_delete - "$@"
else
list_delete + "$@"
list_add - "$@"
fi
}
show_exclude() {
if [ -f "$LBU_LIST" ] ; then
vecho "Exclude files:"
grep -- '^-' "$LBU_LIST" | sed 's/^-//'
fi
}
#---------------------------------------------------------------------------
# lbu_listbackup - Show old commits
usage_listbackup() {
cat <<-__EOF__
$PROGRAM $VERSION
Show old commits.
usage: $PROGRAM list-backup [<media>]
__EOF__
exit $1
}
cmd_listbackup() {
local media="${1:-"$LBU_MEDIA"}"
local mnt="${LBU_BACKUPDIR:-/media/$media}"
[ -z "$media" ] && [ -z "$LBU_BACKUPDIR" ] && usage_listbackup
if [ -n "$media" ]; then
mount_once "$mnt" || die "failed to mount $mnt"
fi
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 <<-__EOF__
$PROGRAM $VERSION
Revert to older commit.
usage: $PROGRAM revert <REVISION> [<media>]
The revision should be one of the files listed by 'lbu list-backup'.
__EOF__
exit $1
}
cmd_revert() {
local media="${2:-"$LBU_MEDIA"}"
[ -z "$media" ] && usage_revert "1" >&2
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_rw "$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() {
cat <<-__EOF__
$PROGRAM $VERSION
Check what files have been changed since last commit.
usage: $PROGRAM status|st [-av]
Options:
-a Compare all files, not just since last commit.
-v Also show include and exclude lists.
__EOF__
exit $1
}
cmd_status() {
if [ -n "$USE_DEFAULT" ]; then
apk audit --backup
return 0
fi
LBU_MEDIA=${1:-"$LBU_MEDIA"}
[ -z "$LBU_MEDIA" ] && [ -z "$LBU_BACKUPDIR" ] && usage_status
local tmp
init_tmpdir tmp
mkdir -p "$tmp/a" "$tmp/b"
# unpack last commited apkovl to tmpdir/a
unpack_apkovl "$tmp/a"
# generate new apkovl and extract to tmpdir/b
cmd_package - | tar -C "$tmp/b" -zx
# show files that exists in a but not in b as deleted
local f
( cd "$tmp"/a && find ) | while read f; do
f=${f#./}
local b="$tmp/b/$f"
if [ "$f" = "." ] || [ -e "$b" ] || [ -L "$b" ]; then
continue
fi
echo "D $f"
done
# compare files in b with files in a
( cd "$tmp"/b && find ) | while read f; do
f=${f#./}
[ "$f" = "." ] && continue
local a="$tmp/a/$f"
local b="$tmp/b/$f"
if [ ! -e "$a" ] && [ ! -L "$a" ]; then
echo "A $f"
elif [ -f "$a" ] && [ -f "$b" ] && [ "$b" -nt "$a" ] \
&& ! cmp -s "$a" "$b"; then
echo "U $f"
fi
done
}
#-----------------------------------------------------------
# lbu_diff - run a diff against last commit
usage_diff() {
cat <<-__EOF__
$PROGRAM $VERSION
Run a diff against last commit
usage: $PROGRAM diff [<media>]
__EOF__
exit $1
}
cmd_diff() {
local diff_opts=
LBU_MEDIA=${1:-"$LBU_MEDIA"}
[ -z "$LBU_MEDIA" ] && [ -z "$LBU_BACKUPDIR" ] && usage_diff "1" >&2
local tmp
init_tmpdir tmp
mkdir -p "$tmp/a" "$tmp/b"
unpack_apkovl "$tmp/a"
cmd_package - | tar -C "$tmp/b" -zx
if diff --help 2>&1 | grep -q -- --no-dereference; then
diff_opts="--no-dereference"
fi
cd "$tmp" && diff -ruN $diff_opts a b
}
#-----------------------------------------------------------
# Main
cmd=$(echo "$PROGRAM" | cut -s -d_ -f2)
PROGRAM=$(echo "$PROGRAM" | cut -d_ -f1)
if [ -z "$cmd" ] ; then
cmd="$1"
[ -z "$cmd" ] && usage "1" >&2
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";;
list-backup|lb) SUBCMD="listbackup";;
revert) SUBCMD="revert";;
diff) SUBCMD="diff";;
-h) usage 0;;
*) usage "1" >&2;;
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 0
;;
l) LIST="-l"
;;
n) [ $SUBCMD = commit ] || usage_$SUBCMD
DRYRUN="-n"
;;
p) PASSWORD="$OPTARG"
;;
q) QUIET="$QUIET -q"
;;
r) REMOVE="-r"
;;
v) VERBOSE="$VERBOSE -v"
;;
'?')
usage_$SUBCMD "1" >&2
;;
esac
done
shift $(expr $OPTIND - 1)
trap exit_clean INT TERM
cmd_$SUBCMD "$@"
retcode=$?
cleanup
exit $retcode