First version, mostly working but featureless...
This commit is contained in:
parent
0b5992c88c
commit
f328cef917
255
bin/zsnapper
Normal file
255
bin/zsnapper
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
import configparser
|
||||||
|
except ImportError:
|
||||||
|
import ConfigParser as configparser
|
||||||
|
|
||||||
|
from string import Template
|
||||||
|
|
||||||
|
import zsnaplib
|
||||||
|
|
||||||
|
LOGGER = 'zsnapper'
|
||||||
|
|
||||||
|
RET_CODES = {
|
||||||
|
'SUCCESS': 0,
|
||||||
|
'ERROR': 1,
|
||||||
|
'FAILED': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
'snapshot_interval': None,
|
||||||
|
'custom_keep_interval': None,
|
||||||
|
'keep_yearly': 0,
|
||||||
|
'keep_monthly': 0,
|
||||||
|
'keep_weekly': 0,
|
||||||
|
'keep_daily': 0,
|
||||||
|
'keep_hourly': 0,
|
||||||
|
'keep_30min': 0,
|
||||||
|
'keep_15min': 0,
|
||||||
|
'keep_5min': 0,
|
||||||
|
'keep_1min': 0,
|
||||||
|
'keep_custom': 0,
|
||||||
|
'remote_enable': False,
|
||||||
|
'remote_zfs_cmd': None,
|
||||||
|
'remote_test_cmd': None,
|
||||||
|
'remote_zfs_target': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
timedelta_regex = re.compile('([0-9]+)([dhm])')
|
||||||
|
|
||||||
|
def remote_is_available(conf):
|
||||||
|
log = logging.getLogger(LOGGER)
|
||||||
|
cmdstr = Template(conf['remote_test_cmd']).safe_substitute(conf)
|
||||||
|
cmd = cmdstr.split()
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
(out, err) = proc.communicate()
|
||||||
|
|
||||||
|
log.info('Healthcheck "{}" returned {}'.format(cmdstr, proc.returncode))
|
||||||
|
return proc.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_timedelta(deltastr):
|
||||||
|
|
||||||
|
delta = datetime.timedelta()
|
||||||
|
for match in timedelta_regex.finditer(deltastr):
|
||||||
|
if match.group(2) == 'd':
|
||||||
|
delta += datetime.timedelta(days=int(match.group(1)))
|
||||||
|
elif match.group(2) == 'h':
|
||||||
|
delta += datetime.timedelta(hours=int(match.group(1)))
|
||||||
|
elif match.group(2) == 'm':
|
||||||
|
delta += datetime.timedelta(minutes=int(match.group(1)))
|
||||||
|
return delta
|
||||||
|
|
||||||
|
def get_config_for_fs(fs, config):
|
||||||
|
fs_config = DEFAULT_CONFIG
|
||||||
|
fs_build = ''
|
||||||
|
for fs_part in fs.split('/'):
|
||||||
|
fs_build += fs_part
|
||||||
|
if fs_build in config:
|
||||||
|
fs_config.update(config[fs_build])
|
||||||
|
if fs_build == fs:
|
||||||
|
break
|
||||||
|
fs_build += '/'
|
||||||
|
|
||||||
|
return fs_config
|
||||||
|
|
||||||
|
def main():
|
||||||
|
config = configparser.SafeConfigParser()
|
||||||
|
config.read('/etc/zsnapper.ini')
|
||||||
|
sudo = False
|
||||||
|
ret = RET_CODES['SUCCESS']
|
||||||
|
log = logging.getLogger(LOGGER)
|
||||||
|
|
||||||
|
if os.getuid() != 0:
|
||||||
|
sudo = True
|
||||||
|
try:
|
||||||
|
sudo = config.get('settings', 'sudo')
|
||||||
|
except (configparser.NoOptionError, configparser.NoSectionError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
fslist = sorted(zsnaplib.get_filesystems(sudo))
|
||||||
|
snapshots = zsnaplib.get_snapshots(sudo)
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
|
||||||
|
# if we fail to create or send a snapshot we do not want to remove
|
||||||
|
# the existing snapshots...
|
||||||
|
failed_snapshots = set()
|
||||||
|
|
||||||
|
# First iteration: create snapshots
|
||||||
|
for fs in fslist:
|
||||||
|
conf = get_config_for_fs(fs, config)
|
||||||
|
if not conf['snapshot_interval']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
interval = str_to_timedelta(conf['snapshot_interval'])
|
||||||
|
if fs in snapshots and snapshots[fs] and snapshots[fs][0]:
|
||||||
|
last_snap = snapshots[fs][0]
|
||||||
|
else:
|
||||||
|
last_snap = datetime.datetime.min
|
||||||
|
if interval > datetime.timedelta() and last_snap+interval < now:
|
||||||
|
try:
|
||||||
|
zsnaplib.create_snapshot(fs, sudo)
|
||||||
|
log.info('{} snapshot created'.format(fs))
|
||||||
|
except zsnaplib.ZFSSnapshotError as e:
|
||||||
|
log.warning(e)
|
||||||
|
ret = RET_CODES['ERROR']
|
||||||
|
failed_snapshots.add(fs)
|
||||||
|
|
||||||
|
# reload all snapshots so we get our new snapshots here
|
||||||
|
snapshots = zsnaplib.get_snapshots(sudo)
|
||||||
|
|
||||||
|
# Second iteration: Send snapshots
|
||||||
|
remote_hosts = {}
|
||||||
|
remote_targets = {}
|
||||||
|
for fs in fslist:
|
||||||
|
conf = get_config_for_fs(fs, config)
|
||||||
|
remote_fslist = None
|
||||||
|
remote_snapshots = None
|
||||||
|
if not conf['remote_enable']:
|
||||||
|
continue
|
||||||
|
if conf['remote_test_cmd'] and not remote_is_available(conf):
|
||||||
|
failed_snapshots.add(fs)
|
||||||
|
continue
|
||||||
|
|
||||||
|
remote_fs = conf['remote_zfs_target']
|
||||||
|
rel_local = [k for k, v in remote_targets.items() if v == remote_fs]
|
||||||
|
if rel_local:
|
||||||
|
rel_local = rel_local[0]
|
||||||
|
rel_fs = fs[len(rel_local):]
|
||||||
|
remote_fs = '{}{}'.format(remote_fs, rel_fs)
|
||||||
|
remote_targets[fs] = remote_fs
|
||||||
|
|
||||||
|
# Figure out the state of remote zfs
|
||||||
|
remote_zfs_cmd = Template(conf['remote_zfs_cmd']).safe_substitute(conf)
|
||||||
|
remote_zfs_cmd = remote_zfs_cmd.split()
|
||||||
|
# to avoid running too many commands on remote host, save result if we
|
||||||
|
# know which host we're working with.
|
||||||
|
if 'remote_host' in conf:
|
||||||
|
if conf['remote_host'] in remote_hosts:
|
||||||
|
remote_fslist = remote_hosts[conf['remote_host']]['fslist']
|
||||||
|
remote_snapshots = remote_hosts[conf['remote_host']]['snapshots']
|
||||||
|
else:
|
||||||
|
remote_fslist = zsnaplib.get_filesystems(zfs_cmd=remote_zfs_cmd)
|
||||||
|
remote_snapshots = zsnaplib.get_snapshots(zfs_cmd=remote_zfs_cmd)
|
||||||
|
remote_hosts[conf['remote_host']] = {
|
||||||
|
'fslist': remote_fslist,
|
||||||
|
'snapshots': remote_snapshots
|
||||||
|
}
|
||||||
|
if not remote_fslist:
|
||||||
|
remote_fslist = zsnaplib.get_filesystems(zfs_cmd=remote_zfs_cmd)
|
||||||
|
if not remote_snapshots:
|
||||||
|
remote_snapshots = zsnaplib.get_snapshots(zfs_cmd=remote_zfs_cmd)
|
||||||
|
|
||||||
|
remote_zfs_cmd.extend(['receive', remote_fs])
|
||||||
|
|
||||||
|
if remote_fs not in remote_snapshots:
|
||||||
|
# Remote FS doesn't exist, send a new copy
|
||||||
|
log.info('{} sending base copy to {}'.format(fs, ' '.join(remote_zfs_cmd)))
|
||||||
|
try:
|
||||||
|
zsnaplib.send_snapshot(fs, snapshots[fs][0], remote_zfs_cmd, sudo)
|
||||||
|
log.info('{} base copy sent'.format(fs))
|
||||||
|
except zsnaplib.ZFSSnapshotError as e:
|
||||||
|
failed_snapshots.add(fs)
|
||||||
|
log.warning(e)
|
||||||
|
ret = RET_CODES['ERROR']
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Remote FS exists, find last common snapshot
|
||||||
|
last_remote = None
|
||||||
|
for remote_snap in remote_snapshots[remote_fs]:
|
||||||
|
if remote_snap in snapshots[fs]:
|
||||||
|
last_remote = remote_snap
|
||||||
|
break
|
||||||
|
if not last_remote:
|
||||||
|
failed_snapshots.add(fs)
|
||||||
|
log.warning('No common snapshot local and remote, you need to create a new base copy!')
|
||||||
|
ret = RET_CODES['ERROR']
|
||||||
|
continue
|
||||||
|
last_local = snapshots[fs][0]
|
||||||
|
if last_remote == last_local:
|
||||||
|
log.info("{} snapshot from {} is already present on remote".format(fs, last_local))
|
||||||
|
continue
|
||||||
|
|
||||||
|
log.info('{} incremental {} -> {}, remote is {}'.format(fs, last_remote, snapshots[fs][0], ' '.join(remote_zfs_cmd)))
|
||||||
|
try:
|
||||||
|
zsnaplib.send_snapshot(fs, snapshots[fs][0], remote_zfs_cmd, sudo, repl_from=last_remote)
|
||||||
|
log.info('{} successfully sent to remote'.format(fs))
|
||||||
|
except zsnaplib.ZFSSnapshotError as e:
|
||||||
|
log.warning(e)
|
||||||
|
|
||||||
|
# Third iteration: weed old snapshots
|
||||||
|
remote_hosts = {}
|
||||||
|
for fs in fslist:
|
||||||
|
conf = get_config_for_fs(fs, config)
|
||||||
|
if fs in failed_snapshots:
|
||||||
|
log.info("Not weeding {} because of snapshot creation/send failure".format(fs))
|
||||||
|
continue
|
||||||
|
if fs not in snapshots:
|
||||||
|
continue
|
||||||
|
|
||||||
|
kwargs = {k: int(v) for k, v in conf.items() if k in [
|
||||||
|
'keep_custom',
|
||||||
|
'keep_yearly',
|
||||||
|
'keep_monthly',
|
||||||
|
'keep_weekly',
|
||||||
|
'keep_daily',
|
||||||
|
'keep_hourly',
|
||||||
|
'keep_30min',
|
||||||
|
'keep_15min',
|
||||||
|
'keep_5min',
|
||||||
|
'keep_1min']}
|
||||||
|
if conf['custom_keep_interval']:
|
||||||
|
kwargs['custom_keep_interval'] = str_to_timedelta(conf['custom_keep_interval'])
|
||||||
|
kwargs['sudo'] = sudo
|
||||||
|
|
||||||
|
zsnaplib.weed_snapshots(
|
||||||
|
fs,
|
||||||
|
# do not remove the snapshot just created
|
||||||
|
snapshots[fs][1:],
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
log = logging.getLogger(LOGGER)
|
||||||
|
log.setLevel(logging.INFO)
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
handler.setLevel(logging.WARNING)
|
||||||
|
log.addHandler(handler)
|
||||||
|
handler = logging.handlers.SysLogHandler(address='/dev/log')
|
||||||
|
handler.setLevel(logging.INFO)
|
||||||
|
log.addHandler(handler)
|
||||||
|
sys.exit(main())
|
34
setup.py
Normal file
34
setup.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from os import environ
|
||||||
|
|
||||||
|
try:
|
||||||
|
from setuptools import setup
|
||||||
|
except ImportError:
|
||||||
|
from distutils import setup
|
||||||
|
|
||||||
|
import pwgen
|
||||||
|
|
||||||
|
version = '0.1'
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='zsnapper',
|
||||||
|
version=str(version),
|
||||||
|
description="ZFS snapshot manager",
|
||||||
|
author="Fredrik Eriksson",
|
||||||
|
author_email="zsnapper@wb9.se",
|
||||||
|
url="https://github.com/fredrik-eriksson/zsnapper",
|
||||||
|
platforms=['any'],
|
||||||
|
license='BSD',
|
||||||
|
packages=['zsnaplib'],
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 1 - Planning',
|
||||||
|
'Environment :: Console',
|
||||||
|
'Intended Audience :: System Administrators',
|
||||||
|
'License :: OSI Approved :: BSD License',
|
||||||
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Topic :: Utilities',
|
||||||
|
],
|
||||||
|
keywords='zfs snapshot backup',
|
||||||
|
scripts=['bin/zsnapper']
|
||||||
|
)
|
273
zsnaplib/__init__.py
Normal file
273
zsnaplib/__init__.py
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
time_format='%Y-%m-%d_%H%M'
|
||||||
|
zfs_bin='/sbin/zfs'
|
||||||
|
sudo_bin='/usr/bin/sudo'
|
||||||
|
re_snapshot = re.compile(r'^(.*)@([0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{4})$')
|
||||||
|
logger = 'zsnapper'
|
||||||
|
|
||||||
|
class ZFSSnapshotError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def do_zfs_command(args, sudo, pipecmd=None, zfs_cmd=[zfs_bin]):
|
||||||
|
cmd = []
|
||||||
|
sudopw = None
|
||||||
|
if sudo:
|
||||||
|
cmd.append(sudo_bin)
|
||||||
|
if sys.version_info[0] == 3:
|
||||||
|
if isinstance(sudo, str):
|
||||||
|
cmd.append('--stdin')
|
||||||
|
sudopw = '{}\n'.format(sudo)
|
||||||
|
elif isinstance(sudo, basestring):
|
||||||
|
cmd.append('--stdin')
|
||||||
|
sudopw = '{}\n'.format(sudo)
|
||||||
|
|
||||||
|
cmd.extend(zfs_cmd)
|
||||||
|
cmd.extend(args)
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
ctrl_proc = proc
|
||||||
|
if pipecmd:
|
||||||
|
proc2 = subprocess.Popen(
|
||||||
|
pipecmd,
|
||||||
|
stdin=proc.stdout,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
proc.stdout.close()
|
||||||
|
ctrl_proc = proc2
|
||||||
|
|
||||||
|
(out, err) = ctrl_proc.communicate()
|
||||||
|
|
||||||
|
if ctrl_proc.returncode != 0:
|
||||||
|
print(proc.returncode)
|
||||||
|
raise ZFSSnapshotError('Failed to execute {}: {}'.format(cmd, err))
|
||||||
|
return out
|
||||||
|
|
||||||
|
def send_snapshot(fs, snap, recv_cmd, sudo=False, repl_from=None):
|
||||||
|
snap = snap.strftime(time_format)
|
||||||
|
pipecmd = recv_cmd
|
||||||
|
if repl_from:
|
||||||
|
repl_from = repl_from.strftime(time_format)
|
||||||
|
args = [ 'send', '-i', repl_from, '{}@{}'.format(fs, snap) ]
|
||||||
|
else:
|
||||||
|
args = [ 'send', '{}@{}'.format(fs, snap) ]
|
||||||
|
|
||||||
|
do_zfs_command(args, sudo, pipecmd=pipecmd)
|
||||||
|
|
||||||
|
|
||||||
|
def create_snapshot(fs, sudo=False):
|
||||||
|
|
||||||
|
d = datetime.datetime.now().strftime(time_format)
|
||||||
|
args = ['snapshot', '{}@{}'.format(fs, d)]
|
||||||
|
do_zfs_command(args, sudo)
|
||||||
|
|
||||||
|
def get_filesystems(sudo=False, zfs_cmd=[zfs_bin]):
|
||||||
|
args = ['list', '-H']
|
||||||
|
out = do_zfs_command(args, sudo, zfs_cmd=zfs_cmd)
|
||||||
|
ret = set()
|
||||||
|
|
||||||
|
for row in out.splitlines():
|
||||||
|
row = row.decode('UTF-8')
|
||||||
|
ret.add(row.split()[0])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def get_snapshots(sudo=False, zfs_cmd=[zfs_bin]):
|
||||||
|
args = [ 'list', '-H', '-t', 'snapshot' ]
|
||||||
|
out = do_zfs_command(args, sudo, zfs_cmd=zfs_cmd)
|
||||||
|
snapshots = {}
|
||||||
|
|
||||||
|
for row in out.splitlines():
|
||||||
|
row = row.decode('UTF-8').split()[0]
|
||||||
|
res = re_snapshot.match(row)
|
||||||
|
if res:
|
||||||
|
d = datetime.datetime.strptime(res.group(2), time_format)
|
||||||
|
if res.group(1) in snapshots:
|
||||||
|
snapshots[res.group(1)].append(d)
|
||||||
|
else:
|
||||||
|
snapshots[res.group(1)] = [d]
|
||||||
|
|
||||||
|
for l in snapshots.values():
|
||||||
|
l.sort(reverse=True)
|
||||||
|
|
||||||
|
return snapshots
|
||||||
|
|
||||||
|
|
||||||
|
def remove_snapshot(fs, date, sudo=False):
|
||||||
|
date = date.strftime(time_format)
|
||||||
|
args = [ 'destroy', '{}@{}'.format(fs, date) ]
|
||||||
|
print("would remove snapshot {}@{}".format(fs, date))
|
||||||
|
return
|
||||||
|
do_zfs_command(args, sudo)
|
||||||
|
|
||||||
|
|
||||||
|
def weed_snapshots(
|
||||||
|
fs,
|
||||||
|
dates,
|
||||||
|
custom_keep_interval = None,
|
||||||
|
keep_custom = 0,
|
||||||
|
keep_yearly = 0,
|
||||||
|
keep_monthly = 0,
|
||||||
|
keep_weekly = 0,
|
||||||
|
keep_daily = 0,
|
||||||
|
keep_hourly = 0,
|
||||||
|
keep_30min = 0,
|
||||||
|
keep_15min = 0,
|
||||||
|
keep_5min = 0,
|
||||||
|
keep_1min = 0,
|
||||||
|
sudo = False):
|
||||||
|
|
||||||
|
log = logging.getLogger(logger)
|
||||||
|
|
||||||
|
keep = {
|
||||||
|
'custom': [],
|
||||||
|
'year' : [],
|
||||||
|
'month' : [],
|
||||||
|
'week' : [],
|
||||||
|
'day' : [],
|
||||||
|
'hour' : [],
|
||||||
|
'min30' : [],
|
||||||
|
'min15' : [],
|
||||||
|
'min5' : [],
|
||||||
|
'min1' : []
|
||||||
|
}
|
||||||
|
saved = {
|
||||||
|
'custom': [],
|
||||||
|
'year' : [],
|
||||||
|
'month' : [],
|
||||||
|
'week' : [],
|
||||||
|
'day' : [],
|
||||||
|
'hour' : [],
|
||||||
|
'min30' : [],
|
||||||
|
'min15' : [],
|
||||||
|
'min5' : [],
|
||||||
|
'min1' : []
|
||||||
|
}
|
||||||
|
|
||||||
|
for date in sorted(dates):
|
||||||
|
min1 = date-datetime.timedelta(seconds=date.second, microseconds=date.microsecond)
|
||||||
|
min5 = date-datetime.timedelta(minutes=date.minute%5, seconds=date.second, microseconds=date.microsecond)
|
||||||
|
min15 = date-datetime.timedelta(minutes=date.minute%15, seconds=date.second, microseconds=date.microsecond)
|
||||||
|
min30 = date-datetime.timedelta(minutes=date.minute%30, seconds=date.second, microseconds=date.microsecond)
|
||||||
|
hour = date-datetime.timedelta(minutes=date.minute, seconds=date.second, microseconds=date.microsecond)
|
||||||
|
day = datetime.datetime.combine(date.date(), datetime.time.min)
|
||||||
|
week = datetime.datetime.combine(date.date()-datetime.timedelta(days=date.weekday()), datetime.time.min)
|
||||||
|
month = datetime.datetime(year=date.year, month=date.month, day=1)
|
||||||
|
year = datetime.datetime(year=date.year, month=1, day=1)
|
||||||
|
# yearly snapshots
|
||||||
|
if year not in saved['year']:
|
||||||
|
saved['year'].append(year)
|
||||||
|
keep['year'].append(date)
|
||||||
|
if month not in saved['month']:
|
||||||
|
saved['month'].append(month)
|
||||||
|
keep['month'].append(date)
|
||||||
|
if week not in saved['week']:
|
||||||
|
saved['week'].append(week)
|
||||||
|
keep['week'].append(date)
|
||||||
|
if day not in saved['day']:
|
||||||
|
saved['day'].append(day)
|
||||||
|
keep['day'].append(date)
|
||||||
|
if hour not in saved['hour']:
|
||||||
|
saved['hour'].append(hour)
|
||||||
|
keep['hour'].append(date)
|
||||||
|
if min30 not in saved['min30']:
|
||||||
|
saved['min30'].append(min30)
|
||||||
|
keep['min30'].append(date)
|
||||||
|
if min15 not in saved['min15']:
|
||||||
|
saved['min15'].append(min15)
|
||||||
|
keep['min15'].append(date)
|
||||||
|
if min5 not in saved['min5']:
|
||||||
|
saved['min5'].append(min5)
|
||||||
|
keep['min5'].append(date)
|
||||||
|
if min1 not in saved['min1']:
|
||||||
|
saved['min1'].append(min1)
|
||||||
|
keep['min1'].append(date)
|
||||||
|
|
||||||
|
if custom_keep_interval:
|
||||||
|
cur = year
|
||||||
|
while cur+custom_keep_interval < date:
|
||||||
|
cur += custom_keep_interval
|
||||||
|
if cur not in saved['custom']:
|
||||||
|
saved['custom'].append(cur)
|
||||||
|
keep['custom'].append(date)
|
||||||
|
|
||||||
|
if keep_yearly:
|
||||||
|
saved['year'] = saved['year'][-keep_yearly:]
|
||||||
|
else:
|
||||||
|
saved['year'] = []
|
||||||
|
|
||||||
|
if keep_monthly:
|
||||||
|
saved['month'] = saved['month'][-keep_monthly:]
|
||||||
|
else:
|
||||||
|
saved['month'] = []
|
||||||
|
|
||||||
|
if keep_weekly:
|
||||||
|
saved['week'] = saved['week'][-keep_weekly:]
|
||||||
|
else:
|
||||||
|
saved['week'] = []
|
||||||
|
|
||||||
|
if keep_daily:
|
||||||
|
saved['day'] = saved['day'][-keep_daily:]
|
||||||
|
else:
|
||||||
|
saved['day'] = []
|
||||||
|
|
||||||
|
if keep_hourly:
|
||||||
|
saved['hour'] = saved['hour'][-keep_hourly:]
|
||||||
|
else:
|
||||||
|
saved['hour'] = []
|
||||||
|
|
||||||
|
if keep_30min:
|
||||||
|
saved['min30'] = saved['min30'][-keep_30min:]
|
||||||
|
else:
|
||||||
|
saved['min30'] = []
|
||||||
|
|
||||||
|
if keep_15min:
|
||||||
|
saved['min15'] = saved['min15'][-keep_15min:]
|
||||||
|
else:
|
||||||
|
saved['min15'] = []
|
||||||
|
|
||||||
|
if keep_5min:
|
||||||
|
saved['min5'] = saved['min5'][-keep_5min:]
|
||||||
|
else:
|
||||||
|
saved['min5'] = []
|
||||||
|
|
||||||
|
if keep_1min:
|
||||||
|
saved['min1'] = saved['min1'][-keep_1min:]
|
||||||
|
else:
|
||||||
|
saved['min1'] = []
|
||||||
|
|
||||||
|
if keep_custom:
|
||||||
|
saved['custom'] = saved['custom'][-keep_custom:]
|
||||||
|
else:
|
||||||
|
saved['custom'] = []
|
||||||
|
|
||||||
|
all_saved = []
|
||||||
|
all_saved.extend(saved['year'])
|
||||||
|
all_saved.extend(saved['month'])
|
||||||
|
all_saved.extend(saved['week'])
|
||||||
|
all_saved.extend(saved['day'])
|
||||||
|
all_saved.extend(saved['hour'])
|
||||||
|
all_saved.extend(saved['min30'])
|
||||||
|
all_saved.extend(saved['min15'])
|
||||||
|
all_saved.extend(saved['min5'])
|
||||||
|
all_saved.extend(saved['min1'])
|
||||||
|
all_saved.extend(saved['custom'])
|
||||||
|
all_saved = set(all_saved)
|
||||||
|
|
||||||
|
to_remove = [date for date in dates if date not in all_saved]
|
||||||
|
for date in to_remove:
|
||||||
|
try:
|
||||||
|
log.info('{}: removing snapshot from {}'.format(fs, date))
|
||||||
|
remove_snapshot(fs, date, sudo=sudo)
|
||||||
|
except ZFSSnapshotError as e:
|
||||||
|
log.error(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
45
zsnapper.ini-sample
Normal file
45
zsnapper.ini-sample
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
[settings]
|
||||||
|
# NOTE:
|
||||||
|
# --stdin is used to pass password to sudo, this does not work the first time
|
||||||
|
# a user uses sudo, so make sure to run a sudo command manually first
|
||||||
|
#
|
||||||
|
# or preferably, leave this commented out and use NOPASSWD for sudo...
|
||||||
|
;sudo=<password>
|
||||||
|
|
||||||
|
|
||||||
|
[tank]
|
||||||
|
snapshot_interval=1h
|
||||||
|
# NOTE:
|
||||||
|
# The command arguments must not contain whitespace characters, since
|
||||||
|
# split() is used to create an array to subprocess.Popen()
|
||||||
|
remote_zfs_cmd=/usr/bin/ssh ${remote_user}@${remote_host} /usr/bin/sudo /sbin/zfs
|
||||||
|
remote_test_cmd=/usr/bin/ssh ${remote_user}@${remote_host} echo "success"
|
||||||
|
remote_user=backup
|
||||||
|
remote_host=hem.winterbird.org
|
||||||
|
remote_zfs_target=tank/backup/asuna/tank
|
||||||
|
# NOTE:
|
||||||
|
# should be empty or 0 for negative value
|
||||||
|
remote_enable=1
|
||||||
|
|
||||||
|
keep_hourly=24
|
||||||
|
keep_weekly=4
|
||||||
|
keep_monthly=4
|
||||||
|
|
||||||
|
|
||||||
|
[tank/SWAP]
|
||||||
|
snapshot_interval=
|
||||||
|
remote_enable=
|
||||||
|
|
||||||
|
[tank/media]
|
||||||
|
snapshot_interval=15m
|
||||||
|
|
||||||
|
[tank/tmp]
|
||||||
|
snapshot_interval=
|
||||||
|
remote_enable=
|
||||||
|
|
||||||
|
[tank/var/log]
|
||||||
|
snapshot_interval=1m
|
||||||
|
|
||||||
|
[tank/var/tmp]
|
||||||
|
snapshot_interval=
|
||||||
|
remote_enable=
|
Loading…
Reference in New Issue
Block a user