clonebox 1.1.9__py3-none-any.whl → 1.1.10__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.
- clonebox/cli.py +54 -5
- clonebox/cloner.py +48 -33
- clonebox/validator.py +17 -0
- {clonebox-1.1.9.dist-info → clonebox-1.1.10.dist-info}/METADATA +1 -1
- {clonebox-1.1.9.dist-info → clonebox-1.1.10.dist-info}/RECORD +9 -9
- {clonebox-1.1.9.dist-info → clonebox-1.1.10.dist-info}/WHEEL +0 -0
- {clonebox-1.1.9.dist-info → clonebox-1.1.10.dist-info}/entry_points.txt +0 -0
- {clonebox-1.1.9.dist-info → clonebox-1.1.10.dist-info}/licenses/LICENSE +0 -0
- {clonebox-1.1.9.dist-info → clonebox-1.1.10.dist-info}/top_level.txt +0 -0
clonebox/cli.py
CHANGED
|
@@ -2724,20 +2724,69 @@ def cmd_monitor(args) -> None:
|
|
|
2724
2724
|
def cmd_exec(args) -> None:
|
|
2725
2725
|
"""Execute command in VM via QEMU Guest Agent."""
|
|
2726
2726
|
vm_name, config_file = _resolve_vm_name_and_config_file(args.name)
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2727
|
+
user_session = getattr(args, "user", False)
|
|
2728
|
+
timeout = getattr(args, "timeout", 30)
|
|
2729
|
+
|
|
2730
|
+
# When using argparse.REMAINDER for `command`, any flags placed after the VM name
|
|
2731
|
+
# may end up inside args.command. Recover common exec flags from the remainder.
|
|
2732
|
+
command_tokens = args.command
|
|
2733
|
+
if not isinstance(command_tokens, list):
|
|
2734
|
+
command_tokens = [str(command_tokens)] if command_tokens is not None else []
|
|
2735
|
+
|
|
2736
|
+
if "--" in command_tokens:
|
|
2737
|
+
sep_idx = command_tokens.index("--")
|
|
2738
|
+
pre_tokens = command_tokens[:sep_idx]
|
|
2739
|
+
post_tokens = command_tokens[sep_idx + 1 :]
|
|
2740
|
+
else:
|
|
2741
|
+
pre_tokens = command_tokens
|
|
2742
|
+
post_tokens = []
|
|
2743
|
+
|
|
2744
|
+
i = 0
|
|
2745
|
+
while i < len(pre_tokens):
|
|
2746
|
+
tok = pre_tokens[i]
|
|
2747
|
+
if tok in ("-u", "--user"):
|
|
2748
|
+
user_session = True
|
|
2749
|
+
i += 1
|
|
2750
|
+
continue
|
|
2751
|
+
if tok in ("-t", "--timeout"):
|
|
2752
|
+
if i + 1 < len(pre_tokens):
|
|
2753
|
+
try:
|
|
2754
|
+
timeout = int(pre_tokens[i + 1])
|
|
2755
|
+
except ValueError:
|
|
2756
|
+
pass
|
|
2757
|
+
i += 2
|
|
2758
|
+
continue
|
|
2759
|
+
break
|
|
2760
|
+
|
|
2761
|
+
remaining_pre = pre_tokens[i:]
|
|
2762
|
+
if post_tokens:
|
|
2763
|
+
command_tokens = remaining_pre + post_tokens
|
|
2764
|
+
else:
|
|
2765
|
+
command_tokens = remaining_pre
|
|
2766
|
+
|
|
2767
|
+
command = " ".join(command_tokens).strip()
|
|
2731
2768
|
if not command:
|
|
2732
2769
|
console.print("[red]❌ No command specified[/]")
|
|
2733
2770
|
return
|
|
2734
|
-
|
|
2771
|
+
|
|
2772
|
+
conn_uri = "qemu:///session" if user_session else "qemu:///system"
|
|
2773
|
+
other_conn_uri = "qemu:///system" if conn_uri == "qemu:///session" else "qemu:///session"
|
|
2735
2774
|
|
|
2736
2775
|
qga_ready = _qga_ping(vm_name, conn_uri)
|
|
2776
|
+
if not qga_ready:
|
|
2777
|
+
alt_ready = _qga_ping(vm_name, other_conn_uri)
|
|
2778
|
+
if alt_ready:
|
|
2779
|
+
conn_uri = other_conn_uri
|
|
2780
|
+
qga_ready = True
|
|
2737
2781
|
if not qga_ready:
|
|
2738
2782
|
for _ in range(12): # ~60s
|
|
2739
2783
|
time.sleep(5)
|
|
2740
2784
|
qga_ready = _qga_ping(vm_name, conn_uri)
|
|
2785
|
+
if not qga_ready:
|
|
2786
|
+
alt_ready = _qga_ping(vm_name, other_conn_uri)
|
|
2787
|
+
if alt_ready:
|
|
2788
|
+
conn_uri = other_conn_uri
|
|
2789
|
+
qga_ready = True
|
|
2741
2790
|
if qga_ready:
|
|
2742
2791
|
break
|
|
2743
2792
|
|
clonebox/cloner.py
CHANGED
|
@@ -1267,25 +1267,33 @@ fi
|
|
|
1267
1267
|
pre_chown_dirs: set[str] = set()
|
|
1268
1268
|
for idx, (host_path, guest_path) in enumerate(all_paths.items()):
|
|
1269
1269
|
if Path(host_path).exists():
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
mount_commands.append(f" -
|
|
1270
|
+
# Ensure all parent directories in /home/ubuntu are owned by user
|
|
1271
|
+
# This prevents "Permission denied" when creating config dirs (e.g. .config) as root
|
|
1272
|
+
if str(guest_path).startswith("/home/ubuntu/"):
|
|
1273
|
+
try:
|
|
1274
|
+
rel_path = Path(guest_path).relative_to("/home/ubuntu")
|
|
1275
|
+
current = Path("/home/ubuntu")
|
|
1276
|
+
# Create and chown each component in the path
|
|
1277
|
+
for part in rel_path.parts:
|
|
1278
|
+
current = current / part
|
|
1279
|
+
d_str = str(current)
|
|
1280
|
+
if d_str not in pre_chown_dirs:
|
|
1281
|
+
pre_chown_dirs.add(d_str)
|
|
1282
|
+
mount_commands.append(f" - mkdir -p {d_str}")
|
|
1283
|
+
mount_commands.append(f" - chown 1000:1000 {d_str}")
|
|
1284
|
+
except ValueError:
|
|
1285
|
+
pass
|
|
1286
|
+
|
|
1283
1287
|
tag = f"mount{idx}"
|
|
1284
1288
|
# Use uid=1000,gid=1000 to give ubuntu user access to mounts
|
|
1285
1289
|
# mmap allows proper file mapping
|
|
1286
1290
|
mount_opts = "trans=virtio,version=9p2000.L,mmap,uid=1000,gid=1000,users"
|
|
1287
|
-
|
|
1288
|
-
|
|
1291
|
+
|
|
1292
|
+
# Ensure target exists and is owned by user (if not already handled)
|
|
1293
|
+
if str(guest_path) not in pre_chown_dirs:
|
|
1294
|
+
mount_commands.append(f" - mkdir -p {guest_path}")
|
|
1295
|
+
mount_commands.append(f" - chown 1000:1000 {guest_path}")
|
|
1296
|
+
|
|
1289
1297
|
mount_commands.append(f" - mount -t 9p -o {mount_opts} {tag} {guest_path} || true")
|
|
1290
1298
|
# Add fstab entry for persistence after reboot
|
|
1291
1299
|
fstab_entries.append(f"{tag} {guest_path} 9p {mount_opts},nofail 0 0")
|
|
@@ -1330,16 +1338,37 @@ fi
|
|
|
1330
1338
|
for cmd in mount_commands:
|
|
1331
1339
|
runcmd_lines.append(cmd)
|
|
1332
1340
|
|
|
1341
|
+
# Create user directories with correct permissions EARLY to avoid race conditions with GDM
|
|
1342
|
+
if config.gui:
|
|
1343
|
+
# Create directories that GNOME services need
|
|
1344
|
+
runcmd_lines.extend(
|
|
1345
|
+
[
|
|
1346
|
+
" - mkdir -p /home/ubuntu/.config/pulse /home/ubuntu/.cache/ibus /home/ubuntu/.local/share",
|
|
1347
|
+
" - mkdir -p /home/ubuntu/.config/dconf /home/ubuntu/.cache/tracker3",
|
|
1348
|
+
" - mkdir -p /home/ubuntu/.config/autostart",
|
|
1349
|
+
" - chown -R 1000:1000 /home/ubuntu/.config /home/ubuntu/.cache /home/ubuntu/.local",
|
|
1350
|
+
" - chmod 700 /home/ubuntu/.config /home/ubuntu/.cache",
|
|
1351
|
+
" - systemctl set-default graphical.target",
|
|
1352
|
+
" - systemctl enable gdm3 || systemctl enable gdm || true",
|
|
1353
|
+
]
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1333
1356
|
runcmd_lines.append(" - chown -R 1000:1000 /home/ubuntu || true")
|
|
1334
1357
|
runcmd_lines.append(" - chown -R 1000:1000 /home/ubuntu/snap || true")
|
|
1335
1358
|
|
|
1336
|
-
# Install snap packages
|
|
1359
|
+
# Install snap packages (with retry logic)
|
|
1337
1360
|
if config.snap_packages:
|
|
1338
1361
|
runcmd_lines.append(" - echo 'Installing snap packages...'")
|
|
1339
1362
|
for snap_pkg in config.snap_packages:
|
|
1340
|
-
|
|
1341
|
-
|
|
1363
|
+
# Try classic first, then strict, with retries
|
|
1364
|
+
cmd = (
|
|
1365
|
+
f"for i in 1 2 3; do "
|
|
1366
|
+
f"snap install {snap_pkg} --classic && break || "
|
|
1367
|
+
f"snap install {snap_pkg} && break || "
|
|
1368
|
+
f"sleep 10; "
|
|
1369
|
+
f"done"
|
|
1342
1370
|
)
|
|
1371
|
+
runcmd_lines.append(f" - {cmd}")
|
|
1343
1372
|
|
|
1344
1373
|
# Connect snap interfaces for GUI apps (not auto-connected via cloud-init)
|
|
1345
1374
|
runcmd_lines.append(" - echo 'Connecting snap interfaces...'")
|
|
@@ -1352,22 +1381,8 @@ fi
|
|
|
1352
1381
|
|
|
1353
1382
|
runcmd_lines.append(" - systemctl restart snapd || true")
|
|
1354
1383
|
|
|
1355
|
-
# Add GUI setup if enabled
|
|
1384
|
+
# Add remaining GUI setup if enabled
|
|
1356
1385
|
if config.gui:
|
|
1357
|
-
# Create directories that GNOME services need BEFORE GUI starts
|
|
1358
|
-
# These may conflict with mounted host directories, so ensure they exist with correct perms
|
|
1359
|
-
runcmd_lines.extend(
|
|
1360
|
-
[
|
|
1361
|
-
" - mkdir -p /home/ubuntu/.config/pulse /home/ubuntu/.cache/ibus /home/ubuntu/.local/share",
|
|
1362
|
-
" - mkdir -p /home/ubuntu/.config/dconf /home/ubuntu/.cache/tracker3",
|
|
1363
|
-
" - mkdir -p /home/ubuntu/.config/autostart",
|
|
1364
|
-
" - chown -R 1000:1000 /home/ubuntu/.config /home/ubuntu/.cache /home/ubuntu/.local",
|
|
1365
|
-
" - chmod 700 /home/ubuntu/.config /home/ubuntu/.cache",
|
|
1366
|
-
" - systemctl set-default graphical.target",
|
|
1367
|
-
" - systemctl enable gdm3 || systemctl enable gdm || true",
|
|
1368
|
-
]
|
|
1369
|
-
)
|
|
1370
|
-
|
|
1371
1386
|
# Create autostart entries for GUI apps
|
|
1372
1387
|
autostart_apps = {
|
|
1373
1388
|
"pycharm-community": (
|
clonebox/validator.py
CHANGED
|
@@ -897,6 +897,23 @@ class VMValidator:
|
|
|
897
897
|
self.results["overall"] = "qga_not_ready"
|
|
898
898
|
return self.results
|
|
899
899
|
|
|
900
|
+
ci_status = self._exec_in_vm("cloud-init status --long 2>/dev/null || cloud-init status 2>/dev/null || true", timeout=20)
|
|
901
|
+
if ci_status:
|
|
902
|
+
ci_lower = ci_status.lower()
|
|
903
|
+
if "running" in ci_lower:
|
|
904
|
+
self.console.print("[yellow]⏳ Cloud-init still running - skipping deep validation for now[/]")
|
|
905
|
+
self.results["overall"] = "cloud_init_running"
|
|
906
|
+
return self.results
|
|
907
|
+
|
|
908
|
+
ready_msg = self._exec_in_vm(
|
|
909
|
+
"cat /var/log/clonebox-ready 2>/dev/null || true",
|
|
910
|
+
timeout=10,
|
|
911
|
+
)
|
|
912
|
+
if not (ready_msg and "clonebox vm ready" in ready_msg.lower()):
|
|
913
|
+
self.console.print(
|
|
914
|
+
"[yellow]⚠️ CloneBox ready marker not found - provisioning may not have completed[/]"
|
|
915
|
+
)
|
|
916
|
+
|
|
900
917
|
# Run all validations
|
|
901
918
|
self.validate_mounts()
|
|
902
919
|
self.validate_packages()
|
|
@@ -1,7 +1,7 @@
|
|
|
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=6aaIMefXxoRQgzlQDOqKFHq7e8U0CIH5TuYKrsobhH4,137848
|
|
4
|
+
clonebox/cloner.py,sha256=gUpv0VIqpkmEZPhafyYZOttpd8ZWh9_kTTfk0cCULEg,91937
|
|
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
|
|
@@ -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=pxW1Np3s-T2TFvJ64BiO-N8lcKnJGzwInu5cPC9lBK0,39528
|
|
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.10.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
38
|
+
clonebox-1.1.10.dist-info/METADATA,sha256=nRh_SZ7TDLcmP5gJPPBXwdyq_VRpaI_nSfbsDeYjL9E,48916
|
|
39
|
+
clonebox-1.1.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
40
|
+
clonebox-1.1.10.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
41
|
+
clonebox-1.1.10.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
42
|
+
clonebox-1.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|