clonebox 0.1.7__py3-none-any.whl → 0.1.8__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 +41 -9
- clonebox/cloner.py +178 -5
- {clonebox-0.1.7.dist-info → clonebox-0.1.8.dist-info}/METADATA +1 -1
- clonebox-0.1.8.dist-info/RECORD +11 -0
- clonebox-0.1.7.dist-info/RECORD +0 -11
- {clonebox-0.1.7.dist-info → clonebox-0.1.8.dist-info}/WHEEL +0 -0
- {clonebox-0.1.7.dist-info → clonebox-0.1.8.dist-info}/entry_points.txt +0 -0
- {clonebox-0.1.7.dist-info → clonebox-0.1.8.dist-info}/licenses/LICENSE +0 -0
- {clonebox-0.1.7.dist-info → clonebox-0.1.8.dist-info}/top_level.txt +0 -0
clonebox/cli.py
CHANGED
|
@@ -602,6 +602,18 @@ def generate_clonebox_yaml(
|
|
|
602
602
|
if host_folder.exists() and str(host_folder) not in paths_mapping:
|
|
603
603
|
paths_mapping[str(host_folder)] = guest_folder
|
|
604
604
|
|
|
605
|
+
# Detect and add app-specific data directories for running applications
|
|
606
|
+
# This includes browser profiles, IDE settings, credentials, extensions, etc.
|
|
607
|
+
app_data_dirs = detector.detect_app_data_dirs(snapshot.applications)
|
|
608
|
+
app_data_mapping = {}
|
|
609
|
+
for app_data in app_data_dirs:
|
|
610
|
+
host_path = app_data["path"]
|
|
611
|
+
if host_path not in paths_mapping:
|
|
612
|
+
# Map to same relative path in VM user home
|
|
613
|
+
rel_path = host_path.replace(str(home_dir), "").lstrip("/")
|
|
614
|
+
guest_path = f"/home/ubuntu/{rel_path}"
|
|
615
|
+
app_data_mapping[host_path] = guest_path
|
|
616
|
+
|
|
605
617
|
# Determine VM name
|
|
606
618
|
if not vm_name:
|
|
607
619
|
if target_path:
|
|
@@ -654,15 +666,20 @@ def generate_clonebox_yaml(
|
|
|
654
666
|
"snap_packages": all_snap_packages,
|
|
655
667
|
"post_commands": [], # User can add custom commands to run after setup
|
|
656
668
|
"paths": paths_mapping,
|
|
669
|
+
"app_data_paths": app_data_mapping, # App-specific config/data directories
|
|
657
670
|
"detected": {
|
|
658
671
|
"running_apps": [
|
|
659
|
-
{"name": a.name, "cwd": a.working_dir, "memory_mb": round(a.memory_mb)}
|
|
672
|
+
{"name": a.name, "cwd": a.working_dir or "", "memory_mb": round(a.memory_mb)}
|
|
660
673
|
for a in snapshot.applications[:10]
|
|
661
674
|
],
|
|
675
|
+
"app_data_dirs": [
|
|
676
|
+
{"path": d["path"], "app": d["app"], "size_mb": d["size_mb"]}
|
|
677
|
+
for d in app_data_dirs[:15]
|
|
678
|
+
],
|
|
662
679
|
"all_paths": {
|
|
663
|
-
"projects": paths_by_type["project"],
|
|
664
|
-
"configs": paths_by_type["config"][:5],
|
|
665
|
-
"data": paths_by_type["data"][:5],
|
|
680
|
+
"projects": list(paths_by_type["project"]),
|
|
681
|
+
"configs": list(paths_by_type["config"][:5]),
|
|
682
|
+
"data": list(paths_by_type["data"][:5]),
|
|
666
683
|
},
|
|
667
684
|
},
|
|
668
685
|
}
|
|
@@ -781,13 +798,17 @@ def create_vm_from_config(
|
|
|
781
798
|
replace: bool = False,
|
|
782
799
|
) -> str:
|
|
783
800
|
"""Create VM from YAML config dict."""
|
|
801
|
+
# Merge paths and app_data_paths
|
|
802
|
+
all_paths = config.get("paths", {}).copy()
|
|
803
|
+
all_paths.update(config.get("app_data_paths", {}))
|
|
804
|
+
|
|
784
805
|
vm_config = VMConfig(
|
|
785
806
|
name=config["vm"]["name"],
|
|
786
807
|
ram_mb=config["vm"].get("ram_mb", 4096),
|
|
787
808
|
vcpus=config["vm"].get("vcpus", 4),
|
|
788
809
|
gui=config["vm"].get("gui", True),
|
|
789
810
|
base_image=config["vm"].get("base_image"),
|
|
790
|
-
paths=
|
|
811
|
+
paths=all_paths,
|
|
791
812
|
packages=config.get("packages", []),
|
|
792
813
|
snap_packages=config.get("snap_packages", []),
|
|
793
814
|
services=config.get("services", []),
|
|
@@ -915,16 +936,27 @@ def cmd_clone(args):
|
|
|
915
936
|
password = config['vm'].get('password', 'ubuntu')
|
|
916
937
|
console.print("\n[bold yellow]⏰ GUI Setup Process:[/]")
|
|
917
938
|
console.print(" [yellow]•[/] Installing desktop environment (~5-10 minutes)")
|
|
939
|
+
console.print(" [yellow]•[/] Running health checks on all components")
|
|
918
940
|
console.print(" [yellow]•[/] Automatic restart after installation")
|
|
919
941
|
console.print(" [yellow]•[/] GUI login screen will appear")
|
|
920
942
|
console.print(f" [yellow]•[/] Login: [cyan]{username}[/] / [cyan]{'*' * len(password)}[/] (from .env)")
|
|
921
943
|
console.print("\n[dim]💡 Progress will be monitored automatically below[/]")
|
|
922
944
|
|
|
945
|
+
# Show health check info
|
|
946
|
+
console.print("\n[bold]📊 Health Check (inside VM):[/]")
|
|
947
|
+
console.print(" [cyan]cat /var/log/clonebox-health.log[/] # View full report")
|
|
948
|
+
console.print(" [cyan]cat /var/log/clonebox-health-status[/] # Quick status")
|
|
949
|
+
console.print(" [cyan]clonebox-health[/] # Re-run health check")
|
|
950
|
+
|
|
923
951
|
# Show mount instructions
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
952
|
+
all_paths = config.get("paths", {}).copy()
|
|
953
|
+
all_paths.update(config.get("app_data_paths", {}))
|
|
954
|
+
if all_paths:
|
|
955
|
+
console.print("\n[bold]📁 Mounted paths (automatic):[/]")
|
|
956
|
+
for idx, (host, guest) in enumerate(list(all_paths.items())[:5]):
|
|
957
|
+
console.print(f" [dim]{host}[/] → [cyan]{guest}[/]")
|
|
958
|
+
if len(all_paths) > 5:
|
|
959
|
+
console.print(f" [dim]... and {len(all_paths) - 5} more paths[/]")
|
|
928
960
|
except PermissionError as e:
|
|
929
961
|
console.print(f"[red]❌ Permission Error:[/]\n{e}")
|
|
930
962
|
console.print("\n[yellow]💡 Try running with --user flag:[/]")
|
clonebox/cloner.py
CHANGED
|
@@ -476,6 +476,179 @@ class SelectiveVMCloner:
|
|
|
476
476
|
|
|
477
477
|
return ET.tostring(root, encoding="unicode")
|
|
478
478
|
|
|
479
|
+
def _generate_health_check_script(self, config: VMConfig) -> str:
|
|
480
|
+
"""Generate a health check script that validates all installed components."""
|
|
481
|
+
import base64
|
|
482
|
+
|
|
483
|
+
# Build package check commands
|
|
484
|
+
apt_checks = []
|
|
485
|
+
for pkg in config.packages:
|
|
486
|
+
apt_checks.append(f'check_apt_package "{pkg}"')
|
|
487
|
+
|
|
488
|
+
snap_checks = []
|
|
489
|
+
for pkg in config.snap_packages:
|
|
490
|
+
snap_checks.append(f'check_snap_package "{pkg}"')
|
|
491
|
+
|
|
492
|
+
service_checks = []
|
|
493
|
+
for svc in config.services:
|
|
494
|
+
service_checks.append(f'check_service "{svc}"')
|
|
495
|
+
|
|
496
|
+
mount_checks = []
|
|
497
|
+
for idx, (host_path, guest_path) in enumerate(config.paths.items()):
|
|
498
|
+
mount_checks.append(f'check_mount "{guest_path}" "mount{idx}"')
|
|
499
|
+
|
|
500
|
+
apt_checks_str = "\n".join(apt_checks) if apt_checks else "echo 'No apt packages to check'"
|
|
501
|
+
snap_checks_str = "\n".join(snap_checks) if snap_checks else "echo 'No snap packages to check'"
|
|
502
|
+
service_checks_str = "\n".join(service_checks) if service_checks else "echo 'No services to check'"
|
|
503
|
+
mount_checks_str = "\n".join(mount_checks) if mount_checks else "echo 'No mounts to check'"
|
|
504
|
+
|
|
505
|
+
script = f'''#!/bin/bash
|
|
506
|
+
# CloneBox Health Check Script
|
|
507
|
+
# Generated automatically - validates all installed components
|
|
508
|
+
|
|
509
|
+
REPORT_FILE="/var/log/clonebox-health.log"
|
|
510
|
+
PASSED=0
|
|
511
|
+
FAILED=0
|
|
512
|
+
WARNINGS=0
|
|
513
|
+
|
|
514
|
+
# Colors for output
|
|
515
|
+
RED='\\033[0;31m'
|
|
516
|
+
GREEN='\\033[0;32m'
|
|
517
|
+
YELLOW='\\033[1;33m'
|
|
518
|
+
NC='\\033[0m'
|
|
519
|
+
|
|
520
|
+
log() {{
|
|
521
|
+
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$REPORT_FILE"
|
|
522
|
+
}}
|
|
523
|
+
|
|
524
|
+
check_apt_package() {{
|
|
525
|
+
local pkg="$1"
|
|
526
|
+
if dpkg -l "$pkg" 2>/dev/null | grep -q "^ii"; then
|
|
527
|
+
log "[PASS] APT package '$pkg' is installed"
|
|
528
|
+
((PASSED++))
|
|
529
|
+
return 0
|
|
530
|
+
else
|
|
531
|
+
log "[FAIL] APT package '$pkg' is NOT installed"
|
|
532
|
+
((FAILED++))
|
|
533
|
+
return 1
|
|
534
|
+
fi
|
|
535
|
+
}}
|
|
536
|
+
|
|
537
|
+
check_snap_package() {{
|
|
538
|
+
local pkg="$1"
|
|
539
|
+
if snap list "$pkg" &>/dev/null; then
|
|
540
|
+
log "[PASS] Snap package '$pkg' is installed"
|
|
541
|
+
((PASSED++))
|
|
542
|
+
return 0
|
|
543
|
+
else
|
|
544
|
+
log "[FAIL] Snap package '$pkg' is NOT installed"
|
|
545
|
+
((FAILED++))
|
|
546
|
+
return 1
|
|
547
|
+
fi
|
|
548
|
+
}}
|
|
549
|
+
|
|
550
|
+
check_service() {{
|
|
551
|
+
local svc="$1"
|
|
552
|
+
if systemctl is-enabled "$svc" &>/dev/null; then
|
|
553
|
+
if systemctl is-active "$svc" &>/dev/null; then
|
|
554
|
+
log "[PASS] Service '$svc' is enabled and running"
|
|
555
|
+
((PASSED++))
|
|
556
|
+
return 0
|
|
557
|
+
else
|
|
558
|
+
log "[WARN] Service '$svc' is enabled but not running"
|
|
559
|
+
((WARNINGS++))
|
|
560
|
+
return 1
|
|
561
|
+
fi
|
|
562
|
+
else
|
|
563
|
+
log "[INFO] Service '$svc' is not enabled (may be optional)"
|
|
564
|
+
return 0
|
|
565
|
+
fi
|
|
566
|
+
}}
|
|
567
|
+
|
|
568
|
+
check_mount() {{
|
|
569
|
+
local path="$1"
|
|
570
|
+
local tag="$2"
|
|
571
|
+
if mountpoint -q "$path" 2>/dev/null; then
|
|
572
|
+
log "[PASS] Mount '$path' ($tag) is active"
|
|
573
|
+
((PASSED++))
|
|
574
|
+
return 0
|
|
575
|
+
elif [ -d "$path" ]; then
|
|
576
|
+
log "[WARN] Directory '$path' exists but not mounted"
|
|
577
|
+
((WARNINGS++))
|
|
578
|
+
return 1
|
|
579
|
+
else
|
|
580
|
+
log "[INFO] Mount point '$path' does not exist yet"
|
|
581
|
+
return 0
|
|
582
|
+
fi
|
|
583
|
+
}}
|
|
584
|
+
|
|
585
|
+
check_gui() {{
|
|
586
|
+
if systemctl get-default | grep -q graphical; then
|
|
587
|
+
log "[PASS] System configured for graphical target"
|
|
588
|
+
((PASSED++))
|
|
589
|
+
if systemctl is-active gdm3 &>/dev/null || systemctl is-active gdm &>/dev/null; then
|
|
590
|
+
log "[PASS] Display manager (GDM) is running"
|
|
591
|
+
((PASSED++))
|
|
592
|
+
else
|
|
593
|
+
log "[WARN] Display manager not yet running (may start after reboot)"
|
|
594
|
+
((WARNINGS++))
|
|
595
|
+
fi
|
|
596
|
+
else
|
|
597
|
+
log "[INFO] System not configured for GUI"
|
|
598
|
+
fi
|
|
599
|
+
}}
|
|
600
|
+
|
|
601
|
+
# Start health check
|
|
602
|
+
log "=========================================="
|
|
603
|
+
log "CloneBox Health Check Report"
|
|
604
|
+
log "VM Name: {config.name}"
|
|
605
|
+
log "Date: $(date)"
|
|
606
|
+
log "=========================================="
|
|
607
|
+
|
|
608
|
+
log ""
|
|
609
|
+
log "--- APT Packages ---"
|
|
610
|
+
{apt_checks_str}
|
|
611
|
+
|
|
612
|
+
log ""
|
|
613
|
+
log "--- Snap Packages ---"
|
|
614
|
+
{snap_checks_str}
|
|
615
|
+
|
|
616
|
+
log ""
|
|
617
|
+
log "--- Services ---"
|
|
618
|
+
{service_checks_str}
|
|
619
|
+
|
|
620
|
+
log ""
|
|
621
|
+
log "--- Mounts ---"
|
|
622
|
+
{mount_checks_str}
|
|
623
|
+
|
|
624
|
+
log ""
|
|
625
|
+
log "--- GUI Status ---"
|
|
626
|
+
check_gui
|
|
627
|
+
|
|
628
|
+
log ""
|
|
629
|
+
log "=========================================="
|
|
630
|
+
log "Health Check Summary"
|
|
631
|
+
log "=========================================="
|
|
632
|
+
log "Passed: $PASSED"
|
|
633
|
+
log "Failed: $FAILED"
|
|
634
|
+
log "Warnings: $WARNINGS"
|
|
635
|
+
|
|
636
|
+
if [ $FAILED -eq 0 ]; then
|
|
637
|
+
log ""
|
|
638
|
+
log "[SUCCESS] All critical checks passed!"
|
|
639
|
+
echo "HEALTH_STATUS=OK" > /var/log/clonebox-health-status
|
|
640
|
+
exit 0
|
|
641
|
+
else
|
|
642
|
+
log ""
|
|
643
|
+
log "[ERROR] Some checks failed. Review log for details."
|
|
644
|
+
echo "HEALTH_STATUS=FAILED" > /var/log/clonebox-health-status
|
|
645
|
+
exit 1
|
|
646
|
+
fi
|
|
647
|
+
'''
|
|
648
|
+
# Encode script to base64 for safe embedding in cloud-init
|
|
649
|
+
encoded = base64.b64encode(script.encode()).decode()
|
|
650
|
+
return encoded
|
|
651
|
+
|
|
479
652
|
def _create_cloudinit_iso(self, vm_dir: Path, config: VMConfig) -> Path:
|
|
480
653
|
"""Create cloud-init ISO with user-data and meta-data."""
|
|
481
654
|
|
|
@@ -540,12 +713,12 @@ class SelectiveVMCloner:
|
|
|
540
713
|
for cmd in config.post_commands:
|
|
541
714
|
runcmd_lines.append(f" - {cmd}")
|
|
542
715
|
|
|
543
|
-
#
|
|
544
|
-
|
|
545
|
-
runcmd_lines.append(" -
|
|
546
|
-
runcmd_lines.append(" -
|
|
716
|
+
# Generate health check script
|
|
717
|
+
health_script = self._generate_health_check_script(config)
|
|
718
|
+
runcmd_lines.append(f" - echo '{health_script}' | base64 -d > /usr/local/bin/clonebox-health")
|
|
719
|
+
runcmd_lines.append(" - chmod +x /usr/local/bin/clonebox-health")
|
|
720
|
+
runcmd_lines.append(" - /usr/local/bin/clonebox-health >> /var/log/clonebox-health.log 2>&1")
|
|
547
721
|
runcmd_lines.append(" - echo 'CloneBox VM ready!' > /var/log/clonebox-ready")
|
|
548
|
-
runcmd_lines.append(" - echo 'Setup completed at:' $(date) >> /var/log/clonebox-setup.log")
|
|
549
722
|
|
|
550
723
|
# Add reboot command at the end if GUI is enabled
|
|
551
724
|
if config.gui:
|
|
@@ -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=Kp-1C9Be39ZK9fVe-4bQLyb25W2otXk8597MvOOee7M,43133
|
|
4
|
+
clonebox/cloner.py,sha256=CMy0NWxOiUMULaQuDHY0_LDUaELW0_h4ewj_dZ_5WHw,31171
|
|
5
|
+
clonebox/detector.py,sha256=4fu04Ty6KC82WkcJZ5UL5TqXpWYE7Kb7R0uJ-9dtbCk,21635
|
|
6
|
+
clonebox-0.1.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
7
|
+
clonebox-0.1.8.dist-info/METADATA,sha256=6a8ry0WjVyY21D3r1teMvSEL7zwVaoweH9GRtFxnZJY,15582
|
|
8
|
+
clonebox-0.1.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
+
clonebox-0.1.8.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
10
|
+
clonebox-0.1.8.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
11
|
+
clonebox-0.1.8.dist-info/RECORD,,
|
clonebox-0.1.7.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=ngC6Pwbfr1brFkf_0ODlFzDikojTOkA3Yw-3iY4vrmY,41441
|
|
4
|
-
clonebox/cloner.py,sha256=eDIxORCtnqG9mFKJl4OmW9F6tkEJp7dENYX-2x1Favg,26453
|
|
5
|
-
clonebox/detector.py,sha256=4fu04Ty6KC82WkcJZ5UL5TqXpWYE7Kb7R0uJ-9dtbCk,21635
|
|
6
|
-
clonebox-0.1.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
7
|
-
clonebox-0.1.7.dist-info/METADATA,sha256=wsaV7GyZ6zfLCxLuNoSopr4XUHdyTlrgxxJMiHuFnKU,15582
|
|
8
|
-
clonebox-0.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
9
|
-
clonebox-0.1.7.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
|
|
10
|
-
clonebox-0.1.7.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
|
|
11
|
-
clonebox-0.1.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|