Compare commits

...
6 Commits
12 changed files with 129 additions and 12 deletions
+2
View File
@@ -3,3 +3,5 @@ syntax: glob
*.pyc
*.swp
.tox
build
dist
+3
View File
@@ -17,3 +17,6 @@ ac5dfa01e9d14d09845f5e94c2c679e21c5eb2f9 0.1.1
83067f995dd391e38544a7722dc3b254b59c5521 0.1.2
acc7fb61566fe8028c179f43ecc735c851220b06 0.1.3
6dda59c12de88f060eb7244e6d330173985a9639 0.1.4
6dda59c12de88f060eb7244e6d330173985a9639 0.1.4
e58246fc92bb22c2b2fd8b86a1227de69d2d0315 0.1.4
0afff209b902698c2266986129d6dc9f5f913101 0.1.5
+9
View File
@@ -1,3 +1,12 @@
0.1.6
* #9: New configuration option for the encryption passphrase.
* #10: Support for Borg's new archive compression feature.
0.1.5
* Changes to support release on PyPI. Now pip installable by name!
0.1.4
* Adding test that setup.py version matches release version.
+7 -5
View File
@@ -45,16 +45,16 @@ Start](https://attic-backup.org/quickstart.html) or the [Borg Quick
Start](https://borgbackup.github.io/borgbackup/quickstart.html) to create a
repository on a local or remote host. Note that if you plan to run atticmatic
on a schedule with cron, and you encrypt your attic repository with a
passphrase instead of a key file, you'll need to set the `ATTIC_PASSPHRASE`
environment variable. See the repository encryption section of the Quick Start
for more info.
passphrase instead of a key file, you'll need to set the atticmatic
`encryption_passphrase` configuration variable. See the repository encryption
section of the Quick Start for more info.
If the repository is on a remote host, make sure that your local root user has
key-based ssh access to the desired user account on the remote host.
To install atticmatic, run the following command to download and install it:
sudo pip install --upgrade hg+https://torsion.org/hg/atticmatic
sudo pip install --upgrade atticmatic
If you are using Attic, copy the following configuration files:
@@ -136,6 +136,8 @@ backups.
## Issues and feedback
Got an issue or an idea for a feature enhancement? Check out the [atticmatic
issue tracker](https://tree.taiga.io/project/witten-atticmatic/issues).
issue tracker](https://tree.taiga.io/project/witten-atticmatic/issues). In
order to create a new issue or comment on an issue, you'll need to [login
first](https://tree.taiga.io/login).
Other questions or comments? Contact <mailto:witten@torsion.org>.
+2
View File
@@ -7,6 +7,8 @@ from atticmatic.backends import shared
COMMAND = 'attic'
CONFIG_FORMAT = shared.CONFIG_FORMAT
initialize = partial(shared.initialize, command=COMMAND)
create_archive = partial(shared.create_archive, command=COMMAND)
prune_archives = partial(shared.prune_archives, command=COMMAND)
check_archives = partial(shared.check_archives, command=COMMAND)
+9 -1
View File
@@ -8,7 +8,14 @@ from atticmatic.backends import shared
COMMAND = 'borg'
CONFIG_FORMAT = (
shared.CONFIG_FORMAT[0], # location
shared.CONFIG_FORMAT[1], # retention
Section_format(
'storage',
(
option('encryption_passphrase', required=False),
option('compression', required=False),
),
),
shared.CONFIG_FORMAT[2], # retention
Section_format(
'consistency',
(
@@ -19,6 +26,7 @@ CONFIG_FORMAT = (
)
initialize = partial(shared.initialize, command=COMMAND)
create_archive = partial(shared.create_archive, command=COMMAND)
prune_archives = partial(shared.prune_archives, command=COMMAND)
check_archives = partial(shared.check_archives, command=COMMAND)
+23 -4
View File
@@ -21,6 +21,12 @@ CONFIG_FORMAT = (
option('repository'),
),
),
Section_format(
'storage',
(
option('encryption_passphrase', required=False),
),
),
Section_format(
'retention',
(
@@ -41,13 +47,26 @@ CONFIG_FORMAT = (
)
)
def create_archive(excludes_filename, verbosity, source_directories, repository, command):
def initialize(storage_config, command):
passphrase = storage_config.get('encryption_passphrase')
if passphrase:
os.environ['{}_PASSPHRASE'.format(command.upper())] = passphrase
def create_archive(
excludes_filename, verbosity, storage_config, source_directories, repository, command,
):
'''
Given an excludes filename (or None), a vebosity flag, a space-separated list of source
directories, a local or remote repository path, and a command to run, create an attic archive.
Given an excludes filename (or None), a vebosity flag, a storage config dict, a space-separated
list of source directories, a local or remote repository path, and a command to run, create an
attic archive.
'''
sources = tuple(source_directories.split(' '))
exclude_flags = ('--exclude-from', excludes_filename) if excludes_filename else ()
compression = storage_config.get('compression', None)
compression_flags = ('--compression', compression) if compression else ()
verbosity_flags = {
VERBOSITY_SOME: ('--stats',),
VERBOSITY_LOTS: ('--verbose', '--stats'),
@@ -60,7 +79,7 @@ def create_archive(excludes_filename, verbosity, source_directories, repository,
hostname=platform.node(),
timestamp=datetime.now().isoformat(),
),
) + sources + exclude_flags + verbosity_flags
) + sources + exclude_flags + compression_flags + verbosity_flags
subprocess.check_call(full_command)
+4 -1
View File
@@ -64,7 +64,10 @@ def main():
config = parse_configuration(args.config_filename, backend.CONFIG_FORMAT)
repository = config.location['repository']
backend.create_archive(args.excludes_filename, args.verbosity, **config.location)
backend.initialize(config.storage)
backend.create_archive(
args.excludes_filename, args.verbosity, config.storage, **config.location
)
backend.prune_archives(args.verbosity, repository, config.retention)
backend.check_archives(args.verbosity, repository, config.consistency)
except (ValueError, IOError, CalledProcessError) as error:
@@ -1,4 +1,5 @@
from collections import OrderedDict
import os
from flexmock import flexmock
@@ -7,6 +8,28 @@ from atticmatic.tests.builtins import builtins_mock
from atticmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
def test_initialize_with_passphrase_should_set_environment():
orig_environ = os.environ
try:
os.environ = {}
module.initialize({'encryption_passphrase': 'pass'}, command='attic')
assert os.environ.get('ATTIC_PASSPHRASE') == 'pass'
finally:
os.environ = orig_environ
def test_initialize_without_passphrase_should_not_set_environment():
orig_environ = os.environ
try:
os.environ = {}
module.initialize({}, command='attic')
assert os.environ.get('ATTIC_PASSPHRASE') == None
finally:
os.environ = orig_environ
def insert_subprocess_mock(check_call_command, **kwargs):
subprocess = flexmock()
subprocess.should_receive('check_call').with_args(check_call_command, **kwargs).once()
@@ -41,6 +64,7 @@ def test_create_archive_should_call_attic_with_parameters():
module.create_archive(
excludes_filename='excludes',
verbosity=None,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
@@ -55,6 +79,7 @@ def test_create_archive_with_none_excludes_filename_should_call_attic_without_ex
module.create_archive(
excludes_filename=None,
verbosity=None,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
@@ -69,6 +94,7 @@ def test_create_archive_with_verbosity_some_should_call_attic_with_stats_paramet
module.create_archive(
excludes_filename='excludes',
verbosity=VERBOSITY_SOME,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
@@ -83,6 +109,22 @@ def test_create_archive_with_verbosity_lots_should_call_attic_with_verbose_param
module.create_archive(
excludes_filename='excludes',
verbosity=VERBOSITY_LOTS,
storage_config={},
source_directories='foo bar',
repository='repo',
command='attic',
)
def test_create_archive_with_compression_should_call_attic_with_compression_parameters():
insert_subprocess_mock(CREATE_COMMAND + ('--compression', 'rle'))
insert_platform_mock()
insert_datetime_mock()
module.create_archive(
excludes_filename='excludes',
verbosity=None,
storage_config={'compression': 'rle'},
source_directories='foo bar',
repository='repo',
command='attic',
+9
View File
@@ -5,6 +5,15 @@ source_directories: /home /etc
# Path to local or remote repository.
repository: user@backupserver:sourcehostname.attic
[storage]
# Passphrase to unlock the encryption key with. Only use on repositories that
# were initialized with passphrase/repokey encryption.
#encryption_passphrase: foo
# For Borg only, you can specify the type of compression to use when creating
# archives. See https://borgbackup.github.io/borgbackup/usage.html#borg-create
# for details. Defaults to no compression.
#compression: lz4
[retention]
# Retention policy for how many backups to keep in each category. See
# https://attic-backup.org/usage.html#attic-prune or
+3
View File
@@ -1,2 +1,5 @@
[metadata]
description-file=README.md
[nosetests]
detailed-errors=1
+16 -1
View File
@@ -1,11 +1,26 @@
from setuptools import setup, find_packages
VERSION = '0.1.6'
setup(
name='atticmatic',
version='0.1.4',
version=VERSION,
description='A wrapper script for Attic/Borg backup software that creates and prunes backups',
author='Dan Helfman',
author_email='witten@torsion.org',
url='https://torsion.org/atticmatic',
download_url='https://torsion.org/hg/atticmatic/archive/%s.tar.gz' % VERSION,
classifiers=(
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python',
'Topic :: Security :: Cryptography',
'Topic :: System :: Archiving :: Backup',
),
packages=find_packages(),
entry_points={
'console_scripts': [