clonebox 0.1.1__py3-none-any.whl → 0.1.3__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 +889 -0
- clonebox/cloner.py +123 -12
- clonebox/detector.py +3 -1
- clonebox-0.1.3.dist-info/METADATA +379 -0
- clonebox-0.1.3.dist-info/RECORD +10 -0
- clonebox-0.1.1.dist-info/METADATA +0 -40
- clonebox-0.1.1.dist-info/RECORD +0 -9
- {clonebox-0.1.1.dist-info → clonebox-0.1.3.dist-info}/WHEEL +0 -0
- {clonebox-0.1.1.dist-info → clonebox-0.1.3.dist-info}/entry_points.txt +0 -0
- {clonebox-0.1.1.dist-info → clonebox-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {clonebox-0.1.1.dist-info → clonebox-0.1.3.dist-info}/top_level.txt +0 -0
clonebox/cloner.py
CHANGED
|
@@ -31,6 +31,8 @@ class VMConfig:
|
|
|
31
31
|
paths: dict = field(default_factory=dict)
|
|
32
32
|
packages: list = field(default_factory=list)
|
|
33
33
|
services: list = field(default_factory=list)
|
|
34
|
+
user_session: bool = False # Use qemu:///session instead of qemu:///system
|
|
35
|
+
network_mode: str = "auto" # auto|default|user
|
|
34
36
|
|
|
35
37
|
def to_dict(self) -> dict:
|
|
36
38
|
return {
|
|
@@ -46,8 +48,16 @@ class SelectiveVMCloner:
|
|
|
46
48
|
Uses bind mounts instead of full disk cloning.
|
|
47
49
|
"""
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
# Default images directories
|
|
52
|
+
SYSTEM_IMAGES_DIR = Path("/var/lib/libvirt/images")
|
|
53
|
+
USER_IMAGES_DIR = Path.home() / ".local/share/libvirt/images"
|
|
54
|
+
|
|
55
|
+
def __init__(self, conn_uri: str = None, user_session: bool = False):
|
|
56
|
+
self.user_session = user_session
|
|
57
|
+
if conn_uri:
|
|
58
|
+
self.conn_uri = conn_uri
|
|
59
|
+
else:
|
|
60
|
+
self.conn_uri = "qemu:///session" if user_session else "qemu:///system"
|
|
51
61
|
self.conn = None
|
|
52
62
|
self._connect()
|
|
53
63
|
|
|
@@ -59,17 +69,59 @@ class SelectiveVMCloner:
|
|
|
59
69
|
"Also ensure libvirt is installed: sudo apt install libvirt-daemon-system"
|
|
60
70
|
)
|
|
61
71
|
|
|
62
|
-
|
|
72
|
+
try:
|
|
73
|
+
self.conn = libvirt.open(self.conn_uri)
|
|
74
|
+
except libvirt.libvirtError as e:
|
|
75
|
+
raise ConnectionError(
|
|
76
|
+
f"Cannot connect to {self.conn_uri}\n"
|
|
77
|
+
f"Error: {e}\n\n"
|
|
78
|
+
f"Troubleshooting:\n"
|
|
79
|
+
f" 1. Check if libvirtd is running: sudo systemctl status libvirtd\n"
|
|
80
|
+
f" 2. Start libvirtd: sudo systemctl start libvirtd\n"
|
|
81
|
+
f" 3. Add user to libvirt group: sudo usermod -aG libvirt $USER\n"
|
|
82
|
+
f" 4. Re-login or run: newgrp libvirt\n"
|
|
83
|
+
f" 5. For user session (no sudo): use --user flag"
|
|
84
|
+
)
|
|
85
|
+
|
|
63
86
|
if self.conn is None:
|
|
64
87
|
raise ConnectionError(f"Cannot connect to {self.conn_uri}")
|
|
65
88
|
|
|
89
|
+
def get_images_dir(self) -> Path:
|
|
90
|
+
"""Get the appropriate images directory based on session type."""
|
|
91
|
+
if self.user_session:
|
|
92
|
+
return self.USER_IMAGES_DIR
|
|
93
|
+
return self.SYSTEM_IMAGES_DIR
|
|
94
|
+
|
|
95
|
+
def _default_network_active(self) -> bool:
|
|
96
|
+
"""Check if libvirt default network is active."""
|
|
97
|
+
try:
|
|
98
|
+
net = self.conn.networkLookupByName("default")
|
|
99
|
+
return net.isActive() == 1
|
|
100
|
+
except libvirt.libvirtError:
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def resolve_network_mode(self, config: VMConfig) -> str:
|
|
104
|
+
"""Resolve network mode based on config and session type."""
|
|
105
|
+
mode = (config.network_mode or "auto").lower()
|
|
106
|
+
if mode == "auto":
|
|
107
|
+
if self.user_session and not self._default_network_active():
|
|
108
|
+
return "user"
|
|
109
|
+
return "default"
|
|
110
|
+
if mode in {"default", "user"}:
|
|
111
|
+
return mode
|
|
112
|
+
return "default"
|
|
113
|
+
|
|
66
114
|
def check_prerequisites(self) -> dict:
|
|
67
115
|
"""Check system prerequisites for VM creation."""
|
|
116
|
+
images_dir = self.get_images_dir()
|
|
117
|
+
|
|
68
118
|
checks = {
|
|
69
119
|
"libvirt_connected": False,
|
|
70
120
|
"kvm_available": False,
|
|
71
121
|
"default_network": False,
|
|
72
122
|
"images_dir_writable": False,
|
|
123
|
+
"images_dir": str(images_dir),
|
|
124
|
+
"session_type": "user" if self.user_session else "system",
|
|
73
125
|
}
|
|
74
126
|
|
|
75
127
|
# Check libvirt connection
|
|
@@ -77,18 +129,48 @@ class SelectiveVMCloner:
|
|
|
77
129
|
checks["libvirt_connected"] = True
|
|
78
130
|
|
|
79
131
|
# Check KVM
|
|
80
|
-
|
|
132
|
+
kvm_path = Path("/dev/kvm")
|
|
133
|
+
checks["kvm_available"] = kvm_path.exists()
|
|
134
|
+
if not checks["kvm_available"]:
|
|
135
|
+
checks["kvm_error"] = "KVM not available. Enable virtualization in BIOS."
|
|
136
|
+
elif not os.access(kvm_path, os.R_OK | os.W_OK):
|
|
137
|
+
checks["kvm_error"] = f"No access to /dev/kvm. Add user to kvm group: sudo usermod -aG kvm $USER"
|
|
81
138
|
|
|
82
139
|
# Check default network
|
|
83
140
|
try:
|
|
84
141
|
net = self.conn.networkLookupByName("default")
|
|
85
142
|
checks["default_network"] = net.isActive() == 1
|
|
86
143
|
except libvirt.libvirtError:
|
|
87
|
-
|
|
144
|
+
checks["network_error"] = (
|
|
145
|
+
"Default network not found or inactive.\n"
|
|
146
|
+
" For user session, CloneBox can use user-mode networking (slirp) automatically.\n"
|
|
147
|
+
" Or create a user network:\n"
|
|
148
|
+
" virsh --connect qemu:///session net-define /tmp/default-network.xml\n"
|
|
149
|
+
" virsh --connect qemu:///session net-start default\n"
|
|
150
|
+
" Or use system session: clonebox clone . (without --user)\n"
|
|
151
|
+
)
|
|
88
152
|
|
|
89
153
|
# Check images directory
|
|
90
|
-
images_dir
|
|
91
|
-
|
|
154
|
+
if images_dir.exists():
|
|
155
|
+
checks["images_dir_writable"] = os.access(images_dir, os.W_OK)
|
|
156
|
+
if not checks["images_dir_writable"]:
|
|
157
|
+
checks["images_dir_error"] = (
|
|
158
|
+
f"Cannot write to {images_dir}\n"
|
|
159
|
+
f" Option 1: Run with sudo\n"
|
|
160
|
+
f" Option 2: Use --user flag for user session (no root needed)\n"
|
|
161
|
+
f" Option 3: Fix permissions: sudo chown -R $USER:libvirt {images_dir}"
|
|
162
|
+
)
|
|
163
|
+
else:
|
|
164
|
+
# Try to create it
|
|
165
|
+
try:
|
|
166
|
+
images_dir.mkdir(parents=True, exist_ok=True)
|
|
167
|
+
checks["images_dir_writable"] = True
|
|
168
|
+
except PermissionError:
|
|
169
|
+
checks["images_dir_writable"] = False
|
|
170
|
+
checks["images_dir_error"] = (
|
|
171
|
+
f"Cannot create {images_dir}\n"
|
|
172
|
+
f" Use --user flag for user session (stores in ~/.local/share/libvirt/images/)"
|
|
173
|
+
)
|
|
92
174
|
|
|
93
175
|
return checks
|
|
94
176
|
|
|
@@ -109,8 +191,25 @@ class SelectiveVMCloner:
|
|
|
109
191
|
else:
|
|
110
192
|
print(msg)
|
|
111
193
|
|
|
112
|
-
|
|
113
|
-
|
|
194
|
+
# Determine images directory
|
|
195
|
+
images_dir = self.get_images_dir()
|
|
196
|
+
vm_dir = images_dir / config.name
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
vm_dir.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
except PermissionError as e:
|
|
201
|
+
raise PermissionError(
|
|
202
|
+
f"Cannot create VM directory: {vm_dir}\n\n"
|
|
203
|
+
f"🔧 Solutions:\n"
|
|
204
|
+
f" 1. Use --user flag to run in user session (recommended):\n"
|
|
205
|
+
f" clonebox clone . --user\n\n"
|
|
206
|
+
f" 2. Run with sudo (not recommended):\n"
|
|
207
|
+
f" sudo clonebox clone .\n\n"
|
|
208
|
+
f" 3. Fix directory permissions:\n"
|
|
209
|
+
f" sudo mkdir -p {images_dir}\n"
|
|
210
|
+
f" sudo chown -R $USER:libvirt {images_dir}\n\n"
|
|
211
|
+
f"Original error: {e}"
|
|
212
|
+
) from e
|
|
114
213
|
|
|
115
214
|
# Create root disk
|
|
116
215
|
root_disk = vm_dir / "root.qcow2"
|
|
@@ -139,6 +238,13 @@ class SelectiveVMCloner:
|
|
|
139
238
|
cloudinit_iso = self._create_cloudinit_iso(vm_dir, config)
|
|
140
239
|
log(f"[cyan]☁️ Created cloud-init ISO with {len(config.packages)} packages[/]")
|
|
141
240
|
|
|
241
|
+
# Resolve network mode
|
|
242
|
+
network_mode = self.resolve_network_mode(config)
|
|
243
|
+
if network_mode == "user":
|
|
244
|
+
log("[yellow]⚠️ Using user-mode networking (slirp) because default libvirt network is unavailable[/]")
|
|
245
|
+
else:
|
|
246
|
+
log(f"[dim]Network mode: {network_mode}[/]")
|
|
247
|
+
|
|
142
248
|
# Generate VM XML
|
|
143
249
|
vm_xml = self._generate_vm_xml(config, root_disk, cloudinit_iso)
|
|
144
250
|
|
|
@@ -212,9 +318,14 @@ class SelectiveVMCloner:
|
|
|
212
318
|
ET.SubElement(fs, "target", dir=tag)
|
|
213
319
|
|
|
214
320
|
# Network interface
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
321
|
+
network_mode = self.resolve_network_mode(config)
|
|
322
|
+
if network_mode == "user":
|
|
323
|
+
iface = ET.SubElement(devices, "interface", type="user")
|
|
324
|
+
ET.SubElement(iface, "model", type="virtio")
|
|
325
|
+
else:
|
|
326
|
+
iface = ET.SubElement(devices, "interface", type="network")
|
|
327
|
+
ET.SubElement(iface, "source", network="default")
|
|
328
|
+
ET.SubElement(iface, "model", type="virtio")
|
|
218
329
|
|
|
219
330
|
# Serial console
|
|
220
331
|
serial = ET.SubElement(devices, "serial", type="pty")
|
clonebox/detector.py
CHANGED
|
@@ -276,6 +276,8 @@ class SystemDetector:
|
|
|
276
276
|
def _get_dir_size(self, path: Path, max_depth: int = 2) -> int:
|
|
277
277
|
"""Get approximate directory size in bytes."""
|
|
278
278
|
total = 0
|
|
279
|
+
if not path.exists():
|
|
280
|
+
return 0
|
|
279
281
|
try:
|
|
280
282
|
for item in path.iterdir():
|
|
281
283
|
if item.is_file():
|
|
@@ -285,7 +287,7 @@ class SystemDetector:
|
|
|
285
287
|
pass
|
|
286
288
|
elif item.is_dir() and max_depth > 0 and not item.is_symlink():
|
|
287
289
|
total += self._get_dir_size(item, max_depth - 1)
|
|
288
|
-
except PermissionError:
|
|
290
|
+
except (PermissionError, FileNotFoundError, OSError):
|
|
289
291
|
pass
|
|
290
292
|
return total
|
|
291
293
|
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clonebox
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: Clone your workstation environment to an isolated VM with selective apps, paths and services
|
|
5
|
+
Author: CloneBox Team
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/wronai/clonebox
|
|
8
|
+
Project-URL: Repository, https://github.com/wronai/clonebox
|
|
9
|
+
Project-URL: Issues, https://github.com/wronai/clonebox/issues
|
|
10
|
+
Keywords: vm,virtualization,libvirt,clone,workstation,qemu,kvm
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: System :: Systems Administration
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: libvirt-python>=9.0.0
|
|
29
|
+
Requires-Dist: rich>=13.0.0
|
|
30
|
+
Requires-Dist: questionary>=2.0.0
|
|
31
|
+
Requires-Dist: psutil>=5.9.0
|
|
32
|
+
Requires-Dist: pyyaml>=6.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# CloneBox 📦
|
|
41
|
+
|
|
42
|
+
```commandline
|
|
43
|
+
╔═══════════════════════════════════════════════════════╗
|
|
44
|
+
║ ____ _ ____ ║
|
|
45
|
+
║ / ___|| | ___ _ __ ___| _ \ ___ __ __ ║
|
|
46
|
+
║ | | | | / _ \ | '_ \ / _ \ |_) |/ _ \\ \/ / ║
|
|
47
|
+
║ | |___ | || (_) || | | | __/ _ <| (_) |> < ║
|
|
48
|
+
║ \____||_| \___/ |_| |_|\___|_| \_\\___//_/\_\ ║
|
|
49
|
+
║ ║
|
|
50
|
+
║ Clone your workstation to an isolated VM ║
|
|
51
|
+
╚═══════════════════════════════════════════════════════╝
|
|
52
|
+
```
|
|
53
|
+
**Clone your workstation environment to an isolated VM with selective apps, paths and services.**
|
|
54
|
+
|
|
55
|
+
CloneBox lets you create isolated virtual machines with only the applications, directories and services you need - using bind mounts instead of full disk cloning. Perfect for development, testing, or creating reproducible environments.
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
- 🎯 **Selective cloning** - Choose exactly which paths, services and apps to include
|
|
60
|
+
- 🔍 **Auto-detection** - Automatically detects running services, applications, and project directories
|
|
61
|
+
- 🔗 **Bind mounts** - Share directories with the VM without copying data
|
|
62
|
+
- ☁️ **Cloud-init** - Automatic package installation and service setup
|
|
63
|
+
- 🖥️ **GUI support** - SPICE graphics with virt-viewer integration
|
|
64
|
+
- ⚡ **Fast creation** - No full disk cloning, VMs are ready in seconds
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
### Quick Setup (Recommended)
|
|
69
|
+
|
|
70
|
+
Run the setup script to automatically install dependencies and configure the environment:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Clone the repository
|
|
74
|
+
git clone https://github.com/wronai/clonebox.git
|
|
75
|
+
cd clonebox
|
|
76
|
+
|
|
77
|
+
# Run the setup script
|
|
78
|
+
./setup.sh
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The setup script will:
|
|
82
|
+
- Install all required packages (QEMU, libvirt, Python, etc.)
|
|
83
|
+
- Add your user to the necessary groups
|
|
84
|
+
- Configure libvirt networks
|
|
85
|
+
- Install clonebox in development mode
|
|
86
|
+
|
|
87
|
+
### Manual Installation
|
|
88
|
+
|
|
89
|
+
#### Prerequisites
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Install libvirt and QEMU/KVM
|
|
93
|
+
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager virt-viewer
|
|
94
|
+
|
|
95
|
+
# Enable and start libvirtd
|
|
96
|
+
sudo systemctl enable --now libvirtd
|
|
97
|
+
|
|
98
|
+
# Add user to libvirt group
|
|
99
|
+
sudo usermod -aG libvirt $USER
|
|
100
|
+
newgrp libvirt
|
|
101
|
+
|
|
102
|
+
# Install genisoimage for cloud-init
|
|
103
|
+
sudo apt install genisoimage
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Install CloneBox
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# From source
|
|
110
|
+
git clone https://github.com/wronai/clonebox.git
|
|
111
|
+
cd clonebox
|
|
112
|
+
pip install -e .
|
|
113
|
+
|
|
114
|
+
# Or directly
|
|
115
|
+
pip install clonebox
|
|
116
|
+
```
|
|
117
|
+
lub
|
|
118
|
+
```bash
|
|
119
|
+
# Aktywuj venv
|
|
120
|
+
source .venv/bin/activate
|
|
121
|
+
|
|
122
|
+
# Interaktywny tryb (wizard)
|
|
123
|
+
clonebox
|
|
124
|
+
|
|
125
|
+
# Lub poszczególne komendy
|
|
126
|
+
clonebox detect # Pokaż wykryte usługi/apps/ścieżki
|
|
127
|
+
clonebox list # Lista VM
|
|
128
|
+
clonebox create --config ... # Utwórz VM z JSON config
|
|
129
|
+
clonebox start <name> # Uruchom VM
|
|
130
|
+
clonebox stop <name> # Zatrzymaj VM
|
|
131
|
+
clonebox delete <name> # Usuń VM
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Quick Start
|
|
135
|
+
|
|
136
|
+
### Interactive Mode (Recommended)
|
|
137
|
+
|
|
138
|
+
Simply run `clonebox` to start the interactive wizard:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
clonebox
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The wizard will:
|
|
145
|
+
1. Detect running services (Docker, PostgreSQL, nginx, etc.)
|
|
146
|
+
2. Detect running applications and their working directories
|
|
147
|
+
3. Detect project directories and config files
|
|
148
|
+
4. Let you select what to include in the VM
|
|
149
|
+
5. Create and optionally start the VM
|
|
150
|
+
|
|
151
|
+
### Command Line
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Create VM with specific config
|
|
155
|
+
clonebox create --name my-dev-vm --config '{
|
|
156
|
+
"paths": {
|
|
157
|
+
"/home/user/projects": "/mnt/projects",
|
|
158
|
+
"/home/user/.config": "/mnt/config"
|
|
159
|
+
},
|
|
160
|
+
"packages": ["python3", "nodejs", "docker.io"],
|
|
161
|
+
"services": ["docker"]
|
|
162
|
+
}' --ram 4096 --vcpus 4 --start
|
|
163
|
+
|
|
164
|
+
# List VMs
|
|
165
|
+
clonebox list
|
|
166
|
+
|
|
167
|
+
# Start/Stop VM
|
|
168
|
+
clonebox start my-dev-vm
|
|
169
|
+
clonebox stop my-dev-vm
|
|
170
|
+
|
|
171
|
+
# Delete VM
|
|
172
|
+
clonebox delete my-dev-vm
|
|
173
|
+
|
|
174
|
+
# Detect system state (useful for scripting)
|
|
175
|
+
clonebox detect --json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Usage Examples
|
|
179
|
+
|
|
180
|
+
### Python Development Environment
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
clonebox create --name python-dev --config '{
|
|
184
|
+
"paths": {
|
|
185
|
+
"/home/user/my-python-project": "/workspace",
|
|
186
|
+
"/home/user/.pyenv": "/root/.pyenv"
|
|
187
|
+
},
|
|
188
|
+
"packages": ["python3", "python3-pip", "python3-venv", "build-essential"],
|
|
189
|
+
"services": []
|
|
190
|
+
}' --ram 2048 --start
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Docker Development
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
clonebox create --name docker-dev --config '{
|
|
197
|
+
"paths": {
|
|
198
|
+
"/home/user/docker-projects": "/projects",
|
|
199
|
+
"/var/run/docker.sock": "/var/run/docker.sock"
|
|
200
|
+
},
|
|
201
|
+
"packages": ["docker.io", "docker-compose"],
|
|
202
|
+
"services": ["docker"]
|
|
203
|
+
}' --ram 4096 --start
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Full Stack (Node.js + PostgreSQL)
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
clonebox create --name fullstack --config '{
|
|
210
|
+
"paths": {
|
|
211
|
+
"/home/user/my-app": "/app",
|
|
212
|
+
"/home/user/pgdata": "/var/lib/postgresql/data"
|
|
213
|
+
},
|
|
214
|
+
"packages": ["nodejs", "npm", "postgresql"],
|
|
215
|
+
"services": ["postgresql"]
|
|
216
|
+
}' --ram 4096 --vcpus 4 --start
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Inside the VM
|
|
220
|
+
|
|
221
|
+
After the VM boots, mount shared directories:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
# Mount shared paths (9p filesystem)
|
|
225
|
+
sudo mkdir -p /mnt/projects
|
|
226
|
+
sudo mount -t 9p -o trans=virtio,version=9p2000.L mount0 /mnt/projects
|
|
227
|
+
|
|
228
|
+
# Or add to /etc/fstab for permanent mount
|
|
229
|
+
echo "mount0 /mnt/projects 9p trans=virtio,version=9p2000.L 0 0" | sudo tee -a /etc/fstab
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Architecture
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
┌────────────────────────────────────────────────────────┐
|
|
236
|
+
│ HOST SYSTEM │
|
|
237
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
238
|
+
│ │ /home/user/ │ │ /var/www/ │ │ Docker │ │
|
|
239
|
+
│ │ projects/ │ │ html/ │ │ Socket │ │
|
|
240
|
+
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
|
|
241
|
+
│ │ │ │ │
|
|
242
|
+
│ │ 9p/virtio │ │ │
|
|
243
|
+
│ │ bind mounts │ │ │
|
|
244
|
+
│ ┌──────▼─────────────────▼─────────────────▼───────┐ │
|
|
245
|
+
│ │ CloneBox VM │ │
|
|
246
|
+
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
|
|
247
|
+
│ │ │ /mnt/proj │ │ /mnt/www │ │ /var/run/ │ │ │
|
|
248
|
+
│ │ │ │ │ │ │ docker.sock│ │ │
|
|
249
|
+
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
|
|
250
|
+
│ │ │ │
|
|
251
|
+
│ │ cloud-init installed packages & services │ │
|
|
252
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
253
|
+
└────────────────────────────────────────────────────────┘
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Quick Clone (Recommended)
|
|
257
|
+
|
|
258
|
+
The fastest way to clone your current working directory:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Clone current directory - generates .clonebox.yaml and asks to create VM
|
|
262
|
+
clonebox clone .
|
|
263
|
+
|
|
264
|
+
# Clone specific path
|
|
265
|
+
clonebox clone ~/projects/my-app
|
|
266
|
+
|
|
267
|
+
# Clone with custom name and auto-start
|
|
268
|
+
clonebox clone ~/projects/my-app --name my-dev-vm --run
|
|
269
|
+
|
|
270
|
+
# Clone and edit config before creating
|
|
271
|
+
clonebox clone . --edit
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Later, start the VM from any directory with `.clonebox.yaml`:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# Start VM from config in current directory
|
|
278
|
+
clonebox start .
|
|
279
|
+
|
|
280
|
+
# Start VM from specific path
|
|
281
|
+
clonebox start ~/projects/my-app
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Export YAML Config
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Export detected state as YAML (with deduplication)
|
|
288
|
+
clonebox detect --yaml --dedupe
|
|
289
|
+
|
|
290
|
+
# Save to file
|
|
291
|
+
clonebox detect --yaml --dedupe -o my-config.yaml
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Commands Reference
|
|
295
|
+
|
|
296
|
+
| Command | Description |
|
|
297
|
+
|---------|-------------|
|
|
298
|
+
| `clonebox` | Interactive VM creation wizard |
|
|
299
|
+
| `clonebox clone <path>` | Generate `.clonebox.yaml` from path + running processes |
|
|
300
|
+
| `clonebox clone . --run` | Clone and immediately start VM |
|
|
301
|
+
| `clonebox clone . --edit` | Clone, edit config, then create |
|
|
302
|
+
| `clonebox start .` | Start VM from `.clonebox.yaml` in current dir |
|
|
303
|
+
| `clonebox start <name>` | Start existing VM by name |
|
|
304
|
+
| `clonebox stop <name>` | Stop a VM (graceful shutdown) |
|
|
305
|
+
| `clonebox stop -f <name>` | Force stop a VM |
|
|
306
|
+
| `clonebox delete <name>` | Delete VM and storage |
|
|
307
|
+
| `clonebox list` | List all VMs |
|
|
308
|
+
| `clonebox detect` | Show detected services/apps/paths |
|
|
309
|
+
| `clonebox detect --yaml` | Output as YAML config |
|
|
310
|
+
| `clonebox detect --yaml --dedupe` | YAML with duplicates removed |
|
|
311
|
+
| `clonebox detect --json` | Output as JSON |
|
|
312
|
+
|
|
313
|
+
## Requirements
|
|
314
|
+
|
|
315
|
+
- Linux with KVM support (`/dev/kvm`)
|
|
316
|
+
- libvirt daemon running
|
|
317
|
+
- Python 3.8+
|
|
318
|
+
- User in `libvirt` group
|
|
319
|
+
|
|
320
|
+
## Troubleshooting
|
|
321
|
+
|
|
322
|
+
### Network Issues
|
|
323
|
+
|
|
324
|
+
If you encounter "Network not found" or "network 'default' is not active" errors:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Run the network fix script
|
|
328
|
+
./fix-network.sh
|
|
329
|
+
|
|
330
|
+
# Or manually fix:
|
|
331
|
+
virsh --connect qemu:///session net-destroy default 2>/dev/null
|
|
332
|
+
virsh --connect qemu:///session net-undefine default 2>/dev/null
|
|
333
|
+
virsh --connect qemu:///session net-define /tmp/default-network.xml
|
|
334
|
+
virsh --connect qemu:///session net-start default
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Permission Issues
|
|
338
|
+
|
|
339
|
+
If you get permission errors:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
# Ensure user is in libvirt and kvm groups
|
|
343
|
+
sudo usermod -aG libvirt $USER
|
|
344
|
+
sudo usermod -aG kvm $USER
|
|
345
|
+
|
|
346
|
+
# Log out and log back in for groups to take effect
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### VM Already Exists
|
|
350
|
+
|
|
351
|
+
If you get "domain already exists" error:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
# List VMs
|
|
355
|
+
clonebox list
|
|
356
|
+
|
|
357
|
+
# Stop and delete the existing VM
|
|
358
|
+
clonebox delete <vm-name>
|
|
359
|
+
|
|
360
|
+
# Or use virsh directly
|
|
361
|
+
virsh --connect qemu:///session destroy <vm-name>
|
|
362
|
+
virsh --connect qemu:///session undefine <vm-name>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### virt-viewer not found
|
|
366
|
+
|
|
367
|
+
If GUI doesn't open:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Install virt-viewer
|
|
371
|
+
sudo apt install virt-viewer
|
|
372
|
+
|
|
373
|
+
# Then connect manually
|
|
374
|
+
virt-viewer --connect qemu:///session <vm-name>
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## License
|
|
378
|
+
|
|
379
|
+
MIT License - see [LICENSE](LICENSE) file.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
clonebox/__init__.py,sha256=IOk7G0DiSQ33EGbFC0xbnnFB9aou_6yuyFxvycQEvA0,407
|
|
2
|
+
clonebox/cli.py,sha256=tg_tinIH3D6Q1xAjhXu7P4msl2XcxLo8XUHMDxkOFis,31996
|
|
3
|
+
clonebox/cloner.py,sha256=bB37BFYY7_xlfOSdk05zrUsrw7ewItRBMb7EJkYFA_0,19671
|
|
4
|
+
clonebox/detector.py,sha256=Umg4CRJU61yV3a1AvR_0tOfjBMCCIbiQdDAAhlrOL5k,11916
|
|
5
|
+
clonebox-0.1.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
6
|
+
clonebox-0.1.3.dist-info/METADATA,sha256=W6d_Km3nbulNWpl5Z6KctOHciT1o14o4OnAELJMAfbc,11996
|
|
7
|
+
clonebox-0.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
clonebox-0.1.3.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
9
|
+
clonebox-0.1.3.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
10
|
+
clonebox-0.1.3.dist-info/RECORD,,
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: clonebox
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary: Clone your workstation environment to an isolated VM with selective apps, paths and services
|
|
5
|
-
Author: CloneBox Team
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/wronai/clonebox
|
|
8
|
-
Project-URL: Repository, https://github.com/wronai/clonebox
|
|
9
|
-
Project-URL: Issues, https://github.com/wronai/clonebox/issues
|
|
10
|
-
Keywords: vm,virtualization,libvirt,clone,workstation,qemu,kvm
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Environment :: Console
|
|
13
|
-
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: Intended Audience :: System Administrators
|
|
15
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
-
Classifier: Topic :: System :: Systems Administration
|
|
24
|
-
Classifier: Topic :: Utilities
|
|
25
|
-
Requires-Python: >=3.8
|
|
26
|
-
Description-Content-Type: text/markdown
|
|
27
|
-
License-File: LICENSE
|
|
28
|
-
Requires-Dist: libvirt-python>=9.0.0
|
|
29
|
-
Requires-Dist: rich>=13.0.0
|
|
30
|
-
Requires-Dist: questionary>=2.0.0
|
|
31
|
-
Requires-Dist: psutil>=5.9.0
|
|
32
|
-
Requires-Dist: pyyaml>=6.0
|
|
33
|
-
Provides-Extra: dev
|
|
34
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
35
|
-
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
36
|
-
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
37
|
-
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
38
|
-
Dynamic: license-file
|
|
39
|
-
|
|
40
|
-
# clonebox
|
clonebox-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
clonebox/__init__.py,sha256=IOk7G0DiSQ33EGbFC0xbnnFB9aou_6yuyFxvycQEvA0,407
|
|
2
|
-
clonebox/cloner.py,sha256=SamhrCJoJ-k_u4b-yvpNCmr_IHpSrVjlo826lq4fD2M,14523
|
|
3
|
-
clonebox/detector.py,sha256=dwtMg2FybGR79c3xce5PXNfrQgMpH4HK-nEsNfucPms,11835
|
|
4
|
-
clonebox-0.1.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
5
|
-
clonebox-0.1.1.dist-info/METADATA,sha256=NQWcmpWvadnOr-UC3Ge3yYMD5vo7ClIcYUFiAXagxuA,1570
|
|
6
|
-
clonebox-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
7
|
-
clonebox-0.1.1.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
8
|
-
clonebox-0.1.1.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
9
|
-
clonebox-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|