commit
c805984e49
@ -0,0 +1,78 @@
|
|||||||
|
|
||||||
|
borg:
|
||||||
|
repository: 'test-repo'
|
||||||
|
|
||||||
|
# Choose compression or 'none'
|
||||||
|
compression: 'zstd,3'
|
||||||
|
|
||||||
|
# enviroment-variables
|
||||||
|
env-vars: []
|
||||||
|
#'BORG_RSH': 'ssh -i ~/.ssh/id_ed25519'
|
||||||
|
#'BORG_PASSPHRASE': ''
|
||||||
|
|
||||||
|
# Additional arguments added [in front] of everything else
|
||||||
|
args: ''
|
||||||
|
|
||||||
|
backup:
|
||||||
|
# Conditions which have to be met for the backup to start
|
||||||
|
run-conditions:
|
||||||
|
battery:
|
||||||
|
# [passive]
|
||||||
|
min-percent: 60
|
||||||
|
or_ac-connected: true
|
||||||
|
|
||||||
|
# How old should the previous backup be so we do another one [in seconds]
|
||||||
|
backup-age: 64800
|
||||||
|
|
||||||
|
tries:
|
||||||
|
# How often should we try
|
||||||
|
amount: 3
|
||||||
|
# How long to wait for another try
|
||||||
|
sleep: 600
|
||||||
|
|
||||||
|
# Files/Folders to in/exclude in the backup
|
||||||
|
# Supports wildcards like *.bak
|
||||||
|
include:
|
||||||
|
[]
|
||||||
|
exclude:
|
||||||
|
- '*.part'
|
||||||
|
- '*.bak'
|
||||||
|
- '*.tmp'
|
||||||
|
- '*/tmp/'
|
||||||
|
- '*/temp/'
|
||||||
|
- '*/cache/'
|
||||||
|
- '*/caches/'
|
||||||
|
- '*/.cache/'
|
||||||
|
- '*/.caches/'
|
||||||
|
- '*/_cacache/'
|
||||||
|
- '*/.Trash-*/'
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
# File to store metadata (empty to use config-name + .data)
|
||||||
|
file: ''
|
||||||
|
|
||||||
|
# Scan for files marking folders to include/exclude
|
||||||
|
scan:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
locations:
|
||||||
|
- './'
|
||||||
|
- '/data/'
|
||||||
|
|
||||||
|
backup: '.borg.backup'
|
||||||
|
nobackup: '.borg.nobackup'
|
||||||
|
|
||||||
|
# Touch file after we found it (update date-accessed)
|
||||||
|
touch: true
|
||||||
|
|
||||||
|
# Run file if executeable
|
||||||
|
execute: true
|
||||||
|
|
||||||
|
# Cache scanned files
|
||||||
|
cache:
|
||||||
|
# File to store cache (empty to use config-name + .cache)
|
||||||
|
file: ''
|
||||||
|
|
||||||
|
# time [in seconds]
|
||||||
|
# (probably good to set this higher than tries.amount * tries.sleep)
|
||||||
|
valid-time: 1860
|
@ -0,0 +1,497 @@
|
|||||||
|
#!/bin/python3
|
||||||
|
|
||||||
|
import os, subprocess, yaml, time, socket, datetime, getopt, sys, re, atexit, logging as log
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
help_text = ''
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
|
||||||
|
# arg-settings
|
||||||
|
arg_short_options = "hc:fv"
|
||||||
|
arg_long_options = ["help", "config=", "cmd=", "force", "verbose"]
|
||||||
|
|
||||||
|
global help_text
|
||||||
|
help_text = f"""
|
||||||
|
Usage:
|
||||||
|
{args[0]} [options]\n
|
||||||
|
Options:
|
||||||
|
-h --help Display this text
|
||||||
|
-c --config Specify the config-file default='backup.conf'
|
||||||
|
--cmd Run command for repository
|
||||||
|
-f --force Force backup (ignore all conditions)"""
|
||||||
|
|
||||||
|
|
||||||
|
args = readArgs(
|
||||||
|
args = args[1:],
|
||||||
|
short_options = arg_short_options,
|
||||||
|
long_options = arg_long_options
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parseCliArgs(
|
||||||
|
args = args,
|
||||||
|
data = {
|
||||||
|
'config': "backup.conf",
|
||||||
|
'force': False,
|
||||||
|
'verbose': False
|
||||||
|
},
|
||||||
|
argMap = {
|
||||||
|
'config': ['config', 'c'],
|
||||||
|
'cmd': ['cmd'],
|
||||||
|
'force': ['force', 'f'],
|
||||||
|
"verbose": ['verbose', 'v']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
setupLogging(args['verbose'])
|
||||||
|
|
||||||
|
config = readConfig(args['config'])
|
||||||
|
setDefaultValues(config, args['config'])
|
||||||
|
|
||||||
|
# Execute cmd if requested
|
||||||
|
if "cmd" in args:
|
||||||
|
return executeBorgCommand(config, args['cmd'])
|
||||||
|
|
||||||
|
# Get config data
|
||||||
|
include = config["backup"]["include"]
|
||||||
|
exclude = config["backup"]["exclude"]
|
||||||
|
|
||||||
|
|
||||||
|
if not args['force']:
|
||||||
|
|
||||||
|
# Pre-backup-checks
|
||||||
|
result = checkRunConditions(config)
|
||||||
|
if not (isinstance(result, bool) and result):
|
||||||
|
log.error(result)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Previous-backup-metadata
|
||||||
|
metadata = readBackupMetadata(config)
|
||||||
|
if metadata:
|
||||||
|
handleBackupMetadata(config, metadata)
|
||||||
|
|
||||||
|
# Folder-scan
|
||||||
|
if config["backup"]["scan"]["enabled"]:
|
||||||
|
includeScanned, excludeScanned = scanForFolders(config)
|
||||||
|
|
||||||
|
# Touch files if requested
|
||||||
|
if config["backup"]["scan"]["touch"]:
|
||||||
|
touchFiles(includeScanned+excludeScanned)
|
||||||
|
|
||||||
|
# PRE - Execute files if requested
|
||||||
|
if config["backup"]["scan"]["execute"]:
|
||||||
|
executeFilesPre(includeScanned+excludeScanned)
|
||||||
|
# Register post at exit
|
||||||
|
atexit.register(executeFilesPost, (includeScanned+excludeScanned))
|
||||||
|
|
||||||
|
# Add folders containing these files to include/exclude
|
||||||
|
include += [os.path.dirname(file) for file in includeScanned]
|
||||||
|
exclude += [os.path.dirname(file) for file in excludeScanned]
|
||||||
|
|
||||||
|
command = generateCommand(config, include, exclude)
|
||||||
|
|
||||||
|
runBackup(config, command)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def setDefaultValues(config, configFile):
|
||||||
|
if config['backup']['metadata']['file'] == '':
|
||||||
|
config['backup']['metadata']['file'] = f"{configFile}.data"
|
||||||
|
|
||||||
|
if config['backup']['scan']['cache']['file'] == '':
|
||||||
|
config['backup']['scan']['cache']['file'] = f"{configFile}.cache"
|
||||||
|
|
||||||
|
def readArgs(args, short_options: str, long_options: list):
|
||||||
|
try:
|
||||||
|
arguments, values = getopt.getopt(sys.argv[1:], short_options, long_options)
|
||||||
|
return arguments
|
||||||
|
except getopt.error as err:
|
||||||
|
print (str(err))
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
def parseCliArgs(args, data={}, argMap={}):
|
||||||
|
|
||||||
|
# If argument-Map is empty, construct it
|
||||||
|
if argMap == {}:
|
||||||
|
for data_key, data_value in data:
|
||||||
|
argMap[data_key] = [data_key]
|
||||||
|
|
||||||
|
# Search in given arguments for argMap and write values
|
||||||
|
for arg, val in args:
|
||||||
|
# Special arguments
|
||||||
|
if arg in ("-h", "--help"):
|
||||||
|
global help_text
|
||||||
|
print (help_text)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
found = False
|
||||||
|
for map_key in argMap:
|
||||||
|
map_altKeys = argMap[map_key]
|
||||||
|
|
||||||
|
for altKey in map_altKeys:
|
||||||
|
keyName = f"-{altKey}"
|
||||||
|
# If name is longer, add second - for long-arg
|
||||||
|
if len(altKey) > 1:
|
||||||
|
keyName = f"-{keyName}"
|
||||||
|
|
||||||
|
# If argument matches
|
||||||
|
if arg == keyName:
|
||||||
|
# If data expects bool write True, otherwise just write given value
|
||||||
|
# and stop for this arg
|
||||||
|
if map_key in data and isinstance(data[map_key], bool):
|
||||||
|
data[map_key] = True
|
||||||
|
else:
|
||||||
|
data[map_key] = val
|
||||||
|
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
break
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def setupLogging(verbose: bool):
|
||||||
|
level = log.INFO
|
||||||
|
format = '%(asctime)s %(levelname)s: %(message)s'
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
level = log.DEBUG
|
||||||
|
|
||||||
|
log.basicConfig(stream=sys.stdout, format=format, level=level)
|
||||||
|
|
||||||
|
def readConfig(file: str):
|
||||||
|
log.debug(f"Reading config '{file}'..")
|
||||||
|
|
||||||
|
if not os.path.isfile(file):
|
||||||
|
raise OSError(2, file)
|
||||||
|
|
||||||
|
return readYamlFile(file)
|
||||||
|
|
||||||
|
def readYamlFile(file: str):
|
||||||
|
with open(file, "r") as f:
|
||||||
|
return yaml.load(f, Loader=yaml.FullLoader)
|
||||||
|
|
||||||
|
def checkRunConditions(config):
|
||||||
|
log.debug("checkRunConditions")
|
||||||
|
|
||||||
|
result = checkBatteryCharge(config)
|
||||||
|
if not (isinstance(result, bool) and result):
|
||||||
|
return result
|
||||||
|
|
||||||
|
result = checkNetwork(config)
|
||||||
|
if not (isinstance(result, bool) and result):
|
||||||
|
return result
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def checkBatteryCharge(config):
|
||||||
|
log.debug("> Battery-charge")
|
||||||
|
|
||||||
|
if not config["backup"]["run-conditions"]["battery"]["min-percent"] > 0:
|
||||||
|
log.debug("Check disabled, skipping")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Stats-file-stream exists?
|
||||||
|
if not os.path.exists("/sys/class/power_supply/battery/capacity"):
|
||||||
|
log.warning("No battery found, skipping")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Read
|
||||||
|
with open("/sys/class/power_supply/battery/capacity", "r") as file:
|
||||||
|
battery_charge = int(file.readlines()[0])
|
||||||
|
|
||||||
|
if battery_charge < config["backup"]["run-conditions"]["battery_min-percent"]:
|
||||||
|
log.debug(f"Too low ({battery_charge} < {config['backup']['run-conditions']['battery']['min-percent']})")
|
||||||
|
|
||||||
|
errorMsg = f"Battery-charge too low ({battery_charge} < {config['backup']['run-conditions']['battery']['min-percent']})"
|
||||||
|
|
||||||
|
log.debug("or-AC-connected")
|
||||||
|
if config["backup"]["run-conditions"]["battery"]["or_ac-connected"]:
|
||||||
|
# Check battery-status
|
||||||
|
battery_status = "Unknown"
|
||||||
|
with open("/sys/class/power_supply/battery/status", "r") as file:
|
||||||
|
battery_status = file.readlines()[0].strip()
|
||||||
|
if not battery_status == "Charging":
|
||||||
|
log.debug("Not charging ("+ battery_status +")")
|
||||||
|
errorMsg += " and not charging"
|
||||||
|
|
||||||
|
return errorMsg
|
||||||
|
else:
|
||||||
|
log.debug(f"Ok ({battery_charge} < {config['backup']['run-conditions']['battery']['min-percent']})")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def checkNetwork(config):
|
||||||
|
log.debug("Check Network")
|
||||||
|
|
||||||
|
hostname = getHostnameFromRepository(config["borg"]["repository"])
|
||||||
|
if hostname == None:
|
||||||
|
log.debug("Repository does not have a hostname, skipping")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
result = checkNetworkHops(config, hostname)
|
||||||
|
if not (isinstance(result, bool) and result):
|
||||||
|
return result
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def checkNetworkHops(config, hostname: str):
|
||||||
|
log.debug("> Network-hops")
|
||||||
|
|
||||||
|
if not config["backup"]["run-conditions"]["network"]["max_hops"] > 0:
|
||||||
|
log.debug("Check disabled (0)")
|
||||||
|
return True
|
||||||
|
|
||||||
|
ip = resolveHostname(hostname)
|
||||||
|
|
||||||
|
lastHopIp = traceroute(
|
||||||
|
maxhops = config["backup"]["run-conditions"]["network"]["max_hops"],
|
||||||
|
ip = ip
|
||||||
|
)[-2].strip()
|
||||||
|
if(len(lastHopIp) >= 7):
|
||||||
|
lastHopIp = lastHopIp.split(" ")[2]
|
||||||
|
log.debug(f"LastHop-IP: {lastHopIp}")
|
||||||
|
|
||||||
|
if ip != lastHopIp:
|
||||||
|
return "Could not reach server in <= "+ str(config['backup']['run-conditions']['network']['max_hops']) +" hops"
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def getHostnameFromRepository(repository: str):
|
||||||
|
# Check if has hostname
|
||||||
|
result = re.search(r'^(\w+@)?(([A-Z0-9-]+\.)+[A-Z0-9-]+):.*$', repository, flags= re.I)
|
||||||
|
if result:
|
||||||
|
return result[2]
|
||||||
|
else:
|
||||||
|
# No hostname
|
||||||
|
return
|
||||||
|
|
||||||
|
def resolveHostname(hostname: str):
|
||||||
|
log.debug(f"Resolving hostname {hostname}..")
|
||||||
|
|
||||||
|
ip = socket.gethostbyname(hostname)
|
||||||
|
|
||||||
|
log.debug(f"IP: {ip}")
|
||||||
|
return ip
|
||||||
|
|
||||||
|
def traceroute(maxhops: int, ip: str):
|
||||||
|
command = ["traceroute", "-n", f"-m {maxhops}", "-q 2", ip]
|
||||||
|
|
||||||
|
log.debug(f"Running traceroute to ip={ip} with maxhops={maxhops}..")
|
||||||
|
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
|
out = result.stdout.decode('utf-8').split('\n')
|
||||||
|
|
||||||
|
log.debug(out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
def readBackupMetadata(config):
|
||||||
|
file = config['backup']['metadata']['file']
|
||||||
|
if os.path.isfile(file):
|
||||||
|
log.debug(f"Reading metadata-file '{file}'..")
|
||||||
|
return readYamlFile(file)
|
||||||
|
return
|
||||||
|
|
||||||
|
def writeBackupMetadata(config, success: bool):
|
||||||
|
data = {
|
||||||
|
"time": int(time.time()),
|
||||||
|
"success": success
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(f"Writing metadata: {data}")
|
||||||
|
with open(config['backup']['metadata']['file'], 'w') as file:
|
||||||
|
yaml.dump(data, file)
|
||||||
|
|
||||||
|
def handleBackupMetadata(config, metadata):
|
||||||
|
log.debug(f"Handling metadata..")
|
||||||
|
|
||||||
|
age = int(time.time()-metadata["time"])
|
||||||
|
if age <= config["backup"]["backup-age"]:
|
||||||
|
if metadata["success"]:
|
||||||
|
log.info("Backup-age is "+ str(age) +"s, next backup will be created in "+ str(age+config["backup"]["backup-age"]) +"s")
|
||||||
|
quit()
|
||||||
|
else:
|
||||||
|
log.warning("Backup-age is "+ str(age) +"s, but was not successful!")
|
||||||
|
|
||||||
|
def scanForFolders(config):
|
||||||
|
log.debug(f"Scan for marked folders")
|
||||||
|
|
||||||
|
includeScannedFiles = []
|
||||||
|
excludeScannedFiles = []
|
||||||
|
|
||||||
|
# Read cache if exists
|
||||||
|
if(os.path.isfile(config["backup"]["scan"]['cache']['file'])):
|
||||||
|
log.debug(f"Cache-file found")
|
||||||
|
|
||||||
|
cacheData = readYamlFile(config["backup"]["scan"]['cache']['file'])
|
||||||
|
age = int(time.time()-cacheData["time"])
|
||||||
|
|
||||||
|
log.debug(f"Cache-age: {str(age)}")
|
||||||
|
|
||||||
|
if age <= config["backup"]["scan"]["cache"]["valid-time"]:
|
||||||
|
log.info("Using Cache ("+ str(age) +"s old)")
|
||||||
|
scan = False
|
||||||
|
includeScannedFiles = cacheData["include"]
|
||||||
|
excludeScannedFiles = cacheData["exclude"]
|
||||||
|
|
||||||
|
log.debug(f"include: {includeScannedFiles}")
|
||||||
|
log.debug(f"exclude: {excludeScannedFiles}")
|
||||||
|
return includeScannedFiles, excludeScannedFiles
|
||||||
|
|
||||||
|
# Scan folders for files
|
||||||
|
log.info(f"Scanning for folders marked with {config['backup']['scan']['backup']} or {config['backup']['scan']['nobackup']}..")
|
||||||
|
for location in config["backup"]["scan"]["locations"]:
|
||||||
|
log.debug(f"Scanning {location}..")
|
||||||
|
|
||||||
|
includeScannedFile = find(location, config["backup"]["scan"]["backup"])
|
||||||
|
excludeScannedFile = find(location, config["backup"]["scan"]["nobackup"])
|
||||||
|
|
||||||
|
if len(includeScannedFile) > 0:
|
||||||
|
log.debug(f"include: {includeScannedFile}")
|
||||||
|
if len(excludeScannedFile) > 0:
|
||||||
|
log.debug(f"exclude: {excludeScannedFile}")
|
||||||
|
|
||||||
|
includeScannedFiles += includeScannedFile
|
||||||
|
excludeScannedFiles += excludeScannedFile
|
||||||
|
|
||||||
|
# Write cache
|
||||||
|
log.debug("Writing cache..")
|
||||||
|
with open(config["backup"]["scan"]['cache']['file'], 'w') as file:
|
||||||
|
yaml.dump(
|
||||||
|
{
|
||||||
|
"time": int(time.time()),
|
||||||
|
"include": includeScannedFiles,
|
||||||
|
"exclude": excludeScannedFiles
|
||||||
|
}
|
||||||
|
, file)
|
||||||
|
|
||||||
|
log.info(f"Scan finished. {len(includeScannedFiles) + len(excludeScannedFiles)} files found")
|
||||||
|
log.debug(f"include: {includeScannedFiles}")
|
||||||
|
log.debug(f"exclude: {excludeScannedFiles}")
|
||||||
|
return includeScannedFiles, excludeScannedFiles
|
||||||
|
|
||||||
|
def touchFiles(files: list):
|
||||||
|
for file in files:
|
||||||
|
if os.path.isfile(file):
|
||||||
|
os.utime(file, None)
|
||||||
|
|
||||||
|
def executeFilesPre(files: list):
|
||||||
|
log.info("Running script-pre..")
|
||||||
|
|
||||||
|
# Store files which were successfully executed
|
||||||
|
executedFiles = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
for file in files:
|
||||||
|
# Check for filesize and execution-permission
|
||||||
|
if os.stat(file).st_size != 0 and os.access(file, os.X_OK):
|
||||||
|
log.info(f"{file}")
|
||||||
|
subprocess.run([file, "pre"], check=True)
|
||||||
|
executedFiles += file
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError as err:
|
||||||
|
log.critical("Script returned error, cannot continue!")
|
||||||
|
executeFilesPost(executedFiles)
|
||||||
|
raise err
|
||||||
|
|
||||||
|
def executeFilesPost(files: list):
|
||||||
|
log.info("Running script-post..")
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
# Check for filesize and execution-permission
|
||||||
|
if os.stat(file).st_size != 0 and os.access(file, os.X_OK):
|
||||||
|
log.info(f"{file}")
|
||||||
|
try:
|
||||||
|
subprocess.run([file, "post"], check=True)
|
||||||
|
except subprocess.CalledProcessError as err:
|
||||||
|
log.error(err)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def generateEnviromentVars(config):
|
||||||
|
args = config["borg"]["args"]
|
||||||
|
envVars = [env_var +"='"+ config["borg"]["env-vars"][env_var] +"'" for env_var in config["borg"]["env-vars"]]
|
||||||
|
return ' '.join(envVars)
|
||||||
|
|
||||||
|
def generateCommand(config, include, exclude):
|
||||||
|
|
||||||
|
# Generate arguments
|
||||||
|
folderArgs = [f"'{folder}'" for folder in include]
|
||||||
|
folderArgs += [f"--exclude '{folder}'" for folder in exclude]
|
||||||
|
folderArgs = ' '.join(folderArgs)
|
||||||
|
|
||||||
|
envVars = generateEnviromentVars(config)
|
||||||
|
|
||||||
|
compression = config["borg"]["compression"]
|
||||||
|
repository = config['borg']['repository']
|
||||||
|
|
||||||
|
backupName = socket.getfqdn() +"__"+ datetime.datetime.now().strftime('%F_%T')
|
||||||
|
|
||||||
|
return f"{envVars} borg create {config['borg']['args']} --exclude-caches --compression {compression} {repository}::{backupName} {folderArgs}"
|
||||||
|
|
||||||
|
def executeBorgCommand(config, command):
|
||||||
|
|
||||||
|
envVars = generateEnviromentVars(config)
|
||||||
|
|
||||||
|
repository = config['borg']['repository']
|
||||||
|
|
||||||
|
borgSubcommand = command
|
||||||
|
commandArgs = ''
|
||||||
|
if ' ' in command:
|
||||||
|
commandData = command.split(' ')
|
||||||
|
borgSubcommand = commandData[0]
|
||||||
|
commandArgs = ' '.join(commandData[1:])
|
||||||
|
|
||||||
|
command = f"{envVars} borg {borgSubcommand} {repository} {commandArgs}"
|
||||||
|
|
||||||
|
log.debug(f'Executing {command}')
|
||||||
|
return os.system(command)
|
||||||
|
|
||||||
|
def runBackup(config, command):
|
||||||
|
writeBackupMetadata(config, False)
|
||||||
|
|
||||||
|
log.info("Running backup")
|
||||||
|
for i in range(1, config["backup"]["tries"]["amount"]):
|
||||||
|
log.debug(f"Try {i}")
|
||||||
|
|
||||||
|
log.debug(command);
|
||||||
|
code = os.system(command)
|
||||||
|
log.debug(f"Return-code {code}")
|
||||||
|
|
||||||
|
if not code == 0:
|
||||||
|
log.error("Backup failed")
|
||||||
|
|
||||||
|
if(i+1 != config["backup"]["tries"]["amount"]):
|
||||||
|
print("- Retry in "+ str(config["backup"]["tries"]["sleep"]) +"s")
|
||||||
|
time.sleep(config["backup"]["tries"]["sleep"])
|
||||||
|
|
||||||
|
else:
|
||||||
|
log.info("Backup successful")
|
||||||
|
|
||||||
|
writeBackupMetadata(config, True)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
log.critical("Backup completly failed!")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def find(path: str, searchName: str):
|
||||||
|
result = subprocess.run(["find", path, "-type", "f", "-name", searchName], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||||
|
return result.stdout.decode('utf-8').split('\n')[:-1]
|
||||||
|
|
||||||
|
def get_platform():
|
||||||
|
platforms = {
|
||||||
|
'linux1' : 'Linux',
|
||||||
|
'linux2' : 'Linux',
|
||||||
|
'darwin' : 'OS X',
|
||||||
|
'win32' : 'Windows'
|
||||||
|
}
|
||||||
|
if sys.platform not in platforms:
|
||||||
|
return sys.platform
|
||||||
|
|
||||||
|
return platforms[sys.platform]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv))
|
Loading…
Reference in New Issue