simo 2.11.1__py3-none-any.whl → 2.11.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/tasks.py CHANGED
@@ -196,30 +196,124 @@ def get_backup_device(lsblk_data):
196
196
  partition’ – is used.
197
197
  """
198
198
 
199
+ _MIN_SIZE_BYTES = 32 * 1024 * 1024 * 1024 # 32 GiB – keep in sync with
200
+ # _find_blank_removable_device.
201
+
202
+ def _device_size_bytes(dev_name: str):
203
+ """Return size of *dev_name* in bytes (or ``None`` on failure)."""
204
+
205
+ for cmd in (
206
+ f"blockdev --getsize64 /dev/{dev_name}",
207
+ f"lsblk -b -dn -o SIZE /dev/{dev_name}",
208
+ ):
209
+ try:
210
+ out = subprocess.check_output(
211
+ cmd, shell=True, stderr=subprocess.DEVNULL
212
+ ).strip()
213
+ return int(out)
214
+ except Exception:
215
+ continue
216
+ return None
217
+
218
+ # ------------------------------------------------------------------
219
+ # Helper: does the filesystem already contain legacy backups?
220
+ # ------------------------------------------------------------------
221
+
222
+ def _entry_has_simo_backups(entry: dict) -> bool:
223
+ """Return *True* when *entry* hosts legacy ``simo_backups`` folder.
224
+
225
+ The implementation borrows heavily from the _fs_is_empty() helper –
226
+ we temporarily mount the filesystem read-only when it is not mounted
227
+ yet, inspect the directory listing and clean everything up.
228
+ """
229
+
230
+ mountpoint = entry.get("mountpoint")
231
+ cleanup = False
232
+
233
+ if not mountpoint:
234
+ tmp_dir = f"/tmp/simo-bk-{uuid.uuid4().hex[:8]}"
235
+ try:
236
+ os.makedirs(tmp_dir, exist_ok=True)
237
+ res = subprocess.run(
238
+ f"mount -o ro /dev/{entry['name']} {tmp_dir}",
239
+ shell=True,
240
+ stderr=subprocess.PIPE,
241
+ )
242
+ if res.returncode:
243
+ shutil.rmtree(tmp_dir, ignore_errors=True)
244
+ return False
245
+ mountpoint = tmp_dir
246
+ cleanup = True
247
+ except Exception:
248
+ shutil.rmtree(tmp_dir, ignore_errors=True)
249
+ return False
250
+
251
+ has_backups = os.path.isdir(os.path.join(mountpoint, "simo_backups"))
252
+
253
+ if cleanup:
254
+ subprocess.run(f"umount {mountpoint}", shell=True)
255
+ shutil.rmtree(mountpoint, ignore_errors=True)
256
+
257
+ return has_backups
258
+
259
+ # ------------------------------------------------------------------
260
+ # Phase 1 – look for properly prepared BACKUP partition **>=32 GiB**.
261
+ # This is the preferred modern approach.
262
+ # ------------------------------------------------------------------
263
+
199
264
  for device in lsblk_data:
200
265
  if not device.get("hotplug"):
201
266
  continue
202
267
 
268
+ # Capacity check – skip devices smaller than the required threshold.
269
+ size_bytes = _device_size_bytes(device["name"])
270
+ if size_bytes is None:
271
+ print(f"Could not obtain capacity of: {device['name']}")
272
+ continue
273
+
274
+ if size_bytes < _MIN_SIZE_BYTES:
275
+ continue
276
+
203
277
  # Prefer partitions explicitly labelled "BACKUP".
204
278
  for child in device.get("children", []):
205
279
  if _has_backup_label(child):
206
280
  return child
207
281
 
208
- # Legacy fallback – whole-disk filesystems or partitions formatted as
209
- # exFAT are still recognised in order to stay compatible with drives
210
- # prepared by older software versions.
211
- # NOTE: We intentionally keep this logic after the new BACKUP label
212
- # check so that freshly provisioned media (ext4+label) wins.
282
+ # Legacy fallback (modern capacity) – whole-disk or partitioned
283
+ # exFAT volumes are still acceptable for backward compatibility when
284
+ # they are large enough.
213
285
 
214
- # 1. Whole-disk (no partition table) exFAT volume.
215
286
  if (device.get("fstype") or "").lower() == "exfat":
216
287
  return device
217
288
 
218
- # 2. Partitioned removable drive – look for any exFAT child.
219
289
  for child in device.get("children", []):
220
290
  if (child.get("fstype") or "").lower() == "exfat":
221
291
  return child
222
292
 
293
+
294
+ # ------------------------------------------------------------------
295
+ # Phase 2 – look for **existing** legacy backup drives.
296
+ # ------------------------------------------------------------------
297
+
298
+ if _find_blank_removable_device(lsblk_data):
299
+ # New empty disk is available, let's use it instead of trying to find
300
+ # legacy media
301
+ return None
302
+
303
+ for device in lsblk_data:
304
+ if not device.get("hotplug"):
305
+ continue
306
+
307
+ # Check the whole device first.
308
+ if device.get("mountpoint") or device.get("fstype"):
309
+ if _entry_has_simo_backups(device):
310
+ return device
311
+
312
+ # Check its partitions (if any).
313
+ for child in device.get("children", []):
314
+ if _entry_has_simo_backups(child):
315
+ return child
316
+
223
317
  # Nothing has been found.
224
318
  return None
225
319
 
@@ -577,6 +671,9 @@ def perform_backup():
577
671
  mac = str(hex(uuid.getnode()))
578
672
  device_backups_path = f'{sd_mountpoint}/simo_backups/hub-{mac}'
579
673
 
674
+ if not os.path.exists(device_backups_path):
675
+ os.makedirs(device_backups_path)
676
+
580
677
  drop_current_instance()
581
678
  hub_meta = {
582
679
  'instances': [inst.name for inst in Instance.objects.all()]
@@ -765,4 +862,4 @@ def setup_periodic_tasks(sender, **kwargs):
765
862
  sender.add_periodic_task(60 * 60, check_backups.s())
766
863
  # perform auto backup every 12 hours
767
864
  sender.add_periodic_task(60 * 60 * 12, perform_backup.s())
768
- sender.add_periodic_task(60 * 60, clean_old_logs.s())
865
+ sender.add_periodic_task(60 * 60, clean_old_logs.s())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simo
3
- Version: 2.11.1
3
+ Version: 2.11.2
4
4
  Summary: Smart Home Supremacy
5
5
  Author-email: Simanas Venčkauskas <simanas@simo.io>
6
6
  Project-URL: Homepage, https://simo.io
@@ -65,7 +65,7 @@ simo/backups/admin.py,sha256=cEakxnQlOHvUf8LdBdekXpDAvnqPoVN8y7pnN3WK29A,2487
65
65
  simo/backups/dynamic_settings.py,sha256=Q52RLa3UQsmAhqkwR16cM6pbBnIbXqmVQ2oIUP2ZVD0,416
66
66
  simo/backups/models.py,sha256=-tgILgkqmBEuxBwoymKZN1a0UVQzmJvqWrIGYMMFDaQ,695
67
67
  simo/backups/rescue.img.xz,sha256=sErJUejbS9wMlmeLXeTbzOjOEsXxLnHMn0tTJpn2ITo,62389472
68
- simo/backups/tasks.py,sha256=7tF0mzOh3F5ZKwJpbVNDju235GHMauS7V1ughEkb09Y,27161
68
+ simo/backups/tasks.py,sha256=ixNslDRYHd2Ujeji5EmTVFrOFPqBLKzgp2D1QgxHJWM,30620
69
69
  simo/backups/__pycache__/__init__.cpython-312.pyc,sha256=TAMZORllxjDG2r6KsDbR085kId1pURDKwMAaykHlt0w,125
70
70
  simo/backups/__pycache__/__init__.cpython-38.pyc,sha256=vzOf-JIMeZ6P85FyvTpYev3mscFosUy-SJTshcQbOHU,161
71
71
  simo/backups/__pycache__/admin.cpython-312.pyc,sha256=f0XBNqkwkFmJMx3C5328Axk_Ph6ZXElPrgOKN8pCuUY,3821
@@ -74,7 +74,7 @@ simo/backups/__pycache__/dynamic_settings.cpython-312.pyc,sha256=PpnbOuz78x3hFQY
74
74
  simo/backups/__pycache__/dynamic_settings.cpython-38.pyc,sha256=51gJFjn_XqQBRoHeubo6ppb9pNuFQKI5hAR0ms9flE8,731
75
75
  simo/backups/__pycache__/models.cpython-312.pyc,sha256=Re77ADeTs4A4SYXvAVJ3kscH4nAx9FtkagejTZRLI7s,1704
76
76
  simo/backups/__pycache__/models.cpython-38.pyc,sha256=zclX7HwOT4_izweyKNQ8LmgSHb3hNcYcfsSiwwfQoLY,1220
77
- simo/backups/__pycache__/tasks.cpython-312.pyc,sha256=KjHKNc2VdfHNirYWfT4uefiaBdiRWteBWCfUPXX_g3Y,18354
77
+ simo/backups/__pycache__/tasks.cpython-312.pyc,sha256=lOvO_kMGq0I92G4oyCpun0aC2g3ebiD9fzavZFE6uns,34499
78
78
  simo/backups/__pycache__/tasks.cpython-38.pyc,sha256=bKz_Rxt_H0lC0d9_4Dxqv7cirQDSH9LVurZZC0LU94s,9179
79
79
  simo/backups/migrations/0001_initial.py,sha256=0LzCusTUyYf61ksiepdnqXIuYYNTNd_djh_Wa484HA0,770
80
80
  simo/backups/migrations/0002_backuplog_backup_level_backup_size.py,sha256=w9T9MQWuecy91OZE1fMExriwPuXA8HMPKsPwXhmC8_k,1023
@@ -11004,9 +11004,9 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
11004
11004
  simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11005
11005
  simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11006
11006
  simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11007
- simo-2.11.1.dist-info/licenses/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
11008
- simo-2.11.1.dist-info/METADATA,sha256=LYtFhwCsx8qEeImrazIlDoWZ2pl0BR7jYggxtNCgqmE,2028
11009
- simo-2.11.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11010
- simo-2.11.1.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
11011
- simo-2.11.1.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
11012
- simo-2.11.1.dist-info/RECORD,,
11007
+ simo-2.11.2.dist-info/licenses/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
11008
+ simo-2.11.2.dist-info/METADATA,sha256=mX2Y773CRkKjFdGgxztQ7qqsJLS13ICLvS9yI-lfQ3g,2028
11009
+ simo-2.11.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11010
+ simo-2.11.2.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
11011
+ simo-2.11.2.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
11012
+ simo-2.11.2.dist-info/RECORD,,
File without changes