clonebox 1.1.10__py3-none-any.whl → 1.1.12__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 +35 -13
- clonebox/cloner.py +93 -8
- clonebox/di.py +1 -1
- clonebox/models.py +2 -5
- clonebox/validator.py +65 -34
- {clonebox-1.1.10.dist-info → clonebox-1.1.12.dist-info}/METADATA +1 -1
- {clonebox-1.1.10.dist-info → clonebox-1.1.12.dist-info}/RECORD +11 -11
- {clonebox-1.1.10.dist-info → clonebox-1.1.12.dist-info}/WHEEL +0 -0
- {clonebox-1.1.10.dist-info → clonebox-1.1.12.dist-info}/entry_points.txt +0 -0
- {clonebox-1.1.10.dist-info → clonebox-1.1.12.dist-info}/licenses/LICENSE +0 -0
- {clonebox-1.1.10.dist-info → clonebox-1.1.12.dist-info}/top_level.txt +0 -0
clonebox/cli.py
CHANGED
|
@@ -1825,23 +1825,37 @@ def cmd_test(args):
|
|
|
1825
1825
|
# Test 4: Check mounts (if running)
|
|
1826
1826
|
if not quick and state == "running":
|
|
1827
1827
|
console.print("[bold]4. Mount Points Check[/]")
|
|
1828
|
-
|
|
1829
|
-
|
|
1828
|
+
paths = config.get("paths", {})
|
|
1829
|
+
app_data_paths = config.get("app_data_paths", {})
|
|
1830
1830
|
|
|
1831
|
-
if
|
|
1831
|
+
if paths or app_data_paths:
|
|
1832
1832
|
if not _qga_ping(vm_name, conn_uri):
|
|
1833
1833
|
console.print("[yellow]⚠️ QEMU guest agent not connected - cannot verify mounts[/]")
|
|
1834
1834
|
else:
|
|
1835
|
-
|
|
1835
|
+
# Check bind mounts
|
|
1836
|
+
for idx, (host_path, guest_path) in enumerate(paths.items()):
|
|
1836
1837
|
try:
|
|
1837
1838
|
# Use the same QGA helper as diagnose/status
|
|
1838
1839
|
is_accessible = _qga_exec(
|
|
1839
1840
|
vm_name, conn_uri, f"test -d {guest_path} && echo yes || echo no", timeout=5
|
|
1840
1841
|
)
|
|
1841
1842
|
if is_accessible == "yes":
|
|
1842
|
-
console.print(f"[green]✅ {guest_path}[/]")
|
|
1843
|
+
console.print(f"[green]✅ {guest_path} (mount)[/]")
|
|
1844
|
+
else:
|
|
1845
|
+
console.print(f"[red]❌ {guest_path} (mount inaccessible)[/]")
|
|
1846
|
+
except Exception:
|
|
1847
|
+
console.print(f"[yellow]⚠️ {guest_path} (could not check)[/]")
|
|
1848
|
+
|
|
1849
|
+
# Check copied paths
|
|
1850
|
+
for idx, (host_path, guest_path) in enumerate(app_data_paths.items()):
|
|
1851
|
+
try:
|
|
1852
|
+
is_accessible = _qga_exec(
|
|
1853
|
+
vm_name, conn_uri, f"test -d {guest_path} && echo yes || echo no", timeout=5
|
|
1854
|
+
)
|
|
1855
|
+
if is_accessible == "yes":
|
|
1856
|
+
console.print(f"[green]✅ {guest_path} (copied)[/]")
|
|
1843
1857
|
else:
|
|
1844
|
-
console.print(f"[red]❌ {guest_path} (
|
|
1858
|
+
console.print(f"[red]❌ {guest_path} (copy missing)[/]")
|
|
1845
1859
|
except Exception:
|
|
1846
1860
|
console.print(f"[yellow]⚠️ {guest_path} (could not check)[/]")
|
|
1847
1861
|
else:
|
|
@@ -2521,14 +2535,22 @@ def cmd_clone(args):
|
|
|
2521
2535
|
console.print(" [cyan]clonebox-health[/] # Re-run health check")
|
|
2522
2536
|
|
|
2523
2537
|
# Show mount instructions
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2538
|
+
paths = config.get("paths", {})
|
|
2539
|
+
app_data_paths = config.get("app_data_paths", {})
|
|
2540
|
+
|
|
2541
|
+
if paths:
|
|
2542
|
+
console.print("\n[bold]📁 Mounted paths (shared live):[/]")
|
|
2543
|
+
for idx, (host, guest) in enumerate(list(paths.items())[:5]):
|
|
2544
|
+
console.print(f" [dim]{host}[/] → [cyan]{guest}[/]")
|
|
2545
|
+
if len(paths) > 5:
|
|
2546
|
+
console.print(f" [dim]... and {len(paths) - 5} more paths[/]")
|
|
2547
|
+
|
|
2548
|
+
if app_data_paths:
|
|
2549
|
+
console.print("\n[bold]📥 Copied paths (one-time import):[/]")
|
|
2550
|
+
for idx, (host, guest) in enumerate(list(app_data_paths.items())[:5]):
|
|
2529
2551
|
console.print(f" [dim]{host}[/] → [cyan]{guest}[/]")
|
|
2530
|
-
if len(
|
|
2531
|
-
console.print(f" [dim]... and {len(
|
|
2552
|
+
if len(app_data_paths) > 5:
|
|
2553
|
+
console.print(f" [dim]... and {len(app_data_paths) - 5} more paths[/]")
|
|
2532
2554
|
except PermissionError as e:
|
|
2533
2555
|
console.print(f"[red]❌ Permission Error:[/]\n{e}")
|
|
2534
2556
|
console.print("\n[yellow]💡 Try running with --user flag:[/]")
|
clonebox/cloner.py
CHANGED
|
@@ -92,6 +92,7 @@ class VMConfig:
|
|
|
92
92
|
snap_packages: list = field(default_factory=list) # Snap packages to install
|
|
93
93
|
services: list = field(default_factory=list)
|
|
94
94
|
post_commands: list = field(default_factory=list) # Commands to run after setup
|
|
95
|
+
copy_paths: dict = field(default_factory=dict) # Paths to copy (import) instead of bind-mount
|
|
95
96
|
user_session: bool = field(
|
|
96
97
|
default_factory=lambda: os.getenv("VM_USER_SESSION", "false").lower() == "true"
|
|
97
98
|
) # Use qemu:///session instead of qemu:///system
|
|
@@ -183,7 +184,13 @@ class SelectiveVMCloner:
|
|
|
183
184
|
)
|
|
184
185
|
|
|
185
186
|
try:
|
|
186
|
-
|
|
187
|
+
# Use openAuth to avoid blocking on graphical auth dialogs (polkit)
|
|
188
|
+
# This is more robust for CLI usage
|
|
189
|
+
def auth_cb(creds, opaque):
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT], auth_cb, None]
|
|
193
|
+
self.conn = libvirt.openAuth(self.conn_uri, auth, 0)
|
|
187
194
|
except libvirt.libvirtError as e:
|
|
188
195
|
raise ConnectionError(
|
|
189
196
|
f"Cannot connect to {self.conn_uri}\n"
|
|
@@ -523,7 +530,8 @@ class SelectiveVMCloner:
|
|
|
523
530
|
pass
|
|
524
531
|
|
|
525
532
|
# CPU tuning element
|
|
526
|
-
|
|
533
|
+
# Only available in system session (requires cgroups)
|
|
534
|
+
if not self.user_session and (limits.cpu.shares or limits.cpu.quota or limits.cpu.pin):
|
|
527
535
|
cputune = ET.SubElement(root, "cputune")
|
|
528
536
|
ET.SubElement(cputune, "shares").text = str(limits.cpu.shares)
|
|
529
537
|
if limits.cpu.quota:
|
|
@@ -534,7 +542,8 @@ class SelectiveVMCloner:
|
|
|
534
542
|
ET.SubElement(cputune, "vcpupin", vcpu=str(idx), cpuset=str(cpu))
|
|
535
543
|
|
|
536
544
|
# Memory tuning element
|
|
537
|
-
|
|
545
|
+
# Only available in system session (requires cgroups)
|
|
546
|
+
if not self.user_session and (limits.memory.soft_limit or limits.memory.swap):
|
|
538
547
|
memtune = ET.SubElement(root, "memtune")
|
|
539
548
|
ET.SubElement(memtune, "hard_limit", unit="KiB").text = str(limit_kib)
|
|
540
549
|
if limits.memory.soft_limit_bytes:
|
|
@@ -558,7 +567,8 @@ class SelectiveVMCloner:
|
|
|
558
567
|
ET.SubElement(disk, "target", dev="vda", bus="virtio")
|
|
559
568
|
|
|
560
569
|
# Disk I/O tuning
|
|
561
|
-
|
|
570
|
+
# Only available in system session (requires cgroups)
|
|
571
|
+
if not self.user_session and (limits.disk.read_bps or limits.disk.write_bps or limits.disk.read_iops or limits.disk.write_iops):
|
|
562
572
|
iotune = ET.SubElement(disk, "iotune")
|
|
563
573
|
if limits.disk.read_bps_bytes:
|
|
564
574
|
ET.SubElement(iotune, "read_bytes_sec").text = str(limits.disk.read_bps_bytes)
|
|
@@ -588,6 +598,16 @@ class SelectiveVMCloner:
|
|
|
588
598
|
tag = f"mount{idx}"
|
|
589
599
|
ET.SubElement(fs, "target", dir=tag)
|
|
590
600
|
|
|
601
|
+
# 9p filesystem mounts for COPY paths (mounted to temp location for import)
|
|
602
|
+
for idx, (host_path, guest_path) in enumerate(config.copy_paths.items()):
|
|
603
|
+
if Path(host_path).exists():
|
|
604
|
+
fs = ET.SubElement(devices, "filesystem", type="mount", accessmode="mapped")
|
|
605
|
+
ET.SubElement(fs, "driver", type="path", wrpolicy="immediate")
|
|
606
|
+
ET.SubElement(fs, "source", dir=host_path)
|
|
607
|
+
# Use import tag names for copy mounts
|
|
608
|
+
tag = f"import{idx}"
|
|
609
|
+
ET.SubElement(fs, "target", dir=tag)
|
|
610
|
+
|
|
591
611
|
# Network interface
|
|
592
612
|
network_mode = self.resolve_network_mode(config)
|
|
593
613
|
if network_mode == "user":
|
|
@@ -655,7 +675,8 @@ class SelectiveVMCloner:
|
|
|
655
675
|
import base64
|
|
656
676
|
|
|
657
677
|
wants_google_chrome = any(
|
|
658
|
-
p == "/home/ubuntu/.config/google-chrome"
|
|
678
|
+
p == "/home/ubuntu/.config/google-chrome"
|
|
679
|
+
for p in list((config.paths or {}).values()) + list((config.copy_paths or {}).values())
|
|
659
680
|
)
|
|
660
681
|
|
|
661
682
|
apt_pkg_list = list(config.packages or [])
|
|
@@ -1298,6 +1319,40 @@ fi
|
|
|
1298
1319
|
# Add fstab entry for persistence after reboot
|
|
1299
1320
|
fstab_entries.append(f"{tag} {guest_path} 9p {mount_opts},nofail 0 0")
|
|
1300
1321
|
|
|
1322
|
+
# Handle copy_paths (import then copy)
|
|
1323
|
+
all_copy_paths = dict(config.copy_paths) if config.copy_paths else {}
|
|
1324
|
+
for idx, (host_path, guest_path) in enumerate(all_copy_paths.items()):
|
|
1325
|
+
if Path(host_path).exists():
|
|
1326
|
+
tag = f"import{idx}"
|
|
1327
|
+
temp_mount_point = f"/mnt/import{idx}"
|
|
1328
|
+
# Use regular mount options
|
|
1329
|
+
mount_opts = "trans=virtio,version=9p2000.L,mmap,uid=1000,gid=1000"
|
|
1330
|
+
|
|
1331
|
+
# 1. Create temp mount point
|
|
1332
|
+
mount_commands.append(f" - mkdir -p {temp_mount_point}")
|
|
1333
|
+
|
|
1334
|
+
# 2. Mount the 9p share
|
|
1335
|
+
mount_commands.append(f" - mount -t 9p -o {mount_opts} {tag} {temp_mount_point} || true")
|
|
1336
|
+
|
|
1337
|
+
# 3. Ensure target directory exists and permissions are prepared
|
|
1338
|
+
if str(guest_path).startswith("/home/ubuntu/"):
|
|
1339
|
+
mount_commands.append(f" - mkdir -p {guest_path}")
|
|
1340
|
+
mount_commands.append(f" - chown 1000:1000 {guest_path}")
|
|
1341
|
+
else:
|
|
1342
|
+
mount_commands.append(f" - mkdir -p {guest_path}")
|
|
1343
|
+
|
|
1344
|
+
# 4. Copy contents (cp -rT to copy contents of source to target)
|
|
1345
|
+
# We use || true to ensure boot continues even if copy fails
|
|
1346
|
+
mount_commands.append(f" - echo 'Importing {host_path} to {guest_path}...'")
|
|
1347
|
+
mount_commands.append(f" - cp -rT {temp_mount_point} {guest_path} || true")
|
|
1348
|
+
|
|
1349
|
+
# 5. Fix ownership recursively
|
|
1350
|
+
mount_commands.append(f" - chown -R 1000:1000 {guest_path}")
|
|
1351
|
+
|
|
1352
|
+
# 6. Unmount and cleanup
|
|
1353
|
+
mount_commands.append(f" - umount {temp_mount_point} || true")
|
|
1354
|
+
mount_commands.append(f" - rmdir {temp_mount_point} || true")
|
|
1355
|
+
|
|
1301
1356
|
# User-data
|
|
1302
1357
|
# Add desktop environment if GUI is enabled
|
|
1303
1358
|
base_packages = ["qemu-guest-agent", "cloud-guest-utils"]
|
|
@@ -1876,8 +1931,14 @@ esac
|
|
|
1876
1931
|
}
|
|
1877
1932
|
)
|
|
1878
1933
|
|
|
1879
|
-
# Check for google-chrome from app_data_paths
|
|
1880
|
-
|
|
1934
|
+
# Check for google-chrome from app_data_paths (now in copy_paths or paths)
|
|
1935
|
+
all_paths_to_check = {}
|
|
1936
|
+
if config.paths:
|
|
1937
|
+
all_paths_to_check.update(config.paths)
|
|
1938
|
+
if config.copy_paths:
|
|
1939
|
+
all_paths_to_check.update(config.copy_paths)
|
|
1940
|
+
|
|
1941
|
+
for host_path, guest_path in all_paths_to_check.items():
|
|
1881
1942
|
if guest_path == "/home/ubuntu/.config/google-chrome":
|
|
1882
1943
|
autostart_apps.append(
|
|
1883
1944
|
{
|
|
@@ -2330,7 +2391,31 @@ final_message: "CloneBox VM is ready after $UPTIME seconds"
|
|
|
2330
2391
|
vm.destroy()
|
|
2331
2392
|
else:
|
|
2332
2393
|
log(f"[cyan]🛑 Shutting down VM '{vm_name}'...[/]")
|
|
2333
|
-
|
|
2394
|
+
try:
|
|
2395
|
+
vm.shutdown()
|
|
2396
|
+
except libvirt.libvirtError as e:
|
|
2397
|
+
log(f"[red]❌ Failed to request shutdown: {e}[/]")
|
|
2398
|
+
return False
|
|
2399
|
+
|
|
2400
|
+
# Wait for shutdown
|
|
2401
|
+
import time
|
|
2402
|
+
waiting = True
|
|
2403
|
+
for i in range(30):
|
|
2404
|
+
try:
|
|
2405
|
+
if not vm.isActive():
|
|
2406
|
+
waiting = False
|
|
2407
|
+
break
|
|
2408
|
+
except libvirt.libvirtError:
|
|
2409
|
+
# Domain might be gone
|
|
2410
|
+
waiting = False
|
|
2411
|
+
break
|
|
2412
|
+
time.sleep(1)
|
|
2413
|
+
|
|
2414
|
+
if waiting:
|
|
2415
|
+
log(f"[red]❌ Shutdown timed out. VM is still running.[/]")
|
|
2416
|
+
log(f"[dim]The guest OS is not responding to ACPI shutdown signal.[/]")
|
|
2417
|
+
log(f"[dim]Try using: clonebox stop {vm_name} --force[/]")
|
|
2418
|
+
return False
|
|
2334
2419
|
|
|
2335
2420
|
log("[green]✅ VM stopped![/]")
|
|
2336
2421
|
return True
|
clonebox/di.py
CHANGED
clonebox/models.py
CHANGED
|
@@ -116,10 +116,6 @@ class CloneBoxConfig(BaseModel):
|
|
|
116
116
|
"""Convert to legacy VMConfig dataclass for compatibility."""
|
|
117
117
|
from clonebox.cloner import VMConfig as VMConfigDataclass
|
|
118
118
|
|
|
119
|
-
# Merge paths and app_data_paths
|
|
120
|
-
all_paths = dict(self.paths)
|
|
121
|
-
all_paths.update(self.app_data_paths)
|
|
122
|
-
|
|
123
119
|
return VMConfigDataclass(
|
|
124
120
|
name=self.vm.name,
|
|
125
121
|
ram_mb=self.vm.ram_mb,
|
|
@@ -127,7 +123,8 @@ class CloneBoxConfig(BaseModel):
|
|
|
127
123
|
disk_size_gb=self.vm.disk_size_gb,
|
|
128
124
|
gui=self.vm.gui,
|
|
129
125
|
base_image=self.vm.base_image,
|
|
130
|
-
paths=
|
|
126
|
+
paths=self.paths,
|
|
127
|
+
copy_paths=self.app_data_paths, # Map app_data_paths to copy_paths
|
|
131
128
|
packages=self.packages,
|
|
132
129
|
snap_packages=self.snap_packages,
|
|
133
130
|
services=self.services,
|
clonebox/validator.py
CHANGED
|
@@ -106,14 +106,14 @@ class VMValidator:
|
|
|
106
106
|
return None
|
|
107
107
|
|
|
108
108
|
def validate_mounts(self) -> Dict:
|
|
109
|
-
"""Validate all mount points
|
|
110
|
-
self.console.print("\n[bold]💾 Validating
|
|
109
|
+
"""Validate all mount points and copied data paths."""
|
|
110
|
+
self.console.print("\n[bold]💾 Validating Mounts & Data...[/]")
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
paths = self.config.get("paths", {})
|
|
113
|
+
app_data_paths = self.config.get("app_data_paths", {})
|
|
114
114
|
|
|
115
|
-
if not
|
|
116
|
-
self.console.print("[dim]No
|
|
115
|
+
if not paths and not app_data_paths:
|
|
116
|
+
self.console.print("[dim]No mounts or data paths configured[/]")
|
|
117
117
|
return self.results["mounts"]
|
|
118
118
|
|
|
119
119
|
# Get mounted filesystems
|
|
@@ -122,64 +122,95 @@ class VMValidator:
|
|
|
122
122
|
if mount_output:
|
|
123
123
|
mounted_paths = [line.split()[2] for line in mount_output.split("\n") if line.strip()]
|
|
124
124
|
|
|
125
|
-
mount_table = Table(title="
|
|
125
|
+
mount_table = Table(title="Data Validation", border_style="cyan")
|
|
126
126
|
mount_table.add_column("Guest Path", style="bold")
|
|
127
|
-
mount_table.add_column("
|
|
128
|
-
mount_table.add_column("
|
|
127
|
+
mount_table.add_column("Type", justify="center")
|
|
128
|
+
mount_table.add_column("Status", justify="center")
|
|
129
129
|
mount_table.add_column("Files", justify="right")
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
# Validate bind mounts (paths)
|
|
132
|
+
for host_path, guest_path in paths.items():
|
|
132
133
|
self.results["mounts"]["total"] += 1
|
|
133
|
-
|
|
134
|
+
|
|
134
135
|
# Check if mounted
|
|
135
136
|
is_mounted = any(guest_path in mp for mp in mounted_paths)
|
|
136
|
-
|
|
137
|
+
|
|
137
138
|
# Check if accessible
|
|
138
139
|
accessible = False
|
|
139
140
|
file_count = "?"
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
if is_mounted:
|
|
142
143
|
test_result = self._exec_in_vm(f"test -d {guest_path} && echo 'yes' || echo 'no'")
|
|
143
144
|
accessible = test_result == "yes"
|
|
144
|
-
|
|
145
|
+
|
|
145
146
|
if accessible:
|
|
146
|
-
# Get file count
|
|
147
147
|
count_str = self._exec_in_vm(f"ls -A {guest_path} 2>/dev/null | wc -l")
|
|
148
148
|
if count_str and count_str.isdigit():
|
|
149
149
|
file_count = count_str
|
|
150
150
|
|
|
151
|
-
# Determine status
|
|
152
151
|
if is_mounted and accessible:
|
|
153
|
-
|
|
154
|
-
access_status = "[green]✅[/]"
|
|
152
|
+
status_icon = "[green]✅ Mounted[/]"
|
|
155
153
|
self.results["mounts"]["passed"] += 1
|
|
156
154
|
status = "pass"
|
|
157
155
|
elif is_mounted:
|
|
158
|
-
|
|
159
|
-
access_status = "[red]❌[/]"
|
|
156
|
+
status_icon = "[red]❌ Inaccessible[/]"
|
|
160
157
|
self.results["mounts"]["failed"] += 1
|
|
161
158
|
status = "mounted_but_inaccessible"
|
|
162
159
|
else:
|
|
163
|
-
|
|
164
|
-
access_status = "[dim]N/A[/]"
|
|
160
|
+
status_icon = "[red]❌ Not Mounted[/]"
|
|
165
161
|
self.results["mounts"]["failed"] += 1
|
|
166
162
|
status = "not_mounted"
|
|
167
163
|
|
|
168
|
-
mount_table.add_row(guest_path,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
164
|
+
mount_table.add_row(guest_path, "Bind Mount", status_icon, str(file_count))
|
|
165
|
+
self.results["mounts"]["details"].append({
|
|
166
|
+
"path": guest_path,
|
|
167
|
+
"type": "mount",
|
|
168
|
+
"mounted": is_mounted,
|
|
169
|
+
"accessible": accessible,
|
|
170
|
+
"files": file_count,
|
|
171
|
+
"status": status
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
# Validate copied paths (app_data_paths)
|
|
175
|
+
for host_path, guest_path in app_data_paths.items():
|
|
176
|
+
self.results["mounts"]["total"] += 1
|
|
177
|
+
|
|
178
|
+
# Check if exists and has content
|
|
179
|
+
exists = False
|
|
180
|
+
file_count = "?"
|
|
181
|
+
|
|
182
|
+
test_result = self._exec_in_vm(f"test -d {guest_path} && echo 'yes' || echo 'no'")
|
|
183
|
+
exists = test_result == "yes"
|
|
184
|
+
|
|
185
|
+
if exists:
|
|
186
|
+
count_str = self._exec_in_vm(f"ls -A {guest_path} 2>/dev/null | wc -l")
|
|
187
|
+
if count_str and count_str.isdigit():
|
|
188
|
+
file_count = count_str
|
|
189
|
+
|
|
190
|
+
# For copied paths, we just check existence and content
|
|
191
|
+
if exists:
|
|
192
|
+
# Warning if empty? Maybe, but strictly it passed existence check
|
|
193
|
+
status_icon = "[green]✅ Copied[/]"
|
|
194
|
+
self.results["mounts"]["passed"] += 1
|
|
195
|
+
status = "pass"
|
|
196
|
+
else:
|
|
197
|
+
status_icon = "[red]❌ Missing[/]"
|
|
198
|
+
self.results["mounts"]["failed"] += 1
|
|
199
|
+
status = "missing"
|
|
200
|
+
|
|
201
|
+
mount_table.add_row(guest_path, "Imported", status_icon, str(file_count))
|
|
202
|
+
self.results["mounts"]["details"].append({
|
|
203
|
+
"path": guest_path,
|
|
204
|
+
"type": "copy",
|
|
205
|
+
"mounted": False, # Expected false for copies
|
|
206
|
+
"accessible": exists,
|
|
207
|
+
"files": file_count,
|
|
208
|
+
"status": status
|
|
209
|
+
})
|
|
179
210
|
|
|
180
211
|
self.console.print(mount_table)
|
|
181
212
|
self.console.print(
|
|
182
|
-
f"[dim]{self.results['mounts']['passed']}/{self.results['mounts']['total']}
|
|
213
|
+
f"[dim]{self.results['mounts']['passed']}/{self.results['mounts']['total']} paths valid[/]"
|
|
183
214
|
)
|
|
184
215
|
|
|
185
216
|
return self.results["mounts"]
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
clonebox/__init__.py,sha256=CyfHVVq6KqBr4CNERBpXk_O6Q5B35q03YpdQbokVvvI,408
|
|
2
2
|
clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
|
|
3
|
-
clonebox/cli.py,sha256=
|
|
4
|
-
clonebox/cloner.py,sha256=
|
|
3
|
+
clonebox/cli.py,sha256=CcqJgVH-hSGm4FXTuJCWniocKnAeQayhPPO17nxK578,139009
|
|
4
|
+
clonebox/cloner.py,sha256=sCUeGLY64rXVoEmstS5oFLbCmwwFyR8LhIzkmplMemI,96242
|
|
5
5
|
clonebox/container.py,sha256=tiYK1ZB-DhdD6A2FuMA0h_sRNkUI7KfYcJ0tFOcdyeM,6105
|
|
6
6
|
clonebox/dashboard.py,sha256=dMY6odvPq3j6FronhRRsX7aY3qdCwznB-aCWKEmHDNw,5768
|
|
7
7
|
clonebox/detector.py,sha256=vS65cvFNPmUBCX1Y_TMTnSRljw6r1Ae9dlVtACs5XFc,23075
|
|
8
|
-
clonebox/di.py,sha256=
|
|
8
|
+
clonebox/di.py,sha256=TrPNNtyDcD_J6K8QMDsxnLVi1E6Qsj47CmOg4560tHs,5649
|
|
9
9
|
clonebox/exporter.py,sha256=WIzVvmA0z_jjrpyXxvnXoLp9oaW6fKS7k0PGwzx_PIM,5629
|
|
10
10
|
clonebox/importer.py,sha256=Q9Uk1IOA41mgGhU4ynW2k-h9GEoGxRKI3c9wWE4uxcA,7097
|
|
11
11
|
clonebox/logging.py,sha256=jD_WgkHmt_h99EmX82QWK7D0OKWyxqcLwgT4UDQFp2g,3675
|
|
12
|
-
clonebox/models.py,sha256=
|
|
12
|
+
clonebox/models.py,sha256=13B0lVAuaGnpY4h4iYUPigIvfxYx2pWaAEM1cYF_Hbo,8263
|
|
13
13
|
clonebox/monitor.py,sha256=zlIarNf8w_i34XI8hZGxxrg5PVZK_Yxm6FQnkhLavRI,9181
|
|
14
14
|
clonebox/p2p.py,sha256=6o0JnscKqF9-BftQhW5fF1W6YY1wXshY9LEklNcHGJc,5913
|
|
15
15
|
clonebox/profiles.py,sha256=UP37fX_rhrG_O9ehNFJBUcULPmUtN1A8KsJ6cM44oK0,1986
|
|
@@ -17,7 +17,7 @@ clonebox/resource_monitor.py,sha256=lDR9KyPbVtImeeOkOBPPVP-5yCgoL5hsVFPZ_UqsY0w,
|
|
|
17
17
|
clonebox/resources.py,sha256=IkuM4OdSDV4qhyc0eIynwbAHBTv0aVSxxW-gghsnCAs,6815
|
|
18
18
|
clonebox/rollback.py,sha256=hpwO-8Ehe1pW0wHuZvJkC_qxZ6yEo9otCJRhGIUArCo,5711
|
|
19
19
|
clonebox/secrets.py,sha256=QtGMIjhTKq14qQRGy-pX0TU3vKLiZt7CMloAlF2GmzQ,10515
|
|
20
|
-
clonebox/validator.py,sha256=
|
|
20
|
+
clonebox/validator.py,sha256=qKbT5dXka1Pts9GYqSt3ZEqF83gqdR0gFtFwOODWKvg,40918
|
|
21
21
|
clonebox/backends/libvirt_backend.py,sha256=sIHFIvFO1hIOXEFR_foSkOGBgIzaJVQs-njOU8GdafA,7170
|
|
22
22
|
clonebox/backends/qemu_disk.py,sha256=YsGjYX5sbEf35Y4yjTpNkZat73a4RGBxY-KTVzJhqIs,1687
|
|
23
23
|
clonebox/backends/subprocess_runner.py,sha256=c-IyaMxM1cmUu64h654oAvulm83K5Mu-VQxXJ_0BOds,1506
|
|
@@ -34,9 +34,9 @@ clonebox/snapshots/manager.py,sha256=hGzM8V6ZJPXjTqj47c4Kr8idlE-c1Q3gPUvuw1HvS1A
|
|
|
34
34
|
clonebox/snapshots/models.py,sha256=sRnn3OZE8JG9FZJlRuA3ihO-JXoPCQ3nD3SQytflAao,6206
|
|
35
35
|
clonebox/templates/profiles/ml-dev.yaml,sha256=w07MToGh31xtxpjbeXTBk9BkpAN8A3gv8HeA3ESKG9M,461
|
|
36
36
|
clonebox/templates/profiles/web-stack.yaml,sha256=EBnnGMzML5vAjXmIUbCpbTCwmRaNJiuWd3EcL43DOK8,485
|
|
37
|
-
clonebox-1.1.
|
|
38
|
-
clonebox-1.1.
|
|
39
|
-
clonebox-1.1.
|
|
40
|
-
clonebox-1.1.
|
|
41
|
-
clonebox-1.1.
|
|
42
|
-
clonebox-1.1.
|
|
37
|
+
clonebox-1.1.12.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
38
|
+
clonebox-1.1.12.dist-info/METADATA,sha256=VwCiMx2lcWX9XB9w89M16zRtQPuPhu1RW1KHdIxeJrA,48916
|
|
39
|
+
clonebox-1.1.12.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
40
|
+
clonebox-1.1.12.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
41
|
+
clonebox-1.1.12.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
42
|
+
clonebox-1.1.12.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|