clonebox 0.1.2__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 +7 -2
- clonebox/cloner.py +40 -5
- {clonebox-0.1.2.dist-info → clonebox-0.1.3.dist-info}/METADATA +81 -3
- clonebox-0.1.3.dist-info/RECORD +10 -0
- clonebox-0.1.2.dist-info/RECORD +0 -10
- {clonebox-0.1.2.dist-info → clonebox-0.1.3.dist-info}/WHEEL +0 -0
- {clonebox-0.1.2.dist-info → clonebox-0.1.3.dist-info}/entry_points.txt +0 -0
- {clonebox-0.1.2.dist-info → clonebox-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {clonebox-0.1.2.dist-info → clonebox-0.1.3.dist-info}/top_level.txt +0 -0
clonebox/cli.py
CHANGED
|
@@ -492,7 +492,7 @@ def deduplicate_list(items: list, key=None) -> list:
|
|
|
492
492
|
|
|
493
493
|
|
|
494
494
|
def generate_clonebox_yaml(snapshot, detector, deduplicate: bool = True,
|
|
495
|
-
target_path: str = None, vm_name: str = None) -> str:
|
|
495
|
+
target_path: str = None, vm_name: str = None, network_mode: str = "auto") -> str:
|
|
496
496
|
"""Generate YAML config from system snapshot."""
|
|
497
497
|
sys_info = detector.get_system_info()
|
|
498
498
|
|
|
@@ -559,6 +559,7 @@ def generate_clonebox_yaml(snapshot, detector, deduplicate: bool = True,
|
|
|
559
559
|
"vcpus": vcpus,
|
|
560
560
|
"gui": True,
|
|
561
561
|
"base_image": None,
|
|
562
|
+
"network_mode": network_mode,
|
|
562
563
|
},
|
|
563
564
|
"services": services,
|
|
564
565
|
"packages": [
|
|
@@ -605,6 +606,7 @@ def create_vm_from_config(config: dict, start: bool = False, user_session: bool
|
|
|
605
606
|
packages=config.get("packages", []),
|
|
606
607
|
services=config.get("services", []),
|
|
607
608
|
user_session=user_session,
|
|
609
|
+
network_mode=config["vm"].get("network_mode", "auto"),
|
|
608
610
|
)
|
|
609
611
|
|
|
610
612
|
cloner = SelectiveVMCloner(user_session=user_session)
|
|
@@ -655,7 +657,8 @@ def cmd_clone(args):
|
|
|
655
657
|
snapshot, detector,
|
|
656
658
|
deduplicate=args.dedupe,
|
|
657
659
|
target_path=str(target_path),
|
|
658
|
-
vm_name=vm_name
|
|
660
|
+
vm_name=vm_name,
|
|
661
|
+
network_mode=args.network
|
|
659
662
|
)
|
|
660
663
|
|
|
661
664
|
# Save config file
|
|
@@ -863,6 +866,8 @@ def main():
|
|
|
863
866
|
clone_parser.add_argument("--dedupe", action="store_true", default=True, help="Remove duplicate entries")
|
|
864
867
|
clone_parser.add_argument("--user", "-u", action="store_true",
|
|
865
868
|
help="Use user session (qemu:///session) - no root required, stores in ~/.local/share/libvirt/")
|
|
869
|
+
clone_parser.add_argument("--network", choices=["auto", "default", "user"], default="auto",
|
|
870
|
+
help="Network mode: auto (default), default (libvirt network), user (slirp)")
|
|
866
871
|
clone_parser.set_defaults(func=cmd_clone)
|
|
867
872
|
|
|
868
873
|
args = parser.parse_args()
|
clonebox/cloner.py
CHANGED
|
@@ -32,6 +32,7 @@ class VMConfig:
|
|
|
32
32
|
packages: list = field(default_factory=list)
|
|
33
33
|
services: list = field(default_factory=list)
|
|
34
34
|
user_session: bool = False # Use qemu:///session instead of qemu:///system
|
|
35
|
+
network_mode: str = "auto" # auto|default|user
|
|
35
36
|
|
|
36
37
|
def to_dict(self) -> dict:
|
|
37
38
|
return {
|
|
@@ -91,6 +92,25 @@ class SelectiveVMCloner:
|
|
|
91
92
|
return self.USER_IMAGES_DIR
|
|
92
93
|
return self.SYSTEM_IMAGES_DIR
|
|
93
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
|
+
|
|
94
114
|
def check_prerequisites(self) -> dict:
|
|
95
115
|
"""Check system prerequisites for VM creation."""
|
|
96
116
|
images_dir = self.get_images_dir()
|
|
@@ -123,8 +143,11 @@ class SelectiveVMCloner:
|
|
|
123
143
|
except libvirt.libvirtError:
|
|
124
144
|
checks["network_error"] = (
|
|
125
145
|
"Default network not found or inactive.\n"
|
|
126
|
-
"
|
|
127
|
-
" Or create
|
|
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"
|
|
128
151
|
)
|
|
129
152
|
|
|
130
153
|
# Check images directory
|
|
@@ -215,6 +238,13 @@ class SelectiveVMCloner:
|
|
|
215
238
|
cloudinit_iso = self._create_cloudinit_iso(vm_dir, config)
|
|
216
239
|
log(f"[cyan]☁️ Created cloud-init ISO with {len(config.packages)} packages[/]")
|
|
217
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
|
+
|
|
218
248
|
# Generate VM XML
|
|
219
249
|
vm_xml = self._generate_vm_xml(config, root_disk, cloudinit_iso)
|
|
220
250
|
|
|
@@ -288,9 +318,14 @@ class SelectiveVMCloner:
|
|
|
288
318
|
ET.SubElement(fs, "target", dir=tag)
|
|
289
319
|
|
|
290
320
|
# Network interface
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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")
|
|
294
329
|
|
|
295
330
|
# Serial console
|
|
296
331
|
serial = ET.SubElement(devices, "serial", type="pty")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clonebox
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -65,7 +65,28 @@ CloneBox lets you create isolated virtual machines with only the applications, d
|
|
|
65
65
|
|
|
66
66
|
## Installation
|
|
67
67
|
|
|
68
|
-
###
|
|
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
|
|
69
90
|
|
|
70
91
|
```bash
|
|
71
92
|
# Install libvirt and QEMU/KVM
|
|
@@ -82,7 +103,7 @@ newgrp libvirt
|
|
|
82
103
|
sudo apt install genisoimage
|
|
83
104
|
```
|
|
84
105
|
|
|
85
|
-
|
|
106
|
+
#### Install CloneBox
|
|
86
107
|
|
|
87
108
|
```bash
|
|
88
109
|
# From source
|
|
@@ -296,6 +317,63 @@ clonebox detect --yaml --dedupe -o my-config.yaml
|
|
|
296
317
|
- Python 3.8+
|
|
297
318
|
- User in `libvirt` group
|
|
298
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
|
+
|
|
299
377
|
## License
|
|
300
378
|
|
|
301
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,,
|
clonebox-0.1.2.dist-info/RECORD
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
clonebox/__init__.py,sha256=IOk7G0DiSQ33EGbFC0xbnnFB9aou_6yuyFxvycQEvA0,407
|
|
2
|
-
clonebox/cli.py,sha256=Zk9D99G2Zcaeb0Pw3eNhv0EtLYKPcpE0GyB3QtuhvgQ,31625
|
|
3
|
-
clonebox/cloner.py,sha256=qfMpx7tS5Eozvhi2ZzBc5GY6HLYotncuMakeknHnTwo,18099
|
|
4
|
-
clonebox/detector.py,sha256=Umg4CRJU61yV3a1AvR_0tOfjBMCCIbiQdDAAhlrOL5k,11916
|
|
5
|
-
clonebox-0.1.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
6
|
-
clonebox-0.1.2.dist-info/METADATA,sha256=YtSqXudDO6TrPtAZzbP5fbMvf-PHarUpkY55ZGpHplw,10374
|
|
7
|
-
clonebox-0.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
-
clonebox-0.1.2.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
9
|
-
clonebox-0.1.2.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
10
|
-
clonebox-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|