clonebox 1.1.5__py3-none-any.whl → 1.1.7__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 +20 -15
- clonebox/models.py +2 -2
- clonebox/validator.py +48 -1
- {clonebox-1.1.5.dist-info → clonebox-1.1.7.dist-info}/METADATA +2 -1
- {clonebox-1.1.5.dist-info → clonebox-1.1.7.dist-info}/RECORD +9 -9
- {clonebox-1.1.5.dist-info → clonebox-1.1.7.dist-info}/WHEEL +0 -0
- {clonebox-1.1.5.dist-info → clonebox-1.1.7.dist-info}/entry_points.txt +0 -0
- {clonebox-1.1.5.dist-info → clonebox-1.1.7.dist-info}/licenses/LICENSE +0 -0
- {clonebox-1.1.5.dist-info → clonebox-1.1.7.dist-info}/top_level.txt +0 -0
clonebox/cli.py
CHANGED
|
@@ -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
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
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(
|
|
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",
|
|
2337
|
-
vcpus=config["vm"].get("vcpus",
|
|
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"),
|
clonebox/models.py
CHANGED
|
@@ -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=
|
|
18
|
-
vcpus: int = Field(default=
|
|
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")
|
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
|
-
|
|
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.
|
|
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"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
clonebox/__init__.py,sha256=CyfHVVq6KqBr4CNERBpXk_O6Q5B35q03YpdQbokVvvI,408
|
|
2
2
|
clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
|
|
3
|
-
clonebox/cli.py,sha256=
|
|
3
|
+
clonebox/cli.py,sha256=KR3naqYB1ZRFDGGoQAfqRk4Ci4NVF3WiY3nOzRKmcyg,136362
|
|
4
4
|
clonebox/cloner.py,sha256=aH7BwmLgPITHzyHn_n_AKqtEADDm_aUXiRG-nB2rxMQ,90063
|
|
5
5
|
clonebox/container.py,sha256=tiYK1ZB-DhdD6A2FuMA0h_sRNkUI7KfYcJ0tFOcdyeM,6105
|
|
6
6
|
clonebox/dashboard.py,sha256=dMY6odvPq3j6FronhRRsX7aY3qdCwznB-aCWKEmHDNw,5768
|
|
@@ -9,7 +9,7 @@ clonebox/di.py,sha256=feFMXP5ff-7gwrIqgnoCpk1ivaiZA_lv2wcpkCSKiew,5648
|
|
|
9
9
|
clonebox/exporter.py,sha256=WIzVvmA0z_jjrpyXxvnXoLp9oaW6fKS7k0PGwzx_PIM,5629
|
|
10
10
|
clonebox/importer.py,sha256=Q9Uk1IOA41mgGhU4ynW2k-h9GEoGxRKI3c9wWE4uxcA,7097
|
|
11
11
|
clonebox/logging.py,sha256=jD_WgkHmt_h99EmX82QWK7D0OKWyxqcLwgT4UDQFp2g,3675
|
|
12
|
-
clonebox/models.py,sha256=
|
|
12
|
+
clonebox/models.py,sha256=l9e5U4ZEEcgCTt2c47GpqFwppwVeifoCoNFi57lFGFo,8307
|
|
13
13
|
clonebox/monitor.py,sha256=zlIarNf8w_i34XI8hZGxxrg5PVZK_Yxm6FQnkhLavRI,9181
|
|
14
14
|
clonebox/p2p.py,sha256=6o0JnscKqF9-BftQhW5fF1W6YY1wXshY9LEklNcHGJc,5913
|
|
15
15
|
clonebox/profiles.py,sha256=UP37fX_rhrG_O9ehNFJBUcULPmUtN1A8KsJ6cM44oK0,1986
|
|
@@ -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=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.
|
|
38
|
-
clonebox-1.1.
|
|
39
|
-
clonebox-1.1.
|
|
40
|
-
clonebox-1.1.
|
|
41
|
-
clonebox-1.1.
|
|
42
|
-
clonebox-1.1.
|
|
37
|
+
clonebox-1.1.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
38
|
+
clonebox-1.1.7.dist-info/METADATA,sha256=ojWCleuifB5DP3QDwjgT-X45ge_SslR30e7CyHl8iwY,48915
|
|
39
|
+
clonebox-1.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
40
|
+
clonebox-1.1.7.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
41
|
+
clonebox-1.1.7.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
42
|
+
clonebox-1.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|