clonebox 1.1.6__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 CHANGED
@@ -92,22 +92,31 @@ def _resolve_vm_name_and_config_file(name: Optional[str]) -> Tuple[str, Optional
92
92
 
93
93
  def _qga_ping(vm_name: str, conn_uri: str) -> bool:
94
94
  import subprocess
95
+ import json
96
+ import time
95
97
 
96
98
  try:
97
- result = subprocess.run(
98
- [
99
- "virsh",
100
- "--connect",
101
- conn_uri,
102
- "qemu-agent-command",
103
- vm_name,
104
- json.dumps({"execute": "guest-ping"}),
105
- ],
106
- capture_output=True,
107
- text=True,
108
- timeout=5,
109
- )
110
- return result.returncode == 0
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
111
120
  except Exception:
112
121
  return False
113
122
 
@@ -116,6 +125,7 @@ def _qga_exec(vm_name: str, conn_uri: str, command: str, timeout: int = 10) -> O
116
125
  import subprocess
117
126
  import base64
118
127
  import time
128
+ import json
119
129
 
120
130
  try:
121
131
  payload = {
@@ -1654,6 +1664,7 @@ def cmd_test(args):
1654
1664
  """Test VM configuration and health."""
1655
1665
  import subprocess
1656
1666
  import json
1667
+ import time
1657
1668
  from clonebox.validator import VMValidator
1658
1669
 
1659
1670
  name = args.name
@@ -1733,6 +1744,15 @@ def cmd_test(args):
1733
1744
  if state == "running":
1734
1745
  console.print("[green]✅ VM is running[/]")
1735
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
+
1736
1756
  # Test network if running
1737
1757
  console.print("\n Checking network...")
1738
1758
  try:
@@ -1751,11 +1771,7 @@ def cmd_test(args):
1751
1771
  else:
1752
1772
  console.print("[yellow]⚠️ No IP address detected via virsh domifaddr[/]")
1753
1773
  # Fallback: try to get IP via QEMU Guest Agent (useful for slirp/user networking)
1754
- try:
1755
- from .cli import _qga_ping, _qga_exec
1756
- except ImportError:
1757
- from clonebox.cli import _qga_ping, _qga_exec
1758
- if _qga_ping(vm_name, conn_uri):
1774
+ if qga_ready:
1759
1775
  try:
1760
1776
  ip_out = _qga_exec(
1761
1777
  vm_name,
@@ -1787,61 +1803,22 @@ def cmd_test(args):
1787
1803
  if not quick and state == "running":
1788
1804
  console.print("[bold]3. Cloud-init Status[/]")
1789
1805
  try:
1790
- # Try to get cloud-init status via QEMU guest agent
1791
- result = subprocess.run(
1792
- [
1793
- "virsh",
1794
- "--connect",
1795
- conn_uri,
1796
- "qemu-agent-command",
1797
- vm_name,
1798
- '{"execute":"guest-exec","arguments":{"path":"cloud-init","arg":["status"],"capture-output":true}}',
1799
- ],
1800
- capture_output=True,
1801
- text=True,
1802
- timeout=15,
1803
- )
1804
- if result.returncode == 0:
1805
- try:
1806
- response = json.loads(result.stdout)
1807
- if "return" in response:
1808
- pid = response["return"]["pid"]
1809
- # Get output
1810
- result2 = subprocess.run(
1811
- [
1812
- "virsh",
1813
- "--connect",
1814
- conn_uri,
1815
- "qemu-agent-command",
1816
- vm_name,
1817
- f'{{"execute":"guest-exec-status","arguments":{"pid":{pid}}}}',
1818
- ],
1819
- capture_output=True,
1820
- text=True,
1821
- timeout=15,
1822
- )
1823
- if result2.returncode == 0:
1824
- resp2 = json.loads(result2.stdout)
1825
- if "return" in resp2 and resp2["return"]["exited"]:
1826
- output = resp2["return"]["out-data"]
1827
- if output:
1828
- import base64
1829
-
1830
- status = base64.b64decode(output).decode()
1831
- if "done" in status.lower():
1832
- console.print("[green]✅ Cloud-init completed[/]")
1833
- elif "running" in status.lower():
1834
- console.print("[yellow]⚠️ Cloud-init still running[/]")
1835
- else:
1836
- console.print(
1837
- f"[yellow]⚠️ Cloud-init status: {status.strip()}[/]"
1838
- )
1839
- except:
1840
- pass
1841
- except:
1842
- console.print(
1843
- "[yellow]⚠️ Could not check cloud-init (QEMU agent may not be running)[/]"
1844
- )
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)[/]")
1845
1822
 
1846
1823
  console.print()
1847
1824
 
@@ -1876,25 +1853,31 @@ def cmd_test(args):
1876
1853
  if not quick and state == "running":
1877
1854
  console.print("[bold]5. Health Check[/]")
1878
1855
  try:
1879
- result = subprocess.run(
1880
- [
1881
- "virsh",
1882
- "--connect",
1883
- conn_uri,
1884
- "qemu-agent-command",
1885
- vm_name,
1886
- '{"execute":"guest-exec","arguments":{"path":"/usr/local/bin/clonebox-health","capture-output":true}}',
1887
- ],
1888
- capture_output=True,
1889
- text=True,
1890
- timeout=60,
1891
- )
1892
- if result.returncode == 0:
1893
- console.print("[green]✅ Health check triggered[/]")
1894
- 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[/]")
1895
1858
  else:
1896
- console.print("[yellow]⚠️ Health check script not found[/]")
1897
- console.print(" VM may not have been created with health checks")
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")
1898
1881
  except Exception as e:
1899
1882
  console.print(f"[yellow]⚠️ Could not run health check: {e}[/]")
1900
1883
 
@@ -2743,6 +2726,11 @@ def cmd_exec(args) -> None:
2743
2726
  vm_name, config_file = _resolve_vm_name_and_config_file(args.name)
2744
2727
  conn_uri = "qemu:///session" if getattr(args, "user", False) else "qemu:///system"
2745
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
2746
2734
  timeout = getattr(args, "timeout", 30)
2747
2735
 
2748
2736
  if not _qga_ping(vm_name, conn_uri):
@@ -3486,7 +3474,9 @@ def main():
3486
3474
  exec_parser.add_argument(
3487
3475
  "name", nargs="?", default=None, help="VM name or '.' to use .clonebox.yaml"
3488
3476
  )
3489
- exec_parser.add_argument("command", help="Command to execute in VM")
3477
+ exec_parser.add_argument(
3478
+ "command", nargs=argparse.REMAINDER, help="Command to execute in VM"
3479
+ )
3490
3480
  exec_parser.add_argument(
3491
3481
  "-u", "--user", action="store_true", help="Use user session (qemu:///session)"
3492
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(mount_commands) if mount_commands else []
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:
clonebox/validator.py CHANGED
@@ -728,7 +728,20 @@ class VMValidator:
728
728
  return None if out is None else out.strip() == "yes"
729
729
 
730
730
  def _run_test(app: str) -> Optional[bool]:
731
- user_env = f"sudo -u {vm_user} env HOME=/home/{vm_user} XDG_RUNTIME_DIR=/run/user/1000"
731
+ uid_out = self._exec_in_vm(f"id -u {vm_user} 2>/dev/null || true", timeout=10)
732
+ vm_uid = (uid_out or "").strip()
733
+ if not vm_uid.isdigit():
734
+ vm_uid = "1000"
735
+
736
+ runtime_dir = f"/run/user/{vm_uid}"
737
+ self._exec_in_vm(
738
+ f"mkdir -p {runtime_dir} && chown {vm_uid}:{vm_uid} {runtime_dir} && chmod 700 {runtime_dir}",
739
+ timeout=10,
740
+ )
741
+
742
+ user_env = (
743
+ f"sudo -u {vm_user} env HOME=/home/{vm_user} USER={vm_user} LOGNAME={vm_user} XDG_RUNTIME_DIR={runtime_dir}"
744
+ )
732
745
 
733
746
  if app == "pycharm-community":
734
747
  out = self._exec_in_vm(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 1.1.6
3
+ Version: 1.1.8
4
4
  Summary: Clone your workstation environment to an isolated VM with selective apps, paths and services
5
5
  Author: CloneBox Team
6
6
  License: Apache-2.0
@@ -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=uVACDtbGh0bNcZY3p2s5W90aU6NQHzuTnTLelWCOlss,136330
4
- clonebox/cloner.py,sha256=aH7BwmLgPITHzyHn_n_AKqtEADDm_aUXiRG-nB2rxMQ,90063
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
@@ -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=2yQXDaL5bzHmxKkEP0fGJ85HV7Ax3iQl7KzSlBVLZkc,38220
20
+ clonebox/validator.py,sha256=zX_E0X3IOZ8Dnmn8i9IVXViYXw-9Nyj0L_YyCKYRj9I,38722
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.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
38
- clonebox-1.1.6.dist-info/METADATA,sha256=36_PJreaPh-6Ot5Irm6NSdFjzwGkAGZGHBs4TYcB9lQ,48915
39
- clonebox-1.1.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
40
- clonebox-1.1.6.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
41
- clonebox-1.1.6.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
42
- clonebox-1.1.6.dist-info/RECORD,,
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,,