lbu 11.6 KB
Newer Older
Natanael Copa's avatar
Natanael Copa committed
1 2 3 4 5 6
#!/bin/sh

# lbu - utility to create local backups.
# Copyright (c) 2006 Natanael Copa
# May be distributed under GPL2

7
VERSION=1.2
Natanael Copa's avatar
Natanael Copa committed
8

9 10 11 12
if [ ! -f ${libalpine:="./libalpine.sh"} ]; then
	libalpine=/usr/share/lbu/libalpine.sh
fi
. $libalpine || exit 1
Natanael Copa's avatar
Natanael Copa committed
13 14 15 16

EXCLUDE_LIST=/etc/lbu/exclude
INCLUDE_LIST=/etc/lbu/include

Natanael Copa's avatar
Natanael Copa committed
17 18 19 20 21
DEFAULT_CIPHER="aes-256-cbc"

LBU_CONF=/etc/lbu/lbu.conf
if [ -f "$LBU_CONF" ]; then
	. "$LBU_CONF"
22 23
fi

Natanael Copa's avatar
Natanael Copa committed
24 25
UMOUNT_LIST=

Natanael Copa's avatar
Natanael Copa committed
26 27 28 29 30 31 32 33
usage() {
	echo "$PROGRAM $VERSION"
	echo "usage: $PROGRAM <subcommand> [options] [args]

Available subcommands:
  commit (ci)
  exclude (ex, delete)
  include (inc, add)
34 35
  list (ls)
  package (pkg)
Natanael Copa's avatar
Natanael Copa committed
36
  status (stat, st)
37
  list-backup (lb) 
Natanael Copa's avatar
Natanael Copa committed
38
  revert
Natanael Copa's avatar
Natanael Copa committed
39 40 41 42 43 44 45 46 47

Common options:
 -h	Show help for subcommand.
 -q	Quiet mode.
 -v	Verbose mode.
"
	exit 1
}

Natanael Copa's avatar
Natanael Copa committed
48 49 50 51 52 53 54 55 56 57 58 59 60
cleanup() {
	local i
	for i in $UMOUNT_LIST; do
		umount $i
	done
}

exit_clean() {
	cleanup
	exit 1
}

mount_once() {
61
	if ! grep $1 /proc/mounts >/dev/null; then
Natanael Copa's avatar
Natanael Copa committed
62 63 64 65
		mount $1 && UMOUNT_LIST="$1 $UMOUNT_LIST"
	fi
}

Natanael Copa's avatar
Natanael Copa committed
66 67 68
# create backupfile
backup_apkovl() {
	local outfile="$1"
69
	local d=$( date -u -r "$outfile" "+%Y%m%d%H%M%S" )
Natanael Copa's avatar
Natanael Copa committed
70 71 72 73 74 75 76
	local backup=$(echo "$outfile" | sed "s/\.apkovl\.tar\.gz/.$d.tar.gz/")
	vecho "Creating backup $backup"
	if [ -z "$DRYRUN" ]; then
		mv "$outfile" "$backup"
	fi
}

Natanael Copa's avatar
Natanael Copa committed
77
# verify we have openssl if we want to encrypt
78 79 80
check_openssl() {
	[ -z "$ENCRYPTION" ] && return 0
	OPENSSL=$(which openssl 2>/dev/null) || die "openssl was not found"
Natanael Copa's avatar
Natanael Copa committed
81

82 83 84
	$OPENSSL list-cipher-commands | grep "^$ENCRYPTION$" > /dev/null \
		|| die "Cipher $ENCRYPTION is not supported"
}
Natanael Copa's avatar
Natanael Copa committed
85 86 87 88 89 90 91 92 93

# 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
94
			vecho "$filename is already in $list."
Natanael Copa's avatar
Natanael Copa committed
95
		else
96
			vecho "Adding $filename to $list."
Natanael Copa's avatar
Natanael Copa committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
			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"
112
		vecho "Removing $filename from list."
Natanael Copa's avatar
Natanael Copa committed
113 114 115 116 117 118 119
		grep -v "^$filename$" "$tmp" > "$list"
		rm "$tmp"
		shift
	done
}


120

Natanael Copa's avatar
Natanael Copa committed
121 122 123 124 125
#
# lbu_include - add/remove files to include list
#
usage_include() {
	echo "$PROGRAM $VERSION
126 127 128 129
Add filename(s) to include list (/etc/lbu/include)

usage: $PROGRAM include|inc|add [-rv] <file> ...
       $PROGRAM include|inc|add [-v] -l
Natanael Copa's avatar
Natanael Copa committed
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

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
145

Natanael Copa's avatar
Natanael Copa committed
146 147 148 149 150 151 152 153 154 155 156
	[ $# -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
157
		vecho "Include files:"
Natanael Copa's avatar
Natanael Copa committed
158 159 160 161
		cat "$INCLUDE_LIST"
	fi
}

162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
#
# lbu_package - create a package
#
usage_package() {
	echo "$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 direcotry, a package with the
specified name willbe created.

180
If <dirname> nor <filename> is not specified, a package named
181 182 183 184 185 186 187 188 189
<hostname>.apkovl.tar.gz will be created in current work directory.
"
	exit 1
}

cmd_package() {
	local pkg="$1"
	local rc=0
	local owd="$PWD"
190
	local suff="apkovl.tar.gz"
Natanael Copa's avatar
Natanael Copa committed
191
	local tmpdir tmppkg
192 193

	check_openssl
Natanael Copa's avatar
Natanael Copa committed
194
	init_tmpdir tmpdir
195 196

	[ -n "$ENCRYPTION" ] && suff="$suff.$ENCRYPTION"
197 198 199

	# find filename
	if [ -d "$pkg" ] ; then
200
		pkg="$pkg/$(hostname).$suff"
201
	elif [ -z "$pkg" ]; then
202
		pkg="$PWD/$(hostname).$suff"
203 204
	fi

Natanael Copa's avatar
Natanael Copa committed
205 206
	tmppkg="$tmpdir/$(basename $pkg)"

207 208
	cd "${ROOT:-/}"
	currentlist=$(apk audit --backup -q)
209 210 211
	if [ -f var/lib/apk/world ]; then
		currentlist="$currentlist var/lib/apk/world"
	fi
212

213 214 215
	# create tar archive
	[ -f "$EXCLUDE_LIST" ] && excl="-X $EXCLUDE_LIST"
	[ -f "$INCLUDE_LIST" ] && incl="-T $INCLUDE_LIST"
Natanael Copa's avatar
Natanael Copa committed
216 217 218 219 220
	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
Natanael Copa's avatar
Natanael Copa committed
221
		rc=$?
Natanael Copa's avatar
Natanael Copa committed
222 223 224 225
	fi
	if [ $rc -eq 0 ]; then
		if [ -z "$ENCRYPTION" ]; then
			tar $excl $incl -c $currentlist | gzip -c  >"$tmppkg"
Natanael Copa's avatar
Natanael Copa committed
226
			rc=$?
Natanael Copa's avatar
Natanael Copa committed
227
		else
Natanael Copa's avatar
Natanael Copa committed
228 229 230 231 232
			set -- enc "-$ENCRYPTION" -salt
			[ -n "$PASSWORD" ] && set -- "$@" -pass pass:"$PASSWORD"
			tar $excl $incl -c $currentlist | gzip -c \
				| $OPENSSL "$@" > "$tmppkg"
			rc=$?
233
		fi
234
	fi
Natanael Copa's avatar
Natanael Copa committed
235
	cd "$owd"
Natanael Copa's avatar
Natanael Copa committed
236

Natanael Copa's avatar
Natanael Copa committed
237
	# actually commit unless dryrun mode
Natanael Copa's avatar
Natanael Copa committed
238
	if [ $rc -eq 0 ]; then
239 240 241 242 243 244 245
		if [ -z "$DRYRUN" ]; then
			if [ "x$pkg" = "x-" ]; then
				cat "$tmppkg"
			else
				cp "$tmppkg" "$pkg"
			fi
		fi
Natanael Copa's avatar
Natanael Copa committed
246
		vecho "Created $pkg"
Natanael Copa's avatar
Natanael Copa committed
247
	fi
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
	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
}
Natanael Copa's avatar
Natanael Copa committed
267 268 269 270 271 272

#
# lbu_commit - commit config files to writeable media
#
usage_commit() {
	echo "$PROGRAM $VERSION
273 274 275
Create a backup of config to writeable media.

usage: $PROGRAM commit|ci [-nv] [<media>]
Natanael Copa's avatar
Natanael Copa committed
276 277

Options:
Natanael Copa's avatar
Natanael Copa committed
278
  -d	Remove old apk overlay files.
279
  -e	Protect configuration with a password.
Natanael Copa's avatar
Natanael Copa committed
280
  -n	Don't commit, just show what would have been commited.
Natanael Copa's avatar
Natanael Copa committed
281
  -p <password>	Give encryption password on the command-line
Natanael Copa's avatar
Natanael Copa committed
282 283 284 285
  -v	Verbose mode.

The following values for <media> is supported: floppy usb
If <media> is not specified, the environment variable LBU_MEDIA will be used.
Natanael Copa's avatar
Natanael Copa committed
286

287
Password protection will use $DEFAULT_CIPHER encryption. Other ciphers can be
Natanael Copa's avatar
Natanael Copa committed
288 289 290
used by setting the DEFAULT_CIPHER or ENCRYPTION environment variables.
For possible ciphers, try: openssl -v

Natanael Copa's avatar
Natanael Copa committed
291 292
The password used to encrypt the file, can either be specified with the -p
option or using the PASSWORD environment variable.
293

Natanael Copa's avatar
Natanael Copa committed
294
The environment varialbes can also be set in $LBU_CONF
Natanael Copa's avatar
Natanael Copa committed
295 296 297 298 299
"
	exit 1
}

cmd_commit() {
Natanael Copa's avatar
Natanael Copa committed
300
	local media mnt statuslist tmplist currentlist
301 302 303 304
	local incl excl outfile ovls lines

	check_openssl

Natanael Copa's avatar
Natanael Copa committed
305 306 307
	# turn on verbose mode if dryrun
	[ -n "$DRYRUN" ] && VERBOSE="-v"

308
	# find what media to use
Natanael Copa's avatar
Natanael Copa committed
309
	media="${1:-$LBU_MEDIA}"
Natanael Copa's avatar
Natanael Copa committed
310 311
	[ -z "$media" ] && usage_commit

312
	# mount media unles its already mounted
Natanael Copa's avatar
Natanael Copa committed
313
	mnt=/media/$media
Natanael Copa's avatar
Natanael Copa committed
314
	[ -d "$mnt" ] || usage
Natanael Copa's avatar
Natanael Copa committed
315
	mount_once "$mnt" || die "failed to mount $mnt"
Natanael Copa's avatar
Natanael Copa committed
316

Natanael Copa's avatar
Natanael Copa committed
317 318 319 320 321 322
	# find the outfile
	outfile="$mnt/$(hostname).apkovl.tar.gz"
	if [ -n "$ENCRYPTION" ]; then
		outfile="$outfile.$ENCRYPTION"
	fi

Natanael Copa's avatar
Natanael Copa committed
323

Natanael Copa's avatar
Natanael Copa committed
324
	# remove old config files
325
	if [ -n "$DELETEOLDCONFIGS" ] ; then
Natanael Copa's avatar
Natanael Copa committed
326 327 328 329
		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
330
				echo "$rmfiles"
Natanael Copa's avatar
Natanael Copa committed
331
				echo "" >&2
332
			fi
Natanael Copa's avatar
Natanael Copa committed
333
			[ -z "$DRYRUN" ] && rm "$mnt/"*.apkovl.tar.gz*
334 335
		fi
	else
Natanael Copa's avatar
Natanael Copa committed
336
       		lines=$(ls -1 "$mnt"/*.apkovl.tar.gz* 2>/dev/null)
Natanael Copa's avatar
Natanael Copa committed
337
		if [ "$lines" = "$outfile" ]; then
Natanael Copa's avatar
Natanael Copa committed
338
			backup_apkovl "$outfile"
Natanael Copa's avatar
Natanael Copa committed
339
		elif [ -n "$lines" ]; then
340
	               	# More then one apkovl, this is a security concern
Natanael Copa's avatar
Natanael Copa committed
341
			cleanup
Natanael Copa's avatar
Natanael Copa committed
342 343 344 345
			eecho "The following apkovl file(s) were found:"
			eecho "$lines"
			eecho ""
	               	die "Please use -d to replace."
346
		fi
Natanael Copa's avatar
Natanael Copa committed
347
	fi
348 349 350

	# create package
	if ! cmd_package "$outfile"; then
Natanael Copa's avatar
Natanael Copa committed
351
		cleanup
Natanael Copa's avatar
Natanael Copa committed
352
		die "Problems creating archive. aborting"
353
	fi
Natanael Copa's avatar
Natanael Copa committed
354

355 356
	# delete old backups if needed
	# poor mans 'head -n -N' done with awk.
357
	ls "$mnt"/$(hostname).[0-9][0-9][0-9][0-9]*[0-9].tar.gz 2>/dev/null \
358 359 360 361 362 363 364
		| awk '{ a[++i] = $0; } END { 
			print a[0]; 
			while (i-- > '"${BACKUP_LIMIT:-0}"') { 
				print a[++j] 
			}
		}' | xargs rm 2>/dev/null

Natanael Copa's avatar
Natanael Copa committed
365 366
	# remove obsolete file. some older version of alpine needs this
	# to be ble to upgrade
367 368
	if [ -z "$DRYRUN" ] && [ -f $mnt/packages.list ]; then
		echo "Note: Removing packages.list from $(basename $mnt)."
Natanael Copa's avatar
Natanael Copa committed
369
		echo "      $PACKAGES_LIST will be used."
370 371
		rm -f $mnt/packages.list
	fi
Natanael Copa's avatar
Natanael Copa committed
372

Natanael Copa's avatar
Natanael Copa committed
373
	# make sure data is written
Natanael Copa's avatar
Natanael Copa committed
374
	sync
Natanael Copa's avatar
Natanael Copa committed
375
	[ "$media" = "floppy" ] && sleep 1
Natanael Copa's avatar
Natanael Copa committed
376 377

	# move current to commited.
378
	vecho "Successfully saved apk overlay files"
Natanael Copa's avatar
Natanael Copa committed
379 380 381 382 383 384 385
}

#---------------------------------------------------------------------------
# lbu_exclude - add remove file(s) from exclude list

usage_exclude() {
	echo "$PROGRAM $VERSION
386 387 388 389
Add filename(s) to exclude list (/etc/lbu/exclude)

usage: $PROGRAM exclude|ex|delete [-rv] <file> ...
       $PROGRAM exclude|ex|delete [-v] -l
Natanael Copa's avatar
Natanael Copa committed
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404

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
405

Natanael Copa's avatar
Natanael Copa committed
406 407 408 409 410 411 412 413 414 415 416
	[ $# -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
417
		vecho "Exclude files:"
Natanael Copa's avatar
Natanael Copa committed
418
		cat "$EXCLUDE_LIST"
419
	fi
Natanael Copa's avatar
Natanael Copa committed
420 421
}

Natanael Copa's avatar
Natanael Copa committed
422
#---------------------------------------------------------------------------
423
# lbu_listbackup - Show old commits
424
usage_listbackup() {
Natanael Copa's avatar
Natanael Copa committed
425 426 427 428
	cat <<EOF
$PROGRAM $VERSION
Show old commits.

429
usage: $PROGRAM list-backup [<media>]
Natanael Copa's avatar
Natanael Copa committed
430 431 432 433 434

EOF
	exit 1
}

435
cmd_listbackup() {
Natanael Copa's avatar
Natanael Copa committed
436 437
	local media=${1:-"$LBU_MEDIA"}
	local mnt="/media/$media"
Natanael Copa's avatar
Natanael Copa committed
438
	[ -z "$media" ] && usage_listbackup
Natanael Copa's avatar
Natanael Copa committed
439 440 441 442 443 444 445 446

	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() {
Natanael Copa's avatar
Natanael Copa committed
447 448 449 450 451 452
	cat <<EOF
$PROGRAM $VERSION
Revert to older commit.

usage: $PROGRAM revert <REVISION> [<media>]

Natanael Copa's avatar
Natanael Copa committed
453
The revision should be one of the files listed by 'lbu list-backup'.
Natanael Copa's avatar
Natanael Copa committed
454

Natanael Copa's avatar
Natanael Copa committed
455
EOF
Natanael Copa's avatar
Natanael Copa committed
456 457 458 459
}

cmd_revert() {
	local media=${2:-"$LBU_MEDIA"}
Natanael Copa's avatar
Natanael Copa committed
460 461 462 463 464 465
	[ -z "$media" ] && usage_revert
	local mnt="/media/$media"
	local revertto="$mnt/$1"
	local current="$mnt/$(hostname).apkovl.tar.gz"

	if [ -n "$ENCRYPTION" ]; then
466
		current="$current.$ENCRYPTION"
Natanael Copa's avatar
Natanael Copa committed
467 468 469
	fi
	mount_once "$mnt" || die "failed to mount $mnt"
	[ -f "$revertto" ] || die "file not found: $revertto"
470
	backup_apkovl "$current"
Natanael Copa's avatar
Natanael Copa committed
471
	vecho "Reverting to $1"
472
	[ -z "$DRYRUN" ] && mv "$revertto" "$current"
Natanael Copa's avatar
Natanael Copa committed
473 474
}

Natanael Copa's avatar
Natanael Copa committed
475 476 477 478 479 480
#---------------------------------------------------------------------------
# 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.

481
usage: $PROGRAM status|st [-av]
Natanael Copa's avatar
Natanael Copa committed
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507

Options:
  -a	Compare all files, not just since last commit.
  -v	Also show include and exclude lists.
"
	exit 1
}



#-----------------------------------------------------------
# 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";;
508 509
	list|ls)		SUBCMD="list";;
	package|pkg)		SUBCMD="package";;
Natanael Copa's avatar
Natanael Copa committed
510
	status|stat|st)		SUBCMD="status";;
511
	list-backup|lb)		SUBCMD="listbackup";;
Natanael Copa's avatar
Natanael Copa committed
512
	revert)			SUBCMD="revert";;
Natanael Copa's avatar
Natanael Copa committed
513 514 515 516
	*)			usage;;
esac

# parse common args
517
while getopts "adehlM:np:qrv" opt ; do
Natanael Copa's avatar
Natanael Copa committed
518 519 520 521
	case "$opt" in
		a) 	[ $SUBCMD = status ] || usage_$SUBCMD
			USE_DEFAULT="-a"
			;;
522 523
		d)	DELETEOLDCONFIGS="yes"
			;;
524
		e)	[ -z "$ENCRYPTION" ] && ENCRYPTION="$DEFAULT_CIPHER"
525
			;;
Natanael Copa's avatar
Natanael Copa committed
526 527 528 529 530 531 532
		h) 	usage_$SUBCMD
			;;
		l)	LIST="-l"
			;;
		n) 	[ $SUBCMD = commit ] || usage_$SUBCMD
			DRYRUN="-n"
			;;
533 534
		p)	PASSWORD="$OPTARG"
			;;
Natanael Copa's avatar
Natanael Copa committed
535 536 537 538 539 540 541 542 543
		q)	QUIET="$QUIET -q"
			;;
		r)	REMOVE="-r"
			;;
		v) 	VERBOSE="$VERBOSE -v"
			;;
	esac
done
shift `expr $OPTIND - 1`
544

Natanael Copa's avatar
Natanael Copa committed
545
trap exit_clean SIGINT SIGTERM
546
cmd_$SUBCMD "$@"
Natanael Copa's avatar
Natanael Copa committed
547 548 549
retcode=$?

cleanup
Natanael Copa's avatar
Natanael Copa committed
550
exit $retcode