clonebox 1.1.16__py3-none-any.whl → 1.1.18__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 clonebox might be problematic. Click here for more details.
- clonebox/cli.py +12 -0
- clonebox/cloner.py +67 -36
- clonebox/validator.py +163 -28
- {clonebox-1.1.16.dist-info → clonebox-1.1.18.dist-info}/METADATA +1 -1
- {clonebox-1.1.16.dist-info → clonebox-1.1.18.dist-info}/RECORD +9 -9
- {clonebox-1.1.16.dist-info → clonebox-1.1.18.dist-info}/WHEEL +0 -0
- {clonebox-1.1.16.dist-info → clonebox-1.1.18.dist-info}/entry_points.txt +0 -0
- {clonebox-1.1.16.dist-info → clonebox-1.1.18.dist-info}/licenses/LICENSE +0 -0
- {clonebox-1.1.16.dist-info → clonebox-1.1.18.dist-info}/top_level.txt +0 -0
clonebox/cli.py
CHANGED
|
@@ -2498,6 +2498,18 @@ def monitor_cloud_init_status(vm_name: str, user_session: bool = False, timeout:
|
|
|
2498
2498
|
"grep -E '\\[[0-9]/[0-9]\\]|→' /var/log/cloud-init-output.log 2>/dev/null | tail -n 5"
|
|
2499
2499
|
)
|
|
2500
2500
|
|
|
2501
|
+
# Check disk space in real-time
|
|
2502
|
+
disk_info = _exec_in_vm_qga(
|
|
2503
|
+
vm_name,
|
|
2504
|
+
conn_uri,
|
|
2505
|
+
"df / --output=pcent | tail -n 1 | tr -dc '0-9'"
|
|
2506
|
+
)
|
|
2507
|
+
if disk_info and disk_info.isdigit():
|
|
2508
|
+
usage = int(disk_info)
|
|
2509
|
+
if usage > 90:
|
|
2510
|
+
console.print(f"[bold red]⚠️ WARNING: VM Disk is nearly full ({usage}%)![/]")
|
|
2511
|
+
console.print("[red] Installation may fail. Consider increasing --disk-size-gb.[/]")
|
|
2512
|
+
|
|
2501
2513
|
if raw_info:
|
|
2502
2514
|
lines = [l.strip() for l in raw_info.strip().split('\n') if l.strip()]
|
|
2503
2515
|
for line in lines:
|
clonebox/cloner.py
CHANGED
|
@@ -1124,7 +1124,7 @@ fi
|
|
|
1124
1124
|
mount_checks.append(f'check_mount "{guest_path}" "mount{idx}"')
|
|
1125
1125
|
|
|
1126
1126
|
# Add copied paths checks
|
|
1127
|
-
copy_paths = config.copy_paths or config
|
|
1127
|
+
copy_paths = config.copy_paths or getattr(config, "app_data_paths", {})
|
|
1128
1128
|
if copy_paths:
|
|
1129
1129
|
for idx, (host_path, guest_path) in enumerate(copy_paths.items()):
|
|
1130
1130
|
mount_checks.append(f'check_copy_path "{guest_path}"')
|
|
@@ -1161,6 +1161,27 @@ log() {{
|
|
|
1161
1161
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$REPORT_FILE"
|
|
1162
1162
|
}}
|
|
1163
1163
|
|
|
1164
|
+
check_disk_space() {{
|
|
1165
|
+
local usage
|
|
1166
|
+
usage=$(df / --output=pcent | tail -n 1 | tr -dc '0-9')
|
|
1167
|
+
local avail
|
|
1168
|
+
avail=$(df -h / --output=avail | tail -n 1 | tr -d ' ')
|
|
1169
|
+
|
|
1170
|
+
if [ "$usage" -gt 95 ]; then
|
|
1171
|
+
log "[FAIL] Disk space nearly full: ${{usage}}% used ($avail available)"
|
|
1172
|
+
((FAILED++))
|
|
1173
|
+
return 1
|
|
1174
|
+
elif [ "$usage" -gt 85 ]; then
|
|
1175
|
+
log "[WARN] Disk usage high: ${{usage}}% used ($avail available)"
|
|
1176
|
+
((WARNINGS++))
|
|
1177
|
+
return 0
|
|
1178
|
+
else
|
|
1179
|
+
log "[PASS] Disk space OK: ${{usage}}% used ($avail available)"
|
|
1180
|
+
((PASSED++))
|
|
1181
|
+
return 0
|
|
1182
|
+
fi
|
|
1183
|
+
}}
|
|
1184
|
+
|
|
1164
1185
|
check_apt_package() {{
|
|
1165
1186
|
local pkg="$1"
|
|
1166
1187
|
if dpkg -l "$pkg" 2>/dev/null | grep -q "^ii"; then
|
|
@@ -1234,9 +1255,9 @@ check_mount() {{
|
|
|
1234
1255
|
log "[INFO] Mount point '$path' does not exist yet"
|
|
1235
1256
|
return 0
|
|
1236
1257
|
fi
|
|
1237
|
-
}
|
|
1258
|
+
}}
|
|
1238
1259
|
|
|
1239
|
-
check_copy_path() {
|
|
1260
|
+
check_copy_path() {{
|
|
1240
1261
|
local path="$1"
|
|
1241
1262
|
if [ -d "$path" ]; then
|
|
1242
1263
|
if [ "$(ls -A "$path" 2>/dev/null | wc -l)" -gt 0 ]; then
|
|
@@ -1283,6 +1304,11 @@ log "VM Name: {config.name}"
|
|
|
1283
1304
|
log "Date: $(date)"
|
|
1284
1305
|
log "=========================================="
|
|
1285
1306
|
|
|
1307
|
+
log ""
|
|
1308
|
+
log "--- System Health ---"
|
|
1309
|
+
check_disk_space
|
|
1310
|
+
check_gui
|
|
1311
|
+
|
|
1286
1312
|
log ""
|
|
1287
1313
|
log "--- APT Packages ---"
|
|
1288
1314
|
{apt_checks_str}
|
|
@@ -1403,7 +1429,7 @@ fi
|
|
|
1403
1429
|
(cloudinit_dir / "meta-data").write_text(meta_data)
|
|
1404
1430
|
|
|
1405
1431
|
# Generate mount commands and fstab entries for 9p filesystems
|
|
1406
|
-
|
|
1432
|
+
bind_mount_commands = []
|
|
1407
1433
|
fstab_entries = []
|
|
1408
1434
|
all_paths = dict(config.paths) if config.paths else {}
|
|
1409
1435
|
pre_chown_dirs: set[str] = set()
|
|
@@ -1421,8 +1447,8 @@ fi
|
|
|
1421
1447
|
d_str = str(current)
|
|
1422
1448
|
if d_str not in pre_chown_dirs:
|
|
1423
1449
|
pre_chown_dirs.add(d_str)
|
|
1424
|
-
|
|
1425
|
-
|
|
1450
|
+
bind_mount_commands.append(f" - mkdir -p {d_str}")
|
|
1451
|
+
bind_mount_commands.append(f" - chown 1000:1000 {d_str}")
|
|
1426
1452
|
except ValueError:
|
|
1427
1453
|
pass
|
|
1428
1454
|
|
|
@@ -1433,14 +1459,15 @@ fi
|
|
|
1433
1459
|
|
|
1434
1460
|
# Ensure target exists and is owned by user (if not already handled)
|
|
1435
1461
|
if str(guest_path) not in pre_chown_dirs:
|
|
1436
|
-
|
|
1437
|
-
|
|
1462
|
+
bind_mount_commands.append(f" - mkdir -p {guest_path}")
|
|
1463
|
+
bind_mount_commands.append(f" - chown 1000:1000 {guest_path}")
|
|
1438
1464
|
|
|
1439
|
-
|
|
1465
|
+
bind_mount_commands.append(f" - mount -t 9p -o {mount_opts} {tag} {guest_path} || true")
|
|
1440
1466
|
# Add fstab entry for persistence after reboot
|
|
1441
1467
|
fstab_entries.append(f"{tag} {guest_path} 9p {mount_opts},nofail 0 0")
|
|
1442
1468
|
|
|
1443
1469
|
# Handle copy_paths (import then copy)
|
|
1470
|
+
import_mount_commands = []
|
|
1444
1471
|
all_copy_paths = dict(config.copy_paths) if config.copy_paths else {}
|
|
1445
1472
|
existing_copy_paths = {h: g for h, g in all_copy_paths.items() if Path(h).exists()}
|
|
1446
1473
|
|
|
@@ -1451,29 +1478,29 @@ fi
|
|
|
1451
1478
|
mount_opts = "trans=virtio,version=9p2000.L,mmap,uid=1000,gid=1000"
|
|
1452
1479
|
|
|
1453
1480
|
# 1. Create temp mount point
|
|
1454
|
-
|
|
1481
|
+
import_mount_commands.append(f" - mkdir -p {temp_mount_point}")
|
|
1455
1482
|
|
|
1456
1483
|
# 2. Mount the 9p share
|
|
1457
|
-
|
|
1484
|
+
import_mount_commands.append(f" - mount -t 9p -o {mount_opts} {tag} {temp_mount_point} || true")
|
|
1458
1485
|
|
|
1459
1486
|
# 3. Ensure target directory exists and permissions are prepared
|
|
1460
1487
|
if str(guest_path).startswith("/home/ubuntu/"):
|
|
1461
|
-
|
|
1462
|
-
|
|
1488
|
+
import_mount_commands.append(f" - mkdir -p {guest_path}")
|
|
1489
|
+
import_mount_commands.append(f" - chown 1000:1000 {guest_path}")
|
|
1463
1490
|
else:
|
|
1464
|
-
|
|
1491
|
+
import_mount_commands.append(f" - mkdir -p {guest_path}")
|
|
1465
1492
|
|
|
1466
1493
|
# 4. Copy contents (cp -rT to copy contents of source to target)
|
|
1467
1494
|
# We use || true to ensure boot continues even if copy fails
|
|
1468
|
-
|
|
1469
|
-
|
|
1495
|
+
import_mount_commands.append(f" - echo 'Importing {host_path} to {guest_path}...'")
|
|
1496
|
+
import_mount_commands.append(f" - cp -rT {temp_mount_point} {guest_path} || true")
|
|
1470
1497
|
|
|
1471
1498
|
# 5. Fix ownership recursively
|
|
1472
|
-
|
|
1499
|
+
import_mount_commands.append(f" - chown -R 1000:1000 {guest_path}")
|
|
1473
1500
|
|
|
1474
1501
|
# 6. Unmount and cleanup
|
|
1475
|
-
|
|
1476
|
-
|
|
1502
|
+
import_mount_commands.append(f" - umount {temp_mount_point} || true")
|
|
1503
|
+
import_mount_commands.append(f" - rmdir {temp_mount_point} || true")
|
|
1477
1504
|
|
|
1478
1505
|
# User-data
|
|
1479
1506
|
# Add desktop environment if GUI is enabled
|
|
@@ -1506,7 +1533,8 @@ fi
|
|
|
1506
1533
|
for i, pkg in enumerate(all_packages, 1):
|
|
1507
1534
|
runcmd_lines.append(f" - echo ' → [{i}/{len(all_packages)}] Installing {pkg}...'")
|
|
1508
1535
|
runcmd_lines.append(f" - apt-get install -y {pkg} || echo ' ⚠️ Failed to install {pkg}'")
|
|
1509
|
-
runcmd_lines.append(" -
|
|
1536
|
+
runcmd_lines.append(" - apt-get clean")
|
|
1537
|
+
runcmd_lines.append(" - echo ' ✓ APT packages installed and cache cleaned'")
|
|
1510
1538
|
runcmd_lines.append(" - echo ''")
|
|
1511
1539
|
else:
|
|
1512
1540
|
runcmd_lines.append(" - echo '[1/9] 📦 No APT packages to install'")
|
|
@@ -1532,14 +1560,21 @@ fi
|
|
|
1532
1560
|
runcmd_lines.append(" - echo ''")
|
|
1533
1561
|
|
|
1534
1562
|
# Phase 4: Filesystem mounts
|
|
1535
|
-
runcmd_lines.append(f" - echo '[4/9] 📁 Mounting shared directories ({len(
|
|
1563
|
+
runcmd_lines.append(f" - echo '[4/9] 📁 Mounting shared directories ({len(config.paths)} mounts)...'")
|
|
1564
|
+
if bind_mount_commands:
|
|
1565
|
+
for cmd in bind_mount_commands:
|
|
1566
|
+
if "mount -t 9p" in cmd:
|
|
1567
|
+
# Extract mount point for logging
|
|
1568
|
+
parts = cmd.split()
|
|
1569
|
+
mp = parts[-2] if len(parts) > 2 else "path"
|
|
1570
|
+
runcmd_lines.append(f" - echo ' → Mounting {mp}...'")
|
|
1571
|
+
runcmd_lines.append(cmd)
|
|
1572
|
+
|
|
1536
1573
|
if fstab_entries:
|
|
1537
1574
|
runcmd_lines.append(
|
|
1538
1575
|
" - grep -q '^# CloneBox 9p mounts' /etc/fstab || echo '# CloneBox 9p mounts' >> /etc/fstab"
|
|
1539
1576
|
)
|
|
1540
1577
|
for i, entry in enumerate(fstab_entries, 1):
|
|
1541
|
-
mount_point = entry.split()[1] if len(entry.split()) > 1 else entry
|
|
1542
|
-
runcmd_lines.append(f" - echo ' → [{i}/{len(fstab_entries)}] {mount_point}'")
|
|
1543
1578
|
runcmd_lines.append(
|
|
1544
1579
|
f" - grep -qF \"{entry}\" /etc/fstab || echo '{entry}' >> /etc/fstab"
|
|
1545
1580
|
)
|
|
@@ -1550,9 +1585,9 @@ fi
|
|
|
1550
1585
|
# Phase 5: Data Import (copied paths)
|
|
1551
1586
|
if existing_copy_paths:
|
|
1552
1587
|
runcmd_lines.append(f" - echo '[5/9] 📥 Importing data ({len(existing_copy_paths)} paths)...'")
|
|
1553
|
-
# Add
|
|
1588
|
+
# Add import commands with progress
|
|
1554
1589
|
import_count = 0
|
|
1555
|
-
for cmd in
|
|
1590
|
+
for cmd in import_mount_commands:
|
|
1556
1591
|
if "Importing" in cmd:
|
|
1557
1592
|
import_count += 1
|
|
1558
1593
|
runcmd_lines.append(cmd.replace("Importing", f" → [{import_count}/{len(existing_copy_paths)}] Importing"))
|
|
@@ -2446,25 +2481,21 @@ if __name__ == "__main__":
|
|
|
2446
2481
|
# Note: The bash monitor is already installed above, no need to install Python monitor
|
|
2447
2482
|
|
|
2448
2483
|
# Create logs disk for host access
|
|
2449
|
-
#
|
|
2450
|
-
|
|
2451
|
-
logs_disk_path = str(Path.home() / ".local/share/libvirt/images/clonebox-logs.qcow2")
|
|
2452
|
-
else:
|
|
2453
|
-
logs_disk_path = "/var/lib/libvirt/images/clonebox-logs.qcow2"
|
|
2484
|
+
# Inside the VM, we use a fixed path for the image file
|
|
2485
|
+
vm_logs_img_path = "/var/lib/clonebox/logs.img"
|
|
2454
2486
|
|
|
2455
2487
|
runcmd_lines.extend(
|
|
2456
2488
|
[
|
|
2457
|
-
" - mkdir -p /mnt/logs",
|
|
2458
|
-
f" - truncate -s 1G {
|
|
2459
|
-
f" - mkfs.ext4 -F {
|
|
2460
|
-
f" - echo '{
|
|
2489
|
+
" - mkdir -p /var/lib/clonebox /mnt/logs",
|
|
2490
|
+
f" - truncate -s 1G {vm_logs_img_path}",
|
|
2491
|
+
f" - mkfs.ext4 -F {vm_logs_img_path}",
|
|
2492
|
+
f" - echo '{vm_logs_img_path} /mnt/logs ext4 loop,defaults 0 0' >> /etc/fstab",
|
|
2461
2493
|
" - mount -a",
|
|
2462
2494
|
" - mkdir -p /mnt/logs/var/log",
|
|
2463
2495
|
" - mkdir -p /mnt/logs/tmp",
|
|
2464
2496
|
" - cp -r /var/log/clonebox*.log /mnt/logs/var/log/ 2>/dev/null || true",
|
|
2465
2497
|
" - cp -r /tmp/*-error.log /mnt/logs/tmp/ 2>/dev/null || true",
|
|
2466
|
-
f" - echo 'Logs disk mounted at /mnt/logs -
|
|
2467
|
-
f" - \"echo 'To view logs on host: sudo mount -o loop {logs_disk_path} /mnt/clonebox-logs'\"",
|
|
2498
|
+
f" - echo 'Logs disk mounted at /mnt/logs - backing file: {vm_logs_img_path}'",
|
|
2468
2499
|
]
|
|
2469
2500
|
)
|
|
2470
2501
|
|
clonebox/validator.py
CHANGED
|
@@ -76,39 +76,38 @@ class VMValidator:
|
|
|
76
76
|
|
|
77
77
|
pid = response["return"]["pid"]
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
time.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
timeout=5,
|
|
95
|
-
)
|
|
79
|
+
deadline = time.time() + timeout
|
|
80
|
+
while time.time() < deadline:
|
|
81
|
+
status_result = subprocess.run(
|
|
82
|
+
[
|
|
83
|
+
"virsh",
|
|
84
|
+
"--connect",
|
|
85
|
+
self.conn_uri,
|
|
86
|
+
"qemu-agent-command",
|
|
87
|
+
self.vm_name,
|
|
88
|
+
f'{{"execute":"guest-exec-status","arguments":{{"pid":{pid}}}}}',
|
|
89
|
+
],
|
|
90
|
+
capture_output=True,
|
|
91
|
+
text=True,
|
|
92
|
+
timeout=5,
|
|
93
|
+
)
|
|
96
94
|
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
if status_result.returncode != 0:
|
|
96
|
+
return None
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
status_resp = json.loads(status_result.stdout)
|
|
99
|
+
if "return" not in status_resp:
|
|
100
|
+
return None
|
|
103
101
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
102
|
+
ret = status_resp["return"]
|
|
103
|
+
if ret.get("exited", False):
|
|
104
|
+
if "out-data" in ret:
|
|
105
|
+
return base64.b64decode(ret["out-data"]).decode().strip()
|
|
106
|
+
return ""
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
return base64.b64decode(ret["out-data"]).decode().strip()
|
|
108
|
+
time.sleep(0.2)
|
|
110
109
|
|
|
111
|
-
return
|
|
110
|
+
return None
|
|
112
111
|
|
|
113
112
|
except Exception:
|
|
114
113
|
return None
|
|
@@ -397,6 +396,8 @@ class VMValidator:
|
|
|
397
396
|
svc_table.add_column("PID", justify="right", style="dim")
|
|
398
397
|
svc_table.add_column("Note", style="dim")
|
|
399
398
|
|
|
399
|
+
setup_in_progress = self._setup_in_progress() is True
|
|
400
|
+
|
|
400
401
|
for service in services:
|
|
401
402
|
if service in self.VM_EXCLUDED_SERVICES:
|
|
402
403
|
svc_table.add_row(service, "[dim]—[/]", "[dim]—[/]", "[dim]—[/]", "host-only")
|
|
@@ -413,6 +414,7 @@ class VMValidator:
|
|
|
413
414
|
continue
|
|
414
415
|
|
|
415
416
|
self.results["services"]["total"] += 1
|
|
417
|
+
setup_in_progress = self._setup_in_progress() is True
|
|
416
418
|
|
|
417
419
|
enabled_cmd = f"systemctl is-enabled {service} 2>/dev/null"
|
|
418
420
|
enabled_status = self._exec_in_vm(enabled_cmd)
|
|
@@ -970,6 +972,138 @@ class VMValidator:
|
|
|
970
972
|
except Exception:
|
|
971
973
|
return False
|
|
972
974
|
|
|
975
|
+
def validate_disk_space(self) -> Dict:
|
|
976
|
+
"""Validate disk space on root filesystem."""
|
|
977
|
+
self.console.print("\n[bold]💾 Validating Disk Space...[/]")
|
|
978
|
+
|
|
979
|
+
df_output = self._exec_in_vm("df -h / --output=pcent,avail,size | tail -n 1", timeout=20)
|
|
980
|
+
if not df_output:
|
|
981
|
+
self.console.print("[red]❌ Could not check disk space[/]")
|
|
982
|
+
return {"status": "error"}
|
|
983
|
+
|
|
984
|
+
try:
|
|
985
|
+
# Format: pcent avail size
|
|
986
|
+
# Example: 98% 100M 30G
|
|
987
|
+
parts = df_output.split()
|
|
988
|
+
usage_pct = int(parts[0].replace('%', ''))
|
|
989
|
+
avail = parts[1]
|
|
990
|
+
total = parts[2]
|
|
991
|
+
|
|
992
|
+
self.results["disk"] = {
|
|
993
|
+
"usage_pct": usage_pct,
|
|
994
|
+
"avail": avail,
|
|
995
|
+
"total": total
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
if usage_pct > 95:
|
|
999
|
+
self.console.print(f"[red]❌ Disk nearly full: {usage_pct}% used ({avail} available of {total})[/]")
|
|
1000
|
+
status = "fail"
|
|
1001
|
+
elif usage_pct > 80:
|
|
1002
|
+
self.console.print(f"[yellow]⚠️ Disk usage high: {usage_pct}% used ({avail} available of {total})[/]")
|
|
1003
|
+
status = "warning"
|
|
1004
|
+
else:
|
|
1005
|
+
self.console.print(f"[green]✅ Disk space OK: {usage_pct}% used ({avail} available of {total})[/]")
|
|
1006
|
+
status = "pass"
|
|
1007
|
+
|
|
1008
|
+
if usage_pct > 80:
|
|
1009
|
+
self._print_disk_usage_breakdown()
|
|
1010
|
+
|
|
1011
|
+
return self.results["disk"]
|
|
1012
|
+
except Exception as e:
|
|
1013
|
+
self.console.print(f"[red]❌ Error parsing df output: {e}[/]")
|
|
1014
|
+
return {"status": "error"}
|
|
1015
|
+
|
|
1016
|
+
def _print_disk_usage_breakdown(self) -> None:
|
|
1017
|
+
def _parse_du_lines(out: Optional[str]) -> List[Tuple[str, str]]:
|
|
1018
|
+
if not out:
|
|
1019
|
+
return []
|
|
1020
|
+
rows: List[Tuple[str, str]] = []
|
|
1021
|
+
for line in out.splitlines():
|
|
1022
|
+
line = line.strip()
|
|
1023
|
+
if not line:
|
|
1024
|
+
continue
|
|
1025
|
+
parts = line.split(maxsplit=1)
|
|
1026
|
+
if len(parts) != 2:
|
|
1027
|
+
continue
|
|
1028
|
+
size, path = parts
|
|
1029
|
+
rows.append((path, size))
|
|
1030
|
+
return rows
|
|
1031
|
+
|
|
1032
|
+
def _dir_size(path: str, timeout: int = 30) -> Optional[str]:
|
|
1033
|
+
out = self._exec_in_vm(f"du -x -s -h {path} 2>/dev/null | head -n 1 | cut -f1", timeout=timeout)
|
|
1034
|
+
return out.strip() if out else None
|
|
1035
|
+
|
|
1036
|
+
self.console.print("\n[bold]📁 Disk usage breakdown (largest directories)[/]")
|
|
1037
|
+
|
|
1038
|
+
top_level = self._exec_in_vm(
|
|
1039
|
+
"du -x -h --max-depth=1 / 2>/dev/null | sort -hr | head -n 15",
|
|
1040
|
+
timeout=60,
|
|
1041
|
+
)
|
|
1042
|
+
top_rows = _parse_du_lines(top_level)
|
|
1043
|
+
|
|
1044
|
+
if top_rows:
|
|
1045
|
+
table = Table(title="Disk Usage: / (Top 15)", border_style="cyan")
|
|
1046
|
+
table.add_column("Path", style="bold")
|
|
1047
|
+
table.add_column("Size", justify="right")
|
|
1048
|
+
for path, size in top_rows:
|
|
1049
|
+
table.add_row(path, size)
|
|
1050
|
+
self.console.print(table)
|
|
1051
|
+
else:
|
|
1052
|
+
self.console.print("[dim]Could not compute top-level directory sizes (du may be busy)[/]")
|
|
1053
|
+
|
|
1054
|
+
var_sz = _dir_size("/var")
|
|
1055
|
+
home_sz = _dir_size("/home")
|
|
1056
|
+
if var_sz or home_sz:
|
|
1057
|
+
sum_table = Table(title="Disk Usage: Key Directories", border_style="cyan")
|
|
1058
|
+
sum_table.add_column("Path", style="bold")
|
|
1059
|
+
sum_table.add_column("Size", justify="right")
|
|
1060
|
+
for p in ["/var", "/var/lib", "/var/log", "/var/cache", "/var/lib/snapd", "/home", "/home/ubuntu", "/tmp"]:
|
|
1061
|
+
sz = _dir_size(p, timeout=30)
|
|
1062
|
+
if sz:
|
|
1063
|
+
sum_table.add_row(p, sz)
|
|
1064
|
+
self.console.print(sum_table)
|
|
1065
|
+
|
|
1066
|
+
var_breakdown = self._exec_in_vm(
|
|
1067
|
+
"du -x -h --max-depth=1 /var 2>/dev/null | sort -hr | head -n 12",
|
|
1068
|
+
timeout=60,
|
|
1069
|
+
)
|
|
1070
|
+
var_rows = _parse_du_lines(var_breakdown)
|
|
1071
|
+
if var_rows:
|
|
1072
|
+
vtable = Table(title="Disk Usage: /var (Top 12)", border_style="cyan")
|
|
1073
|
+
vtable.add_column("Path", style="bold")
|
|
1074
|
+
vtable.add_column("Size", justify="right")
|
|
1075
|
+
for path, size in var_rows:
|
|
1076
|
+
vtable.add_row(path, size)
|
|
1077
|
+
self.console.print(vtable)
|
|
1078
|
+
|
|
1079
|
+
home_breakdown = self._exec_in_vm(
|
|
1080
|
+
"du -x -h --max-depth=2 /home/ubuntu 2>/dev/null | sort -hr | head -n 12",
|
|
1081
|
+
timeout=60,
|
|
1082
|
+
)
|
|
1083
|
+
home_rows = _parse_du_lines(home_breakdown)
|
|
1084
|
+
if home_rows:
|
|
1085
|
+
htable = Table(title="Disk Usage: /home/ubuntu (Top 12)", border_style="cyan")
|
|
1086
|
+
htable.add_column("Path", style="bold")
|
|
1087
|
+
htable.add_column("Size", justify="right")
|
|
1088
|
+
for path, size in home_rows:
|
|
1089
|
+
htable.add_row(path, size)
|
|
1090
|
+
self.console.print(htable)
|
|
1091
|
+
|
|
1092
|
+
copy_paths = self.config.get("copy_paths", None)
|
|
1093
|
+
if not isinstance(copy_paths, dict) or not copy_paths:
|
|
1094
|
+
copy_paths = self.config.get("app_data_paths", {})
|
|
1095
|
+
if copy_paths:
|
|
1096
|
+
ctable = Table(title="Disk Usage: Configured Imported Paths", border_style="cyan")
|
|
1097
|
+
ctable.add_column("Guest Path", style="bold")
|
|
1098
|
+
ctable.add_column("Size", justify="right")
|
|
1099
|
+
for _, guest_path in copy_paths.items():
|
|
1100
|
+
sz = _dir_size(guest_path, timeout=30)
|
|
1101
|
+
if sz:
|
|
1102
|
+
ctable.add_row(str(guest_path), sz)
|
|
1103
|
+
else:
|
|
1104
|
+
ctable.add_row(str(guest_path), "—")
|
|
1105
|
+
self.console.print(ctable)
|
|
1106
|
+
|
|
973
1107
|
def validate_all(self) -> Dict:
|
|
974
1108
|
"""Run all validations and return comprehensive results."""
|
|
975
1109
|
self.console.print("[bold cyan]🔍 Running Full Validation...[/]")
|
|
@@ -1026,6 +1160,7 @@ class VMValidator:
|
|
|
1026
1160
|
)
|
|
1027
1161
|
|
|
1028
1162
|
# Run all validations
|
|
1163
|
+
self.validate_disk_space()
|
|
1029
1164
|
self.validate_mounts()
|
|
1030
1165
|
self.validate_packages()
|
|
1031
1166
|
self.validate_snap_packages()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
clonebox/__init__.py,sha256=CyfHVVq6KqBr4CNERBpXk_O6Q5B35q03YpdQbokVvvI,408
|
|
2
2
|
clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
|
|
3
3
|
clonebox/audit.py,sha256=1W9vaIjB0A--_p7CgE3cIP5RNckJG1RxJrL-tOb-QmU,14298
|
|
4
|
-
clonebox/cli.py,sha256=
|
|
5
|
-
clonebox/cloner.py,sha256=
|
|
4
|
+
clonebox/cli.py,sha256=x0VQ9WI1KZrNQGAh7FDf_036jDypdT5Q7vuTlr-dgi0,179087
|
|
5
|
+
clonebox/cloner.py,sha256=WEMNjhHVdXwiA2zCq14fr5eVD6vhLuX4vnJ47p3SiNI,107839
|
|
6
6
|
clonebox/container.py,sha256=tiYK1ZB-DhdD6A2FuMA0h_sRNkUI7KfYcJ0tFOcdyeM,6105
|
|
7
7
|
clonebox/dashboard.py,sha256=dMY6odvPq3j6FronhRRsX7aY3qdCwznB-aCWKEmHDNw,5768
|
|
8
8
|
clonebox/detector.py,sha256=vS65cvFNPmUBCX1Y_TMTnSRljw6r1Ae9dlVtACs5XFc,23075
|
|
@@ -20,7 +20,7 @@ clonebox/resource_monitor.py,sha256=lDR9KyPbVtImeeOkOBPPVP-5yCgoL5hsVFPZ_UqsY0w,
|
|
|
20
20
|
clonebox/resources.py,sha256=IkuM4OdSDV4qhyc0eIynwbAHBTv0aVSxxW-gghsnCAs,6815
|
|
21
21
|
clonebox/rollback.py,sha256=hpwO-8Ehe1pW0wHuZvJkC_qxZ6yEo9otCJRhGIUArCo,5711
|
|
22
22
|
clonebox/secrets.py,sha256=l1jwJcEPB1qMoGNLPjyrkKKr1khh9VmftFJI9BWhgK0,10628
|
|
23
|
-
clonebox/validator.py,sha256=
|
|
23
|
+
clonebox/validator.py,sha256=PWPgkR9cjewTh6-uW_2YyjxuEj2wI9O7h7hk7NK8nAI,51181
|
|
24
24
|
clonebox/backends/libvirt_backend.py,sha256=sIHFIvFO1hIOXEFR_foSkOGBgIzaJVQs-njOU8GdafA,7170
|
|
25
25
|
clonebox/backends/qemu_disk.py,sha256=YsGjYX5sbEf35Y4yjTpNkZat73a4RGBxY-KTVzJhqIs,1687
|
|
26
26
|
clonebox/backends/subprocess_runner.py,sha256=c-IyaMxM1cmUu64h654oAvulm83K5Mu-VQxXJ_0BOds,1506
|
|
@@ -40,9 +40,9 @@ clonebox/snapshots/manager.py,sha256=hGzM8V6ZJPXjTqj47c4Kr8idlE-c1Q3gPUvuw1HvS1A
|
|
|
40
40
|
clonebox/snapshots/models.py,sha256=sRnn3OZE8JG9FZJlRuA3ihO-JXoPCQ3nD3SQytflAao,6206
|
|
41
41
|
clonebox/templates/profiles/ml-dev.yaml,sha256=w07MToGh31xtxpjbeXTBk9BkpAN8A3gv8HeA3ESKG9M,461
|
|
42
42
|
clonebox/templates/profiles/web-stack.yaml,sha256=EBnnGMzML5vAjXmIUbCpbTCwmRaNJiuWd3EcL43DOK8,485
|
|
43
|
-
clonebox-1.1.
|
|
44
|
-
clonebox-1.1.
|
|
45
|
-
clonebox-1.1.
|
|
46
|
-
clonebox-1.1.
|
|
47
|
-
clonebox-1.1.
|
|
48
|
-
clonebox-1.1.
|
|
43
|
+
clonebox-1.1.18.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
44
|
+
clonebox-1.1.18.dist-info/METADATA,sha256=n1VJE8-Mk-zf9QgHBbvt1Z5fbJMdpD1vjZVVjEBeaxE,49052
|
|
45
|
+
clonebox-1.1.18.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
46
|
+
clonebox-1.1.18.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
47
|
+
clonebox-1.1.18.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
48
|
+
clonebox-1.1.18.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|