diff options
-rwxr-xr-x | scripts/system-backup | 101 | ||||
-rwxr-xr-x | scripts/system-restore | 134 | ||||
-rw-r--r-- | src/main/java/org/openslx/taskmanager/tasks/BackupRestore.java | 58 |
3 files changed, 234 insertions, 59 deletions
diff --git a/scripts/system-backup b/scripts/system-backup index 9d65cd2..52422bb 100755 --- a/scripts/system-backup +++ b/scripts/system-backup @@ -1,11 +1,31 @@ #!/bin/bash +encrypt= +destination= +while (( $# > 0 )); do + case "$1" in + --encrypt) + encrypt="$2" + shift + ;; + --destination) + destination="$2" + shift + ;; + *) + echo "Unknown option, '$1'" + exit 1 + ;; + esac + shift +done + if [ "$(whoami)" != "root" ]; then echo "Must be running as root!" exit 1 fi -DIR="/root/backup/$(date +%s)" +DIR="/tmp/bwlp-backup-$(date +%s)" if [ -d "$DIR" ]; then echo "Backup already running!?" @@ -15,48 +35,85 @@ fi mkdir -p "$DIR" cd "$DIR" || exit 1 -mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --add-locks --add-drop-database --default-character-set=utf8 --databases openslx > openslx.sql +trap 'rm -rf -- "$DIR"' EXIT + +mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --add-locks --add-drop-database --default-character-set=utf8mb4 --databases openslx > openslx.sql RET1=$? -mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --add-locks --add-drop-database --default-character-set=utf8 --databases sat > sat.sql +mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --add-locks --add-drop-database --default-character-set=utf8mb4 --databases sat > sat.sql RET2=$? -if [ $RET1 -ne 0 ] || [ $RET2 -ne 0 ]; then +if (( RET1 != 0 || RET2 != 0 )); then echo "Database dump failed with exit code $RET1/$RET2" exit 1 fi -FILELIST=" - /opt/openslx/configs - /etc/lighttpd/server.pem - /etc/lighttpd/chain.pem - /etc/lighttpd/pub-cert.pem -" +FILELIST=( + "/opt/openslx/configs" + "/etc/lighttpd/server.pem" + "/etc/lighttpd/chain.pem" + "/etc/lighttpd/pub-cert.pem" +) -tar --ignore-failed-read -k -c -p -z -f files.tgz $FILELIST # no quotes here! +tar --ignore-failed-read -k -c -p -z -f "files.tgz" "${FILELIST[@]}" RET=$? -if [ $RET -ne 0 ]; then +if (( RET != 0 )); then echo "WARNING: filesystem-tar exited with code $RET - backup might be incomplete!" fi -tar -k -c -z -f backup.tgz files.tgz openslx.sql sat.sql +ext="tgz" +tmpfile="/tmp/bwlp-${RANDOM}-$(date +%s)-backup.${ext}" +tar -k -c -z -f "backup.tgz" "files.tgz" "openslx.sql" "sat.sql" RET=$? -if [ ! -f backup.tgz ]; then +if ! [ -f "backup.tgz" ]; then echo "Creating backup.tgz failed!" exit 1 fi -if [ $RET -ne 0 ]; then +if (( RET != 0 )); then echo "WARNING: final tar exited with code $RET - backup might be incomplete!" fi -chown www-data backup.tgz -chmod 0600 backup.tgz - -FILE="/tmp/bwlp-backup-$(date +%s)-${RANDOM}.tgz" -if ! mv backup.tgz "$FILE"; then - echo "moving backup to $FILE failed." +chmod 0600 "backup.tgz" +if ! mv "backup.tgz" "$tmpfile"; then + echo "ERROR: Could not move backup.tgz to $tmpfile" exit 1 fi -rm -rf -- /root/backup/1* +if [ -n "$encrypt" ]; then + if ! openssl enc -aes-256-cbc -pbkdf2 -pass "env:$encrypt" -in "${tmpfile}" -out "${tmpfile}.aes" \ + && ! openssl enc -aes-256-cbc -pass "env:$encrypt" -in "${tmpfile}" -out "${tmpfile}.aes"; then + rm -f -- "$tmpfile" + echo "Error encrypting backup with openssl" + exit 1 + fi + rm -f -- "$tmpfile" + ext="${ext}.aes" + tmpfile="${tmpfile}.aes" +fi + +if [ -z "$destination" ]; then + # No destination given, as this is for download, give www-data user access to file + FILE="${tmpfile}" + chown www-data "${tmpfile}" +else + FILE="${destination}.${ext}" + dir="${destination%/*}" + for usr in "" "dmsd" "dnbd3" "FAIL"; do + [ "$usr" = "FAIL" ] && break + if [ -z "$usr" ]; then + mkdir -p "$dir" + mv "$tmpfile" "$FILE" && break + else + chown "$usr:$(id -g "$usr")" "$tmpfile" + sudo -n -u "$usr" mkdir -p "$dir" + sudo -n -u "$usr" cp "$tmpfile" "$FILE" && break + fi + done + if [ "$usr" = "FAIL" ] || ! [ -s "$FILE" ]; then + echo "Moving backup to '$FILE' failed." + exit 1 + fi +fi + +chmod 0600 "$FILE" echo "Location: $FILE" exit 0 diff --git a/scripts/system-restore b/scripts/system-restore index d65460e..0a9d02e 100755 --- a/scripts/system-restore +++ b/scripts/system-restore @@ -1,5 +1,7 @@ #!/bin/bash +# $0 [--decrypt <pass>] <backup_file> [openslx] [dozmod] + TMDIR="/opt/taskmanager" BACKUP="$1" @@ -11,6 +13,8 @@ shift RES_OPENSLX=0 RES_SAT=0 +decrypt= +mode= while [ $# -gt 0 ]; do case "$1" in openslx) @@ -21,6 +25,17 @@ while [ $# -gt 0 ]; do RES_SAT=1 echo "Restoring VM and lecture db" ;; + --decrypt) + decrypt="$2" + echo "Expecting AES encrypted archive" + shift + ;; + --restore) + mode="restore" + ;; + --test) + mode="test" + ;; *) echo "Error: Restore mode params must be one of openslx, dozmod (Got $1)" exit 1 @@ -29,6 +44,41 @@ while [ $# -gt 0 ]; do shift done +decryptor() { + if ! openssl enc -d -aes-256-cbc -pbkdf2 -pass "env:$decrypt" -in "$1" -out "$2" \ + && ! openssl enc -d -aes-256-cbc -pass "env:$decrypt" -in "$1" -out "$2"; then + echo "- - - - - - - - - - - - - - - - -" + echo "- Could not decrypt backup" + echo "- Wrong password?" + echo "- - - - - - - - - - - - - - - - -" + rm -f -- "$2" + exit 1 + fi +} + +if [ -z "$mode" ]; then + echo "No mode given" + exit 1 +elif [ "$mode" = "test" ]; then + # test + if [ -n "$decrypt" ]; then + out="/tmp/bwlp-test-${RANDOM}-$$.tgz" + decryptor "$BACKUP" "$out" + echo "- Decrypted backup successfully" + rm -f -- "$BACKUP" + BACKUP="$out" + fi + num=$( tar tf "$BACKUP" | grep -c -x -F -e "files.tgz" -e "sat.sql" -e "openslx.sql" -e "db.sql" ) + rm -f -- "$BACKUP" + if (( num < 2 )); then + echo "- - -" + echo "- This does not look like a .tar.gz containing a Satellite Server backup" + exit 1 + fi + exit 0 + # End test +fi + [ "$RES_OPENSLX$RES_SAT" = "00" ] && exit 1 if [ "$(whoami)" != "root" ]; then @@ -36,6 +86,19 @@ if [ "$(whoami)" != "root" ]; then exit 1 fi +slxsql() { + mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8mb4 "$@" +} + +slxsqldump() { + mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8mb4 "$@" +} + +cleanup_hook() { + rm -rf -- "$DIR" + rm -f -- "$BACKUP" +} + DIR="/root/restore/$(date +%s)" if [ -d "$DIR" ]; then @@ -43,14 +106,26 @@ if [ -d "$DIR" ]; then exit 1 fi +trap cleanup_hook EXIT + mkdir -p "$DIR" if ! cd "$DIR"; then echo "Could not cd to $DIR" exit 1 fi +# Decrypt +if [ -n "$decrypt" ]; then + out="${BACKUP%.aes}" + [ "$out" = "$BACKUP" ] && out="${BACKUP}.tgz" + decryptor "$BACKUP" "$out" + rm -f -- "$BACKUP" + BACKUP="$out" +fi + if ! tar --ignore-failed-read -x -f "$BACKUP"; then - echo "Could not extract $BACKUP - make sure it's a valid .tar.gz / .tgz" + echo "Could not extract $BACKUP - make sure it's a valid .tar.gz[.aes] / .tgz[.aes]" + echo "And that you provide the password in case of .aes" exit 1 fi @@ -63,69 +138,69 @@ else exit 1 fi -if [ $RES_SAT -eq 1 -a $DB_OLD -eq 0 -a ! -f sat.sql ]; then - echo "Error: this backup does not contain the DozMod database" +if (( RES_SAT == 1 && DB_OLD == 0 )) && ! [ -f sat.sql ]; then + echo "Error: this backup does not contain the dmsd database" echo "Error: cannot restore VM/lecture information" exit 1 fi -if [ $RES_OPENSLX -eq 1 -a $DB_OLD -eq 0 -a ! -f openslx.sql ]; then +if (( RES_OPENSLX == 1 && DB_OLD == 0 )) && ! [ -f openslx.sql ]; then echo "Error: this backup does not contain the OpenSLX database" echo "Error: cannot restore satellite configuration" exit 1 fi -if [ $RES_OPENSLX -eq 1 -a ! -f files.tgz ]; then +if (( RES_OPENSLX == 1 )) && ! [ -f files.tgz ]; then echo "Error: files.tgz not found in backup - are your sure this is a valid backup?" exit 1 fi echo "-- Restoring Database" -if [ $DB_OLD -eq 1 ]; then +if (( DB_OLD == 1 )); then echo "--- Importing legacy database dump" # Restoring from dozmod v1.0 db - mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8 < db.sql + slxsql < db.sql RET=$? - if [ $RES_SAT -eq 1 ]; then + if (( RES_SAT == 1 )); then echo "--- Trying to convert dozmod data (this might not work too well...)" - mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8 < "${TMDIR}/data/dozmod-upgrade.sql" + slxsql < "${TMDIR}/data/dozmod-upgrade.sql" else - echo "DROP DATABASE bwLehrpool" | mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8 + echo "DROP DATABASE bwLehrpool" | slxsql fi else # Restoring from v1.1+ db RET=0 - if [ $RES_SAT -eq 1 ]; then + if (( RES_SAT == 1 )); then echo "--- Importing dozmod database (vms/lectures meta data)" - mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8 < sat.sql + slxsql < sat.sql RET=$? fi - if [ $RET -eq 0 -a $RES_OPENSLX -eq 1 ]; then + if (( RET == 0 && RES_OPENSLX == 1 )); then echo "--- Importing system configuration" # Backup and restore minilinux metadata -- doesn't make sense to import this from the backup mtmp="$( mktemp )" - mysqldump --defaults-extra-file=/etc/mysql/debian.cnf --add-locks --default-character-set=utf8 openslx minilinux_source minilinux_branch minilinux_version > "${mtmp}" 2> /dev/null + slxsqldump --add-locks openslx minilinux_source minilinux_branch minilinux_version > "${mtmp}" 2> /dev/null mlret=$? - mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8 < openslx.sql + slxsql < openslx.sql RET=$? - if [ "$mlret" = 0 ]; then - mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8 --database=openslx -e "DROP TABLE IF EXISTS minilinux_version, minilinux_branch, minilinux_source" - mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8 --database=openslx < "${mtmp}" + if (( mlret == 0 )); then + slxsql --database=openslx -e "DROP TABLE IF EXISTS minilinux_version, minilinux_branch, minilinux_source" + slxsql --database=openslx < "${mtmp}" fi rm -f -- "$mtmp" fi fi -if [ $RET -ne 0 ]; then +if (( RET != 0 )); then echo "Error: Restoring database contents failed with exit code $RET" exit 1 fi -if [ $RES_OPENSLX -eq 1 ]; then +if (( RES_OPENSLX == 1 )); then echo "-- Restoring system files" # Since we came that far we'll delete some old configs (if existent) rm -rf /opt/ldadp/{configs,pid,logs}/* /opt/openslx/configs/* /srv/openslx/www/boot/default/config.tgz 2> /dev/null # Force triggering IP detection/setting, which should in turn regenerate ldadp configs and launch ldadp instances if applicable - mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8 -e "UPDATE openslx.property SET value = 'invalid' WHERE name = 'server-ip' LIMIT 1" + slxsql -e "UPDATE openslx.property SET value = 'invalid' WHERE name = 'server-ip' LIMIT 1" tar --ignore-failed-read -x -f files.tgz -C / RET=$? @@ -138,23 +213,22 @@ if [ $RES_OPENSLX -eq 1 ]; then # Try to update the db (if required) ( - cd /srv/openslx/www/slx-admin - ./install-all + cd /srv/openslx/www/slx-admin && ./install-all ) - # config.tgz symlink -> db entry - if [ -L /srv/openslx/www/boot/default/config.tgz ]; then - CONFTGZ=$(readlink /srv/openslx/www/boot/default/config.tgz | sed "s/'/\\\'/g") + # legacy config.tgz symlink -> db entry + if [ -L "/srv/openslx/www/boot/default/config.tgz" ]; then + CONFTGZ=$( readlink /srv/openslx/www/boot/default/config.tgz | sed "s/\\\\/\\\\\\\\/g;s/'/\\\'/g" ) echo "Config.tgz links to '$CONFTGZ'" - mysql --defaults-extra-file=/etc/mysql/debian.cnf --default-character-set=utf8 -e "INSERT IGNORE INTO openslx.configtgz_location (locationid, configid) SELECT 0, configid FROM openslx.configtgz WHERE filepath = '$CONFTGZ' LIMIT 1" \ + slxsql -e "INSERT IGNORE INTO openslx.configtgz_location (locationid, configid) SELECT 0, configid FROM openslx.configtgz WHERE filepath = '$CONFTGZ' LIMIT 1" \ || echo "Could not convert default config.tgz setting - do so manually" rm -f -- /srv/openslx/www/boot/default/config.tgz fi sleep 0.5 for i in 1 1 1 1 1 2 2 3 4 END; do - CB=$(sudo -u www-data -n php /srv/openslx/www/slx-admin/api.php cb) - [ "x$CB" != "xTrue" ] && break + CB=$( sudo -u www-data -n php /srv/openslx/www/slx-admin/api.php cb ) + [ "$CB" != "True" ] && break [ "$i" = "END" ] && break sleep $i done @@ -170,8 +244,6 @@ for i in /opt/openslx/restore.d/*/init.sh; do "$i" || echo "ERROR running post-restore script $i: $?" done -rm -rf -- "$DIR" -rm -f -- "$BACKUP" echo "Success." diff --git a/src/main/java/org/openslx/taskmanager/tasks/BackupRestore.java b/src/main/java/org/openslx/taskmanager/tasks/BackupRestore.java index 32a5beb..7c05b91 100644 --- a/src/main/java/org/openslx/taskmanager/tasks/BackupRestore.java +++ b/src/main/java/org/openslx/taskmanager/tasks/BackupRestore.java @@ -1,7 +1,9 @@ package org.openslx.taskmanager.tasks; +import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import org.openslx.satserver.util.Constants; @@ -19,6 +21,10 @@ public class BackupRestore extends SystemCommandTask private boolean restoreOpenslx; @Expose private boolean restoreDozmod; + @Expose + private String password; + @Expose + private String destination; private Output status = new Output(); @@ -29,15 +35,15 @@ public class BackupRestore extends SystemCommandTask { this.setStatusObject( this.status ); if ( mode == null ) { - status.addMessage( "Mode given." ); + status.addMessage( "No mode given." ); return false; } - if ( !mode.equals( "backup" ) && !mode.equals( "restore" ) ) { + if ( !mode.equals( "backup" ) && !mode.equals( "restore" ) && !mode.equals( "test" ) ) { status.addMessage( "Invalid mode: " + mode ); return false; } - if ( mode.equals( "restore" ) && backupFile == null ) { - status.addMessage( "No backup file given to restore!" ); + if ( backupFile == null && ( mode.equals( "restore" ) || mode.equals( "test" ) ) ) { + status.addMessage( "No backup file given to restore/test!" ); return false; } this.timeoutSeconds = 180; @@ -56,8 +62,9 @@ public class BackupRestore extends SystemCommandTask args.add( "-n" ); args.add( "-u" ); args.add( "root" ); - args.add( Constants.BASEDIR + "/scripts/system-" + mode ); if ( mode.equals( "restore" ) ) { + // Restore + args.add( Constants.BASEDIR + "/scripts/system-restore" ); if ( backupFile != null ) { args.add( backupFile ); } @@ -67,15 +74,54 @@ public class BackupRestore extends SystemCommandTask if ( restoreOpenslx ) { args.add( "openslx" ); } + args.add( "--restore" ); + if ( password != null ) { + args.add( "--decrypt" ); + args.add( "TM_PW_ENV_VAR" ); + } + } else if ( mode.equals( "test" ) ) { + // Test archive encryption / archive format + args.add( Constants.BASEDIR + "/scripts/system-restore" ); + args.add( backupFile ); + args.add( "--test" ); + if ( password != null ) { + args.add( "--decrypt" ); + args.add( "TM_PW_ENV_VAR" ); + } + } else { + // Backup + args.add( Constants.BASEDIR + "/scripts/system-backup" ); + if ( password != null ) { + args.add( "--encrypt" ); + args.add( "TM_PW_ENV_VAR" ); + } + if ( destination != null ) { + args.add( "--destination" ); + args.add( destination ); + } } return args.toArray( new String[ args.size() ] ); } @Override + protected void initEnvironment( Map<String, String> environment ) + { + if ( password != null ) { + environment.put( "TM_PW_ENV_VAR", password ); + } + } + + @Override protected boolean processEnded( int exitCode ) { + if ( backupFile != null ) { + try { + new File( backupFile ).delete(); + } catch ( Exception e ) { + } + } isRunning.set( false ); - return exitCode == 0 && ( mode.equals( "restore" ) || status.backupFile != null ); + return exitCode == 0 && ( mode.equals( "test" ) || mode.equals( "restore" ) || status.backupFile != null ); } @Override |