clonebox 1.1.5__tar.gz → 1.1.7__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.
Files changed (59) hide show
  1. {clonebox-1.1.5/src/clonebox.egg-info → clonebox-1.1.7}/PKG-INFO +2 -1
  2. {clonebox-1.1.5 → clonebox-1.1.7}/pyproject.toml +2 -1
  3. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/cli.py +20 -15
  4. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/models.py +2 -2
  5. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/validator.py +48 -1
  6. {clonebox-1.1.5 → clonebox-1.1.7/src/clonebox.egg-info}/PKG-INFO +2 -1
  7. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox.egg-info/requires.txt +1 -0
  8. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_models.py +3 -3
  9. {clonebox-1.1.5 → clonebox-1.1.7}/LICENSE +0 -0
  10. {clonebox-1.1.5 → clonebox-1.1.7}/README.md +0 -0
  11. {clonebox-1.1.5 → clonebox-1.1.7}/setup.cfg +0 -0
  12. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/__init__.py +0 -0
  13. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/__main__.py +0 -0
  14. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/backends/libvirt_backend.py +0 -0
  15. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/backends/qemu_disk.py +0 -0
  16. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/backends/subprocess_runner.py +0 -0
  17. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/cloner.py +0 -0
  18. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/container.py +0 -0
  19. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/dashboard.py +0 -0
  20. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/detector.py +0 -0
  21. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/di.py +0 -0
  22. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/exporter.py +0 -0
  23. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/health/__init__.py +0 -0
  24. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/health/manager.py +0 -0
  25. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/health/models.py +0 -0
  26. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/health/probes.py +0 -0
  27. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/importer.py +0 -0
  28. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/interfaces/disk.py +0 -0
  29. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/interfaces/hypervisor.py +0 -0
  30. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/interfaces/network.py +0 -0
  31. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/interfaces/process.py +0 -0
  32. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/logging.py +0 -0
  33. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/monitor.py +0 -0
  34. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/p2p.py +0 -0
  35. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/profiles.py +0 -0
  36. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/resource_monitor.py +0 -0
  37. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/resources.py +0 -0
  38. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/rollback.py +0 -0
  39. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/secrets.py +0 -0
  40. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/snapshots/__init__.py +0 -0
  41. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/snapshots/manager.py +0 -0
  42. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/snapshots/models.py +0 -0
  43. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/templates/profiles/ml-dev.yaml +0 -0
  44. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox/templates/profiles/web-stack.yaml +0 -0
  45. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox.egg-info/SOURCES.txt +0 -0
  46. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox.egg-info/dependency_links.txt +0 -0
  47. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox.egg-info/entry_points.txt +0 -0
  48. {clonebox-1.1.5 → clonebox-1.1.7}/src/clonebox.egg-info/top_level.txt +0 -0
  49. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_cli.py +0 -0
  50. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_cloner.py +0 -0
  51. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_cloner_simple.py +0 -0
  52. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_container.py +0 -0
  53. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_coverage_additional.py +0 -0
  54. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_coverage_boost_final.py +0 -0
  55. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_dashboard_coverage.py +0 -0
  56. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_detector.py +0 -0
  57. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_network.py +0 -0
  58. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_profiles.py +0 -0
  59. {clonebox-1.1.5 → clonebox-1.1.7}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 1.1.5
3
+ Version: 1.1.7
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
@@ -33,6 +33,7 @@ Requires-Dist: pyyaml>=6.0
33
33
  Requires-Dist: pydantic>=2.0.0
34
34
  Requires-Dist: python-dotenv>=1.0.0
35
35
  Requires-Dist: cryptography>=42.0.0
36
+ Requires-Dist: structlog>=24.0.0
36
37
  Provides-Extra: dev
37
38
  Requires-Dist: pytest>=7.0.0; extra == "dev"
38
39
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "clonebox"
7
- version = "1.1.5"
7
+ version = "1.1.7"
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"}
@@ -39,6 +39,7 @@ dependencies = [
39
39
  "pydantic>=2.0.0",
40
40
  "python-dotenv>=1.0.0",
41
41
  "cryptography>=42.0.0",
42
+ "structlog>=24.0.0",
42
43
  ]
43
44
 
44
45
  [project.optional-dependencies]
@@ -92,6 +92,7 @@ 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
95
96
 
96
97
  try:
97
98
  result = subprocess.run(
@@ -116,6 +117,7 @@ def _qga_exec(vm_name: str, conn_uri: str, command: str, timeout: int = 10) -> O
116
117
  import subprocess
117
118
  import base64
118
119
  import time
120
+ import json
119
121
 
120
122
  try:
121
123
  payload = {
@@ -1852,18 +1854,21 @@ def cmd_test(args):
1852
1854
  all_paths.update(config.get("app_data_paths", {}))
1853
1855
 
1854
1856
  if all_paths:
1855
- for idx, (host_path, guest_path) in enumerate(all_paths.items()):
1856
- try:
1857
- # Use the same QGA helper as diagnose/status
1858
- is_accessible = _qga_exec(
1859
- vm_name, conn_uri, f"test -d {guest_path} && echo yes || echo no", timeout=5
1860
- )
1861
- if is_accessible == "yes":
1862
- console.print(f"[green]✅ {guest_path}[/]")
1863
- else:
1864
- console.print(f"[red]❌ {guest_path} (not accessible)[/]")
1865
- except Exception:
1866
- console.print(f"[yellow]⚠️ {guest_path} (could not check)[/]")
1857
+ if not _qga_ping(vm_name, conn_uri):
1858
+ console.print("[yellow]⚠️ QEMU guest agent not connected - cannot verify mounts[/]")
1859
+ else:
1860
+ for idx, (host_path, guest_path) in enumerate(all_paths.items()):
1861
+ try:
1862
+ # Use the same QGA helper as diagnose/status
1863
+ is_accessible = _qga_exec(
1864
+ vm_name, conn_uri, f"test -d {guest_path} && echo yes || echo no", timeout=5
1865
+ )
1866
+ if is_accessible == "yes":
1867
+ console.print(f"[green]✅ {guest_path}[/]")
1868
+ else:
1869
+ console.print(f"[red]❌ {guest_path} (not accessible)[/]")
1870
+ except Exception:
1871
+ console.print(f"[yellow]⚠️ {guest_path} (could not check)[/]")
1867
1872
  else:
1868
1873
  console.print("[dim]No mount points configured[/]")
1869
1874
 
@@ -2100,7 +2105,7 @@ def generate_clonebox_yaml(
2100
2105
  vm_name = f"clone-{sys_info['hostname']}"
2101
2106
 
2102
2107
  # Calculate recommended resources
2103
- ram_mb = min(4096, int(sys_info["memory_available_gb"] * 1024 * 0.5))
2108
+ ram_mb = min(8192, int(sys_info["memory_available_gb"] * 1024 * 0.5))
2104
2109
  vcpus = max(2, sys_info["cpu_count"] // 2)
2105
2110
 
2106
2111
  if disk_size_gb is None:
@@ -2333,8 +2338,8 @@ def create_vm_from_config(
2333
2338
 
2334
2339
  vm_config = VMConfig(
2335
2340
  name=config["vm"]["name"],
2336
- ram_mb=config["vm"].get("ram_mb", 4096),
2337
- vcpus=config["vm"].get("vcpus", 4),
2341
+ ram_mb=config["vm"].get("ram_mb", 8192),
2342
+ vcpus=config["vm"].get("vcpus", 8),
2338
2343
  disk_size_gb=config["vm"].get("disk_size_gb", 10),
2339
2344
  gui=config["vm"].get("gui", True),
2340
2345
  base_image=config["vm"].get("base_image"),
@@ -14,8 +14,8 @@ class VMSettings(BaseModel):
14
14
  """VM-specific settings."""
15
15
 
16
16
  name: str = Field(default="clonebox-vm", description="VM name")
17
- ram_mb: int = Field(default=4096, ge=512, le=131072, description="RAM in MB")
18
- vcpus: int = Field(default=4, ge=1, le=128, description="Number of vCPUs")
17
+ ram_mb: int = Field(default=8192, ge=512, le=131072, description="RAM in MB")
18
+ vcpus: int = Field(default=8, ge=1, le=128, description="Number of vCPUs")
19
19
  disk_size_gb: int = Field(default=20, ge=1, le=2048, description="Disk size in GB")
20
20
  gui: bool = Field(default=True, description="Enable SPICE graphics")
21
21
  base_image: Optional[str] = Field(default=None, description="Path to base qcow2 image")
@@ -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(
@@ -826,6 +839,26 @@ class VMValidator:
826
839
  self.console.print(table)
827
840
  return self.results["smoke"]
828
841
 
842
+ def _check_qga_ready(self) -> bool:
843
+ """Check if QEMU guest agent is responding."""
844
+ try:
845
+ result = subprocess.run(
846
+ [
847
+ "virsh",
848
+ "--connect",
849
+ self.conn_uri,
850
+ "qemu-agent-command",
851
+ self.vm_name,
852
+ '{"execute":"guest-ping"}',
853
+ ],
854
+ capture_output=True,
855
+ text=True,
856
+ timeout=5,
857
+ )
858
+ return result.returncode == 0
859
+ except Exception:
860
+ return False
861
+
829
862
  def validate_all(self) -> Dict:
830
863
  """Run all validations and return comprehensive results."""
831
864
  self.console.print("[bold cyan]🔍 Running Full Validation...[/]")
@@ -850,6 +883,20 @@ class VMValidator:
850
883
  self.results["overall"] = "error"
851
884
  return self.results
852
885
 
886
+ # Check QEMU Guest Agent
887
+ if not self._check_qga_ready():
888
+ self.console.print("[red]❌ QEMU Guest Agent not responding[/]")
889
+ self.console.print("\n[bold]🔧 Troubleshooting QGA:[/]")
890
+ self.console.print(" 1. The VM might still be booting. Wait 30-60 seconds.")
891
+ self.console.print(" 2. Ensure the agent is installed and running inside the VM:")
892
+ self.console.print(" [dim]virsh console " + self.vm_name + "[/]")
893
+ self.console.print(" [dim]sudo systemctl status qemu-guest-agent[/]")
894
+ self.console.print(" 3. If newly created, cloud-init might still be running.")
895
+ self.console.print(" 4. Check VM logs: [dim]clonebox logs " + self.vm_name + "[/]")
896
+ self.console.print(f"\n[yellow]⚠️ Skipping deep validation as it requires a working Guest Agent.[/]")
897
+ self.results["overall"] = "qga_not_ready"
898
+ return self.results
899
+
853
900
  # Run all validations
854
901
  self.validate_mounts()
855
902
  self.validate_packages()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 1.1.5
3
+ Version: 1.1.7
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
@@ -33,6 +33,7 @@ Requires-Dist: pyyaml>=6.0
33
33
  Requires-Dist: pydantic>=2.0.0
34
34
  Requires-Dist: python-dotenv>=1.0.0
35
35
  Requires-Dist: cryptography>=42.0.0
36
+ Requires-Dist: structlog>=24.0.0
36
37
  Provides-Extra: dev
37
38
  Requires-Dist: pytest>=7.0.0; extra == "dev"
38
39
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -6,6 +6,7 @@ pyyaml>=6.0
6
6
  pydantic>=2.0.0
7
7
  python-dotenv>=1.0.0
8
8
  cryptography>=42.0.0
9
+ structlog>=24.0.0
9
10
 
10
11
  [dashboard]
11
12
  fastapi>=0.100.0
@@ -15,8 +15,8 @@ class TestVMSettings:
15
15
  def test_default_values(self):
16
16
  settings = VMSettings()
17
17
  assert settings.name == "clonebox-vm"
18
- assert settings.ram_mb == 4096
19
- assert settings.vcpus == 4
18
+ assert settings.ram_mb == 8192
19
+ assert settings.vcpus == 8
20
20
  assert settings.gui is True
21
21
 
22
22
  @pytest.mark.parametrize(
@@ -173,7 +173,7 @@ class TestCloneBoxConfigYAML:
173
173
  def test_yaml_roundtrip(self, tmp_path):
174
174
  original = CloneBoxConfig(
175
175
  version="1",
176
- vm=VMSettings(name="yaml-vm", ram_mb=4096, network_mode="user"),
176
+ vm=VMSettings(name="yaml-vm", ram_mb=8192, network_mode="user"),
177
177
  paths={"/home/user/code": "/mnt/code"},
178
178
  app_data_paths={"/home/user/.config/app": "/home/ubuntu/.config/app"},
179
179
  packages=["python3", "nodejs"],
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes