simo 2.3.7__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/__pycache__/settings.cpython-38.pyc +0 -0
- simo/backups/__pycache__/admin.cpython-38.pyc +0 -0
- simo/backups/__pycache__/dynamic_settings.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 +64 -3
- simo/backups/dynamic_settings.py +0 -7
- simo/backups/migrations/0002_backuplog_backup_level_backup_size.py +32 -0
- 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__/0002_backuplog_backup_level_backup_size.cpython-38.pyc +0 -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 +9 -2
- simo/backups/tasks.py +255 -113
- simo/core/__pycache__/api.cpython-38.pyc +0 -0
- simo/core/__pycache__/context.cpython-38.pyc +0 -0
- simo/core/__pycache__/controllers.cpython-38.pyc +0 -0
- simo/core/__pycache__/models.cpython-38.pyc +0 -0
- simo/core/__pycache__/tasks.cpython-38.pyc +0 -0
- simo/core/__pycache__/views.cpython-38.pyc +0 -0
- simo/core/api.py +2 -7
- simo/core/controllers.py +8 -1
- simo/core/management/_hub_template/hub/supervisor.conf +4 -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/core/models.py +6 -2
- simo/core/tasks.py +8 -1
- simo/core/views.py +4 -12
- 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/settings.py +1 -0
- simo/users/__pycache__/models.cpython-38.pyc +0 -0
- simo/users/__pycache__/serializers.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/users/models.py +38 -16
- {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/METADATA +2 -1
- {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/RECORD +46 -33
- {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/LICENSE.md +0 -0
- {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/WHEEL +0 -0
- {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/entry_points.txt +0 -0
- {simo-2.3.7.dist-info → simo-2.4.2.dist-info}/top_level.txt +0 -0
simo/backups/tasks.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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
|
|
5
|
+
from simo.core.utils.helpers import get_random_string
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
@celery_app.task
|
|
@@ -9,15 +10,15 @@ def check_backups():
|
|
|
9
10
|
'''
|
|
10
11
|
syncs up backups on external medium to the database
|
|
11
12
|
'''
|
|
12
|
-
from .models import Backup
|
|
13
|
+
from simo.backups.models import Backup
|
|
13
14
|
|
|
14
15
|
try:
|
|
15
|
-
lv_group, lv_name,
|
|
16
|
+
lv_group, lv_name, sd_mountpoint = get_partitions()
|
|
16
17
|
except:
|
|
17
18
|
return Backup.objects.all().delete()
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
backups_dir = os.path.join(
|
|
21
|
+
backups_dir = os.path.join(sd_mountpoint, 'simo_backups')
|
|
21
22
|
if not os.path.exists(backups_dir):
|
|
22
23
|
return Backup.objects.all().delete()
|
|
23
24
|
|
|
@@ -33,40 +34,108 @@ def check_backups():
|
|
|
33
34
|
year, month = int(year), int(month)
|
|
34
35
|
except:
|
|
35
36
|
continue
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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']}"
|
|
44
52
|
|
|
45
53
|
obj, new = Backup.objects.update_or_create(
|
|
46
|
-
datetime=
|
|
47
|
-
|
|
48
|
-
tzinfo=timezone.utc
|
|
49
|
-
), mac=hub_mac, defaults={
|
|
50
|
-
'filepath': os.path.join(
|
|
51
|
-
hub_dir, month_folder, filename
|
|
52
|
-
)
|
|
54
|
+
datetime=make_datetime, mac=hub_mac, defaults={
|
|
55
|
+
'filepath': f"{month_folder_path}::{archive['name']}",
|
|
53
56
|
}
|
|
54
57
|
)
|
|
55
58
|
backups_mentioned.append(obj.id)
|
|
56
59
|
|
|
57
60
|
Backup.objects.all().exclude(id__in=backups_mentioned).delete()
|
|
58
61
|
|
|
59
|
-
dynamic_settings['backups__last_check'] = datetime.now()
|
|
62
|
+
dynamic_settings['backups__last_check'] = int(datetime.now().timestamp())
|
|
63
|
+
|
|
64
|
+
|
|
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
|
+
|
|
60
81
|
|
|
61
82
|
|
|
62
|
-
def create_snap(lv_group, lv_name):
|
|
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
|
+
)
|
|
63
99
|
try:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
shell=True
|
|
67
|
-
).decode()
|
|
100
|
+
vgs_data = json.loads(res.stdout.decode())
|
|
101
|
+
free_space = vgs_data['report'][0]['vg'][0]['vg_free']
|
|
68
102
|
except:
|
|
69
|
-
|
|
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
|
+
|
|
70
139
|
|
|
71
140
|
|
|
72
141
|
def get_lvm_partition(lsblk_data):
|
|
@@ -93,6 +162,7 @@ def get_backup_device(lsblk_data):
|
|
|
93
162
|
|
|
94
163
|
|
|
95
164
|
def get_partitions():
|
|
165
|
+
from simo.backups.models import BackupLog
|
|
96
166
|
|
|
97
167
|
lsblk_data = json.loads(subprocess.check_output(
|
|
98
168
|
'lsblk --output NAME,HOTPLUG,MOUNTPOINT,FSTYPE,TYPE,LABEL,PARTLABEL --json',
|
|
@@ -104,7 +174,9 @@ def get_partitions():
|
|
|
104
174
|
lvm_partition = get_lvm_partition(lsblk_data)
|
|
105
175
|
if not lvm_partition:
|
|
106
176
|
print("No LVM partition!")
|
|
107
|
-
|
|
177
|
+
BackupLog.objects.create(
|
|
178
|
+
level='warning', msg="Can't backup. No LVM partition!"
|
|
179
|
+
)
|
|
108
180
|
return
|
|
109
181
|
|
|
110
182
|
try:
|
|
@@ -114,14 +186,17 @@ def get_partitions():
|
|
|
114
186
|
lv_name = name[split_at + 1:].replace('--', '-')
|
|
115
187
|
except:
|
|
116
188
|
print("Failed to identify LVM partition")
|
|
117
|
-
|
|
118
|
-
'Failed to identify LVM partition
|
|
189
|
+
BackupLog.objects.create(
|
|
190
|
+
level='warning', msg="Can't backup. Failed to identify LVM partition."
|
|
191
|
+
)
|
|
119
192
|
return
|
|
120
193
|
|
|
121
194
|
if not lv_name:
|
|
122
195
|
print("LVM was not found on this system. Abort!")
|
|
123
|
-
|
|
124
|
-
'
|
|
196
|
+
BackupLog.objects.create(
|
|
197
|
+
level='warning',
|
|
198
|
+
msg="Can't backup. Failed to identify LVM partition name."
|
|
199
|
+
)
|
|
125
200
|
return
|
|
126
201
|
|
|
127
202
|
|
|
@@ -130,143 +205,210 @@ def get_partitions():
|
|
|
130
205
|
backup_device = get_backup_device(lsblk_data)
|
|
131
206
|
|
|
132
207
|
if not backup_device:
|
|
133
|
-
|
|
134
|
-
'
|
|
208
|
+
BackupLog.objects.create(
|
|
209
|
+
level='warning',
|
|
210
|
+
msg="Can't backup. No external exFAT backup device on this machine."
|
|
211
|
+
)
|
|
135
212
|
return
|
|
136
213
|
|
|
137
214
|
if lvm_partition.get('partlabel'):
|
|
138
|
-
|
|
215
|
+
sd_mountpoint = f"/media/{backup_device['partlabel']}"
|
|
139
216
|
elif lvm_partition.get('label'):
|
|
140
|
-
|
|
217
|
+
sd_mountpoint = f"/media/{backup_device['label']}"
|
|
141
218
|
else:
|
|
142
|
-
|
|
219
|
+
sd_mountpoint = f"/media/{backup_device['name']}"
|
|
143
220
|
|
|
144
|
-
if not os.path.exists(
|
|
145
|
-
os.makedirs(
|
|
221
|
+
if not os.path.exists(sd_mountpoint):
|
|
222
|
+
os.makedirs(sd_mountpoint)
|
|
146
223
|
|
|
147
|
-
if backup_device.get('mountpoint') !=
|
|
224
|
+
if backup_device.get('mountpoint') != sd_mountpoint:
|
|
148
225
|
|
|
149
226
|
if backup_device.get('mountpoint'):
|
|
150
227
|
subprocess.call(f"umount {backup_device['mountpoint']}", shell=True)
|
|
151
228
|
|
|
152
229
|
subprocess.call(
|
|
153
|
-
f'mount /dev/{backup_device["name"]} {
|
|
230
|
+
f'mount /dev/{backup_device["name"]} {sd_mountpoint}', shell=True,
|
|
154
231
|
stdout=subprocess.PIPE
|
|
155
232
|
)
|
|
156
233
|
|
|
157
|
-
return lv_group, lv_name,
|
|
234
|
+
return lv_group, lv_name, sd_mountpoint
|
|
158
235
|
|
|
159
236
|
|
|
160
237
|
@celery_app.task
|
|
161
238
|
def perform_backup():
|
|
162
|
-
|
|
239
|
+
from simo.backups.models import BackupLog
|
|
163
240
|
try:
|
|
164
|
-
lv_group, lv_name,
|
|
241
|
+
lv_group, lv_name, sd_mountpoint = get_partitions()
|
|
165
242
|
except:
|
|
166
243
|
return
|
|
167
244
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
subprocess.check_output(
|
|
171
|
-
f'lvremove -f {lv_group}/{lv_name}-snap',
|
|
172
|
-
shell=True
|
|
173
|
-
)
|
|
174
|
-
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)
|
|
175
247
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
)
|
|
179
256
|
return
|
|
180
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
|
+
|
|
181
266
|
mac = str(hex(uuid.getnode()))
|
|
182
|
-
device_backups_path = f'{
|
|
183
|
-
if not os.path.exists(device_backups_path):
|
|
184
|
-
os.makedirs(device_backups_path)
|
|
267
|
+
device_backups_path = f'{sd_mountpoint}/simo_backups/hub-{mac}'
|
|
185
268
|
|
|
186
|
-
|
|
187
|
-
|
|
269
|
+
|
|
270
|
+
now = datetime.now()
|
|
188
271
|
month_folder = os.path.join(
|
|
189
272
|
device_backups_path, f'{now.year}-{now.month}'
|
|
190
273
|
)
|
|
191
274
|
if not os.path.exists(month_folder):
|
|
192
275
|
os.makedirs(month_folder)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
level, date = filename.split('-')
|
|
203
|
-
level = int(level)
|
|
204
|
-
if level == 0:
|
|
205
|
-
level_0_exists = True
|
|
206
|
-
break
|
|
207
|
-
except:
|
|
208
|
-
continue
|
|
209
|
-
if not level_0_exists:
|
|
210
|
-
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:
|
|
211
285
|
shutil.rmtree(month_folder)
|
|
212
|
-
|
|
213
|
-
|
|
286
|
+
subprocess.run(
|
|
287
|
+
f'borg init --encryption=none {month_folder}', shell=True
|
|
288
|
+
)
|
|
214
289
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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}'
|
|
220
297
|
|
|
221
|
-
estimated_size = int(subprocess.check_output(
|
|
222
|
-
f'dump -{level} -Squz9 -b 1024 {snap_mapper}',
|
|
223
|
-
shell=True
|
|
224
|
-
)) * 0.5
|
|
225
298
|
|
|
226
|
-
|
|
299
|
+
other_month_folders = []
|
|
227
300
|
for item in os.listdir(device_backups_path):
|
|
228
301
|
if not os.path.isdir(os.path.join(device_backups_path, item)):
|
|
229
302
|
continue
|
|
303
|
+
if os.path.join(device_backups_path, item) == month_folder:
|
|
304
|
+
continue
|
|
230
305
|
try:
|
|
231
306
|
year, month = item.split('-')
|
|
232
|
-
|
|
307
|
+
other_month_folders.append([
|
|
233
308
|
os.path.join(device_backups_path, item),
|
|
234
309
|
int(year) * 12 + int(month)
|
|
235
310
|
])
|
|
236
311
|
except:
|
|
237
312
|
continue
|
|
238
|
-
|
|
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
|
+
)
|
|
239
327
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
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
|
+
)
|
|
245
332
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
shell=True,
|
|
333
|
+
if res.returncode:
|
|
334
|
+
print("Backup error!")
|
|
335
|
+
BackupLog.objects.create(
|
|
336
|
+
level='error', msg="Backup error: \n" + res.stderr.decode()
|
|
251
337
|
)
|
|
252
|
-
|
|
338
|
+
else:
|
|
339
|
+
print("Backup done!")
|
|
340
|
+
BackupLog.objects.create(
|
|
341
|
+
level='info', msg="Backup success!"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@celery_app.task
|
|
346
|
+
def restore_backup(backup_id):
|
|
347
|
+
from simo.backups.models import Backup, BackupLog
|
|
348
|
+
backup = Backup.objects.get(id=backup_id)
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
lv_group, lv_name, sd_mountpoint = get_partitions()
|
|
253
352
|
except:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
353
|
+
BackupLog.objects.create(
|
|
354
|
+
level='error',
|
|
355
|
+
msg="Can't restore. LVM group is not present on this machine."
|
|
356
|
+
)
|
|
357
|
+
return
|
|
358
|
+
|
|
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))
|
|
366
|
+
BackupLog.objects.create(
|
|
367
|
+
level='error',
|
|
368
|
+
msg="Can't restore. \n\n" + str(e)
|
|
369
|
+
)
|
|
370
|
+
return
|
|
259
371
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
|
263
389
|
)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
390
|
+
|
|
391
|
+
subprocess.run(["umount", snap_mount_point])
|
|
392
|
+
subprocess.run(
|
|
393
|
+
f"lvremove -f {lv_group}/{snap_name}", shell=True
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
if res.returncode:
|
|
397
|
+
BackupLog.objects.create(
|
|
398
|
+
level='error',
|
|
399
|
+
msg="Can't restore. \n\n" + res.stderr.decode()
|
|
400
|
+
)
|
|
401
|
+
else:
|
|
402
|
+
print("Restore successful! Merge snapshot and reboot!")
|
|
403
|
+
subprocess.call(
|
|
404
|
+
f"lvconvert --mergesnapshot {lv_group}/{snap_name}",
|
|
405
|
+
shell=True
|
|
406
|
+
)
|
|
407
|
+
subprocess.run('reboot', shell=True)
|
|
267
408
|
|
|
268
409
|
|
|
269
410
|
@celery_app.on_after_finalize.connect
|
|
270
411
|
def setup_periodic_tasks(sender, **kwargs):
|
|
271
412
|
sender.add_periodic_task(60 * 60, check_backups.s())
|
|
272
|
-
|
|
413
|
+
# perform auto backup every 12 hours
|
|
414
|
+
sender.add_periodic_task(60 * 60 * 12, perform_backup.s())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/core/api.py
CHANGED
|
@@ -262,13 +262,11 @@ class ComponentViewSet(
|
|
|
262
262
|
def controller(self, request, pk=None, *args, **kwargs):
|
|
263
263
|
start = time.time()
|
|
264
264
|
component = self.get_object()
|
|
265
|
-
print(f"Component retrieved in : {time.time() - start}s")
|
|
266
265
|
data = request.data
|
|
267
266
|
if not isinstance(request.data, dict):
|
|
268
267
|
data = data.dict()
|
|
269
268
|
request_data = restore_json(data)
|
|
270
269
|
resp = self.perform_controller_method(request_data, component)
|
|
271
|
-
print(f"Command executed in : {time.time() - start}s")
|
|
272
270
|
return resp
|
|
273
271
|
|
|
274
272
|
@action(detail=False, methods=['post'])
|
|
@@ -359,8 +357,6 @@ class ComponentHistoryViewSet(InstanceMixin, viewsets.ReadOnlyModelViewSet):
|
|
|
359
357
|
minutes=start_from.minute, seconds=start_from.second
|
|
360
358
|
)
|
|
361
359
|
|
|
362
|
-
print("START FROM: ", start_from)
|
|
363
|
-
|
|
364
360
|
return RESTResponse(
|
|
365
361
|
self.get_aggregated_data(
|
|
366
362
|
component, request.GET['interval'], start_from
|
|
@@ -645,9 +641,9 @@ class StatesViewSet(InstanceMixin, viewsets.GenericViewSet):
|
|
|
645
641
|
component_values = get_components_queryset(
|
|
646
642
|
self.instance, request.user
|
|
647
643
|
).filter(zone__instance=self.instance).values(
|
|
648
|
-
'id', '
|
|
644
|
+
'id', 'last_change', 'last_modified',
|
|
649
645
|
'arm_status', 'battery_level', 'alive', 'error_msg',
|
|
650
|
-
'meta'
|
|
646
|
+
'value', 'meta'
|
|
651
647
|
)
|
|
652
648
|
for vals in component_values:
|
|
653
649
|
vals['last_change'] = datetime.datetime.timestamp(
|
|
@@ -665,7 +661,6 @@ class StatesViewSet(InstanceMixin, viewsets.GenericViewSet):
|
|
|
665
661
|
cat['last_modified']
|
|
666
662
|
)
|
|
667
663
|
|
|
668
|
-
|
|
669
664
|
return RESTResponse({
|
|
670
665
|
'zones': Zone.objects.filter(instance=self.instance).values(
|
|
671
666
|
'id', 'name'
|
simo/core/controllers.py
CHANGED
|
@@ -539,6 +539,14 @@ class Dimmer(ControllerBase, TimerMixin, OnOffPokerMixin):
|
|
|
539
539
|
default_value = 0
|
|
540
540
|
default_value_units = '%'
|
|
541
541
|
|
|
542
|
+
def _receive_from_device(self, value, *args, **kwargs):
|
|
543
|
+
if isinstance(value, bool):
|
|
544
|
+
if value:
|
|
545
|
+
value = self.component.config.get('max', 100.0)
|
|
546
|
+
else:
|
|
547
|
+
value = self.component.config.get('min', 0)
|
|
548
|
+
return super()._receive_from_device(value, *args, **kwargs)
|
|
549
|
+
|
|
542
550
|
def _prepare_for_send(self, value):
|
|
543
551
|
if isinstance(value, bool):
|
|
544
552
|
if value:
|
|
@@ -579,7 +587,6 @@ class Dimmer(ControllerBase, TimerMixin, OnOffPokerMixin):
|
|
|
579
587
|
self.turn_on()
|
|
580
588
|
|
|
581
589
|
|
|
582
|
-
|
|
583
590
|
class DimmerPlus(ControllerBase, TimerMixin, OnOffPokerMixin):
|
|
584
591
|
name = _("Dimmer Plus")
|
|
585
592
|
base_type = 'dimmer-plus'
|