clonebox 0.1.16__tar.gz → 0.1.17__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 (25) hide show
  1. {clonebox-0.1.16/src/clonebox.egg-info → clonebox-0.1.17}/PKG-INFO +1 -1
  2. {clonebox-0.1.16 → clonebox-0.1.17}/pyproject.toml +1 -1
  3. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox/cli.py +76 -75
  4. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox/cloner.py +19 -12
  5. {clonebox-0.1.16 → clonebox-0.1.17/src/clonebox.egg-info}/PKG-INFO +1 -1
  6. {clonebox-0.1.16 → clonebox-0.1.17}/LICENSE +0 -0
  7. {clonebox-0.1.16 → clonebox-0.1.17}/README.md +0 -0
  8. {clonebox-0.1.16 → clonebox-0.1.17}/setup.cfg +0 -0
  9. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox/__init__.py +0 -0
  10. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox/__main__.py +0 -0
  11. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox/container.py +0 -0
  12. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox/detector.py +0 -0
  13. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox/models.py +0 -0
  14. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox/validator.py +0 -0
  15. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox.egg-info/SOURCES.txt +0 -0
  16. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox.egg-info/dependency_links.txt +0 -0
  17. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox.egg-info/entry_points.txt +0 -0
  18. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox.egg-info/requires.txt +0 -0
  19. {clonebox-0.1.16 → clonebox-0.1.17}/src/clonebox.egg-info/top_level.txt +0 -0
  20. {clonebox-0.1.16 → clonebox-0.1.17}/tests/test_cli.py +0 -0
  21. {clonebox-0.1.16 → clonebox-0.1.17}/tests/test_cloner.py +0 -0
  22. {clonebox-0.1.16 → clonebox-0.1.17}/tests/test_detector.py +0 -0
  23. {clonebox-0.1.16 → clonebox-0.1.17}/tests/test_models.py +0 -0
  24. {clonebox-0.1.16 → clonebox-0.1.17}/tests/test_network.py +0 -0
  25. {clonebox-0.1.16 → clonebox-0.1.17}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 0.1.16
3
+ Version: 0.1.17
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 = "0.1.16"
7
+ version = "0.1.17"
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"}
@@ -80,96 +80,98 @@ def _resolve_vm_name_and_config_file(name: Optional[str]) -> tuple[str, Optional
80
80
 
81
81
 
82
82
  def _qga_ping(vm_name: str, conn_uri: str) -> bool:
83
- import subprocess
83
+ def _qga_ping() -> bool:
84
+ try:
85
+ result = subprocess.run(
86
+ [
87
+ "virsh",
88
+ "--connect",
89
+ conn_uri,
90
+ "qemu-agent-command",
91
+ vm_name,
92
+ json.dumps({"execute": "guest-ping"}),
93
+ ],
94
+ capture_output=True,
95
+ text=True,
96
+ timeout=5,
97
+ )
98
+ return result.returncode == 0
99
+ except Exception:
100
+ return False
84
101
 
85
- try:
86
- result = subprocess.run(
87
- [
88
- "virsh",
89
- "--connect",
90
- conn_uri,
91
- "qemu-agent-command",
92
- vm_name,
93
- json.dumps({"execute": "guest-ping"}),
94
- ],
95
- capture_output=True,
96
- text=True,
97
- timeout=5,
98
- )
99
- return result.returncode == 0
100
- except Exception:
101
- return False
102
+ return _qga_ping()
102
103
 
103
104
 
104
105
  def _qga_exec(vm_name: str, conn_uri: str, command: str, timeout: int = 10) -> Optional[str]:
105
- import subprocess
106
-
107
- try:
108
- payload = {
109
- "execute": "guest-exec",
110
- "arguments": {
111
- "path": "/bin/sh",
112
- "arg": ["-c", command],
113
- "capture-output": True,
114
- },
115
- }
116
- exec_result = subprocess.run(
117
- [
118
- "virsh",
119
- "--connect",
120
- conn_uri,
121
- "qemu-agent-command",
122
- vm_name,
123
- json.dumps(payload),
124
- ],
125
- capture_output=True,
126
- text=True,
127
- timeout=timeout,
128
- )
129
- if exec_result.returncode != 0:
130
- return None
131
-
132
- resp = json.loads(exec_result.stdout)
133
- pid = resp.get("return", {}).get("pid")
134
- if not pid:
135
- return None
136
-
137
- import base64
138
- import time
139
-
140
- deadline = time.time() + timeout
141
- while time.time() < deadline:
142
- status_payload = {"execute": "guest-exec-status", "arguments": {"pid": pid}}
143
- status_result = subprocess.run(
106
+ def _qga_exec() -> Optional[str]:
107
+ try:
108
+ payload = {
109
+ "execute": "guest-exec",
110
+ "arguments": {
111
+ "path": "/bin/sh",
112
+ "arg": ["-c", command],
113
+ "capture-output": True,
114
+ },
115
+ }
116
+ exec_result = subprocess.run(
144
117
  [
145
118
  "virsh",
146
119
  "--connect",
147
120
  conn_uri,
148
121
  "qemu-agent-command",
149
122
  vm_name,
150
- json.dumps(status_payload),
123
+ json.dumps(payload),
151
124
  ],
152
125
  capture_output=True,
153
126
  text=True,
154
- timeout=5,
127
+ timeout=timeout,
155
128
  )
156
- if status_result.returncode != 0:
129
+ if exec_result.returncode != 0:
157
130
  return None
158
131
 
159
- status_resp = json.loads(status_result.stdout)
160
- ret = status_resp.get("return", {})
161
- if not ret.get("exited", False):
162
- time.sleep(0.3)
163
- continue
132
+ resp = json.loads(exec_result.stdout)
133
+ pid = resp.get("return", {}).get("pid")
134
+ if not pid:
135
+ return None
164
136
 
165
- out_data = ret.get("out-data")
166
- if out_data:
167
- return base64.b64decode(out_data).decode().strip()
168
- return ""
137
+ import base64
138
+ import time
169
139
 
170
- return None
171
- except Exception:
172
- return None
140
+ deadline = time.time() + timeout
141
+ while time.time() < deadline:
142
+ status_payload = {"execute": "guest-exec-status", "arguments": {"pid": pid}}
143
+ status_result = subprocess.run(
144
+ [
145
+ "virsh",
146
+ "--connect",
147
+ conn_uri,
148
+ "qemu-agent-command",
149
+ vm_name,
150
+ json.dumps(status_payload),
151
+ ],
152
+ capture_output=True,
153
+ text=True,
154
+ timeout=5,
155
+ )
156
+ if status_result.returncode != 0:
157
+ return None
158
+
159
+ status_resp = json.loads(status_result.stdout)
160
+ ret = status_resp.get("return", {})
161
+ if not ret.get("exited", False):
162
+ time.sleep(0.3)
163
+ continue
164
+
165
+ out_data = ret.get("out-data")
166
+ if out_data:
167
+ return base64.b64decode(out_data).decode().strip()
168
+ return ""
169
+
170
+ return None
171
+ except Exception:
172
+ return None
173
+
174
+ return _qga_exec()
173
175
 
174
176
 
175
177
  def run_vm_diagnostics(
@@ -698,14 +700,13 @@ def interactive_mode():
698
700
  if questionary.confirm("Start VM now?", default=True, style=custom_style).ask():
699
701
  cloner.start_vm(vm_name, open_viewer=enable_gui, console=console)
700
702
  console.print("\n[bold green]🎉 VM is running![/]")
703
+ console.print(f"\n[dim]UUID: {vm_uuid}[/]")
701
704
 
702
705
  if paths_mapping:
703
706
  console.print("\n[bold]Inside the VM, mount shared folders with:[/]")
704
707
  for idx, (host, guest) in enumerate(paths_mapping.items()):
705
708
  console.print(f" [cyan]sudo mount -t 9p -o trans=virtio mount{idx} {guest}[/]")
706
709
 
707
- console.print(f"\n[dim]VM UUID: {vm_uuid}[/]")
708
-
709
710
  except Exception as e:
710
711
  console.print(f"\n[red]❌ Error: {e}[/]")
711
712
  raise
@@ -942,7 +943,7 @@ def cmd_container_up(args):
942
943
  "Container features require extra dependencies (e.g. pydantic). Install them to use 'clonebox container'."
943
944
  ) from e
944
945
 
945
- mounts: dict[str, str] = {}
946
+ mounts = {}
946
947
  for m in getattr(args, "mount", []) or []:
947
948
  if ":" not in m:
948
949
  raise ValueError(f"Invalid mount: {m} (expected HOST:CONTAINER)")
@@ -679,19 +679,27 @@ fi
679
679
 
680
680
  # User-data
681
681
  # Add desktop environment if GUI is enabled
682
- runcmd_lines = []
683
682
  base_packages = ["qemu-guest-agent"]
684
683
  if config.gui:
685
- base_packages.extend(
686
- [
687
- "ubuntu-desktop-minimal",
688
- "firefox",
689
- ]
690
- )
684
+ base_packages.extend([
685
+ "ubuntu-desktop-minimal",
686
+ "firefox",
687
+ ])
691
688
 
692
689
  all_packages = base_packages + list(config.packages)
693
- packages_yaml = "\n".join([f" - {pkg}" for pkg in all_packages])
690
+ packages_yaml = (
691
+ "\n".join(f" - {pkg}" for pkg in all_packages) if all_packages else ""
692
+ )
693
+
694
+ # Build runcmd - services, mounts, snaps, post_commands
695
+ runcmd_lines = []
694
696
 
697
+ runcmd_lines.append(" - systemctl enable --now qemu-guest-agent || true")
698
+
699
+ # Add service enablement
700
+ for svc in config.services:
701
+ runcmd_lines.append(f" - systemctl enable --now {svc} || true")
702
+
695
703
  # Add fstab entries for persistent mounts after reboot
696
704
  if fstab_entries:
697
705
  runcmd_lines.append(" - grep -q '^# CloneBox 9p mounts' /etc/fstab || echo '# CloneBox 9p mounts' >> /etc/fstab")
@@ -699,10 +707,9 @@ fi
699
707
  runcmd_lines.append(f" - grep -qF \"{entry}\" /etc/fstab || echo '{entry}' >> /etc/fstab")
700
708
  runcmd_lines.append(" - mount -a || true")
701
709
 
702
- # Install APT packages
703
- runcmd_lines.append(" - echo 'Installing APT packages...'")
704
- for pkg in config.packages:
705
- runcmd_lines.append(f" - apt-get install -y {pkg} || true")
710
+ # Add mounts (immediate, before reboot)
711
+ for cmd in mount_commands:
712
+ runcmd_lines.append(cmd)
706
713
 
707
714
  # Install snap packages
708
715
  if config.snap_packages:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 0.1.16
3
+ Version: 0.1.17
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