Skip to content

Commit

Permalink
v0.6.4 - Custom pre/post backup commands (#104)
Browse files Browse the repository at this point in the history
* Add pre/post-backup command setting. Fixes #109
* Bug fixes for #111 and #106
  • Loading branch information
m3nu committed Jan 6, 2019
1 parent e71669f commit 5cbdaaa
Show file tree
Hide file tree
Showing 12 changed files with 587 additions and 426 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Expand Up @@ -34,6 +34,8 @@ $ brew cask install qt-creator
$ brew install qt
```

For UI icons, we use Fontawesome. You can browse available icons [here](https://fontawesome.com/icons) and download them as SVG [here](https://github.com/encharm/Font-Awesome-SVG-PNG). New icons are first added to `vorta/assets/icons.collection.qrc` and then the command `pyrcc5 -o src/vorta/views/collection_rc.py src/vorta/assets/icons/collection.qrc` is run to compile them to a resource file, which is used by the UI files.

## Building Binaries
To build a macOS app package:
- add `Sparkle.framework` from [here](https://github.com/sparkle-project/Sparkle) and `borg` from [here](https://github.com/borgbackup/borg/releases) in `bin/macosx64`
Expand Down
4 changes: 3 additions & 1 deletion setup.cfg
Expand Up @@ -66,7 +66,9 @@ source = src
[flake8]
ignore =
max-line-length = 120
exclude = build,dist,.git,.idea,.cache,.tox,.eggs
exclude =
build,dist,.git,.idea,.cache,.tox,.eggs,
./src/vorta/views/collection_rc.py

[tox:tox]
envlist = py36,py37,flake8
Expand Down
3 changes: 1 addition & 2 deletions src/vorta/__main__.py
Expand Up @@ -15,9 +15,8 @@ def main():
args = parse_args()

frozen_binary = getattr(sys, 'frozen', False)

# Don't fork if user specifies it or when running from onedir app bundle on macOS.
if hasattr(args, 'foreground') or (frozen_binary and sys.platform == 'darwin'):
if (hasattr(args, 'foreground') and args.foreground) or (frozen_binary and sys.platform == 'darwin'):
pass
else:
print('Forking to background (see system tray).')
Expand Down
2 changes: 1 addition & 1 deletion src/vorta/assets/UI/repotab.ui
Expand Up @@ -129,7 +129,7 @@ margin-left: 5</string>
margin-left: 5</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Remote or local backup repository. For secure remote backups, try &lt;a href=&quot;https://www.borgbase.com/?utm_source=vorta&amp;utm_medium=app&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;BorgBase&lt;/span&gt;&lt;/a&gt;. 100GB free during Beta.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Remote or local backup repository. For simple and secure backup hosting, try &lt;a href=&quot;https://www.borgbase.com/?utm_source=vorta&amp;amp;utm_medium=app&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;BorgBase&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
Expand Down
80 changes: 76 additions & 4 deletions src/vorta/assets/UI/scheduletab.ui
Expand Up @@ -32,15 +32,15 @@ font-weight: bold;
}</string>
</property>
<property name="currentIndex">
<number>0</number>
<number>3</number>
</property>
<widget class="QWidget" name="schedule">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>663</width>
<height>388</height>
<height>354</height>
</rect>
</property>
<property name="font">
Expand Down Expand Up @@ -341,7 +341,7 @@ font-weight: bold;
<x>0</x>
<y>0</y>
<width>663</width>
<height>388</height>
<height>354</height>
</rect>
</property>
<attribute name="icon">
Expand Down Expand Up @@ -370,7 +370,7 @@ font-weight: bold;
<x>0</x>
<y>0</y>
<width>663</width>
<height>388</height>
<height>354</height>
</rect>
</property>
<attribute name="icon">
Expand Down Expand Up @@ -426,6 +426,78 @@ font-weight: bold;
</item>
</layout>
</widget>
<widget class="QWidget" name="page_3">
<attribute name="icon">
<iconset resource="../icons/collection.qrc">
<normaloff>:/icons/terminal.svg</normaloff>:/icons/terminal.svg</iconset>
</attribute>
<attribute name="label">
<string>Shell Commands</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Run custom shell commands before and after each backup. The actual backup and post-backup command will only run, if the pre-backup command exits without error (return code 0).</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="preBackupCmdLineEdit">
<property name="styleSheet">
<string notr="true">font-family:'Courier';</string>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Pre-backup command to run BEFORE backups.</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="postBackupCmdLineEdit">
<property name="styleSheet">
<string notr="true">font-family:'Courier';</string>
</property>
<property name="placeholderText">
<string>Post-backup command to run AFTER backups.</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="font">
<font>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Available env variables: &lt;span style=&quot; font-family:'Courier';&quot;&gt;$repo_url, $profile_name, $profile_slug, $returncode&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
Expand Down
1 change: 1 addition & 0 deletions src/vorta/assets/icons/collection.qrc
Expand Up @@ -15,5 +15,6 @@
<file>edit.svg</file>
<file>globe.svg</file>
<file>cloud-download.svg</file>
<file>terminal.svg</file>
</qresource>
</RCC>
2 changes: 2 additions & 0 deletions src/vorta/assets/icons/terminal.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 26 additions & 1 deletion src/vorta/borg/create.py
@@ -1,6 +1,7 @@
import os
import tempfile
from dateutil import parser
import subprocess

from ..utils import get_current_wifi, format_archive_name
from ..models import SourceFileModel, ArchiveModel, WifiSettingModel, RepoModel
Expand Down Expand Up @@ -41,6 +42,23 @@ def started_event(self):

def finished_event(self, result):
self.app.backup_finished_event.emit(result)
self.pre_post_backup_cmd(self.params, cmd='post_backup_cmd', returncode=result['returncode'])

@classmethod
def pre_post_backup_cmd(cls, params, cmd='pre_backup_cmd', returncode=0):
cmd = getattr(params['profile'], cmd)
if cmd:
env = {
**os.environ.copy(),
'repo_url': params['repo'].url,
'profile_name': params['profile'].name,
'profile_slug': params['profile'].slug(),
'returncode': str(returncode)
}
proc = subprocess.run(cmd, shell=True, env=env)
return proc.returncode
else:
return 0 # 0 if no command was run.

@classmethod
def prepare(cls, profile):
Expand All @@ -52,7 +70,7 @@ def prepare(cls, profile):
if not ret['ok']:
return ret
else:
ret['ok'] = False # Set back to false, so we can do our own checks here.
ret['ok'] = False # Set back to False, so we can do our own checks here.

n_backup_folders = SourceFileModel.select().count()
if n_backup_folders == 0:
Expand Down Expand Up @@ -107,6 +125,13 @@ def prepare(cls, profile):
for f in SourceFileModel.select().where(SourceFileModel.profile == profile.id):
cmd.append(f.dir)

# Run user-supplied pre-backup command
ret['profile'] = profile
ret['repo'] = profile.repo
if cls.pre_post_backup_cmd(ret) != 0:
ret['message'] = 'Pre-backup command returned non-zero exit code.'
return ret

ret['message'] = 'Starting backup..'
ret['ok'] = True
ret['cmd'] = cmd
Expand Down
13 changes: 12 additions & 1 deletion src/vorta/models.py
Expand Up @@ -12,7 +12,7 @@

from vorta.utils import slugify

SCHEMA_VERSION = 9
SCHEMA_VERSION = 10

db = pw.Proxy()

Expand Down Expand Up @@ -83,6 +83,8 @@ class BackupProfileModel(pw.Model):
prune_keep_within = pw.CharField(default='10H', null=True)
new_archive_name = pw.CharField(default="{hostname}-{profile_slug}-{now:%Y-%m-%dT%H:%M:%S}")
prune_prefix = pw.CharField(default="{hostname}-{profile_slug}-")
pre_backup_cmd = pw.CharField(default='')
post_backup_cmd = pw.CharField(default='')

def refresh(self):
return type(self).get(self._pk_expr())
Expand Down Expand Up @@ -278,3 +280,12 @@ def init_db(con):
migrator.add_column(BackupProfileModel._meta.table_name, 'prune_prefix',
pw.CharField(default="{hostname}-{profile_slug}-")),
)

if current_schema.version < 10:
_apply_schema_update(
current_schema, 10,
migrator.add_column(BackupProfileModel._meta.table_name, 'pre_backup_cmd',
pw.CharField(default='')),
migrator.add_column(BackupProfileModel._meta.table_name, 'post_backup_cmd',
pw.CharField(default='')),
)
10 changes: 8 additions & 2 deletions src/vorta/scheduler.py
Expand Up @@ -25,11 +25,17 @@ def reload(self):
trigger = None
job_id = f'{profile.id}'
if profile.schedule_mode == 'interval':
if profile.schedule_interval_hours > 23:
if profile.schedule_interval_hours >= 24:
days = profile.schedule_interval_hours // 24
leftover_hours = profile.schedule_interval_hours % 24

if leftover_hours == 0:
cron_hours = '1'
else:
cron_hours = f'*/{leftover_hours}'

trigger = cron.CronTrigger(day=f'*/{days}',
hour=f'*/{leftover_hours}',
hour=cron_hours,
minute=profile.schedule_interval_minutes)
else:
trigger = cron.CronTrigger(hour=f'*/{profile.schedule_interval_hours}',
Expand Down

0 comments on commit 5cbdaaa

Please sign in to comment.