clonebox 0.1.9__py3-none-any.whl → 0.1.10__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 +307 -1
- clonebox/cloner.py +2 -1
- {clonebox-0.1.9.dist-info → clonebox-0.1.10.dist-info}/METADATA +290 -8
- clonebox-0.1.10.dist-info/RECORD +11 -0
- clonebox-0.1.9.dist-info/RECORD +0 -11
- {clonebox-0.1.9.dist-info → clonebox-0.1.10.dist-info}/WHEEL +0 -0
- {clonebox-0.1.9.dist-info → clonebox-0.1.10.dist-info}/entry_points.txt +0 -0
- {clonebox-0.1.9.dist-info → clonebox-0.1.10.dist-info}/licenses/LICENSE +0 -0
- {clonebox-0.1.9.dist-info → clonebox-0.1.10.dist-info}/top_level.txt +0 -0
clonebox/cli.py
CHANGED
|
@@ -445,7 +445,72 @@ def cmd_start(args):
|
|
|
445
445
|
return
|
|
446
446
|
|
|
447
447
|
cloner = SelectiveVMCloner(user_session=getattr(args, "user", False))
|
|
448
|
-
|
|
448
|
+
open_viewer = getattr(args, "viewer", False) or not getattr(args, "no_viewer", False)
|
|
449
|
+
cloner.start_vm(name, open_viewer=open_viewer, console=console)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def cmd_open(args):
|
|
453
|
+
"""Open VM viewer window."""
|
|
454
|
+
import subprocess
|
|
455
|
+
|
|
456
|
+
name = args.name
|
|
457
|
+
user_session = getattr(args, "user", False)
|
|
458
|
+
conn_uri = "qemu:///session" if user_session else "qemu:///system"
|
|
459
|
+
|
|
460
|
+
# If name is a path, load config
|
|
461
|
+
if name and (name.startswith(".") or name.startswith("/") or name.startswith("~")):
|
|
462
|
+
target_path = Path(name).expanduser().resolve()
|
|
463
|
+
config_file = target_path / ".clonebox.yaml" if target_path.is_dir() else target_path
|
|
464
|
+
if config_file.exists():
|
|
465
|
+
config = load_clonebox_config(config_file)
|
|
466
|
+
name = config["vm"]["name"]
|
|
467
|
+
else:
|
|
468
|
+
console.print(f"[red]❌ Config not found: {config_file}[/]")
|
|
469
|
+
return
|
|
470
|
+
elif name == "." or not name:
|
|
471
|
+
config_file = Path.cwd() / ".clonebox.yaml"
|
|
472
|
+
if config_file.exists():
|
|
473
|
+
config = load_clonebox_config(config_file)
|
|
474
|
+
name = config["vm"]["name"]
|
|
475
|
+
else:
|
|
476
|
+
console.print("[red]❌ No VM name specified and no .clonebox.yaml in current directory[/]")
|
|
477
|
+
console.print("[dim]Usage: clonebox open <vm-name> or clonebox open .[/]")
|
|
478
|
+
return
|
|
479
|
+
|
|
480
|
+
# Check if VM is running
|
|
481
|
+
try:
|
|
482
|
+
result = subprocess.run(
|
|
483
|
+
["virsh", "--connect", conn_uri, "domstate", name],
|
|
484
|
+
capture_output=True, text=True, timeout=10
|
|
485
|
+
)
|
|
486
|
+
state = result.stdout.strip()
|
|
487
|
+
|
|
488
|
+
if state != "running":
|
|
489
|
+
console.print(f"[yellow]⚠️ VM '{name}' is not running (state: {state})[/]")
|
|
490
|
+
if questionary.confirm(
|
|
491
|
+
f"Start VM '{name}' and open viewer?", default=True, style=custom_style
|
|
492
|
+
).ask():
|
|
493
|
+
cloner = SelectiveVMCloner(user_session=user_session)
|
|
494
|
+
cloner.start_vm(name, open_viewer=True, console=console)
|
|
495
|
+
else:
|
|
496
|
+
console.print("[dim]Use 'clonebox start' to start the VM first.[/]")
|
|
497
|
+
return
|
|
498
|
+
except Exception as e:
|
|
499
|
+
console.print(f"[red]❌ Error checking VM state: {e}[/]")
|
|
500
|
+
return
|
|
501
|
+
|
|
502
|
+
# Open virt-viewer
|
|
503
|
+
console.print(f"[cyan]Opening viewer for VM: {name}[/]")
|
|
504
|
+
try:
|
|
505
|
+
subprocess.run(
|
|
506
|
+
["virt-viewer", "--connect", conn_uri, name],
|
|
507
|
+
check=True
|
|
508
|
+
)
|
|
509
|
+
except FileNotFoundError:
|
|
510
|
+
console.print("[red]❌ virt-viewer not found[/]")
|
|
511
|
+
console.print("Install with: sudo apt install virt-viewer")
|
|
512
|
+
except subprocess.CalledProcessError as e:
|
|
513
|
+
console.print(f"[red]❌ Failed to open viewer: {e}[/]")
|
|
449
514
|
|
|
450
515
|
|
|
451
516
|
def cmd_stop(args):
|
|
@@ -924,6 +989,217 @@ def cmd_import(args):
|
|
|
924
989
|
shutil.rmtree(temp_dir)
|
|
925
990
|
|
|
926
991
|
|
|
992
|
+
def cmd_test(args):
|
|
993
|
+
"""Test VM configuration and health."""
|
|
994
|
+
import subprocess
|
|
995
|
+
import json
|
|
996
|
+
|
|
997
|
+
name = args.name
|
|
998
|
+
user_session = getattr(args, "user", False)
|
|
999
|
+
quick = getattr(args, "quick", False)
|
|
1000
|
+
verbose = getattr(args, "verbose", False)
|
|
1001
|
+
conn_uri = "qemu:///session" if user_session else "qemu:///system"
|
|
1002
|
+
|
|
1003
|
+
# If name is a path, load config
|
|
1004
|
+
if name and (name.startswith(".") or name.startswith("/") or name.startswith("~")):
|
|
1005
|
+
target_path = Path(name).expanduser().resolve()
|
|
1006
|
+
config_file = target_path / ".clonebox.yaml" if target_path.is_dir() else target_path
|
|
1007
|
+
if not config_file.exists():
|
|
1008
|
+
console.print(f"[red]❌ Config not found: {config_file}[/]")
|
|
1009
|
+
return
|
|
1010
|
+
else:
|
|
1011
|
+
config_file = Path.cwd() / ".clonebox.yaml"
|
|
1012
|
+
if not config_file.exists():
|
|
1013
|
+
console.print("[red]❌ No .clonebox.yaml found in current directory[/]")
|
|
1014
|
+
return
|
|
1015
|
+
|
|
1016
|
+
console.print(f"[bold cyan]🧪 Testing VM configuration: {config_file}[/]\n")
|
|
1017
|
+
|
|
1018
|
+
# Load config
|
|
1019
|
+
try:
|
|
1020
|
+
config = load_clonebox_config(config_file)
|
|
1021
|
+
vm_name = config["vm"]["name"]
|
|
1022
|
+
console.print(f"[green]✅ Config loaded successfully[/]")
|
|
1023
|
+
console.print(f" VM Name: {vm_name}")
|
|
1024
|
+
console.print(f" RAM: {config['vm']['ram_mb']}MB")
|
|
1025
|
+
console.print(f" vCPUs: {config['vm']['vcpus']}")
|
|
1026
|
+
console.print(f" GUI: {'Yes' if config['vm']['gui'] else 'No'}")
|
|
1027
|
+
except Exception as e:
|
|
1028
|
+
console.print(f"[red]❌ Failed to load config: {e}[/]")
|
|
1029
|
+
return
|
|
1030
|
+
|
|
1031
|
+
console.print()
|
|
1032
|
+
|
|
1033
|
+
# Test 1: Check VM exists
|
|
1034
|
+
console.print("[bold]1. VM Existence Check[/]")
|
|
1035
|
+
try:
|
|
1036
|
+
result = subprocess.run(
|
|
1037
|
+
["virsh", "--connect", conn_uri, "dominfo", vm_name],
|
|
1038
|
+
capture_output=True, text=True, timeout=10
|
|
1039
|
+
)
|
|
1040
|
+
if result.returncode == 0:
|
|
1041
|
+
console.print("[green]✅ VM is defined in libvirt[/]")
|
|
1042
|
+
if verbose:
|
|
1043
|
+
for line in result.stdout.split('\n'):
|
|
1044
|
+
if ':' in line:
|
|
1045
|
+
console.print(f" {line}")
|
|
1046
|
+
else:
|
|
1047
|
+
console.print("[red]❌ VM not found in libvirt[/]")
|
|
1048
|
+
console.print(" Run: clonebox create .clonebox.yaml --start")
|
|
1049
|
+
return
|
|
1050
|
+
except Exception as e:
|
|
1051
|
+
console.print(f"[red]❌ Error checking VM: {e}[/]")
|
|
1052
|
+
return
|
|
1053
|
+
|
|
1054
|
+
console.print()
|
|
1055
|
+
|
|
1056
|
+
# Test 2: Check VM state
|
|
1057
|
+
console.print("[bold]2. VM State Check[/]")
|
|
1058
|
+
try:
|
|
1059
|
+
result = subprocess.run(
|
|
1060
|
+
["virsh", "--connect", conn_uri, "domstate", vm_name],
|
|
1061
|
+
capture_output=True, text=True, timeout=10
|
|
1062
|
+
)
|
|
1063
|
+
state = result.stdout.strip()
|
|
1064
|
+
if state == "running":
|
|
1065
|
+
console.print("[green]✅ VM is running[/]")
|
|
1066
|
+
|
|
1067
|
+
# Test network if running
|
|
1068
|
+
console.print("\n Checking network...")
|
|
1069
|
+
try:
|
|
1070
|
+
result = subprocess.run(
|
|
1071
|
+
["virsh", "--connect", conn_uri, "domifaddr", vm_name],
|
|
1072
|
+
capture_output=True, text=True, timeout=10
|
|
1073
|
+
)
|
|
1074
|
+
if "192.168" in result.stdout or "10.0" in result.stdout:
|
|
1075
|
+
console.print("[green]✅ VM has network access[/]")
|
|
1076
|
+
if verbose:
|
|
1077
|
+
for line in result.stdout.split('\n'):
|
|
1078
|
+
if '192.168' in line or '10.0' in line:
|
|
1079
|
+
console.print(f" IP: {line.split()[-1]}")
|
|
1080
|
+
else:
|
|
1081
|
+
console.print("[yellow]⚠️ No IP address detected[/]")
|
|
1082
|
+
except:
|
|
1083
|
+
console.print("[yellow]⚠️ Could not check network[/]")
|
|
1084
|
+
else:
|
|
1085
|
+
console.print(f"[yellow]⚠️ VM is not running (state: {state})[/]")
|
|
1086
|
+
console.print(" Run: clonebox start .")
|
|
1087
|
+
except Exception as e:
|
|
1088
|
+
console.print(f"[red]❌ Error checking VM state: {e}[/]")
|
|
1089
|
+
|
|
1090
|
+
console.print()
|
|
1091
|
+
|
|
1092
|
+
# Test 3: Check cloud-init status (if running)
|
|
1093
|
+
if not quick and state == "running":
|
|
1094
|
+
console.print("[bold]3. Cloud-init Status[/]")
|
|
1095
|
+
try:
|
|
1096
|
+
# Try to get cloud-init status via QEMU guest agent
|
|
1097
|
+
result = subprocess.run(
|
|
1098
|
+
["virsh", "--connect", conn_uri, "qemu-agent-command", vm_name,
|
|
1099
|
+
'{"execute":"guest-exec","arguments":{"path":"cloud-init","arg":["status"],"capture-output":true}}'],
|
|
1100
|
+
capture_output=True, text=True, timeout=15
|
|
1101
|
+
)
|
|
1102
|
+
if result.returncode == 0:
|
|
1103
|
+
try:
|
|
1104
|
+
response = json.loads(result.stdout)
|
|
1105
|
+
if "return" in response:
|
|
1106
|
+
pid = response["return"]["pid"]
|
|
1107
|
+
# Get output
|
|
1108
|
+
result2 = subprocess.run(
|
|
1109
|
+
["virsh", "--connect", conn_uri, "qemu-agent-command", vm_name,
|
|
1110
|
+
f'{{"execute":"guest-exec-status","arguments":{"pid":{pid}}}}'],
|
|
1111
|
+
capture_output=True, text=True, timeout=15
|
|
1112
|
+
)
|
|
1113
|
+
if result2.returncode == 0:
|
|
1114
|
+
resp2 = json.loads(result2.stdout)
|
|
1115
|
+
if "return" in resp2 and resp2["return"]["exited"]:
|
|
1116
|
+
output = resp2["return"]["out-data"]
|
|
1117
|
+
if output:
|
|
1118
|
+
import base64
|
|
1119
|
+
status = base64.b64decode(output).decode()
|
|
1120
|
+
if "done" in status.lower():
|
|
1121
|
+
console.print("[green]✅ Cloud-init completed[/]")
|
|
1122
|
+
elif "running" in status.lower():
|
|
1123
|
+
console.print("[yellow]⚠️ Cloud-init still running[/]")
|
|
1124
|
+
else:
|
|
1125
|
+
console.print(f"[yellow]⚠️ Cloud-init status: {status.strip()}[/]")
|
|
1126
|
+
except:
|
|
1127
|
+
pass
|
|
1128
|
+
except:
|
|
1129
|
+
console.print("[yellow]⚠️ Could not check cloud-init (QEMU agent may not be running)[/]")
|
|
1130
|
+
|
|
1131
|
+
console.print()
|
|
1132
|
+
|
|
1133
|
+
# Test 4: Check mounts (if running)
|
|
1134
|
+
if not quick and state == "running":
|
|
1135
|
+
console.print("[bold]4. Mount Points Check[/]")
|
|
1136
|
+
all_paths = config.get("paths", {}).copy()
|
|
1137
|
+
all_paths.update(config.get("app_data_paths", {}))
|
|
1138
|
+
|
|
1139
|
+
if all_paths:
|
|
1140
|
+
for idx, (host_path, guest_path) in enumerate(all_paths.items()):
|
|
1141
|
+
try:
|
|
1142
|
+
result = subprocess.run(
|
|
1143
|
+
["virsh", "--connect", conn_uri, "qemu-agent-command", vm_name,
|
|
1144
|
+
f'{{"execute":"guest-exec","arguments":{{"path":"test","arg":["-d","{guest_path}"],"capture-output":true}}}}'],
|
|
1145
|
+
capture_output=True, text=True, timeout=10
|
|
1146
|
+
)
|
|
1147
|
+
if result.returncode == 0:
|
|
1148
|
+
try:
|
|
1149
|
+
response = json.loads(result.stdout)
|
|
1150
|
+
if "return" in response:
|
|
1151
|
+
pid = response["return"]["pid"]
|
|
1152
|
+
result2 = subprocess.run(
|
|
1153
|
+
["virsh", "--connect", conn_uri, "qemu-agent-command", vm_name,
|
|
1154
|
+
f'{{"execute":"guest-exec-status","arguments":{"pid":{pid}}}}'],
|
|
1155
|
+
capture_output=True, text=True, timeout=10
|
|
1156
|
+
)
|
|
1157
|
+
if result2.returncode == 0:
|
|
1158
|
+
resp2 = json.loads(result2.stdout)
|
|
1159
|
+
if "return" in resp2 and resp2["return"]["exited"]:
|
|
1160
|
+
exit_code = resp2["return"]["exitcode"]
|
|
1161
|
+
if exit_code == 0:
|
|
1162
|
+
console.print(f"[green]✅ {guest_path}[/]")
|
|
1163
|
+
else:
|
|
1164
|
+
console.print(f"[red]❌ {guest_path} (not accessible)[/]")
|
|
1165
|
+
continue
|
|
1166
|
+
except:
|
|
1167
|
+
pass
|
|
1168
|
+
console.print(f"[yellow]⚠️ {guest_path} (unknown)[/]")
|
|
1169
|
+
except:
|
|
1170
|
+
console.print(f"[yellow]⚠️ {guest_path} (could not check)[/]")
|
|
1171
|
+
else:
|
|
1172
|
+
console.print("[dim]No mount points configured[/]")
|
|
1173
|
+
|
|
1174
|
+
console.print()
|
|
1175
|
+
|
|
1176
|
+
# Test 5: Run health check (if running and not quick)
|
|
1177
|
+
if not quick and state == "running":
|
|
1178
|
+
console.print("[bold]5. Health Check[/]")
|
|
1179
|
+
try:
|
|
1180
|
+
result = subprocess.run(
|
|
1181
|
+
["virsh", "--connect", conn_uri, "qemu-agent-command", vm_name,
|
|
1182
|
+
'{"execute":"guest-exec","arguments":{"path":"/usr/local/bin/clonebox-health","capture-output":true}}'],
|
|
1183
|
+
capture_output=True, text=True, timeout=60
|
|
1184
|
+
)
|
|
1185
|
+
if result.returncode == 0:
|
|
1186
|
+
console.print("[green]✅ Health check triggered[/]")
|
|
1187
|
+
console.print(" View results in VM: cat /var/log/clonebox-health.log")
|
|
1188
|
+
else:
|
|
1189
|
+
console.print("[yellow]⚠️ Health check script not found[/]")
|
|
1190
|
+
console.print(" VM may not have been created with health checks")
|
|
1191
|
+
except Exception as e:
|
|
1192
|
+
console.print(f"[yellow]⚠️ Could not run health check: {e}[/]")
|
|
1193
|
+
|
|
1194
|
+
console.print()
|
|
1195
|
+
|
|
1196
|
+
# Summary
|
|
1197
|
+
console.print("[bold]Test Summary[/]")
|
|
1198
|
+
console.print("VM configuration is valid and VM is accessible.")
|
|
1199
|
+
console.print("\n[dim]For detailed health report, run in VM:[/]")
|
|
1200
|
+
console.print("[dim] cat /var/log/clonebox-health.log[/]")
|
|
1201
|
+
|
|
1202
|
+
|
|
927
1203
|
CLONEBOX_CONFIG_FILE = ".clonebox.yaml"
|
|
928
1204
|
CLONEBOX_ENV_FILE = ".env"
|
|
929
1205
|
|
|
@@ -1527,6 +1803,7 @@ def main():
|
|
|
1527
1803
|
"name", nargs="?", default=None, help="VM name or '.' to use .clonebox.yaml"
|
|
1528
1804
|
)
|
|
1529
1805
|
start_parser.add_argument("--no-viewer", action="store_true", help="Don't open virt-viewer")
|
|
1806
|
+
start_parser.add_argument("--viewer", action="store_true", help="Open virt-viewer GUI")
|
|
1530
1807
|
start_parser.add_argument(
|
|
1531
1808
|
"-u",
|
|
1532
1809
|
"--user",
|
|
@@ -1535,6 +1812,19 @@ def main():
|
|
|
1535
1812
|
)
|
|
1536
1813
|
start_parser.set_defaults(func=cmd_start)
|
|
1537
1814
|
|
|
1815
|
+
# Open command - open VM viewer
|
|
1816
|
+
open_parser = subparsers.add_parser("open", help="Open VM viewer window")
|
|
1817
|
+
open_parser.add_argument(
|
|
1818
|
+
"name", nargs="?", default=None, help="VM name or '.' to use .clonebox.yaml"
|
|
1819
|
+
)
|
|
1820
|
+
open_parser.add_argument(
|
|
1821
|
+
"-u",
|
|
1822
|
+
"--user",
|
|
1823
|
+
action="store_true",
|
|
1824
|
+
help="Use user session (qemu:///session) - no root required",
|
|
1825
|
+
)
|
|
1826
|
+
open_parser.set_defaults(func=cmd_open)
|
|
1827
|
+
|
|
1538
1828
|
# Stop command
|
|
1539
1829
|
stop_parser = subparsers.add_parser("stop", help="Stop a VM")
|
|
1540
1830
|
stop_parser.add_argument("name", help="VM name")
|
|
@@ -1660,6 +1950,22 @@ def main():
|
|
|
1660
1950
|
)
|
|
1661
1951
|
import_parser.set_defaults(func=cmd_import)
|
|
1662
1952
|
|
|
1953
|
+
# Test command - validate VM configuration
|
|
1954
|
+
test_parser = subparsers.add_parser("test", help="Test VM configuration and health")
|
|
1955
|
+
test_parser.add_argument(
|
|
1956
|
+
"name", nargs="?", default=None, help="VM name or '.' to use .clonebox.yaml"
|
|
1957
|
+
)
|
|
1958
|
+
test_parser.add_argument(
|
|
1959
|
+
"-u", "--user", action="store_true", help="Use user session (qemu:///session)"
|
|
1960
|
+
)
|
|
1961
|
+
test_parser.add_argument(
|
|
1962
|
+
"--quick", action="store_true", help="Quick test (no deep health checks)"
|
|
1963
|
+
)
|
|
1964
|
+
test_parser.add_argument(
|
|
1965
|
+
"--verbose", "-v", action="store_true", help="Verbose output"
|
|
1966
|
+
)
|
|
1967
|
+
test_parser.set_defaults(func=cmd_test)
|
|
1968
|
+
|
|
1663
1969
|
args = parser.parse_args()
|
|
1664
1970
|
|
|
1665
1971
|
if hasattr(args, "func"):
|
clonebox/cloner.py
CHANGED
|
@@ -417,9 +417,10 @@ class SelectiveVMCloner:
|
|
|
417
417
|
ET.SubElement(cdrom, "readonly")
|
|
418
418
|
|
|
419
419
|
# 9p filesystem mounts (bind mounts from host)
|
|
420
|
+
# Use accessmode="mapped" to allow VM user to access host files regardless of UID
|
|
420
421
|
for idx, (host_path, guest_tag) in enumerate(config.paths.items()):
|
|
421
422
|
if Path(host_path).exists():
|
|
422
|
-
fs = ET.SubElement(devices, "filesystem", type="mount", accessmode="
|
|
423
|
+
fs = ET.SubElement(devices, "filesystem", type="mount", accessmode="mapped")
|
|
423
424
|
ET.SubElement(fs, "driver", type="path", wrpolicy="immediate")
|
|
424
425
|
ET.SubElement(fs, "source", dir=host_path)
|
|
425
426
|
# Use simple tag names for 9p mounts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clonebox
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
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
|
|
@@ -64,6 +64,41 @@ CloneBox lets you create isolated virtual machines with only the applications, d
|
|
|
64
64
|
- 🖥️ **GUI support** - SPICE graphics with virt-viewer integration
|
|
65
65
|
- ⚡ **Fast creation** - No full disk cloning, VMs are ready in seconds
|
|
66
66
|
- 📥 **Auto-download** - Automatically downloads and caches Ubuntu cloud images (stored in ~/Downloads)
|
|
67
|
+
- 📊 **Health monitoring** - Built-in health checks for packages, services, and mounts
|
|
68
|
+
- 🔄 **VM migration** - Export/import VMs with data between workstations
|
|
69
|
+
- 🧪 **Configuration testing** - Validate VM settings and functionality
|
|
70
|
+
- 📁 **App data sync** - Include browser profiles, IDE settings, and app configs
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
CloneBox to narzędzie CLI do **szybkiego klonowania aktualnego środowiska workstation do izolowanej maszyny wirtualnej (VM)**.
|
|
76
|
+
Zamiast pełnego kopiowania dysku, używa **bind mounts** (udostępnianie katalogów na żywo) i **cloud-init** do selektywnego przeniesienia tylko potrzebnych elementów: uruchomionych usług (Docker, PostgreSQL, nginx), aplikacji, ścieżek projektów i konfiguracji. Automatycznie pobiera obrazy Ubuntu, instaluje pakiety i uruchamia VM z SPICE GUI. Idealne dla deweloperów na Linuxie – VM powstaje w minuty, bez duplikowania danych.
|
|
77
|
+
|
|
78
|
+
Kluczowe komendy:
|
|
79
|
+
- `clonebox` – interaktywny wizard (detect + create + start)
|
|
80
|
+
- `clonebox detect` – skanuje usługi/apps/ścieżki
|
|
81
|
+
- `clonebox clone . --user --run` – szybki klon bieżącego katalogu z użytkownikiem i autostartem
|
|
82
|
+
|
|
83
|
+
### Dlaczego wirtualne klony workstation mają sens?
|
|
84
|
+
|
|
85
|
+
**Problem**: Developerzy/Vibecoderzy nie izolują środowisk dev/test (np. dla AI agentów), bo ręczne odtwarzanie setupu to ból – godziny na instalację apps, usług, configów, dotfiles. Przechodzenie z fizycznego PC na VM wymagałoby pełnego rebuilda, co blokuje workflow.
|
|
86
|
+
|
|
87
|
+
**Rozwiązanie CloneBox**: Automatycznie **skanuje i klonuje stan "tu i teraz"** (usługi z `ps`, dockery z `docker ps`, projekty z git/.env). VM dziedziczy środowisko bez kopiowania całego śmietnika – tylko wybrane bind mounty.
|
|
88
|
+
|
|
89
|
+
**Korzyści w twoim kontekście (embedded/distributed systems, AI automation)**:
|
|
90
|
+
- **Sandbox dla eksperymentów**: Testuj AI agenty, edge computing (RPi/ESP32 symulacje) czy Camel/ERP integracje w izolacji, bez psucia hosta.
|
|
91
|
+
- **Reprodukcja workstation**: Na firmowym PC masz setup z domu (Python/Rust/Go envs, Docker compose, Postgres dev DB) – klonujesz i pracujesz identycznie.
|
|
92
|
+
- **Szybkość > dotfiles**: Dotfiles odtwarzają configi, ale nie łapią runtime stanu (uruchomione serwery, otwarte projekty). CloneBox to "snapshot na sterydach".
|
|
93
|
+
- **Bezpieczeństwo/cost-optymalizacja**: Izolacja od plików hosta (tylko mounts), zero downtime, tanie w zasobach (libvirt/QEMU). Dla SME: szybki onboarding dev env bez migracji fizycznej.
|
|
94
|
+
- **AI-friendly**: Agenci LLMs (jak te z twoich hobby) mogą działać w VM z pełnym kontekstem, bez ryzyka "zasmiecania" main PC.
|
|
95
|
+
|
|
96
|
+
Przykład: Masz uruchomiony Kubernetes Podman z twoim home labem + projekt automotive leasing. `clonebox clone ~/projects --run` → VM gotowa w 30s, z tymi samymi serwisami, ale izolowana. Lepsze niż Docker (brak GUI/full OS) czy pełna migracja.
|
|
97
|
+
|
|
98
|
+
**Dlaczego ludzie tego nie robią?** Brak automatyzacji – nikt nie chce ręcznie rebuildować.
|
|
99
|
+
- CloneBox rozwiązuje to jednym poleceniem. Super match dla twoich interesów (distributed infra, AI tools, business automation).
|
|
100
|
+
|
|
101
|
+
|
|
67
102
|
|
|
68
103
|
## Installation
|
|
69
104
|
|
|
@@ -142,7 +177,6 @@ Simply run `clonebox` to start the interactive wizard:
|
|
|
142
177
|
```bash
|
|
143
178
|
clonebox
|
|
144
179
|
clonebox clone . --user --run --replace --base-image ~/ubuntu-22.04-cloud.qcow2
|
|
145
|
-
|
|
146
180
|
```
|
|
147
181
|
|
|
148
182
|
The wizard will:
|
|
@@ -181,6 +215,130 @@ clonebox detect --json
|
|
|
181
215
|
|
|
182
216
|
## Usage Examples
|
|
183
217
|
|
|
218
|
+
### Basic Workflow
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# 1. Clone current directory with auto-detection
|
|
222
|
+
clonebox clone . --user
|
|
223
|
+
|
|
224
|
+
# 2. Review generated config
|
|
225
|
+
cat .clonebox.yaml
|
|
226
|
+
|
|
227
|
+
# 3. Create and start VM
|
|
228
|
+
clonebox start . --user --viewer
|
|
229
|
+
|
|
230
|
+
# 4. Check VM status
|
|
231
|
+
clonebox status . --user
|
|
232
|
+
|
|
233
|
+
# 5. Open VM window later
|
|
234
|
+
clonebox open . --user
|
|
235
|
+
|
|
236
|
+
# 6. Stop VM when done
|
|
237
|
+
clonebox stop . --user
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Development Environment with Browser Profiles
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
# Clone with app data (browser profiles, IDE settings)
|
|
244
|
+
clonebox clone . --user --run
|
|
245
|
+
|
|
246
|
+
# VM will have:
|
|
247
|
+
# - All your project directories
|
|
248
|
+
# - Browser profiles (Chrome, Firefox) with bookmarks and passwords
|
|
249
|
+
# - IDE settings (PyCharm, VSCode)
|
|
250
|
+
# - Docker containers and services
|
|
251
|
+
|
|
252
|
+
# Access in VM:
|
|
253
|
+
ls ~/.config/google-chrome # Chrome profile
|
|
254
|
+
ls ~/.mozilla/firefox # Firefox profile
|
|
255
|
+
ls ~/.config/JetBrains # PyCharm settings
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Testing VM Configuration
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
# Quick test - basic checks
|
|
262
|
+
clonebox test . --user --quick
|
|
263
|
+
|
|
264
|
+
# Full test with verbose output
|
|
265
|
+
clonebox test . --user --verbose
|
|
266
|
+
|
|
267
|
+
# Test output shows:
|
|
268
|
+
# ✅ VM is defined in libvirt
|
|
269
|
+
# ✅ VM is running
|
|
270
|
+
# ✅ VM has network access (IP: 192.168.122.89)
|
|
271
|
+
# ✅ Cloud-init completed
|
|
272
|
+
# ✅ All mount points accessible
|
|
273
|
+
# ✅ Health check triggered
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### VM Health Monitoring
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Check overall status
|
|
280
|
+
clonebox status . --user
|
|
281
|
+
|
|
282
|
+
# Output:
|
|
283
|
+
# 📊 Checking VM status: clone-clonebox
|
|
284
|
+
# ✅ VM State: running
|
|
285
|
+
# ✅ VM has network access
|
|
286
|
+
# ☁️ Cloud-init: Still running (packages installing)
|
|
287
|
+
# 🏥 Health Check Status... ⏳ Health check not yet run
|
|
288
|
+
|
|
289
|
+
# Trigger health check
|
|
290
|
+
clonebox status . --user --health
|
|
291
|
+
|
|
292
|
+
# View detailed health report in VM:
|
|
293
|
+
# cat /var/log/clonebox-health.log
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Export/Import Workflow
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# On workstation A - Export VM with all data
|
|
300
|
+
clonebox export . --user --include-data -o my-dev-env.tar.gz
|
|
301
|
+
|
|
302
|
+
# Transfer file to workstation B, then import
|
|
303
|
+
clonebox import my-dev-env.tar.gz --user
|
|
304
|
+
|
|
305
|
+
# Start VM on new workstation
|
|
306
|
+
clonebox start . --user
|
|
307
|
+
clonebox open . --user
|
|
308
|
+
|
|
309
|
+
# VM includes:
|
|
310
|
+
# - Complete disk image
|
|
311
|
+
# - All browser profiles and settings
|
|
312
|
+
# - Project files
|
|
313
|
+
# - Docker images and containers
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Troubleshooting Common Issues
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# If mounts are empty after reboot:
|
|
320
|
+
clonebox status . --user # Check VM status
|
|
321
|
+
# Then in VM:
|
|
322
|
+
sudo mount -a # Remount all fstab entries
|
|
323
|
+
|
|
324
|
+
# If browser profiles don't sync:
|
|
325
|
+
rm .clonebox.yaml
|
|
326
|
+
clonebox clone . --user --run --replace
|
|
327
|
+
|
|
328
|
+
# If GUI doesn't open:
|
|
329
|
+
clonebox open . --user # Easiest way
|
|
330
|
+
# or:
|
|
331
|
+
virt-viewer --connect qemu:///session clone-clonebox
|
|
332
|
+
|
|
333
|
+
# Check VM details:
|
|
334
|
+
clonebox list # List all VMs
|
|
335
|
+
virsh --connect qemu:///session dominfo clone-clonebox
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Legacy Examples (Manual Config)
|
|
339
|
+
|
|
340
|
+
These examples use the older `create` command with manual JSON config. For most users, the `clone` command with auto-detection is easier.
|
|
341
|
+
|
|
184
342
|
### Python Development Environment
|
|
185
343
|
|
|
186
344
|
```bash
|
|
@@ -222,17 +380,36 @@ clonebox create --name fullstack --config '{
|
|
|
222
380
|
|
|
223
381
|
## Inside the VM
|
|
224
382
|
|
|
225
|
-
After the VM boots,
|
|
383
|
+
After the VM boots, shared directories are automatically mounted via fstab entries. You can check their status:
|
|
226
384
|
|
|
227
385
|
```bash
|
|
228
|
-
#
|
|
229
|
-
|
|
230
|
-
sudo mount -t 9p -o trans=virtio,version=9p2000.L mount0 /mnt/projects
|
|
386
|
+
# Check mount status
|
|
387
|
+
mount | grep 9p
|
|
231
388
|
|
|
232
|
-
#
|
|
233
|
-
|
|
389
|
+
# View health check report
|
|
390
|
+
cat /var/log/clonebox-health.log
|
|
391
|
+
|
|
392
|
+
# Re-run health check manually
|
|
393
|
+
clonebox-health
|
|
394
|
+
|
|
395
|
+
# Check cloud-init status
|
|
396
|
+
sudo cloud-init status
|
|
397
|
+
|
|
398
|
+
# Manual mount (if needed)
|
|
399
|
+
sudo mkdir -p /mnt/projects
|
|
400
|
+
sudo mount -t 9p -o trans=virtio,version=9p2000.L,nofail mount0 /mnt/projects
|
|
234
401
|
```
|
|
235
402
|
|
|
403
|
+
### Health Check System
|
|
404
|
+
|
|
405
|
+
CloneBox includes automated health checks that verify:
|
|
406
|
+
- Package installation (apt/snap)
|
|
407
|
+
- Service status
|
|
408
|
+
- Mount points accessibility
|
|
409
|
+
- GUI readiness
|
|
410
|
+
|
|
411
|
+
Health check logs are saved to `/var/log/clonebox-health.log` with a summary in `/var/log/clonebox-health-status`.
|
|
412
|
+
|
|
236
413
|
## Architecture
|
|
237
414
|
|
|
238
415
|
```
|
|
@@ -399,6 +576,7 @@ clonebox clone . --network auto
|
|
|
399
576
|
| `clonebox clone . --network user` | Use user-mode networking (slirp) |
|
|
400
577
|
| `clonebox clone . --network auto` | Auto-detect network mode (default) |
|
|
401
578
|
| `clonebox start .` | Start VM from `.clonebox.yaml` in current dir |
|
|
579
|
+
| `clonebox start . --viewer` | Start VM and open GUI window |
|
|
402
580
|
| `clonebox start <name>` | Start existing VM by name |
|
|
403
581
|
| `clonebox stop <name>` | Stop a VM (graceful shutdown) |
|
|
404
582
|
| `clonebox stop -f <name>` | Force stop a VM |
|
|
@@ -408,6 +586,14 @@ clonebox clone . --network auto
|
|
|
408
586
|
| `clonebox detect --yaml` | Output as YAML config |
|
|
409
587
|
| `clonebox detect --yaml --dedupe` | YAML with duplicates removed |
|
|
410
588
|
| `clonebox detect --json` | Output as JSON |
|
|
589
|
+
| `clonebox status . --user` | Check VM health, cloud-init status, and IP address |
|
|
590
|
+
| `clonebox test . --user` | Test VM configuration and validate all settings |
|
|
591
|
+
| `clonebox export . --user` | Export VM for migration to another workstation |
|
|
592
|
+
| `clonebox export . --user --include-data` | Export VM with browser profiles and configs |
|
|
593
|
+
| `clonebox import archive.tar.gz --user` | Import VM from export archive |
|
|
594
|
+
| `clonebox open . --user` | Open GUI viewer for VM (same as virt-viewer) |
|
|
595
|
+
| `virt-viewer --connect qemu:///session <vm>` | Open GUI for running VM |
|
|
596
|
+
| `virsh --connect qemu:///session console <vm>` | Open text console (Ctrl+] to exit) |
|
|
411
597
|
|
|
412
598
|
## Requirements
|
|
413
599
|
|
|
@@ -479,6 +665,102 @@ sudo apt install virt-viewer
|
|
|
479
665
|
virt-viewer --connect qemu:///session <vm-name>
|
|
480
666
|
```
|
|
481
667
|
|
|
668
|
+
### Browser Profiles Not Syncing
|
|
669
|
+
|
|
670
|
+
If browser profiles or app data aren't available:
|
|
671
|
+
|
|
672
|
+
1. **Regenerate config with app data:**
|
|
673
|
+
```bash
|
|
674
|
+
rm .clonebox.yaml
|
|
675
|
+
clonebox clone . --user --run --replace
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
2. **Check mount permissions in VM:**
|
|
679
|
+
```bash
|
|
680
|
+
# Verify mounts are accessible
|
|
681
|
+
ls -la ~/.config/google-chrome
|
|
682
|
+
ls -la ~/.mozilla/firefox
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Mount Points Empty After Reboot
|
|
686
|
+
|
|
687
|
+
If shared directories appear empty after VM restart:
|
|
688
|
+
|
|
689
|
+
1. **Check fstab entries:**
|
|
690
|
+
```bash
|
|
691
|
+
cat /etc/fstab | grep 9p
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
2. **Mount manually:**
|
|
695
|
+
```bash
|
|
696
|
+
sudo mount -a
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
3. **Verify access mode:**
|
|
700
|
+
- VMs created with `accessmode="mapped"` allow any user to access mounts
|
|
701
|
+
- Older VMs used `accessmode="passthrough"` which preserves host UIDs
|
|
702
|
+
|
|
703
|
+
## Advanced Usage
|
|
704
|
+
|
|
705
|
+
### VM Migration Between Workstations
|
|
706
|
+
|
|
707
|
+
Export your complete VM environment:
|
|
708
|
+
|
|
709
|
+
```bash
|
|
710
|
+
# Export VM with all data
|
|
711
|
+
clonebox export . --user --include-data -o my-dev-env.tar.gz
|
|
712
|
+
|
|
713
|
+
# Transfer to new workstation, then import
|
|
714
|
+
clonebox import my-dev-env.tar.gz --user
|
|
715
|
+
clonebox start . --user
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
### Testing VM Configuration
|
|
719
|
+
|
|
720
|
+
Validate your VM setup:
|
|
721
|
+
|
|
722
|
+
```bash
|
|
723
|
+
# Quick test (basic checks)
|
|
724
|
+
clonebox test . --user --quick
|
|
725
|
+
|
|
726
|
+
# Full test (includes health checks)
|
|
727
|
+
clonebox test . --user --verbose
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### Monitoring VM Health
|
|
731
|
+
|
|
732
|
+
Check VM status from workstation:
|
|
733
|
+
|
|
734
|
+
```bash
|
|
735
|
+
# Check VM state, IP, cloud-init, and health
|
|
736
|
+
clonebox status . --user
|
|
737
|
+
|
|
738
|
+
# Trigger health check in VM
|
|
739
|
+
clonebox status . --user --health
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
### Reopening VM Window
|
|
743
|
+
|
|
744
|
+
If you close the VM window, you can reopen it:
|
|
745
|
+
|
|
746
|
+
```bash
|
|
747
|
+
# Open GUI viewer (easiest)
|
|
748
|
+
clonebox open . --user
|
|
749
|
+
|
|
750
|
+
# Start VM and open GUI (if VM is stopped)
|
|
751
|
+
clonebox start . --user --viewer
|
|
752
|
+
|
|
753
|
+
# Open GUI for running VM
|
|
754
|
+
virt-viewer --connect qemu:///session clone-clonebox
|
|
755
|
+
|
|
756
|
+
# List VMs to get the correct name
|
|
757
|
+
clonebox list
|
|
758
|
+
|
|
759
|
+
# Text console (no GUI)
|
|
760
|
+
virsh --connect qemu:///session console clone-clonebox
|
|
761
|
+
# Press Ctrl + ] to exit console
|
|
762
|
+
```
|
|
763
|
+
|
|
482
764
|
## License
|
|
483
765
|
|
|
484
766
|
MIT License - see [LICENSE](LICENSE) file.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
clonebox/__init__.py,sha256=IOk7G0DiSQ33EGbFC0xbnnFB9aou_6yuyFxvycQEvA0,407
|
|
2
|
+
clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
|
|
3
|
+
clonebox/cli.py,sha256=NFScoojeI1XJ982SuNt01iW52hHIYSttGy2UzZuJCCQ,76413
|
|
4
|
+
clonebox/cloner.py,sha256=0puM04SzifccPfIVqc2CXFFcdNLWKpbiXXbBplrm9s8,31850
|
|
5
|
+
clonebox/detector.py,sha256=4fu04Ty6KC82WkcJZ5UL5TqXpWYE7Kb7R0uJ-9dtbCk,21635
|
|
6
|
+
clonebox-0.1.10.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
7
|
+
clonebox-0.1.10.dist-info/METADATA,sha256=hzdP5bntKA1s_RyRLx0hgbBzGrlt3NBGvDKu5YZfabE,24526
|
|
8
|
+
clonebox-0.1.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
+
clonebox-0.1.10.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
10
|
+
clonebox-0.1.10.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
11
|
+
clonebox-0.1.10.dist-info/RECORD,,
|
clonebox-0.1.9.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
clonebox/__init__.py,sha256=IOk7G0DiSQ33EGbFC0xbnnFB9aou_6yuyFxvycQEvA0,407
|
|
2
|
-
clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
|
|
3
|
-
clonebox/cli.py,sha256=BOAzIJCcRb52y_KgR7rU1yZiPt4r_Ws6Hk1hIPIH5bk,62714
|
|
4
|
-
clonebox/cloner.py,sha256=Z9vpRu-imAWS-AwcPMG7jysvNhX7xn_Xk5Vze8y7DYE,31765
|
|
5
|
-
clonebox/detector.py,sha256=4fu04Ty6KC82WkcJZ5UL5TqXpWYE7Kb7R0uJ-9dtbCk,21635
|
|
6
|
-
clonebox-0.1.9.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
7
|
-
clonebox-0.1.9.dist-info/METADATA,sha256=A68kRx6uoFsNsEyH989LA1_9I1Oc6-saf3HzrAFtFd8,15582
|
|
8
|
-
clonebox-0.1.9.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
-
clonebox-0.1.9.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
10
|
-
clonebox-0.1.9.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
11
|
-
clonebox-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|