Code Monkey home page Code Monkey logo

tarsnapper's Introduction

Tarsnapper

A wrapper around tarsnap which does two things:

  • Lets you define "backup jobs" (tarsnap invocations) in a config file, though on it's own this has little advantage over just using a a shell script.

  • The ability to expire old backups using a Grandfather-father-son backup scheme. This feature can be used in conjunction with tarsnapper backup jobs, or standalone, to be applied to any existing set of tarsnap backup archives, regardless of how they have been created.

Installation

Using pip:

$ pip install tarsnapper

Making a single backup without a configuration file

tarsnapper --target foobar-\$date --sources /etc/  --deltas 6h 7d 31d - make

This will backup the /etc/ folder every time you call this command (put it in cron, for example), and after each backup made, attempts to expire old backups to match the deltas given.

Note the following:

  • You need to give the $date placeholder for expiration to work, and you will need to escape the dollar sign in your shell.

  • You need to end the list of deltas with a - character.

  • tarsnap needs to be setup on your machine correctly, that is, tarsnap needs to be able to find it's keyfile and so on via tarsnap.conf. The ability to pass through options to tarsnap via the tarsnapper CLI exists, though.

Using a configuration file

We also support a configuration file. It allows multiple jobs to be defined, and has more feature, such as pre-/post job commands. It looks like this:

# Global values, valid for all jobs unless overridden:
# A job's delta controls when old backups are expired
# (see "How expiring backups works" below)
deltas: 1d 7d 30d
# You can avoid repetition by giving deltas names
delta-names:
  super-important: 1h 1d 30d 90d 360d
# A job's target sets the name of the created archive
target: /localmachine/$name-$date
# You can also include jobs from separate files
include-jobs: /usr/local/etc/tarsnapper/*.yml

jobs:
  # define a job called images (names must be unique)
  images:
    source: /var/lib/mysql
    exclude: /var/lib/mysql/temp
    exec_before: service mysql stop
    exec_after: service mysql start
    # Aliases can be used when renaming a job to match old archives.
    alias: img

  some-other-job:
    sources:
      - /var/dir/1
      - /etc/google
    excludes:
      - /etc/google/cache
    target: /custom-target-$date.zip
    deltas: 1h 6h 1d 7d 24d 180d

For the images job, the global target will be used, with the name placeholder replaced by the backup job name, in this case images.

You can then ask tarsnapper to create new backups for each job:

$ tarsnapper -c myconfigfile make

The name of the archive will be the target option, with the $date placeholder replaced by the current timestamp, using either the dateformat option, or %Y%m%d-%H%M%S.

Or to expire those archives no longer needed, as per the chosen deltas:

$ tarsnapper -c myconfigfile expire

If you need to pass arguments through to tarsnap, you can do this as well:

$ tarsnapper -o configfile tarsnap.conf -o v -c tarsnapper.conf make

This will use tarsnap.conf as the tarsnap configuration file, tarsnapper.conf as the tarsnapper configuration file, and will also put tarsnap into verbose mode via the -v flag.

Using the include-jobs option, you could insert 1 or more jobs in (for example) /usr/local/etc/tarsnapper/extra-backup-jobs.yml:

# Included jobs act just like jobs in the main config file, so for
# example the default target is active and named deltas are
# available, and job names must still be globally unique.
yet-another-job:
  source: /var/dir/2
  deltas: 1h 1d 30d

an-important-job:
  source: /var/something-important
  delta: super-important

include-jobs uses Python's globbing to find job files and hence is subject to the limitations thereof.

Expiring backups

Note that if you're running tarsnapper with make, it will implicitly expire backups as well; there is no need to run make AND expire both.

If you want to create the backups yourself, and are only interested in the expiration functionality, you can do just that:

$ tarsnapper --target "foobar-\$date" --deltas 1d 7d 30d - expire

The --target argument selects which set of backups to apply the expire operation to. All archives that match this expression are considered to be part of the same backup set that you want to operate on.

tarsnapper will then look at the date of each archive (this is why you need the $date placeholder) and determine those which are not needed to accommodate the given given delta range. It will parse the date using the python-dateutil library, which supports a vast array of different formats, though some restrictions apply: If you are using yyyy-dd-mm, it cannot generally differentiate that from yyyy-mm-dd.

You can specify a custom dateformat using the --dateformat option, which should be a format string as expected by the Python strptime function (e.g. %Y%m%d-%H%M%S). Usually, a custom format is not necessary.

Note the single "-" that needs to be given between the --deltas argument and the command.

The expire command supports a --dry-run argument that will allow you to see what would be deleted:

$ tarsnapper --target "foobar-\$date" --deltas 1d 7d 30d - expire --dry-run

How expiring backups works

The design goals for this were as follows:

  • Do not require backup names to include information on which generation a backup belongs to, like for example tarsnap-generations does. That is, you can create your backups anyway you wish, and simply use this utility to delete old backups.

  • Do not use any fixed generations (weekly, monthly etc), but freeform timespans.

  • Similarily, do not make any assumptions about when or if backup jobs have actually run or will run, but try to match the given deltas as closely as possible.

The generations are defined by a list of deltas. 60s means a minute, 12h is half a day, 7d is a week. The number of backups in each generation is implied by it's and the parent generation's delta.

For example, given the deltas 1h 1d 7d, the first generation will consist of 24 backups each one hour older than the previous (or the closest approximation possible given the available backups), the second generation of 7 backups each one day older than the previous, and backups older than 7 days will be discarded for good.

The most recent backup is always kept.

As an example, here is a list of backups from a Desktop computer that has often been running non-stop for days, but also has on occasion been turned off for weeks at a time, using the deltas 1d 7d 30d 360d 18000d:

  dropbox-20140424-054252
  dropbox-20140423-054120
  dropbox-20140422-053921
  dropbox-20140421-053920
  dropbox-20140420-054246
  dropbox-20140419-054007
  dropbox-20140418-060211
  dropbox-20140226-065032
  dropbox-20140214-063824
  dropbox-20140115-072109
  dropbox-20131216-100926
  dropbox-20131115-211256
  dropbox-20131012-054438
  dropbox-20130912-054731
  dropbox-20130813-090621
  dropbox-20130713-160422
  dropbox-20130610-054348
  dropbox-20130511-055537
  dropbox-20130312-064042
  dropbox-20120325-054505
  dropbox-20110331-12174

tarsnapper's People

Contributors

caspark avatar coaxial avatar flamingice avatar hansmi avatar jamur2 avatar jeffwidman avatar miracle2k avatar nmandery avatar pbowyer avatar psde avatar pushcx avatar ralish avatar statik avatar zqad avatar zr40 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

tarsnapper's Issues

Does `make` imply `expire`?

Hello,

I am using make at the moment to produce backups. Does it imply expire or should I always also run expire to trim the backups that are no longer necessary?

I probably misunderstood the README, could you please clarify?

Thank you!

UnicodeEncodeError

  • fresh (previously nuked) tarsnapper "instance"
  • first backup completes
  • on second run:
Creating backup www.example.com: www.example.com-20170316-070936
Executing: tarsnap -c -f www.example.com-20170316-070936 /srv/www.example.com
tarsnap: Removing leading '/' from member names^M
Something went wrong with backup job: 'www.example.com'
Traceback (most recent call last):
  File "/usr/lib64/python2.7/site-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 381, in run
    self.backend.make(job)
  File "/usr/lib64/python2.7/site-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 223, in make
    self.call(*args)
  File "/usr/lib64/python2.7/site-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 65, in call
    return self._exec_tarsnap(call_with)
  File "/usr/lib64/python2.7/site-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 78, in _exec_tarsnap
    has_prompt = (child.expect([u'Please enter passphrase for keyfile .*?:', pexpect.EOF]) == 0)
  File "/usr/lib64/python2.7/site-packages/pexpect-4.2.1-py2.7.egg/pexpect/spawnbase.py", line 321, in expect
    timeout, searchwindowsize, async)
  File "/usr/lib64/python2.7/site-packages/pexpect-4.2.1-py2.7.egg/pexpect/spawnbase.py", line 345, in expect_list
    return exp.expect_loop(timeout)
  File "/usr/lib64/python2.7/site-packages/pexpect-4.2.1-py2.7.egg/pexpect/expect.py", line 99, in expect_loop
    incoming = spawn.read_nonblocking(spawn.maxread, timeout)
  File "/usr/lib64/python2.7/site-packages/pexpect-4.2.1-py2.7.egg/pexpect/pty_spawn.py", line 465, in read_nonblocking
    return super(spawn, self).read_nonblocking(size)
  File "/usr/lib64/python2.7/site-packages/pexpect-4.2.1-py2.7.egg/pexpect/spawnbase.py", line 163, in read_nonblocking
    self._log(s, 'read')
  File "/usr/lib64/python2.7/site-packages/pexpect-4.2.1-py2.7.egg/pexpect/spawnbase.py", line 121, in _log
    self.logfile.write(s)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xdc' in position 66: ordinal not in range(128)

tarsnapper doesn't launch with cron

When adding tarsnapper into a cronjob, it generates an error, I think it's due to a path problem (cron have a limited env):

Am I the only one who use tarsnapper with cron?

   Creating backup images: /images-20141113-104001
   Traceback (most recent call last):
  File "/usr/local/bin/tarsnapper", line 9, in <module>
    load_entry_point('tarsnapper==0.2.1', 'console_scripts', 'tarsnapper')()
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.2.1-py2.7.egg/tarsnapper/script.py", line 498, in run
    sys.exit(main(sys.argv[1:]) or 0)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.2.1-py2.7.egg/tarsnapper/script.py", line 491, in main
    command.run(job)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.2.1-py2.7.egg/tarsnapper/script.py", line 347, in run
    self.backend.make(job)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.2.1-py2.7.egg/tarsnapper/script.py", line 182, in make
    self.call(*args)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.2.1-py2.7.egg/tarsnapper/script.py", line 59, in call
    return self._exec_tarsnap(call_with)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.2.1-py2.7.egg/tarsnapper/script.py", line 64, in _exec_tarsnap
    stderr=subprocess.PIPE)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory

ValueError exception trying to expire backups

Hello,

I came upon this script, looking for a solution to cleanup old backups.

This maybe a silly newbie question, but at my first experimentation with a dry-run expire command (following indications in the README file), the program crashed with the following trace:

# tarsnapper --target "mantisbt_org_\$date" --deltas 1d 30d 360d - expire --dry-run 
1193 backups are matching
Traceback (most recent call last):
  File "/usr/local/bin/tarsnapper", line 8, in <module>
    sys.exit(run())
  File "/usr/local/lib/python3.8/dist-packages/tarsnapper/script.py", line 556, in run
    sys.exit(main(sys.argv[1:]) or 0)
  File "/usr/local/lib/python3.8/dist-packages/tarsnapper/script.py", line 546, in main
    command.run(job)
  File "/usr/local/lib/python3.8/dist-packages/tarsnapper/script.py", line 324, in run
    self.expire(job)
  File "/usr/local/lib/python3.8/dist-packages/tarsnapper/script.py", line 321, in expire
    self.backend.expire(job)
  File "/usr/local/lib/python3.8/dist-packages/tarsnapper/script.py", line 185, in expire
    to_keep = expire.expire(backups, job.deltas)
  File "/usr/local/lib/python3.8/dist-packages/tarsnapper/expire.py", line 50, in expire
    backups = sorted(backups.items(), key=lambda x: x[1], reverse=True)
ValueError: offset must be a timedelta strictly between -timedelta(hours=24) and timedelta(hours=24).

I'm not sure what to make of this error, or how to fix it. Thanks in advance for your help !

Please find attached the output of tarsnap --list-archives for reference
list-archives-20210529.txt

Documentation incorrectly claims that dates are parsed with python-dateutil

The tarsnapper Readme.rst states:

It [tarsnapper] will parse the date using the python-dateutil library, which supports a vast array of different formats,

However, the tarsnapper source shows it to be using datetime.strptime, with a couple of pre-canned formats and the ability to specify a date format on the command line.

This difference is significant, as dates that one might expect to be correctly parsed are actually rejected.

tarsnapper depends on python-yaml but it is not a stated dependency

$ tarsnapper
Traceback (most recent call last):
File "/usr/local/bin/tarsnapper", line 9, in
load_entry_point('tarsnapper==0.1', 'console_scripts', 'tarsnapper')()
File "/usr/lib/python2.6/dist-packages/pkg_resources.py", line 305, in load_entry_point
return get_distribution(dist).load_entry_point(group, name)
File "/usr/lib/python2.6/dist-packages/pkg_resources.py", line 2244, in load_entry_point
return ep.load()
File "/usr/lib/python2.6/dist-packages/pkg_resources.py", line 1954, in load
entry = import(self.module_name, globals(),globals(), ['name'])
File "/usr/local/lib/python2.6/dist-packages/tarsnapper/script.py", line 11, in
import expire, config
File "/usr/local/lib/python2.6/dist-packages/tarsnapper/config.py", line 23, in
import yaml
ImportError: No module named yaml
$ sudo aptitude install python-yaml
...

Setting up python-yaml (3.09-5) ...

$ tarsnapper
usage: tarsnapper [-h] [-q | -v] [-o name value] [--config CONFIG]
...

tarsnapper fails with UnicodeDecodeError: 'utf8' codec can't decode byte

one of my tarsnapper jobs always fails with the following error:

tarsnap: Removing leading '/' from member names
Something went wrong with backup job: 'crypt'
Traceback (most recent call last):
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/site-packages/tarsnapper/script.py", line 381, in run
    self.backend.make(job)
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/site-packages/tarsnapper/script.py", line 223, in make
    self.call(*args)
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/site-packages/tarsnapper/script.py", line 65, in call
    return self._exec_tarsnap(call_with)
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/site-packages/tarsnapper/script.py", line 78, in _exec_tarsnap
    has_prompt = (child.expect([u'Please enter passphrase for keyfile .*?:', pexpect.EOF]) == 0)
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/site-packages/pexpect/spawnbase.py", line 341, in expect
    timeout, searchwindowsize, async_)
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/site-packages/pexpect/spawnbase.py", line 369, in expect_list
    return exp.expect_loop(timeout)
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/site-packages/pexpect/expect.py", line 111, in expect_loop
    incoming = spawn.read_nonblocking(spawn.maxread, timeout)
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/site-packages/pexpect/pty_spawn.py", line 485, in read_nonblocking
    return super(spawn, self).read_nonblocking(size)
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/site-packages/pexpect/spawnbase.py", line 178, in read_nonblocking
    s = self._decoder.decode(s, final=False)
  File "/home/fho/apps/tarsnapper/ENV/lib/python2.7/codecs.py", line 314, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf8' codec can't decode byte 0xfc in position 47: invalid start byte

I'm using the following application versions:

(ENV) [fho@zerozero tarsnapper]$ python --version
Python 2.7.15
(ENV) [fho@zerozero tarsnapper]$ pip list
Package         Version
--------------- -------
pexpect         4.6.0  
pip             18.0   
ptyprocess      0.6.0  
python-dateutil 2.7.3  
PyYAML          3.13   
setuptools      40.0.0 
six             1.11.0 
tarsnapper      0.4.0  
wheel           0.31.1 

Update: For the job the problem occurs, also the following tarsnap message is logged:
Can't translate pathname 'crypt/home-fho/Documents/Bilder/travemünde 7.7.07/DSC00513.JPG' to UTF-8 (http://mail.tarsnap.com/tarsnap-users/msg00447.html)
This might be the cause of the encoding error in tarsnapper.

-o option

the -o option seems to always prepend '--', even for arguments where it should only be '-', and hence tarsnap fails

also, it seems to expect a key, value pair even for options that do not have a value. passing "" seems to be work around.

Thank you for sharing tarsnapper.

TypeError: __init__() got an unexpected keyword argument 'encoding'

running tarsnapper --target "\$date-03-HOURLY-tripoli-/home/backups/media/Pictures" --deltas 1d 7d 30d 360d 18000d - expire --dry-run I get the following error:

Exception AttributeError: "'spawn' object has no attribute 'closed'" in <bound method spawn.__del__ of <pexpect.spawn object at 0x7fb18b286210>> ignored
Traceback (most recent call last):
  File "/usr/local/bin/tarsnapper", line 9, in <module>
    load_entry_point('tarsnapper==0.4.0', 'console_scripts', 'tarsnapper')()
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 546, in run
    sys.exit(main(sys.argv[1:]) or 0)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 536, in main
    command.run(job)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 314, in run
    self.expire(job)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 311, in expire
    self.backend.expire(job)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 179, in expire
    backups = self.get_backups(job)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 143, in get_backups
    for backup_path in self.get_archives():
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 118, in get_archives
    response = StringIO(self.call('--list-archives'))
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 65, in call
    return self._exec_tarsnap(call_with)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.4.0-py2.7.egg/tarsnapper/script.py", line 72, in _exec_tarsnap
    encoding='utf-8')
TypeError: __init__() got an unexpected keyword argument 'encoding'

I'm using Python 2.7.6.

Strange behavior of expiration

Hi,

I'm in the process of setting up tarsnap + tarsnapper on my FreeBSD server.

I currently have the following small tarsnapper.conf:

deltas: 1d 7d 30d
target: $name-$date

jobs:
  backup:
    sources:
      - /etc
      - /usr/local/etc

I tried to create a couple of backups by invoking tarsnapper -c /usr/local/etc/tarsnapper.conf make. But then I encountered this behavior:

root@www:/etc/periodic/daily # tarsnap --list-archives
backup-20141026-181732
backup-20141026-174741
root@www:/etc/periodic/daily # tarsnap -c /usr/local/etc/tarsnapper.conf make
Creating backup backup: backup-20141026-182234
3 backups are matching
1 of those can be deleted
Deleting backup-20141026-181732
root@www:/etc/periodic/daily # tarsnap --list-archives
backup-20141026-182234
backup-20141026-174741

Why did it remove the backup-20141026-181732 archive?

(These commands were all run today while I was testing).

Normal tarsnapper output goes to stderr instead of stdout

When I run

tarsnapper -c /usr/local/etc/tarsnapper.conf make

I get output like

Creating backup mail: mybackupjob-20150610-092951
3 backups are matching
0 of those can be deleted

I would like to suppress normal output like this (which should be stdout) when I run tarsnapper using cron so that I only get email from cron when there is an error (something output to stderr)

So I tried:

tarsnapper -c /usr/local/etc/tarsnapper.conf make >/dev/null

But I still get the same output! I figured only when I redirect using 2>/dev/null I get rid of this output so tarsnapper seems to output this normal output to stderr which is a bug in my opinion. Only errors should be output to stderr and everything else to stdout.

I'm running on Ubuntu 15.04 using tarsnapper installed with easy_install.

Automating backups via cron - is tarsnapper the tool for it?

Hi there,

I'd like to set up a backup schema via tarsnap that creates the backups on a certain interval and deletes the old ones according to the defined scheme.

So I created the following config file:

deltas: 1h 6h 1d 7d 24d 180d
target: $name-$date

jobs:
  image:
     source: /

Now, it works well by calling it manually, but from the readme i'm having some issues with understanding how to actually automate it.

The Readme says:
The ability to expire old backups using a Grandfather-father-son backup scheme. This feature can be used in conjunction with tarsnapper backup jobs, or standalone, to be applied to any existing set of tarsnap backup archives, regardless of how they have been created.

Am I understanding it correctly that I can just cron 1 command and it'll create a new backup and then check the settings to see if some old backups can be removed (and if so, remove them)?
I've tried: tarsnapper -c /usr/local/etc/tarsnap-cron.conf make,expire but that doesn't work, as I can only do -or- make, -or- expire. Does that mean I need 2 crons?

Having tried it with a cron that just ran every minute or every 5 minutes, it does create a new backup every 1/5 minutes. However what I need it to do is check the deltas settings, and then only create a new backup if it is needed (so based on the above, if 1h passed since the previous backup).
Is that possible via tarsnapper, or do I have to configure the cron based on the frequency I want backups to run?

Perhaps I'm missing something here, but I can't find all that much online either for using tarsnapper to automate crons in this way, especially not using crons.

Thank you in advance!
Niels

Incompatible with Python 3

I tried to use tarsnapper with Python 3.5.0 and 3.4.3 and I get this error during pip install in a virtualenv:

$ pip install tarsnapper
Collecting tarsnapper
  Using cached tarsnapper-0.2.5.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 20, in <module>
      File "/private/var/folders/9j/vyqph8z14g72ftq41_wlndjh0000gn/T/pip-build-fzuxjgi6/tarsnapper/setup.py", line 28
        exec "version = %s" % match.group(1)
                          ^
    SyntaxError: invalid syntax

I will use Python 2.7.10 for now.

Passing -v to tarsnap doesn't work

Thanks for making this helpful script. I've encountered a problem trying to debug what tarsnap is doing internally. The tarsnapper --help says you can pass options to tarsnap. I'm trying tarsnapper -o v without success. It may make sense to forward -v to tarsnap when passed to tarsnapper.

Dates with timezone offsets cannot be parsed (at least under OS X and FreeBSD)

I have tarsnap archives with the dates formatted like this:

hg-2015-01-27T19:34:01+0000

One might expect these to be parsed correctly with a suitable --dateformat argument, e.g.

tarsnapper --target "hg-$date" --dateformat "%Y-%m-%dT%H:%M:%S%z" --deltas 1h 1d 7d 30d - expire --dry-run

However, this does not work (at least under OS X Yosemite using Homebrew’s Python 2.7.9 and FreeBSD 9.3 with Python 2.7.9), as Python 2.7.9’s datetime.strptime does not recognize the %z strftime format conversion specification. (Note, however, that Python 3.4.2’s strptime on the same platforms does support %z.)

It would thus be helpful if tarsnapper were to parse dates using a mechanism that supported timezone offsets in a platform-independent way, or if tarsnapper were to be available in a version that could run under Python 3.

As a workaround, if the timezone offset never changes, one can specify the offset as part of the target, like this:

tarsnapper --target "hg-$date+0000" --dateformat "%Y-%m-%dT%H:%M:%S" --deltas 1h 1d 7d 30d - expire --dry-run

However, this solution obviously cannot be used where the timezone offset varies.

Deleting archives does not actually remove them from the cached list of archives

tarsnapper caches the output of tarsnap --list-archives.

So when tarsnapper expires an archives, it also deletes it from the cache:

self.archives.remove(name)

However, self.archives is actually a property, which concatenates two lists and then returns them:

return self._queried_archives + self._known_archives
archives = property(get_archives)

So the self.archives.remove call is pointless because it only removes the filename from the temporary list created for that specific call to self.archives... it never actually removes the filename from the underlying cache.

Allow passing -L option to tarsnap in -c only

In wanting to follow and archive symbolic links, I need to specify -L to tarsnap -c.
To do so with tarsnapper, I used the following option: -o L.

However, this leads to an error:

tarsnap execution failed:
Traceback (most recent call last):
 File "/usr/local/lib/python3.9/dist-packages/tarsnapper/script.py", line 546, in main
   command.run(job)
 File "/usr/local/lib/python3.9/dist-packages/tarsnapper/script.py", line 402, in run
   self.expire(job)
 File "/usr/local/lib/python3.9/dist-packages/tarsnapper/script.py", line 321, in expire
   self.backend.expire(job)
 File "/usr/local/lib/python3.9/dist-packages/tarsnapper/script.py", line 181, in expire
   backups = self.get_backups(job)
 File "/usr/local/lib/python3.9/dist-packages/tarsnapper/script.py", line 145, in get_backups
   for backup_path in self.get_archives():
 File "/usr/local/lib/python3.9/dist-packages/tarsnapper/script.py", line 120, in get_archives
   response = StringIO(self.call('--list-archives'))
 File "/usr/local/lib/python3.9/dist-packages/tarsnapper/script.py", line 68, in call
   return self._exec_tarsnap(call_with)
 File "/usr/local/lib/python3.9/dist-packages/tarsnapper/script.py", line 88, in _exec_tarsnap
   raise TarsnapError("tarsnap failed with status {0}:{1}{2}".format(
tarsnapper.script.TarsnapError: tarsnap failed with status 1:
tarsnap: Option -L is not permitted in mode --list-archives

It also does not look like tarsnap.conf supports specifying the -L option.
Is there any way to do this?

Breaks when no xpect.io URL given

Latest code in github breaks if an xpect.io URL is not given:

  File "/usr/local/bin/tarsnapper", line 9, in <module>
    load_entry_point('tarsnapper==0.2.1', 'console_scripts', 'tarsnapper')()
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.2.1-py2.7.egg/tarsnapper/script.py", line 548, in run
    sys.exit(main(sys.argv[1:]) or 0)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.2.1-py2.7.egg/tarsnapper/script.py", line 541, in main
    plugin.all_jobs_done(args, global_config, args.command)
  File "/usr/local/lib/python2.7/dist-packages/tarsnapper-0.2.1-py2.7.egg/tarsnapper/script.py", line 252, in all_jobs_done
    'action': 'eventSuccess'
  File "/usr/lib/python2.7/urllib2.py", line 202, in **init**
    self.__original = unwrap(url)
  File "/usr/lib/python2.7/urllib.py", line 1060, in unwrap
    url = url.strip()
AttributeError: 'NoneType' object has no attribute 'strip'

Date-time confusion

I have a bunch of test files, named like this:

test-20141025-163600.txt
test-20141025-164600.txt
test-20141025-165600.txt
test-20141025-170600.txt

This is my job configuration:

  test-txt:
    source: /home/backup/
    alias: test
    target: test-$date.txt
    deltas: 1h 1d 3d

I would expect that this reads the filename "test-" + date 20141025-163600 + ".txt". But when calling make I get:

Creating backup test-txt: test-20141025-204440.txt

It discards the time value in the file name and inserts the current time of the machine. It also creates only 1 backup file. This seems like a bug to me.

Feature request: Add support for passing `--print-stats` to only `-c` tarsnap calls

Giving -o print-stats (or anything that affects output, like -o v) to tarsnapper seems to confuse the parsing that tarsnapper does of some of the tarsnap commands. It would be great if there was a tarsnapper specific print-stats config file option and command line flag that would only pass --print-stats to the tarsnap -c command (the output from which is not parsed).

Possible to keep only the latest backup?

For some of my backup sets I don't need to keep more than the latest (the set contains PITR data). Is it possible to tell tarsnapper to keep only the latest set? The best I can do so far is deltas: 1d 1d which keeps 2 sets.

Feature: include extra jobs

Tarsnapper is extremely useful, thanks!

I would find it very convenient if there were a way to specify job in separate files, something like:

# Global values, valid for all jobs unless overridden:
deltas: 1d 7d 30d
target: /localmachine/$name-$date
extra_jobs: /usr/local/etc/tarsnapper/extra/*

jobs:
  # some jobs

and then have one or more jobs per file in the given directory.

The rationale for this is, I use Chef and I have created my own tarsnap cookbooks.
This feature would make it easy for other recipes (e.g. a database) to configure their jobs, which would get picked up automatically.

I would send a pull request but my knowledge of Python is next to 0 :)

Feature Request: add --no-expire

I'd like to user tarsnapper from cron without expiration because the keyfile has no delete access, and the deletion key is passphrased. Then I could run the -expire manually interactively with the passphrased key using the same configuration.

Feature request: command to only create archives

Hello, I have the following usecase:

I would like to create append-only backups in order to protect against ransomware attacks, for this I have thought of the following workflow:

  • The master-key is passphrase-encrypted
  • Using the tarsnap-keymgmt-utility, I have created a subkey without a passphrase which has only write-permission
  • Using this key I would like to automatically create backups with a systemd-timer
  • I would then manually expire old backups using the tarsnapper expire command

As things are now, tarsnapper crashes when running with a write-only key, since it can't read or delete existing archives.

Therefore, I would like to suggest the addition of a new command (maybe create) which only creates new archives and does not try to expire existing ones.

dateutil.parser._parser.ParserError: Unknown string format

Hi,

It looks like tarsnapper breaks when the job name has more than one -:

$ /usr/local/bin/tarsnapper -c /opt/tarsnap/etc/tarsnapper.conf list

Error snippet:

Ignoring '/xxx/snipe-it-db-20210121-222959': Unknown string format: db-20210121-222959
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/dist-packages/tarsnapper/script.py", line 155, in get_backups
    date = parse_date(match.groupdict()['date'], job.dateformat)
  File "/usr/local/lib/python3.7/dist-packages/tarsnapper/script.py", line 254, in parse_date
    return dateutil.parser.parse(string)
  File "/usr/local/lib/python3.7/dist-packages/dateutil/parser/_parser.py", line 1374, in parse
    return DEFAULTPARSER.parse(timestr, **kwargs)
  File "/usr/local/lib/python3.7/dist-packages/dateutil/parser/_parser.py", line 649, in parse
    raise ParserError("Unknown string format: %s", timestr)
dateutil.parser._parser.ParserError: Unknown string format: db-20210121-222959

Config snippet:

snipe-it-db:
    sources:
      - /opt/snipe-it/sql-backup-archives
    target: /xxx/snipe-it-db-$date

jobs are run in randomised order

Jobs defined in a config file (at least) are run in a random order each time. A stable order such as alphabetic, or (best) the order jobs appear in the config file / command line, would be better for predictability and readability.

Thanks for tarsnapper!

Feature request: allow globs in source paths

It would be nice if one could include globs in source paths:

  myscripts:
      sources:
        - /path/to/*.py
        - /path/to/*.sh

to get the equivalent of the shell expression:

tarsnap -cf myscripts /path/to/*.py /path/to/*.sh

While just using Python's glob.glob() would do the trick, perhaps just letting shell expansions operate would be more powerful and reduce surprises (often one tests raw tarsnap commands first before encoding them in tarsnapper).

Which version of the BSD license?

Hi! I am curious: is this project licensed under BSD 2-clause, 3-clause, 4-clause or a different BSD license variant? May I suggest you include the full license text in the project root?

I noticed there is a license text in debian/copyright, however if that is the license you're using, I don't think this is considered free and open source software because it contains the following clause:

Redistribution and use in source and binary forms, without modification,
is permitted for the sole purpose of using the "tarsnap" backup service
provided by Colin Percival. ??????????????????

... which restricts redistribution and creation of derivatives.

Feature request: Specify time delta generation size

I propose an addition to the current deltas syntax to specify the generations size:
deltas: 1h 7d;12h 14d;1d 90d;7d 360d

This would mean:

  • Hourly backups for the first 7 days
  • Two backups per day for the following 7 days
  • Daily backups untill day 90
  • Weekly backups for the rest of the year

I realize the syntax is leaving something to be desired.

Please tell me if I overlooked something and this is already possible with the current deltas syntax.

expire (and make) "Skipping" expiry of all but the first job if deltas are defined at top level

I have a config file defining four jobs. deltas is defined once for all jobs, at the top level. The expire & make commands seem to skip expiry of all but the first job in a run. Eg:

# tarsnapper -c /etc/tarsnapper.conf expire --dry-run
24 backups are matching
3 of those can be deleted
Deleting mail-20190707-082116 mail-20190708-082224 mail-20190709-081501
Skipping 'conf', does not define deltas
Skipping 'apps', does not define deltas
Skipping 'data', does not define deltas

In fact all of these jobs have deletable backups, eg here it is with a different first job (cf #60):

# tarsnapper -c /etc/tarsnapper.conf expire --dry-run
27 backups are matching
5 of those can be deleted
Deleting conf-20190530-084419 conf-20190706-082120 conf-20190707-082044 conf-20190708-081501 conf-20190709-083729
Skipping 'apps', does not define deltas
Skipping 'mail', does not define deltas
Skipping 'data', does not define deltas

Matching mechanism

I've got a question about how exactly matching works; I've got a list of old backups created manually using a script, named like "$(uname -r)-$(date +%Y-%m-%d_%H-%M-%S)" (in effect this is machine-2017-09-13_00-00-00 for example).

When toying with the tool I ran tarsnapper -v --target 'test-$date' --deltas 6h 1d 7d 30d 360d 3600d - expire --dry-run, which resulted in the following output:

Executing: tarsnap --list-archives
# ...
0 backups are matching
0 of those can be deleted

This surprised me as the README said it would just delete all old backups? I changed it to --target 'machine-$date' to match the other backups more closely (which I thought I didn't have to do) and got a couple of Ignoring 'hostname-2016-12-12_00-06-02': Unknown string format. Any help?

donation link ?

Thanks for tarsnapper. I would donate to this project, and more so if it looks maintained. Consider setting up ways to do this, eg an open collective project, github sponsor button, paypal instructions etc.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.