clonebox 1.1.2__py3-none-any.whl → 1.1.4__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.
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env python3
2
+ """Data models for snapshot management."""
3
+
4
+ from dataclasses import dataclass, field
5
+ from datetime import datetime, timedelta
6
+ from enum import Enum
7
+ from pathlib import Path
8
+ from typing import Any, Dict, List, Optional
9
+
10
+
11
+ class SnapshotType(Enum):
12
+ """Type of snapshot."""
13
+
14
+ DISK_ONLY = "disk" # Only disk state (offline)
15
+ FULL = "full" # Disk + memory + device state (online)
16
+ EXTERNAL = "external" # External snapshot file
17
+
18
+
19
+ class SnapshotState(Enum):
20
+ """State of snapshot operation."""
21
+
22
+ CREATING = "creating"
23
+ READY = "ready"
24
+ REVERTING = "reverting"
25
+ DELETING = "deleting"
26
+ FAILED = "failed"
27
+
28
+
29
+ @dataclass
30
+ class Snapshot:
31
+ """Represents a VM snapshot."""
32
+
33
+ name: str
34
+ vm_name: str
35
+ snapshot_type: SnapshotType
36
+ state: SnapshotState
37
+ created_at: datetime
38
+
39
+ description: Optional[str] = None
40
+
41
+ # Snapshot hierarchy
42
+ parent_name: Optional[str] = None
43
+ children: List[str] = field(default_factory=list)
44
+
45
+ # Storage info
46
+ disk_path: Optional[Path] = None
47
+ memory_path: Optional[Path] = None
48
+ size_bytes: int = 0
49
+
50
+ # Metadata
51
+ metadata: Dict[str, Any] = field(default_factory=dict)
52
+ tags: List[str] = field(default_factory=list)
53
+
54
+ # Auto-snapshot info
55
+ auto_created: bool = False
56
+ auto_policy: Optional[str] = None
57
+ expires_at: Optional[datetime] = None
58
+
59
+ def to_dict(self) -> Dict[str, Any]:
60
+ """Convert to dictionary for serialization."""
61
+ return {
62
+ "name": self.name,
63
+ "vm_name": self.vm_name,
64
+ "type": self.snapshot_type.value,
65
+ "state": self.state.value,
66
+ "created_at": self.created_at.isoformat(),
67
+ "description": self.description,
68
+ "parent_name": self.parent_name,
69
+ "children": self.children,
70
+ "disk_path": str(self.disk_path) if self.disk_path else None,
71
+ "memory_path": str(self.memory_path) if self.memory_path else None,
72
+ "size_bytes": self.size_bytes,
73
+ "metadata": self.metadata,
74
+ "tags": self.tags,
75
+ "auto_created": self.auto_created,
76
+ "auto_policy": self.auto_policy,
77
+ "expires_at": self.expires_at.isoformat() if self.expires_at else None,
78
+ }
79
+
80
+ @classmethod
81
+ def from_dict(cls, data: Dict[str, Any]) -> "Snapshot":
82
+ """Create from dictionary."""
83
+ return cls(
84
+ name=data["name"],
85
+ vm_name=data["vm_name"],
86
+ snapshot_type=SnapshotType(data["type"]),
87
+ state=SnapshotState(data["state"]),
88
+ created_at=datetime.fromisoformat(data["created_at"]),
89
+ description=data.get("description"),
90
+ parent_name=data.get("parent_name"),
91
+ children=data.get("children", []),
92
+ disk_path=Path(data["disk_path"]) if data.get("disk_path") else None,
93
+ memory_path=Path(data["memory_path"]) if data.get("memory_path") else None,
94
+ size_bytes=data.get("size_bytes", 0),
95
+ metadata=data.get("metadata", {}),
96
+ tags=data.get("tags", []),
97
+ auto_created=data.get("auto_created", False),
98
+ auto_policy=data.get("auto_policy"),
99
+ expires_at=(
100
+ datetime.fromisoformat(data["expires_at"])
101
+ if data.get("expires_at")
102
+ else None
103
+ ),
104
+ )
105
+
106
+ @property
107
+ def is_expired(self) -> bool:
108
+ """Check if snapshot has expired."""
109
+ if self.expires_at is None:
110
+ return False
111
+ return datetime.now() > self.expires_at
112
+
113
+ @property
114
+ def age(self) -> timedelta:
115
+ """Get snapshot age."""
116
+ return datetime.now() - self.created_at
117
+
118
+
119
+ @dataclass
120
+ class SnapshotPolicy:
121
+ """Policy for automatic snapshots."""
122
+
123
+ name: str
124
+ enabled: bool = True
125
+
126
+ # Retention settings
127
+ max_snapshots: int = 10
128
+ max_age_days: int = 30
129
+ min_snapshots: int = 1 # Keep at least N snapshots
130
+
131
+ # Auto-snapshot triggers
132
+ before_operations: List[str] = field(
133
+ default_factory=lambda: ["upgrade", "resize", "config-change"]
134
+ )
135
+ scheduled_interval_hours: Optional[int] = None # e.g., 24 for daily
136
+
137
+ # Naming
138
+ name_prefix: str = "auto-"
139
+ include_timestamp: bool = True
140
+
141
+ # Cleanup
142
+ auto_cleanup: bool = True
143
+ cleanup_on_success: bool = False # Remove pre-operation snapshot if op succeeds
144
+
145
+ def to_dict(self) -> Dict[str, Any]:
146
+ """Convert to dictionary."""
147
+ return {
148
+ "name": self.name,
149
+ "enabled": self.enabled,
150
+ "max_snapshots": self.max_snapshots,
151
+ "max_age_days": self.max_age_days,
152
+ "min_snapshots": self.min_snapshots,
153
+ "before_operations": self.before_operations,
154
+ "scheduled_interval_hours": self.scheduled_interval_hours,
155
+ "name_prefix": self.name_prefix,
156
+ "include_timestamp": self.include_timestamp,
157
+ "auto_cleanup": self.auto_cleanup,
158
+ "cleanup_on_success": self.cleanup_on_success,
159
+ }
160
+
161
+ @classmethod
162
+ def from_dict(cls, data: Dict[str, Any]) -> "SnapshotPolicy":
163
+ """Create from dictionary."""
164
+ return cls(
165
+ name=data["name"],
166
+ enabled=data.get("enabled", True),
167
+ max_snapshots=data.get("max_snapshots", 10),
168
+ max_age_days=data.get("max_age_days", 30),
169
+ min_snapshots=data.get("min_snapshots", 1),
170
+ before_operations=data.get(
171
+ "before_operations", ["upgrade", "resize", "config-change"]
172
+ ),
173
+ scheduled_interval_hours=data.get("scheduled_interval_hours"),
174
+ name_prefix=data.get("name_prefix", "auto-"),
175
+ include_timestamp=data.get("include_timestamp", True),
176
+ auto_cleanup=data.get("auto_cleanup", True),
177
+ cleanup_on_success=data.get("cleanup_on_success", False),
178
+ )
179
+
180
+ def generate_snapshot_name(self, operation: Optional[str] = None) -> str:
181
+ """Generate snapshot name based on policy."""
182
+ parts = [self.name_prefix]
183
+ if operation:
184
+ parts.append(operation)
185
+ if self.include_timestamp:
186
+ parts.append(datetime.now().strftime("%Y%m%d-%H%M%S"))
187
+ return "-".join(parts)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 1.1.2
3
+ Version: 1.1.4
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
@@ -104,7 +104,7 @@ CloneBox excels in scenarios where developers need:
104
104
 
105
105
  ## What's New in v1.1
106
106
 
107
- **v1.1.0** is production-ready with two full runtimes:
107
+ **v1.1.2** is production-ready with two full runtimes and P2P secure sharing:
108
108
 
109
109
  | Feature | Status |
110
110
  |---------|--------|
@@ -113,8 +113,34 @@ CloneBox excels in scenarios where developers need:
113
113
  | 📊 Web Dashboard (FastAPI + HTMX + Tailwind) | ✅ Stable |
114
114
  | 🎛️ Profiles System (`ml-dev`, `web-stack`) | ✅ Stable |
115
115
  | 🔍 Auto-detection (services, apps, paths) | ✅ Stable |
116
+ | 🔒 P2P Secure Transfer (AES-256) | ✅ **NEW** |
116
117
  | 🧪 95%+ Test Coverage | ✅ |
117
118
 
119
+ ### P2P Secure VM Sharing
120
+
121
+ Share VMs between workstations with AES-256 encryption:
122
+
123
+ ```bash
124
+ # Generate team encryption key (once per team)
125
+ clonebox keygen
126
+ # 🔑 Key saved: ~/.clonebox.key
127
+
128
+ # Export encrypted VM
129
+ clonebox export-encrypted my-dev-vm -o team-env.enc --user-data
130
+
131
+ # Transfer via SCP/SMB/USB
132
+ scp team-env.enc user@workstationB:~/
133
+
134
+ # Import on another machine (needs same key)
135
+ clonebox import-encrypted team-env.enc --name my-dev-copy
136
+
137
+ # Or use P2P commands directly
138
+ clonebox export-remote user@hostA my-vm -o local.enc --encrypted
139
+ clonebox import-remote local.enc user@hostB --encrypted
140
+ clonebox sync-key user@hostB # Sync encryption key
141
+ clonebox list-remote user@hostB # List remote VMs
142
+ ```
143
+
118
144
  ### Roadmap
119
145
 
120
146
  - **v1.2.0**: `clonebox exec` command, VM snapshots, snapshot restore
@@ -0,0 +1,27 @@
1
+ clonebox/__init__.py,sha256=CyfHVVq6KqBr4CNERBpXk_O6Q5B35q03YpdQbokVvvI,408
2
+ clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
3
+ clonebox/cli.py,sha256=MeW_Jnbmrp9QrfA72a2pydeeINCY_LNBqJEGaD2rxtE,128687
4
+ clonebox/cloner.py,sha256=2YQO4SHCv0xOsU1hL9IqdgmxxJN-2j75X9pe-LpTpJE,82696
5
+ clonebox/container.py,sha256=tiYK1ZB-DhdD6A2FuMA0h_sRNkUI7KfYcJ0tFOcdyeM,6105
6
+ clonebox/dashboard.py,sha256=dMY6odvPq3j6FronhRRsX7aY3qdCwznB-aCWKEmHDNw,5768
7
+ clonebox/detector.py,sha256=vS65cvFNPmUBCX1Y_TMTnSRljw6r1Ae9dlVtACs5XFc,23075
8
+ clonebox/exporter.py,sha256=WIzVvmA0z_jjrpyXxvnXoLp9oaW6fKS7k0PGwzx_PIM,5629
9
+ clonebox/importer.py,sha256=Q9Uk1IOA41mgGhU4ynW2k-h9GEoGxRKI3c9wWE4uxcA,7097
10
+ clonebox/models.py,sha256=zwejkNtEEO_aPy_Q5UzXG5tszU-c7lkqh9LQus9eWMo,8307
11
+ clonebox/monitor.py,sha256=KQKi63mcz6KULi2SpD5oi1g05CKaFTC2dAyyRJtJX-E,9211
12
+ clonebox/p2p.py,sha256=LPQQ7wNO84yDnpVrGkaRU-FDUzqmC4URdZXVeHsNOew,5889
13
+ clonebox/profiles.py,sha256=UP37fX_rhrG_O9ehNFJBUcULPmUtN1A8KsJ6cM44oK0,1986
14
+ clonebox/validator.py,sha256=CF4hMlY69-AGRH5HdG8HAA9_LNCwDKD4xPlYQPWJ9Rw,36647
15
+ clonebox/health/__init__.py,sha256=hW6MB8qc3pE-Jub1Djnz2G1AGs4Tn4Y2FbuYur6m8aE,394
16
+ clonebox/health/models.py,sha256=sPumwj8S-88KgzSGw1Kq9bBbPVRd2RR0R87Z8hKJ_28,6001
17
+ clonebox/snapshots/__init__.py,sha256=ndlrIavPAiA8z4Ep3-D_EPhOcjNKYFnP3rIpEKaGdb8,273
18
+ clonebox/snapshots/manager.py,sha256=FuJB_q9fUs7GScVdX5vePezBDI9m8zwIrG1BDFvjeNM,11469
19
+ clonebox/snapshots/models.py,sha256=upJhlHLYFWBrMzCMI8Zzd1z66JRV69R2qLDCTrDtJUY,6268
20
+ clonebox/templates/profiles/ml-dev.yaml,sha256=w07MToGh31xtxpjbeXTBk9BkpAN8A3gv8HeA3ESKG9M,461
21
+ clonebox/templates/profiles/web-stack.yaml,sha256=EBnnGMzML5vAjXmIUbCpbTCwmRaNJiuWd3EcL43DOK8,485
22
+ clonebox-1.1.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
23
+ clonebox-1.1.4.dist-info/METADATA,sha256=TfNWk7EAyhCtPtEoyERrJz2f2Xhn6Lg08DjfC6zuj0E,47947
24
+ clonebox-1.1.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
25
+ clonebox-1.1.4.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
26
+ clonebox-1.1.4.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
27
+ clonebox-1.1.4.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- clonebox/__init__.py,sha256=CyfHVVq6KqBr4CNERBpXk_O6Q5B35q03YpdQbokVvvI,408
2
- clonebox/__main__.py,sha256=Fcoyzwwyz5-eC_sBlQk5a5RbKx8uodQz5sKJ190U0NU,135
3
- clonebox/cli.py,sha256=0ZOE66WVbIDdmIBf0_VyWw4U-jzPo_T4pCTXkWect5o,114804
4
- clonebox/cloner.py,sha256=2YQO4SHCv0xOsU1hL9IqdgmxxJN-2j75X9pe-LpTpJE,82696
5
- clonebox/container.py,sha256=tiYK1ZB-DhdD6A2FuMA0h_sRNkUI7KfYcJ0tFOcdyeM,6105
6
- clonebox/dashboard.py,sha256=dMY6odvPq3j6FronhRRsX7aY3qdCwznB-aCWKEmHDNw,5768
7
- clonebox/detector.py,sha256=vS65cvFNPmUBCX1Y_TMTnSRljw6r1Ae9dlVtACs5XFc,23075
8
- clonebox/models.py,sha256=zwejkNtEEO_aPy_Q5UzXG5tszU-c7lkqh9LQus9eWMo,8307
9
- clonebox/profiles.py,sha256=UP37fX_rhrG_O9ehNFJBUcULPmUtN1A8KsJ6cM44oK0,1986
10
- clonebox/validator.py,sha256=CF4hMlY69-AGRH5HdG8HAA9_LNCwDKD4xPlYQPWJ9Rw,36647
11
- clonebox/templates/profiles/ml-dev.yaml,sha256=w07MToGh31xtxpjbeXTBk9BkpAN8A3gv8HeA3ESKG9M,461
12
- clonebox/templates/profiles/web-stack.yaml,sha256=EBnnGMzML5vAjXmIUbCpbTCwmRaNJiuWd3EcL43DOK8,485
13
- clonebox-1.1.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
14
- clonebox-1.1.2.dist-info/METADATA,sha256=rcpTr77zlQIQ2VwauZBL5QEIGvmxuD0-4HQYIYj_jbE,47164
15
- clonebox-1.1.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
16
- clonebox-1.1.2.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
17
- clonebox-1.1.2.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
18
- clonebox-1.1.2.dist-info/RECORD,,