clonebox 1.1.7__py3-none-any.whl → 1.1.8__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 +81 -93
- clonebox/cloner.py +22 -4
- {clonebox-1.1.7.dist-info → clonebox-1.1.8.dist-info}/METADATA +1 -1
- {clonebox-1.1.7.dist-info → clonebox-1.1.8.dist-info}/RECORD +8 -8
- {clonebox-1.1.7.dist-info → clonebox-1.1.8.dist-info}/WHEEL +0 -0
- {clonebox-1.1.7.dist-info → clonebox-1.1.8.dist-info}/entry_points.txt +0 -0
- {clonebox-1.1.7.dist-info → clonebox-1.1.8.dist-info}/licenses/LICENSE +0 -0
- {clonebox-1.1.7.dist-info → clonebox-1.1.8.dist-info}/top_level.txt +0 -0
clonebox/cli.py
CHANGED
|
@@ -93,22 +93,30 @@ def _resolve_vm_name_and_config_file(name: Optional[str]) -> Tuple[str, Optional
|
|
|
93
93
|
def _qga_ping(vm_name: str, conn_uri: str) -> bool:
|
|
94
94
|
import subprocess
|
|
95
95
|
import json
|
|
96
|
+
import time
|
|
96
97
|
|
|
97
98
|
try:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
99
|
+
for _ in range(5):
|
|
100
|
+
try:
|
|
101
|
+
result = subprocess.run(
|
|
102
|
+
[
|
|
103
|
+
"virsh",
|
|
104
|
+
"--connect",
|
|
105
|
+
conn_uri,
|
|
106
|
+
"qemu-agent-command",
|
|
107
|
+
vm_name,
|
|
108
|
+
json.dumps({"execute": "guest-ping"}),
|
|
109
|
+
],
|
|
110
|
+
capture_output=True,
|
|
111
|
+
text=True,
|
|
112
|
+
timeout=5,
|
|
113
|
+
)
|
|
114
|
+
if result.returncode == 0:
|
|
115
|
+
return True
|
|
116
|
+
except subprocess.TimeoutExpired:
|
|
117
|
+
pass
|
|
118
|
+
time.sleep(1)
|
|
119
|
+
return False
|
|
112
120
|
except Exception:
|
|
113
121
|
return False
|
|
114
122
|
|
|
@@ -1656,6 +1664,7 @@ def cmd_test(args):
|
|
|
1656
1664
|
"""Test VM configuration and health."""
|
|
1657
1665
|
import subprocess
|
|
1658
1666
|
import json
|
|
1667
|
+
import time
|
|
1659
1668
|
from clonebox.validator import VMValidator
|
|
1660
1669
|
|
|
1661
1670
|
name = args.name
|
|
@@ -1735,6 +1744,15 @@ def cmd_test(args):
|
|
|
1735
1744
|
if state == "running":
|
|
1736
1745
|
console.print("[green]✅ VM is running[/]")
|
|
1737
1746
|
|
|
1747
|
+
# Give QEMU Guest Agent some time to come up (common during early boot)
|
|
1748
|
+
qga_ready = _qga_ping(vm_name, conn_uri)
|
|
1749
|
+
if not qga_ready:
|
|
1750
|
+
for _ in range(12): # ~60s
|
|
1751
|
+
time.sleep(5)
|
|
1752
|
+
qga_ready = _qga_ping(vm_name, conn_uri)
|
|
1753
|
+
if qga_ready:
|
|
1754
|
+
break
|
|
1755
|
+
|
|
1738
1756
|
# Test network if running
|
|
1739
1757
|
console.print("\n Checking network...")
|
|
1740
1758
|
try:
|
|
@@ -1753,11 +1771,7 @@ def cmd_test(args):
|
|
|
1753
1771
|
else:
|
|
1754
1772
|
console.print("[yellow]⚠️ No IP address detected via virsh domifaddr[/]")
|
|
1755
1773
|
# Fallback: try to get IP via QEMU Guest Agent (useful for slirp/user networking)
|
|
1756
|
-
|
|
1757
|
-
from .cli import _qga_ping, _qga_exec
|
|
1758
|
-
except ImportError:
|
|
1759
|
-
from clonebox.cli import _qga_ping, _qga_exec
|
|
1760
|
-
if _qga_ping(vm_name, conn_uri):
|
|
1774
|
+
if qga_ready:
|
|
1761
1775
|
try:
|
|
1762
1776
|
ip_out = _qga_exec(
|
|
1763
1777
|
vm_name,
|
|
@@ -1789,61 +1803,22 @@ def cmd_test(args):
|
|
|
1789
1803
|
if not quick and state == "running":
|
|
1790
1804
|
console.print("[bold]3. Cloud-init Status[/]")
|
|
1791
1805
|
try:
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
response = json.loads(result.stdout)
|
|
1809
|
-
if "return" in response:
|
|
1810
|
-
pid = response["return"]["pid"]
|
|
1811
|
-
# Get output
|
|
1812
|
-
result2 = subprocess.run(
|
|
1813
|
-
[
|
|
1814
|
-
"virsh",
|
|
1815
|
-
"--connect",
|
|
1816
|
-
conn_uri,
|
|
1817
|
-
"qemu-agent-command",
|
|
1818
|
-
vm_name,
|
|
1819
|
-
f'{{"execute":"guest-exec-status","arguments":{"pid":{pid}}}}',
|
|
1820
|
-
],
|
|
1821
|
-
capture_output=True,
|
|
1822
|
-
text=True,
|
|
1823
|
-
timeout=15,
|
|
1824
|
-
)
|
|
1825
|
-
if result2.returncode == 0:
|
|
1826
|
-
resp2 = json.loads(result2.stdout)
|
|
1827
|
-
if "return" in resp2 and resp2["return"]["exited"]:
|
|
1828
|
-
output = resp2["return"]["out-data"]
|
|
1829
|
-
if output:
|
|
1830
|
-
import base64
|
|
1831
|
-
|
|
1832
|
-
status = base64.b64decode(output).decode()
|
|
1833
|
-
if "done" in status.lower():
|
|
1834
|
-
console.print("[green]✅ Cloud-init completed[/]")
|
|
1835
|
-
elif "running" in status.lower():
|
|
1836
|
-
console.print("[yellow]⚠️ Cloud-init still running[/]")
|
|
1837
|
-
else:
|
|
1838
|
-
console.print(
|
|
1839
|
-
f"[yellow]⚠️ Cloud-init status: {status.strip()}[/]"
|
|
1840
|
-
)
|
|
1841
|
-
except:
|
|
1842
|
-
pass
|
|
1843
|
-
except:
|
|
1844
|
-
console.print(
|
|
1845
|
-
"[yellow]⚠️ Could not check cloud-init (QEMU agent may not be running)[/]"
|
|
1846
|
-
)
|
|
1806
|
+
if not qga_ready:
|
|
1807
|
+
console.print("[yellow]⚠️ Cloud-init status unknown (QEMU Guest Agent not connected)[/]")
|
|
1808
|
+
else:
|
|
1809
|
+
status = _qga_exec(vm_name, conn_uri, "cloud-init status 2>/dev/null || true", timeout=15)
|
|
1810
|
+
if status is None:
|
|
1811
|
+
console.print("[yellow]⚠️ Could not check cloud-init (QGA command failed)[/]")
|
|
1812
|
+
elif "done" in status.lower():
|
|
1813
|
+
console.print("[green]✅ Cloud-init completed[/]")
|
|
1814
|
+
elif "running" in status.lower():
|
|
1815
|
+
console.print("[yellow]⚠️ Cloud-init still running[/]")
|
|
1816
|
+
elif status.strip():
|
|
1817
|
+
console.print(f"[yellow]⚠️ Cloud-init status: {status.strip()}[/]")
|
|
1818
|
+
else:
|
|
1819
|
+
console.print("[yellow]⚠️ Cloud-init status: unknown[/]")
|
|
1820
|
+
except Exception:
|
|
1821
|
+
console.print("[yellow]⚠️ Could not check cloud-init (QEMU agent may not be running)[/]")
|
|
1847
1822
|
|
|
1848
1823
|
console.print()
|
|
1849
1824
|
|
|
@@ -1878,25 +1853,31 @@ def cmd_test(args):
|
|
|
1878
1853
|
if not quick and state == "running":
|
|
1879
1854
|
console.print("[bold]5. Health Check[/]")
|
|
1880
1855
|
try:
|
|
1881
|
-
|
|
1882
|
-
[
|
|
1883
|
-
"virsh",
|
|
1884
|
-
"--connect",
|
|
1885
|
-
conn_uri,
|
|
1886
|
-
"qemu-agent-command",
|
|
1887
|
-
vm_name,
|
|
1888
|
-
'{"execute":"guest-exec","arguments":{"path":"/usr/local/bin/clonebox-health","capture-output":true}}',
|
|
1889
|
-
],
|
|
1890
|
-
capture_output=True,
|
|
1891
|
-
text=True,
|
|
1892
|
-
timeout=60,
|
|
1893
|
-
)
|
|
1894
|
-
if result.returncode == 0:
|
|
1895
|
-
console.print("[green]✅ Health check triggered[/]")
|
|
1896
|
-
console.print(" View results in VM: cat /var/log/clonebox-health.log")
|
|
1856
|
+
if not qga_ready:
|
|
1857
|
+
console.print("[yellow]⚠️ QEMU Guest Agent not connected - cannot run health check[/]")
|
|
1897
1858
|
else:
|
|
1898
|
-
|
|
1899
|
-
|
|
1859
|
+
exists = _qga_exec(
|
|
1860
|
+
vm_name,
|
|
1861
|
+
conn_uri,
|
|
1862
|
+
"test -x /usr/local/bin/clonebox-health && echo yes || echo no",
|
|
1863
|
+
timeout=10,
|
|
1864
|
+
)
|
|
1865
|
+
if exists and exists.strip() == "yes":
|
|
1866
|
+
out = _qga_exec(
|
|
1867
|
+
vm_name,
|
|
1868
|
+
conn_uri,
|
|
1869
|
+
"/usr/local/bin/clonebox-health >/dev/null 2>&1 && echo yes || echo no",
|
|
1870
|
+
timeout=60,
|
|
1871
|
+
)
|
|
1872
|
+
if out and out.strip() == "yes":
|
|
1873
|
+
console.print("[green]✅ Health check ran successfully[/]")
|
|
1874
|
+
console.print(" View results in VM: cat /var/log/clonebox-health.log")
|
|
1875
|
+
else:
|
|
1876
|
+
console.print("[yellow]⚠️ Health check did not report success[/]")
|
|
1877
|
+
console.print(" View logs in VM: cat /var/log/clonebox-health.log")
|
|
1878
|
+
else:
|
|
1879
|
+
console.print("[yellow]⚠️ Health check script not found[/]")
|
|
1880
|
+
console.print(" This is expected until cloud-init completes")
|
|
1900
1881
|
except Exception as e:
|
|
1901
1882
|
console.print(f"[yellow]⚠️ Could not run health check: {e}[/]")
|
|
1902
1883
|
|
|
@@ -2745,6 +2726,11 @@ def cmd_exec(args) -> None:
|
|
|
2745
2726
|
vm_name, config_file = _resolve_vm_name_and_config_file(args.name)
|
|
2746
2727
|
conn_uri = "qemu:///session" if getattr(args, "user", False) else "qemu:///system"
|
|
2747
2728
|
command = args.command
|
|
2729
|
+
if isinstance(command, list):
|
|
2730
|
+
command = " ".join(command).strip()
|
|
2731
|
+
if not command:
|
|
2732
|
+
console.print("[red]❌ No command specified[/]")
|
|
2733
|
+
return
|
|
2748
2734
|
timeout = getattr(args, "timeout", 30)
|
|
2749
2735
|
|
|
2750
2736
|
if not _qga_ping(vm_name, conn_uri):
|
|
@@ -3488,7 +3474,9 @@ def main():
|
|
|
3488
3474
|
exec_parser.add_argument(
|
|
3489
3475
|
"name", nargs="?", default=None, help="VM name or '.' to use .clonebox.yaml"
|
|
3490
3476
|
)
|
|
3491
|
-
exec_parser.add_argument(
|
|
3477
|
+
exec_parser.add_argument(
|
|
3478
|
+
"command", nargs=argparse.REMAINDER, help="Command to execute in VM"
|
|
3479
|
+
)
|
|
3492
3480
|
exec_parser.add_argument(
|
|
3493
3481
|
"-u", "--user", action="store_true", help="Use user session (qemu:///session)"
|
|
3494
3482
|
)
|
clonebox/cloner.py
CHANGED
|
@@ -623,8 +623,11 @@ class SelectiveVMCloner:
|
|
|
623
623
|
ET.SubElement(devices, "input", type="tablet", bus="usb")
|
|
624
624
|
ET.SubElement(devices, "input", type="keyboard", bus="usb")
|
|
625
625
|
|
|
626
|
+
ET.SubElement(devices, "controller", type="virtio-serial", index="0")
|
|
627
|
+
|
|
626
628
|
# Channel for guest agent
|
|
627
629
|
channel = ET.SubElement(devices, "channel", type="unix")
|
|
630
|
+
ET.SubElement(channel, "source", mode="bind")
|
|
628
631
|
ET.SubElement(channel, "target", type="virtio", name="org.qemu.guest_agent.0")
|
|
629
632
|
|
|
630
633
|
# Memory balloon
|
|
@@ -1254,8 +1257,22 @@ fi
|
|
|
1254
1257
|
mount_commands = []
|
|
1255
1258
|
fstab_entries = []
|
|
1256
1259
|
all_paths = dict(config.paths) if config.paths else {}
|
|
1260
|
+
pre_chown_dirs: set[str] = set()
|
|
1257
1261
|
for idx, (host_path, guest_path) in enumerate(all_paths.items()):
|
|
1258
1262
|
if Path(host_path).exists():
|
|
1263
|
+
if str(guest_path).startswith("/home/ubuntu/snap/"):
|
|
1264
|
+
guest_parts = Path(guest_path).parts
|
|
1265
|
+
if len(guest_parts) > 4:
|
|
1266
|
+
snap_name = guest_parts[4]
|
|
1267
|
+
for d in (
|
|
1268
|
+
"/home/ubuntu",
|
|
1269
|
+
"/home/ubuntu/snap",
|
|
1270
|
+
f"/home/ubuntu/snap/{snap_name}",
|
|
1271
|
+
):
|
|
1272
|
+
if d not in pre_chown_dirs:
|
|
1273
|
+
pre_chown_dirs.add(d)
|
|
1274
|
+
mount_commands.append(f" - mkdir -p {d}")
|
|
1275
|
+
mount_commands.append(f" - chown 1000:1000 {d}")
|
|
1259
1276
|
tag = f"mount{idx}"
|
|
1260
1277
|
# Use uid=1000,gid=1000 to give ubuntu user access to mounts
|
|
1261
1278
|
# mmap allows proper file mapping
|
|
@@ -1306,6 +1323,9 @@ fi
|
|
|
1306
1323
|
for cmd in mount_commands:
|
|
1307
1324
|
runcmd_lines.append(cmd)
|
|
1308
1325
|
|
|
1326
|
+
runcmd_lines.append(" - chown -R 1000:1000 /home/ubuntu || true")
|
|
1327
|
+
runcmd_lines.append(" - chown -R 1000:1000 /home/ubuntu/snap || true")
|
|
1328
|
+
|
|
1309
1329
|
# Install snap packages
|
|
1310
1330
|
if config.snap_packages:
|
|
1311
1331
|
runcmd_lines.append(" - echo 'Installing snap packages...'")
|
|
@@ -2146,7 +2166,7 @@ if __name__ == "__main__":
|
|
|
2146
2166
|
" - cp -r /var/log/clonebox*.log /mnt/logs/var/log/ 2>/dev/null || true",
|
|
2147
2167
|
" - cp -r /tmp/*-error.log /mnt/logs/tmp/ 2>/dev/null || true",
|
|
2148
2168
|
" - echo 'Logs disk mounted at /mnt/logs - accessible from host as /var/lib/libvirt/images/clonebox-logs.qcow2'",
|
|
2149
|
-
" - echo 'To view logs on host: sudo mount -o loop /var/lib/libvirt/images/clonebox-logs.qcow2 /mnt/clonebox-logs'",
|
|
2169
|
+
" - \"echo 'To view logs on host: sudo mount -o loop /var/lib/libvirt/images/clonebox-logs.qcow2 /mnt/clonebox-logs'\"",
|
|
2150
2170
|
]
|
|
2151
2171
|
)
|
|
2152
2172
|
|
|
@@ -2158,9 +2178,7 @@ if __name__ == "__main__":
|
|
|
2158
2178
|
runcmd_yaml = "\n".join(runcmd_lines) if runcmd_lines else ""
|
|
2159
2179
|
|
|
2160
2180
|
# Build bootcmd combining mount commands and extra security bootcmds
|
|
2161
|
-
bootcmd_lines = list(
|
|
2162
|
-
if bootcmd_extra:
|
|
2163
|
-
bootcmd_lines.extend(bootcmd_extra)
|
|
2181
|
+
bootcmd_lines = list(bootcmd_extra) if bootcmd_extra else []
|
|
2164
2182
|
|
|
2165
2183
|
bootcmd_block = ""
|
|
2166
2184
|
if bootcmd_lines:
|
|
@@ -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=AIXIeLhBBU6Aol1uLLcb0LFoqH2b3o0CDQ1HbEOSZZs,136002
|
|
4
|
+
clonebox/cloner.py,sha256=temlahcPoq6nQWj69dQ4QfbwTa7d1GfmRxcpMFeUnEs,91027
|
|
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
|
|
@@ -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.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
38
|
+
clonebox-1.1.8.dist-info/METADATA,sha256=VHbu93SAsVHagIjmTFvna5Iwd3nDkTH4oaPC442cn_Y,48915
|
|
39
|
+
clonebox-1.1.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
40
|
+
clonebox-1.1.8.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
41
|
+
clonebox-1.1.8.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
42
|
+
clonebox-1.1.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|