|
@@ -1,131 +1,139 @@
|
|
|
-#!/bin/sh
|
|
|
-# Requirements : borgbackup
|
|
|
-
|
|
|
-# Read configuration
|
|
|
-. /etc/concierge/backup.cfg
|
|
|
-
|
|
|
-if [ -z "${LOCAL_DIR}" ]; then
|
|
|
- echo >&2 "LOCAL_DIR is not set. Aborting.";
|
|
|
- exit 1;
|
|
|
-fi
|
|
|
-
|
|
|
-if [ -z "${REMOTE_ENABLE}" ]; then
|
|
|
-
|
|
|
- echo >&2 "REMOTE_ENABLE is not set. Aborting.";
|
|
|
- exit 1;
|
|
|
-
|
|
|
-elif [ ${REMOTE_ENABLE} = true ]; then
|
|
|
-
|
|
|
- if [ -z "${REMOTE_USER}" ]; then
|
|
|
- echo >&2 "REMOTE_USER is not set. Aborting.";
|
|
|
- exit 1;
|
|
|
- fi
|
|
|
-
|
|
|
- if [ -z "${REMOTE_HOSTNAME}" ]; then
|
|
|
- echo >&2 "REMOTE_HOSTNAME is not set. Aborting.";
|
|
|
- exit 1;
|
|
|
- fi
|
|
|
-
|
|
|
- if [ -z "${REMOTE_DIR}" ]; then
|
|
|
- echo >&2 "REMOTE_DIR is not set. Aborting.";
|
|
|
- exit 1;
|
|
|
- fi
|
|
|
-
|
|
|
-fi
|
|
|
-
|
|
|
-if [ -z "${ENCRYPTION_ENABLE}" ]; then
|
|
|
-
|
|
|
- echo >&2 "ENCRYPTION_ENABLE is not set. Aborting.";
|
|
|
- exit 1;
|
|
|
-
|
|
|
-elif [ ${ENCRYPTION_ENABLE} = true ]; then
|
|
|
-
|
|
|
- if [ -z "${ENCRYPTION_PASSPHRASE_PATH}" ]; then
|
|
|
- echo >&2 "ENCRYPTION_PASSPHRASE_PATH is not set. Aborting.";
|
|
|
- exit 1;
|
|
|
- fi
|
|
|
-
|
|
|
- ENCRYPTION=--encryption=repokey
|
|
|
- export BORG_PASSPHRASE=`cat ${ENCRYPTION_PASSPHRASE_PATH}`
|
|
|
-
|
|
|
-else
|
|
|
-
|
|
|
- ENCRYPTION=--encryption=none
|
|
|
-
|
|
|
-fi
|
|
|
-
|
|
|
-if [ -z "${PGSQL_EXCLUDE_DATNAME}" ]; then
|
|
|
- PGSQL_EXCLUDE_DATNAME="['template_']"
|
|
|
-fi
|
|
|
-
|
|
|
-HOSTNAME=`hostname`
|
|
|
-LOCAL_DEST=${LOCAL_DIR}/${HOSTNAME}.borg
|
|
|
-
|
|
|
-DATE=`date +%Y-%m-%d`
|
|
|
-BACKUP_NAME="${HOSTNAME}_${DATE}"
|
|
|
-
|
|
|
-# Temporary directory for backup
|
|
|
-BACKUP_TEMP="${LOCAL_DIR}/output-daily"
|
|
|
-
|
|
|
-if ! type "borg" > /dev/null; then
|
|
|
- echo >&2 "borgbackup is not installed. Aborting.";
|
|
|
- exit 1;
|
|
|
-fi
|
|
|
-
|
|
|
-if [ -d "${BACKUP_TEMP}" ]; then
|
|
|
- echo >&2 "Directory ${BACKUP_TEMP} already exists. Aborting.";
|
|
|
- exit 1;
|
|
|
-else
|
|
|
- mkdir "${BACKUP_TEMP}"
|
|
|
-fi
|
|
|
-
|
|
|
-# Backup ejabberd data if ejabberd_ctl is available
|
|
|
-if type "ejabberdctl" > /dev/null; then
|
|
|
- EJABBERD_BACKUP=`mktemp -p ~ejabberd/`
|
|
|
- ejabberdctl backup "$EJABBERD_BACKUP"
|
|
|
- mv "$EJABBERD_BACKUP" $BACKUP_TEMP/ejabberd.backup
|
|
|
-fi
|
|
|
-
|
|
|
-# Backup PostgreSQL data if pg_dump is available, and user postgres exists
|
|
|
-if type "pg_dump" > /dev/null; then
|
|
|
- if id -u "postgres" > /dev/null 2>&1; then
|
|
|
- PG_DBS=`sudo -i -u postgres psql template1 -t -c "SELECT datname FROM pg_database WHERE NOT datname LIKE ANY(ARRAY${PGSQL_EXCLUDE_DATNAME})"`
|
|
|
- for PG_DB in $PG_DBS
|
|
|
- do
|
|
|
- PG_BACKUP=`sudo -u postgres mktemp -t pg_dump_XXXX`
|
|
|
- sudo -u postgres -i pg_dump -Z3 -Fc "${PG_DB}" > "${PG_BACKUP}"
|
|
|
- mv "${PG_BACKUP}" "${BACKUP_TEMP}/pgsql_${PG_DB}_Fc.dump"
|
|
|
- done
|
|
|
- fi
|
|
|
-fi
|
|
|
-
|
|
|
-if [ ! -d "${LOCAL_DEST}" ]; then
|
|
|
- # Initialize local borg repository
|
|
|
- borg init ${ENCRYPTION} "${LOCAL_DEST}"
|
|
|
-fi
|
|
|
-
|
|
|
-# Do local backup
|
|
|
-borg create --exclude-from /etc/concierge/backup.exclude ${LOCAL_DEST}::${BACKUP_NAME} "${BACKUP_TEMP}" ${BACKUP_DIR_INCL}
|
|
|
+#!/usr/bin/python3
|
|
|
+
|
|
|
+import configparser
|
|
|
+import datetime
|
|
|
+import os
|
|
|
+import pwd
|
|
|
+import signal
|
|
|
+import socket
|
|
|
+import shutil
|
|
|
+import subprocess
|
|
|
+import sys
|
|
|
+import tempfile
|
|
|
+
|
|
|
+users = set(map(lambda x : x[0], pwd.getpwall()))
|
|
|
+
|
|
|
+class EjabberdTask:
|
|
|
+ def __init__(self, outputPath):
|
|
|
+ self.outputPath = outputPath
|
|
|
+ def is_supported(self):
|
|
|
+ return shutil.which('ejabberdctl') != None and 'ejabberd' in users
|
|
|
+ def run(self):
|
|
|
+ pwe = pwd.getpwnam('ejabberd')
|
|
|
+ (tempfh, tempfname) = tempfile.mkstemp(dir = pwe.pw_dir)
|
|
|
+ returncode = subprocess.call(['ejabberdctl','backup',tempfname])
|
|
|
+ os.close(tempfh)
|
|
|
+ if returncode == 0:
|
|
|
+ outfname = os.path.join(self.outputPath, 'ejabberd_backup')
|
|
|
+ shutil.move(tempfname, outfname)
|
|
|
+ else:
|
|
|
+ os.remove(tempfname)
|
|
|
+
|
|
|
+class PgDumpTask:
|
|
|
+ def __init__(self, outputPath, excludeDatname):
|
|
|
+ self.outputPath = outputPath
|
|
|
+ self.excludeDatname = excludeDatname
|
|
|
+ def is_supported(self):
|
|
|
+ return shutil.which('pg_dump') != None and 'postgres' in users
|
|
|
+ def run(self):
|
|
|
+ pwe = pwd.getpwnam('postgres')
|
|
|
+ command = 'psql template1 -t -c "SELECT datname FROM pg_database WHERE NOT datname LIKE ANY(ARRAY%s)"' % self.excludeDatname
|
|
|
+ comOut = subprocess.check_output(['su','-c',command,'postgres'], cwd=pwe.pw_dir)
|
|
|
+ dbs = comOut.decode('utf-8').strip("\n ").split("\n ")
|
|
|
+ for dbname in dbs:
|
|
|
+ tempfname = os.path.join(self.outputPath, 'psql_%s_Fc.dump' % dbname)
|
|
|
+ tempfh = open(tempfname, mode='w')
|
|
|
+ command = 'pg_dump -Z3 -Fc "%s"' % (dbname)
|
|
|
+ subprocess.call(['su','-c',command,'postgres'], cwd=pwe.pw_dir, stdout=tempfh)
|
|
|
+
|
|
|
+backupTemp = None
|
|
|
+
|
|
|
+def sighandler(signum, frame):
|
|
|
+ if backupTemp != None:
|
|
|
+ print("Backup was interrupted. Deleting temporary directory")
|
|
|
+ shutil.rmtree(backupTemp)
|
|
|
+ sys.exit(1)
|
|
|
+
|
|
|
+signal.signal(signal.SIGINT, sighandler)
|
|
|
+signal.signal(signal.SIGTERM, sighandler)
|
|
|
+
|
|
|
+conf = configparser.ConfigParser()
|
|
|
+confFile = open('/etc/concierge/backup.cfg', 'r')
|
|
|
+confStr = "[Common]\n" + confFile.read()
|
|
|
+conf.read_string(confStr)
|
|
|
+
|
|
|
+hostname = socket.gethostname()
|
|
|
+backupName = "%s_%s" % (hostname, datetime.date.today().isoformat())
|
|
|
+
|
|
|
+localDir = conf.get('Common', 'LOCAL_DIR')
|
|
|
+localDest = os.path.join(localDir, hostname + ".borg")
|
|
|
+
|
|
|
+remoteEnable = conf.getboolean('Common', 'REMOTE_ENABLE')
|
|
|
+remoteDest = None
|
|
|
+remoteRepo = None
|
|
|
+if remoteEnable:
|
|
|
+ remoteUser = conf.get('Common', 'REMOTE_USER')
|
|
|
+ remoteHost = conf.get('Common', 'REMOTE_HOSTNAME')
|
|
|
+ remoteDir = conf.get('Common', 'REMOTE_DIR')
|
|
|
+ remoteDest = os.path.join(remoteDir, hostname + ".borg")
|
|
|
+ remoteRepo = "%s@%s:%s" % (remoteUser, remoteHost, remoteDest)
|
|
|
+
|
|
|
+encryptionEnable = conf.getboolean('Common', 'ENCRYPTION_ENABLE')
|
|
|
+encryption = "--encryption=none"
|
|
|
+passphrase = None
|
|
|
+if encryptionEnable:
|
|
|
+ encryption = "--encryption=repokey"
|
|
|
+ passphrasePath = conf.get('Common', 'ENCRYPTION_PASSPHRASE_PATH')
|
|
|
+ passphraseFile = open(passphrasePath, 'r')
|
|
|
+ passphrase = passphraseFile.read().strip("\r\n")
|
|
|
+ os.environ['BORG_PASSPHRASE'] = passphrase
|
|
|
+
|
|
|
+pgsqlExcludeDatname = conf.get('Common', 'PGSQL_EXCLUDE_DATNAME', fallback="['template_']").strip('"')
|
|
|
+
|
|
|
+# Temporary directory
|
|
|
+backupTemp = os.path.join(localDir, "output-daily")
|
|
|
+if os.path.exists(backupTemp):
|
|
|
+ print("Directory %s already exists. Aborting. " % backupTemp)
|
|
|
+ sys.exit(1)
|
|
|
+else:
|
|
|
+ os.makedirs(backupTemp)
|
|
|
+
|
|
|
+# Run software-specific backup tasks
|
|
|
+tasks = []
|
|
|
+tasks.append(EjabberdTask(backupTemp))
|
|
|
+tasks.append(PgDumpTask(backupTemp, pgsqlExcludeDatname))
|
|
|
+
|
|
|
+for task in tasks:
|
|
|
+ if task.is_supported():
|
|
|
+ try:
|
|
|
+ task.run()
|
|
|
+ except Exception as e:
|
|
|
+ print("Task %s failed: %s" % (task.__class__.__name__, str(e)))
|
|
|
+
|
|
|
+# Enumerate directories to backup
|
|
|
+backupInclPaths = [backupTemp]
|
|
|
+backupInclPaths += conf.get('Common', 'BACKUP_DIR_INCL', fallback='').strip('"').split(' ')
|
|
|
+# Initialize local borg repository
|
|
|
+if not os.path.exists(localDest):
|
|
|
+ subprocess.call(['borg','init', encryption, localDest])
|
|
|
+
|
|
|
+# Do a local backup
|
|
|
+localArchive = localDest + '::' + backupName
|
|
|
+subprocess.call(['borg','create','--exclude-from','/etc/concierge/backup.exclude',localArchive] + backupInclPaths)
|
|
|
|
|
|
# Prune local archives to keep only 7 daily, and 4 weekly, and 3 monthly ones
|
|
|
-borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=3 ${LOCAL_DEST}
|
|
|
-
|
|
|
-if [ ${REMOTE_ENABLE} = true ]; then
|
|
|
-
|
|
|
- REMOTE_DEST=${REMOTE_DIR}/${HOSTNAME}.borg
|
|
|
-
|
|
|
- # Quick check of the remote repository before doing a remote backup
|
|
|
- if borg check --repository-only ${REMOTE_USER}@${REMOTE_HOSTNAME}:${REMOTE_DEST}; then
|
|
|
- # Do remote backup
|
|
|
- borg create --exclude-from /etc/concierge/backup.exclude ${REMOTE_USER}@${REMOTE_HOSTNAME}:${REMOTE_DEST}::${BACKUP_NAME} "${BACKUP_TEMP}" ${BACKUP_DIR_INCL}
|
|
|
+subprocess.call(['borg','prune','--keep-daily=7','--keep-weekly=4','--keep-monthly=3',localDest])
|
|
|
+
|
|
|
+if remoteEnable:
|
|
|
+ returncode = subprocess.call(['borg','check','--repository-only',remoteRepo])
|
|
|
+ if returncode != 0:
|
|
|
+ print("Check failed on remote repository")
|
|
|
+ else:
|
|
|
+ # Do a remote backup
|
|
|
+ remoteArchive = remoteRepo + '::' + backupName
|
|
|
+ subprocess.call(['borg','create','--exclude-from','/etc/concierge/backup.exclude', remoteArchive] + backupInclPaths)
|
|
|
# Prune remote archives to keep only 7 daily, and 4 weekly, and 3 monthly ones
|
|
|
- borg prune --keep-daily=7 --keep-weekly=4 --keep-monthly=3 ${REMOTE_USER}@${REMOTE_HOSTNAME}:${REMOTE_DEST}
|
|
|
- else
|
|
|
- echo >&2 "Remote repository is missing of corrupted. Try running: "
|
|
|
- echo >&2 "borg init ${ENCRYPTION} ${REMOTE_USER}@${REMOTE_HOSTNAME}:${REMOTE_DEST}"
|
|
|
- fi
|
|
|
-
|
|
|
-fi
|
|
|
+ subprocess.call(['borg','prune','--keep-daily=7','--keep-weekly=4','--keep-monthly=3',remoteRepo])
|
|
|
|
|
|
# Delete temporary directory
|
|
|
-rm -rf "${BACKUP_TEMP}"
|
|
|
+shutil.rmtree(backupTemp)
|