clonebox 1.1.7__tar.gz → 1.1.8__tar.gz

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.

Files changed (59) hide show
  1. {clonebox-1.1.7/src/clonebox.egg-info → clonebox-1.1.8}/PKG-INFO +1 -1
  2. {clonebox-1.1.7 → clonebox-1.1.8}/pyproject.toml +1 -1
  3. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/cli.py +81 -93
  4. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/cloner.py +22 -4
  5. {clonebox-1.1.7 → clonebox-1.1.8/src/clonebox.egg-info}/PKG-INFO +1 -1
  6. {clonebox-1.1.7 → clonebox-1.1.8}/LICENSE +0 -0
  7. {clonebox-1.1.7 → clonebox-1.1.8}/README.md +0 -0
  8. {clonebox-1.1.7 → clonebox-1.1.8}/setup.cfg +0 -0
  9. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/__init__.py +0 -0
  10. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/__main__.py +0 -0
  11. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/backends/libvirt_backend.py +0 -0
  12. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/backends/qemu_disk.py +0 -0
  13. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/backends/subprocess_runner.py +0 -0
  14. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/container.py +0 -0
  15. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/dashboard.py +0 -0
  16. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/detector.py +0 -0
  17. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/di.py +0 -0
  18. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/exporter.py +0 -0
  19. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/health/__init__.py +0 -0
  20. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/health/manager.py +0 -0
  21. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/health/models.py +0 -0
  22. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/health/probes.py +0 -0
  23. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/importer.py +0 -0
  24. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/interfaces/disk.py +0 -0
  25. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/interfaces/hypervisor.py +0 -0
  26. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/interfaces/network.py +0 -0
  27. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/interfaces/process.py +0 -0
  28. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/logging.py +0 -0
  29. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/models.py +0 -0
  30. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/monitor.py +0 -0
  31. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/p2p.py +0 -0
  32. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/profiles.py +0 -0
  33. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/resource_monitor.py +0 -0
  34. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/resources.py +0 -0
  35. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/rollback.py +0 -0
  36. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/secrets.py +0 -0
  37. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/snapshots/__init__.py +0 -0
  38. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/snapshots/manager.py +0 -0
  39. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/snapshots/models.py +0 -0
  40. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/templates/profiles/ml-dev.yaml +0 -0
  41. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/templates/profiles/web-stack.yaml +0 -0
  42. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox/validator.py +0 -0
  43. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox.egg-info/SOURCES.txt +0 -0
  44. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox.egg-info/dependency_links.txt +0 -0
  45. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox.egg-info/entry_points.txt +0 -0
  46. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox.egg-info/requires.txt +0 -0
  47. {clonebox-1.1.7 → clonebox-1.1.8}/src/clonebox.egg-info/top_level.txt +0 -0
  48. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_cli.py +0 -0
  49. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_cloner.py +0 -0
  50. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_cloner_simple.py +0 -0
  51. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_container.py +0 -0
  52. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_coverage_additional.py +0 -0
  53. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_coverage_boost_final.py +0 -0
  54. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_dashboard_coverage.py +0 -0
  55. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_detector.py +0 -0
  56. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_models.py +0 -0
  57. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_network.py +0 -0
  58. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_profiles.py +0 -0
  59. {clonebox-1.1.7 → clonebox-1.1.8}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 1.1.7
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "clonebox"
7
- version = "1.1.7"
7
+ version = "1.1.8"
8
8
  description = "Clone your workstation environment to an isolated VM with selective apps, paths and services"
9
9
  readme = "README.md"
10
10
  license = {text = "Apache-2.0"}
@@ -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
- result = subprocess.run(
99
- [
100
- "virsh",
101
- "--connect",
102
- conn_uri,
103
- "qemu-agent-command",
104
- vm_name,
105
- json.dumps({"execute": "guest-ping"}),
106
- ],
107
- capture_output=True,
108
- text=True,
109
- timeout=5,
110
- )
111
- 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
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
- try:
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
- # Try to get cloud-init status via QEMU guest agent
1793
- result = subprocess.run(
1794
- [
1795
- "virsh",
1796
- "--connect",
1797
- conn_uri,
1798
- "qemu-agent-command",
1799
- vm_name,
1800
- '{"execute":"guest-exec","arguments":{"path":"cloud-init","arg":["status"],"capture-output":true}}',
1801
- ],
1802
- capture_output=True,
1803
- text=True,
1804
- timeout=15,
1805
- )
1806
- if result.returncode == 0:
1807
- try:
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
- result = subprocess.run(
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
- console.print("[yellow]⚠️ Health check script not found[/]")
1899
- 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")
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("command", help="Command to execute in VM")
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
  )
@@ -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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 1.1.7
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes