simo 2.4.1__py3-none-any.whl → 2.4.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of simo might be problematic. Click here for more details.
- simo/backups/__pycache__/admin.cpython-38.pyc +0 -0
- simo/backups/__pycache__/models.cpython-38.pyc +0 -0
- simo/backups/__pycache__/tasks.cpython-38.pyc +0 -0
- simo/backups/admin.py +10 -13
- simo/backups/migrations/0003_alter_backuplog_options_alter_backup_size.py +22 -0
- simo/backups/migrations/0004_alter_backup_options_alter_backuplog_options_and_more.py +29 -0
- simo/backups/migrations/__pycache__/0003_alter_backuplog_options_alter_backup_size.cpython-38.pyc +0 -0
- simo/backups/migrations/__pycache__/0004_alter_backup_options_alter_backuplog_options_and_more.cpython-38.pyc +0 -0
- simo/backups/models.py +1 -7
- simo/backups/tasks.py +210 -144
- simo/core/__pycache__/models.cpython-38.pyc +0 -0
- simo/core/migrations/0042_alter_instance_timezone.py +18 -0
- simo/core/migrations/__pycache__/0042_alter_instance_timezone.cpython-38.pyc +0 -0
- simo/fleet/__pycache__/forms.cpython-38.pyc +0 -0
- simo/fleet/forms.py +140 -176
- simo/fleet/migrations/0038_alter_colonel_type.py +18 -0
- simo/fleet/migrations/__pycache__/0038_alter_colonel_type.cpython-38.pyc +0 -0
- simo/notifications/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/migrations/0033_alter_user_ssh_key.py +18 -0
- simo/users/migrations/__pycache__/0033_alter_user_ssh_key.cpython-38.pyc +0 -0
- {simo-2.4.1.dist-info → simo-2.4.2.dist-info}/METADATA +1 -1
- {simo-2.4.1.dist-info → simo-2.4.2.dist-info}/RECORD +26 -15
- {simo-2.4.1.dist-info → simo-2.4.2.dist-info}/LICENSE.md +0 -0
- {simo-2.4.1.dist-info → simo-2.4.2.dist-info}/WHEEL +0 -0
- {simo-2.4.1.dist-info → simo-2.4.2.dist-info}/entry_points.txt +0 -0
- {simo-2.4.1.dist-info → simo-2.4.2.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/backups/admin.py
CHANGED
|
@@ -7,10 +7,10 @@ from .models import Backup, BackupLog
|
|
|
7
7
|
|
|
8
8
|
@admin.register(Backup)
|
|
9
9
|
class BackupAdmin(DjangoObjectActions, admin.ModelAdmin):
|
|
10
|
-
list_display = 'datetime', 'device', '
|
|
11
|
-
fields = 'datetime', 'device', '
|
|
12
|
-
readonly_fields = 'datetime', 'device', '
|
|
13
|
-
list_filter = 'datetime', 'mac',
|
|
10
|
+
list_display = 'datetime', 'device', 'filepath'
|
|
11
|
+
fields = 'datetime', 'device', 'filepath'
|
|
12
|
+
readonly_fields = 'datetime', 'device', 'filepath'
|
|
13
|
+
list_filter = 'datetime', 'mac',
|
|
14
14
|
actions = 'restore',
|
|
15
15
|
changelist_actions = ('backup',)
|
|
16
16
|
|
|
@@ -23,15 +23,11 @@ class BackupAdmin(DjangoObjectActions, admin.ModelAdmin):
|
|
|
23
23
|
def has_delete_permission(self, request, obj=None):
|
|
24
24
|
return False
|
|
25
25
|
|
|
26
|
-
def
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
elif obj.size > 1024 * 1024:
|
|
32
|
-
return f'{round(obj.size / (1024 * 1024), 2)} Kb'
|
|
33
|
-
return f'{obj.size} bytes'
|
|
34
|
-
size_display.short_description = 'size'
|
|
26
|
+
def changelist_view(self, *args, **kwargs):
|
|
27
|
+
from .tasks import check_backups
|
|
28
|
+
check_backups()
|
|
29
|
+
return super().changelist_view(*args, **kwargs)
|
|
30
|
+
|
|
35
31
|
|
|
36
32
|
def restore(self, request, queryset):
|
|
37
33
|
if queryset.count() > 1:
|
|
@@ -69,6 +65,7 @@ class BackupAdmin(DjangoObjectActions, admin.ModelAdmin):
|
|
|
69
65
|
@admin.register(BackupLog)
|
|
70
66
|
class BackupLogAdmin(admin.ModelAdmin):
|
|
71
67
|
fields = 'datetime', 'level', 'msg'
|
|
68
|
+
list_display = fields
|
|
72
69
|
readonly_fields = fields
|
|
73
70
|
list_fields = fields
|
|
74
71
|
list_filter = 'datetime', 'level'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated by Django 4.2.10 on 2024-10-09 09:16
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('backups', '0002_backuplog_backup_level_backup_size'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterModelOptions(
|
|
14
|
+
name='backuplog',
|
|
15
|
+
options={'ordering': ('datetime',)},
|
|
16
|
+
),
|
|
17
|
+
migrations.AlterField(
|
|
18
|
+
model_name='backup',
|
|
19
|
+
name='size',
|
|
20
|
+
field=models.BigIntegerField(default=0),
|
|
21
|
+
),
|
|
22
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated by Django 4.2.10 on 2024-10-10 12:05
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('backups', '0003_alter_backuplog_options_alter_backup_size'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterModelOptions(
|
|
14
|
+
name='backup',
|
|
15
|
+
options={},
|
|
16
|
+
),
|
|
17
|
+
migrations.AlterModelOptions(
|
|
18
|
+
name='backuplog',
|
|
19
|
+
options={},
|
|
20
|
+
),
|
|
21
|
+
migrations.RemoveField(
|
|
22
|
+
model_name='backup',
|
|
23
|
+
name='level',
|
|
24
|
+
),
|
|
25
|
+
migrations.RemoveField(
|
|
26
|
+
model_name='backup',
|
|
27
|
+
name='size',
|
|
28
|
+
),
|
|
29
|
+
]
|
simo/backups/migrations/__pycache__/0003_alter_backuplog_options_alter_backup_size.cpython-38.pyc
ADDED
|
Binary file
|
|
Binary file
|
simo/backups/models.py
CHANGED
|
@@ -6,12 +6,9 @@ class Backup(models.Model):
|
|
|
6
6
|
datetime = models.DateTimeField(db_index=True)
|
|
7
7
|
mac = models.CharField(max_length=100, db_index=True)
|
|
8
8
|
filepath = models.CharField(max_length=200)
|
|
9
|
-
level = models.IntegerField(default=0, db_index=True)
|
|
10
|
-
size = models.IntegerField(default=0)
|
|
11
9
|
|
|
12
10
|
class Meta:
|
|
13
11
|
unique_together = 'datetime', 'mac'
|
|
14
|
-
ordering = 'datetime',
|
|
15
12
|
|
|
16
13
|
@property
|
|
17
14
|
def device(self):
|
|
@@ -25,7 +22,4 @@ class BackupLog(models.Model):
|
|
|
25
22
|
level = models.CharField(default='info', choices=(
|
|
26
23
|
('info', "INFO"), ('warning', "WARNING"), ('error', "ERROR")
|
|
27
24
|
))
|
|
28
|
-
msg = models.TextField()
|
|
29
|
-
|
|
30
|
-
class Meta:
|
|
31
|
-
ordering = 'datetime',
|
|
25
|
+
msg = models.TextField()
|
simo/backups/tasks.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import os, subprocess, json, uuid, datetime, shutil
|
|
1
|
+
import os, subprocess, json, uuid, datetime, shutil, pytz
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
3
|
from celeryc import celery_app
|
|
4
4
|
from simo.conf import dynamic_settings
|
|
@@ -10,15 +10,15 @@ def check_backups():
|
|
|
10
10
|
'''
|
|
11
11
|
syncs up backups on external medium to the database
|
|
12
12
|
'''
|
|
13
|
-
from simo.backups.models import Backup
|
|
13
|
+
from simo.backups.models import Backup
|
|
14
14
|
|
|
15
15
|
try:
|
|
16
|
-
lv_group, lv_name,
|
|
16
|
+
lv_group, lv_name, sd_mountpoint = get_partitions()
|
|
17
17
|
except:
|
|
18
18
|
return Backup.objects.all().delete()
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
backups_dir = os.path.join(
|
|
21
|
+
backups_dir = os.path.join(sd_mountpoint, 'simo_backups')
|
|
22
22
|
if not os.path.exists(backups_dir):
|
|
23
23
|
return Backup.objects.all().delete()
|
|
24
24
|
|
|
@@ -34,24 +34,25 @@ def check_backups():
|
|
|
34
34
|
year, month = int(year), int(month)
|
|
35
35
|
except:
|
|
36
36
|
continue
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
|
|
38
|
+
month_folder_path = os.path.join(hub_dir, month_folder)
|
|
39
|
+
res = subprocess.run(
|
|
40
|
+
f"borg list {month_folder_path} --json",
|
|
41
|
+
shell=True, stdout=subprocess.PIPE
|
|
42
|
+
)
|
|
43
|
+
try:
|
|
44
|
+
archives = json.loads(res.stdout.decode())['archives']
|
|
45
|
+
except Exception as e:
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
for archive in archives:
|
|
49
|
+
make_datetime = datetime.fromisoformat(archive['start'])
|
|
50
|
+
make_datetime = make_datetime.replace(tzinfo=pytz.UTC)
|
|
51
|
+
filepath = f"{month_folder_path}::{archive['name']}"
|
|
52
|
+
|
|
48
53
|
obj, new = Backup.objects.update_or_create(
|
|
49
|
-
datetime=
|
|
50
|
-
|
|
51
|
-
tzinfo=timezone.utc
|
|
52
|
-
), mac=hub_mac, defaults={
|
|
53
|
-
'filepath': filepath, 'level': level,
|
|
54
|
-
'size': file_stats.st_size
|
|
54
|
+
datetime=make_datetime, mac=hub_mac, defaults={
|
|
55
|
+
'filepath': f"{month_folder_path}::{archive['name']}",
|
|
55
56
|
}
|
|
56
57
|
)
|
|
57
58
|
backups_mentioned.append(obj.id)
|
|
@@ -61,14 +62,80 @@ def check_backups():
|
|
|
61
62
|
dynamic_settings['backups__last_check'] = int(datetime.now().timestamp())
|
|
62
63
|
|
|
63
64
|
|
|
64
|
-
def
|
|
65
|
+
def clean_backup_snaps(lv_group, lv_name):
|
|
66
|
+
res = subprocess.run(
|
|
67
|
+
'lvs --report-format json', shell=True, stdout=subprocess.PIPE
|
|
68
|
+
)
|
|
69
|
+
lvs_data = json.loads(res.stdout.decode())
|
|
70
|
+
for volume in lvs_data['report'][0]['lv']:
|
|
71
|
+
if volume['vg_name'] != lv_group:
|
|
72
|
+
continue
|
|
73
|
+
if volume['origin'] != lv_name:
|
|
74
|
+
continue
|
|
75
|
+
if not volume['lv_name'].startswith(f"{lv_name}-bk-"):
|
|
76
|
+
continue
|
|
77
|
+
subprocess.run(
|
|
78
|
+
f"lvremove -f {lv_group}/{volume['lv_name']}", shell=True
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def create_snap(lv_group, lv_name, snap_name=None, size=None, try_no=1):
|
|
84
|
+
'''
|
|
85
|
+
:param lv_group:
|
|
86
|
+
:param lv_name:
|
|
87
|
+
:param snap_name: random snap name will be generated if not provided
|
|
88
|
+
:param size: Size in GB. If not provided, maximum available space in lvm will be used.
|
|
89
|
+
:return: snap_name
|
|
90
|
+
'''
|
|
91
|
+
if not snap_name:
|
|
92
|
+
snap_name = f"{lv_name}-bk-{get_random_string(5)}"
|
|
93
|
+
|
|
94
|
+
clean_backup_snaps(lv_group, lv_name)
|
|
95
|
+
|
|
96
|
+
res = subprocess.run(
|
|
97
|
+
'vgs --report-format json', shell=True, stdout=subprocess.PIPE
|
|
98
|
+
)
|
|
65
99
|
try:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
shell=True
|
|
69
|
-
).decode()
|
|
100
|
+
vgs_data = json.loads(res.stdout.decode())
|
|
101
|
+
free_space = vgs_data['report'][0]['vg'][0]['vg_free']
|
|
70
102
|
except:
|
|
71
|
-
|
|
103
|
+
if try_no < 3:
|
|
104
|
+
clean_backup_snaps(lv_group, lv_name)
|
|
105
|
+
return create_snap(lv_group, lv_name, snap_name, size, try_no+1)
|
|
106
|
+
raise Exception("Unable to find free space on LVM!")
|
|
107
|
+
|
|
108
|
+
if not free_space.lower().endswith('g'):
|
|
109
|
+
if try_no < 3:
|
|
110
|
+
clean_backup_snaps(lv_group, lv_name)
|
|
111
|
+
return create_snap(lv_group, lv_name, snap_name, size, try_no+1)
|
|
112
|
+
raise Exception("Not enough free space on LVM!")
|
|
113
|
+
|
|
114
|
+
free_space = int(float(
|
|
115
|
+
vgs_data['report'][0]['vg'][0]['vg_free'].strip('g').strip('<')
|
|
116
|
+
))
|
|
117
|
+
|
|
118
|
+
if not size:
|
|
119
|
+
size = free_space
|
|
120
|
+
else:
|
|
121
|
+
if size > free_space:
|
|
122
|
+
if try_no < 3:
|
|
123
|
+
clean_backup_snaps(lv_group, lv_name)
|
|
124
|
+
return create_snap(lv_group, lv_name, snap_name, size, try_no + 1)
|
|
125
|
+
raise Exception(
|
|
126
|
+
f"There's only {free_space}G available on LVM, "
|
|
127
|
+
f"but you asked for {size}G"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
res = subprocess.run(
|
|
131
|
+
f'lvcreate -s -n {snap_name} {lv_group}/{lv_name} -L {size}G',
|
|
132
|
+
shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
133
|
+
)
|
|
134
|
+
if res.returncode:
|
|
135
|
+
raise Exception(res.stderr)
|
|
136
|
+
|
|
137
|
+
return snap_name
|
|
138
|
+
|
|
72
139
|
|
|
73
140
|
|
|
74
141
|
def get_lvm_partition(lsblk_data):
|
|
@@ -145,140 +212,131 @@ def get_partitions():
|
|
|
145
212
|
return
|
|
146
213
|
|
|
147
214
|
if lvm_partition.get('partlabel'):
|
|
148
|
-
|
|
215
|
+
sd_mountpoint = f"/media/{backup_device['partlabel']}"
|
|
149
216
|
elif lvm_partition.get('label'):
|
|
150
|
-
|
|
217
|
+
sd_mountpoint = f"/media/{backup_device['label']}"
|
|
151
218
|
else:
|
|
152
|
-
|
|
219
|
+
sd_mountpoint = f"/media/{backup_device['name']}"
|
|
153
220
|
|
|
154
|
-
if not os.path.exists(
|
|
155
|
-
os.makedirs(
|
|
221
|
+
if not os.path.exists(sd_mountpoint):
|
|
222
|
+
os.makedirs(sd_mountpoint)
|
|
156
223
|
|
|
157
|
-
if backup_device.get('mountpoint') !=
|
|
224
|
+
if backup_device.get('mountpoint') != sd_mountpoint:
|
|
158
225
|
|
|
159
226
|
if backup_device.get('mountpoint'):
|
|
160
227
|
subprocess.call(f"umount {backup_device['mountpoint']}", shell=True)
|
|
161
228
|
|
|
162
229
|
subprocess.call(
|
|
163
|
-
f'mount /dev/{backup_device["name"]} {
|
|
230
|
+
f'mount /dev/{backup_device["name"]} {sd_mountpoint}', shell=True,
|
|
164
231
|
stdout=subprocess.PIPE
|
|
165
232
|
)
|
|
166
233
|
|
|
167
|
-
return lv_group, lv_name,
|
|
234
|
+
return lv_group, lv_name, sd_mountpoint
|
|
168
235
|
|
|
169
236
|
|
|
170
237
|
@celery_app.task
|
|
171
238
|
def perform_backup():
|
|
172
239
|
from simo.backups.models import BackupLog
|
|
173
240
|
try:
|
|
174
|
-
lv_group, lv_name,
|
|
241
|
+
lv_group, lv_name, sd_mountpoint = get_partitions()
|
|
175
242
|
except:
|
|
176
243
|
return
|
|
177
244
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
try:
|
|
181
|
-
subprocess.check_output(
|
|
182
|
-
f'lvremove -f {lv_group}/{lv_name}-snap',
|
|
183
|
-
shell=True
|
|
184
|
-
)
|
|
185
|
-
except:
|
|
186
|
-
pass
|
|
187
|
-
output = create_snap(lv_group, lv_name)
|
|
245
|
+
snap_mount_point = '/var/backups/simo-main'
|
|
246
|
+
subprocess.run(f'umount {snap_mount_point}', shell=True)
|
|
188
247
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
248
|
+
try:
|
|
249
|
+
snap_name = create_snap(lv_group, lv_name)
|
|
250
|
+
except Exception as e:
|
|
251
|
+
print("Error creating temporary snap\n" + str(e))
|
|
252
|
+
BackupLog.objects.create(
|
|
253
|
+
level='error',
|
|
254
|
+
msg="Backup error. Unable to create temporary snap\n" + str(e)
|
|
255
|
+
)
|
|
192
256
|
return
|
|
193
257
|
|
|
258
|
+
shutil.rmtree(snap_mount_point, ignore_errors=True)
|
|
259
|
+
os.makedirs(snap_mount_point)
|
|
260
|
+
subprocess.run([
|
|
261
|
+
"mount",
|
|
262
|
+
f"/dev/mapper/{lv_group}-{snap_name.replace('-', '--')}",
|
|
263
|
+
snap_mount_point
|
|
264
|
+
])
|
|
265
|
+
|
|
194
266
|
mac = str(hex(uuid.getnode()))
|
|
195
|
-
device_backups_path = f'{
|
|
196
|
-
|
|
197
|
-
os.makedirs(device_backups_path)
|
|
267
|
+
device_backups_path = f'{sd_mountpoint}/simo_backups/hub-{mac}'
|
|
268
|
+
|
|
198
269
|
|
|
199
270
|
now = datetime.now()
|
|
200
|
-
level = now.day
|
|
201
271
|
month_folder = os.path.join(
|
|
202
272
|
device_backups_path, f'{now.year}-{now.month}'
|
|
203
273
|
)
|
|
204
274
|
if not os.path.exists(month_folder):
|
|
205
275
|
os.makedirs(month_folder)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
level, date = filename.split('-')
|
|
216
|
-
level = int(level)
|
|
217
|
-
if level == 0:
|
|
218
|
-
level_0_exists = True
|
|
219
|
-
break
|
|
220
|
-
except:
|
|
221
|
-
continue
|
|
222
|
-
if not level_0_exists:
|
|
223
|
-
print("Level 0 does not exist! Backups must be started from 0!")
|
|
276
|
+
subprocess.run(
|
|
277
|
+
f'borg init --encryption=none {month_folder}', shell=True
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
res = subprocess.run(
|
|
281
|
+
f'borg info --json {month_folder}',
|
|
282
|
+
shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
283
|
+
)
|
|
284
|
+
if res.returncode:
|
|
224
285
|
shutil.rmtree(month_folder)
|
|
225
|
-
|
|
226
|
-
|
|
286
|
+
subprocess.run(
|
|
287
|
+
f'borg init --encryption=none {month_folder}', shell=True
|
|
288
|
+
)
|
|
227
289
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
290
|
+
exclude_dirs = (
|
|
291
|
+
'tmp', 'lost+found', 'proc', 'cdrom', 'dev', 'mnt', 'sys', 'run',
|
|
292
|
+
'var/tmp', 'var/cache', 'var/log', 'media',
|
|
293
|
+
)
|
|
294
|
+
backup_command = 'borg create --compression lz4'
|
|
295
|
+
for dir in exclude_dirs:
|
|
296
|
+
backup_command += f' --exclude={dir}'
|
|
233
297
|
|
|
234
|
-
estimated_size = int(subprocess.check_output(
|
|
235
|
-
f'dump -{level} -Squz9 -b 1024 {snap_mapper}',
|
|
236
|
-
shell=True
|
|
237
|
-
)) * 0.5
|
|
238
298
|
|
|
239
|
-
|
|
299
|
+
other_month_folders = []
|
|
240
300
|
for item in os.listdir(device_backups_path):
|
|
241
301
|
if not os.path.isdir(os.path.join(device_backups_path, item)):
|
|
242
302
|
continue
|
|
303
|
+
if os.path.join(device_backups_path, item) == month_folder:
|
|
304
|
+
continue
|
|
243
305
|
try:
|
|
244
306
|
year, month = item.split('-')
|
|
245
|
-
|
|
307
|
+
other_month_folders.append([
|
|
246
308
|
os.path.join(device_backups_path, item),
|
|
247
309
|
int(year) * 12 + int(month)
|
|
248
310
|
])
|
|
249
311
|
except:
|
|
250
312
|
continue
|
|
251
|
-
|
|
313
|
+
other_month_folders.sort(key=lambda v: v[1])
|
|
314
|
+
|
|
315
|
+
if other_month_folders:
|
|
316
|
+
# delete old backups to free up at least 20G of space
|
|
317
|
+
while shutil.disk_usage('/media/backup').free < 20 * 1024 * 1024 * 1024:
|
|
318
|
+
remove_folder = other_month_folders.pop()[0]
|
|
319
|
+
print(f"REMOVE: {remove_folder}")
|
|
320
|
+
shutil.rmtree(remove_folder)
|
|
321
|
+
|
|
322
|
+
backup_command += f' {month_folder}::{get_random_string()} .'
|
|
323
|
+
res = subprocess.run(
|
|
324
|
+
backup_command, shell=True, cwd=snap_mount_point,
|
|
325
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
326
|
+
)
|
|
252
327
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
shutil.rmtree(remove_folder)
|
|
328
|
+
subprocess.run(["umount", snap_mount_point])
|
|
329
|
+
subprocess.run(
|
|
330
|
+
f"lvremove -f {lv_group}/{snap_name}", shell=True
|
|
331
|
+
)
|
|
258
332
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
shell=True,
|
|
333
|
+
if res.returncode:
|
|
334
|
+
print("Backup error!")
|
|
335
|
+
BackupLog.objects.create(
|
|
336
|
+
level='error', msg="Backup error: \n" + res.stderr.decode()
|
|
264
337
|
)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
try:
|
|
268
|
-
os.remove(backup_file)
|
|
269
|
-
BackupLog.objects.create(
|
|
270
|
-
level='error', msg="Can't backup. Dump failed."
|
|
271
|
-
)
|
|
272
|
-
print("Dump failed!")
|
|
273
|
-
except:
|
|
274
|
-
pass
|
|
275
|
-
|
|
276
|
-
subprocess.call(
|
|
277
|
-
f"lvremove -f {lv_group}/{lv_name}-snap", shell=True,
|
|
278
|
-
stdout=subprocess.PIPE
|
|
279
|
-
)
|
|
280
|
-
if success:
|
|
281
|
-
print("DONE!")
|
|
338
|
+
else:
|
|
339
|
+
print("Backup done!")
|
|
282
340
|
BackupLog.objects.create(
|
|
283
341
|
level='info', msg="Backup success!"
|
|
284
342
|
)
|
|
@@ -290,7 +348,7 @@ def restore_backup(backup_id):
|
|
|
290
348
|
backup = Backup.objects.get(id=backup_id)
|
|
291
349
|
|
|
292
350
|
try:
|
|
293
|
-
lv_group, lv_name,
|
|
351
|
+
lv_group, lv_name, sd_mountpoint = get_partitions()
|
|
294
352
|
except:
|
|
295
353
|
BackupLog.objects.create(
|
|
296
354
|
level='error',
|
|
@@ -298,51 +356,59 @@ def restore_backup(backup_id):
|
|
|
298
356
|
)
|
|
299
357
|
return
|
|
300
358
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
359
|
+
snap_mount_point = '/var/backups/simo-main'
|
|
360
|
+
subprocess.run(f'umount {snap_mount_point}', shell=True)
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
snap_name = create_snap(lv_group, lv_name)
|
|
364
|
+
except Exception as e:
|
|
365
|
+
print("Error creating temporary snap\n" + str(e))
|
|
304
366
|
BackupLog.objects.create(
|
|
305
367
|
level='error',
|
|
306
|
-
msg="Can't restore.
|
|
368
|
+
msg="Can't restore. \n\n" + str(e)
|
|
307
369
|
)
|
|
308
370
|
return
|
|
309
371
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
372
|
+
shutil.rmtree(snap_mount_point, ignore_errors=True)
|
|
373
|
+
os.makedirs(snap_mount_point)
|
|
374
|
+
subprocess.run([
|
|
375
|
+
"mount",
|
|
376
|
+
f"/dev/mapper/{lv_group}-{snap_name.replace('-', '--')}",
|
|
377
|
+
snap_mount_point
|
|
378
|
+
])
|
|
379
|
+
|
|
380
|
+
# delete current contents of a snap
|
|
381
|
+
print("Delete original files and folders")
|
|
382
|
+
for f in os.listdir(snap_mount_point):
|
|
383
|
+
shutil.rmtree(os.path.join(snap_mount_point, f), ignore_errors=True)
|
|
384
|
+
|
|
385
|
+
print("Perform restoration")
|
|
386
|
+
res = subprocess.run(
|
|
387
|
+
f"borg extract {backup.filepath}", shell=True, cwd=snap_mount_point,
|
|
388
|
+
stderr=subprocess.PIPE
|
|
389
|
+
)
|
|
316
390
|
|
|
317
|
-
subprocess.
|
|
318
|
-
|
|
319
|
-
shell=True
|
|
391
|
+
subprocess.run(["umount", snap_mount_point])
|
|
392
|
+
subprocess.run(
|
|
393
|
+
f"lvremove -f {lv_group}/{snap_name}", shell=True
|
|
320
394
|
)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
f"restore -C -v -b 1024 -f {backup.filepath} -D /var/backup/simo-main",
|
|
324
|
-
shell=True
|
|
325
|
-
)
|
|
326
|
-
subprocess.call(
|
|
327
|
-
f"lvconvert --mergesnapshot {lv_group}/{snap_name}",
|
|
328
|
-
shell=True
|
|
329
|
-
)
|
|
330
|
-
except Exception as e:
|
|
395
|
+
|
|
396
|
+
if res.returncode:
|
|
331
397
|
BackupLog.objects.create(
|
|
332
398
|
level='error',
|
|
333
|
-
msg="Can't restore. \n\n" +
|
|
399
|
+
msg="Can't restore. \n\n" + res.stderr.decode()
|
|
334
400
|
)
|
|
335
|
-
|
|
401
|
+
else:
|
|
402
|
+
print("Restore successful! Merge snapshot and reboot!")
|
|
336
403
|
subprocess.call(
|
|
337
|
-
f"
|
|
338
|
-
|
|
404
|
+
f"lvconvert --mergesnapshot {lv_group}/{snap_name}",
|
|
405
|
+
shell=True
|
|
339
406
|
)
|
|
340
|
-
|
|
341
|
-
print("All good! REBOOT!")
|
|
342
|
-
subprocess.call('reboot')
|
|
407
|
+
subprocess.run('reboot', shell=True)
|
|
343
408
|
|
|
344
409
|
|
|
345
410
|
@celery_app.on_after_finalize.connect
|
|
346
411
|
def setup_periodic_tasks(sender, **kwargs):
|
|
347
412
|
sender.add_periodic_task(60 * 60, check_backups.s())
|
|
348
|
-
|
|
413
|
+
# perform auto backup every 12 hours
|
|
414
|
+
sender.add_periodic_task(60 * 60 * 12, perform_backup.s())
|
|
Binary file
|