clonebox 0.1.6__py3-none-any.whl → 0.1.7__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 CHANGED
@@ -592,6 +592,16 @@ def generate_clonebox_yaml(
592
592
  paths_mapping[host_path] = f"/mnt/workdir{idx}"
593
593
  idx += 1
594
594
 
595
+ # Add default user folders (Downloads, Documents)
596
+ home_dir = Path.home()
597
+ default_folders = [
598
+ (home_dir / "Downloads", "/home/ubuntu/Downloads"),
599
+ (home_dir / "Documents", "/home/ubuntu/Documents"),
600
+ ]
601
+ for host_folder, guest_folder in default_folders:
602
+ if host_folder.exists() and str(host_folder) not in paths_mapping:
603
+ paths_mapping[str(host_folder)] = guest_folder
604
+
595
605
  # Determine VM name
596
606
  if not vm_name:
597
607
  if target_path:
@@ -604,10 +614,10 @@ def generate_clonebox_yaml(
604
614
  vcpus = max(2, sys_info["cpu_count"] // 2)
605
615
 
606
616
  # Auto-detect packages from running applications and services
607
- suggested_app_packages = detector.suggest_packages_for_apps(snapshot.applications)
608
- suggested_service_packages = detector.suggest_packages_for_services(snapshot.running_services)
617
+ app_packages = detector.suggest_packages_for_apps(snapshot.applications)
618
+ service_packages = detector.suggest_packages_for_services(snapshot.running_services)
609
619
 
610
- # Combine with base packages
620
+ # Combine with base packages (apt only)
611
621
  base_packages = [
612
622
  "build-essential",
613
623
  "git",
@@ -615,10 +625,15 @@ def generate_clonebox_yaml(
615
625
  "vim",
616
626
  ]
617
627
 
618
- # Merge all packages and deduplicate
619
- all_packages = base_packages + suggested_app_packages + suggested_service_packages
628
+ # Merge apt packages and deduplicate
629
+ all_apt_packages = base_packages + app_packages["apt"] + service_packages["apt"]
630
+ if deduplicate:
631
+ all_apt_packages = deduplicate_list(all_apt_packages)
632
+
633
+ # Merge snap packages and deduplicate
634
+ all_snap_packages = app_packages["snap"] + service_packages["snap"]
620
635
  if deduplicate:
621
- all_packages = deduplicate_list(all_packages)
636
+ all_snap_packages = deduplicate_list(all_snap_packages)
622
637
 
623
638
  # Build config
624
639
  config = {
@@ -635,7 +650,9 @@ def generate_clonebox_yaml(
635
650
  "password": "${VM_PASSWORD}",
636
651
  },
637
652
  "services": services,
638
- "packages": all_packages,
653
+ "packages": all_apt_packages,
654
+ "snap_packages": all_snap_packages,
655
+ "post_commands": [], # User can add custom commands to run after setup
639
656
  "paths": paths_mapping,
640
657
  "detected": {
641
658
  "running_apps": [
@@ -772,7 +789,9 @@ def create_vm_from_config(
772
789
  base_image=config["vm"].get("base_image"),
773
790
  paths=config.get("paths", {}),
774
791
  packages=config.get("packages", []),
792
+ snap_packages=config.get("snap_packages", []),
775
793
  services=config.get("services", []),
794
+ post_commands=config.get("post_commands", []),
776
795
  user_session=user_session,
777
796
  network_mode=config["vm"].get("network_mode", "auto"),
778
797
  username=config["vm"].get("username", "ubuntu"),
clonebox/cloner.py CHANGED
@@ -31,7 +31,9 @@ class VMConfig:
31
31
  base_image: Optional[str] = None
32
32
  paths: dict = field(default_factory=dict)
33
33
  packages: list = field(default_factory=list)
34
+ snap_packages: list = field(default_factory=list) # Snap packages to install
34
35
  services: list = field(default_factory=list)
36
+ post_commands: list = field(default_factory=list) # Commands to run after setup
35
37
  user_session: bool = False # Use qemu:///session instead of qemu:///system
36
38
  network_mode: str = "auto" # auto|default|user
37
39
  username: str = "ubuntu" # VM default username
@@ -508,7 +510,7 @@ class SelectiveVMCloner:
508
510
  "\n".join(f" - {pkg}" for pkg in all_packages) if all_packages else ""
509
511
  )
510
512
 
511
- # Build runcmd - services and mounts
513
+ # Build runcmd - services, mounts, snaps, post_commands
512
514
  runcmd_lines = []
513
515
 
514
516
  # Add service enablement
@@ -519,6 +521,12 @@ class SelectiveVMCloner:
519
521
  for cmd in mount_commands:
520
522
  runcmd_lines.append(cmd)
521
523
 
524
+ # Install snap packages
525
+ if config.snap_packages:
526
+ runcmd_lines.append(" - echo 'Installing snap packages...'")
527
+ for snap_pkg in config.snap_packages:
528
+ runcmd_lines.append(f" - snap install {snap_pkg} --classic || snap install {snap_pkg} || true")
529
+
522
530
  # Add GUI setup if enabled - runs AFTER package installation completes
523
531
  if config.gui:
524
532
  runcmd_lines.extend([
@@ -526,7 +534,18 @@ class SelectiveVMCloner:
526
534
  " - systemctl enable gdm3 || systemctl enable gdm || true",
527
535
  ])
528
536
 
537
+ # Run user-defined post commands
538
+ if config.post_commands:
539
+ runcmd_lines.append(" - echo 'Running post-setup commands...'")
540
+ for cmd in config.post_commands:
541
+ runcmd_lines.append(f" - {cmd}")
542
+
543
+ # Validation - check installed packages and log results
544
+ runcmd_lines.append(" - echo '=== CloneBox Setup Validation ===' >> /var/log/clonebox-setup.log")
545
+ runcmd_lines.append(" - dpkg -l | grep -E 'ii' | wc -l >> /var/log/clonebox-setup.log")
546
+ runcmd_lines.append(" - snap list >> /var/log/clonebox-setup.log 2>/dev/null || true")
529
547
  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")
530
549
 
531
550
  # Add reboot command at the end if GUI is enabled
532
551
  if config.gui:
clonebox/detector.py CHANGED
@@ -144,59 +144,209 @@ class SystemDetector:
144
144
  "esbuild",
145
145
  "tmux",
146
146
  "screen",
147
+ # IDEs and desktop apps
148
+ "pycharm",
149
+ "idea",
150
+ "webstorm",
151
+ "phpstorm",
152
+ "goland",
153
+ "clion",
154
+ "rider",
155
+ "datagrip",
156
+ "sublime",
157
+ "atom",
158
+ "slack",
159
+ "discord",
160
+ "telegram",
161
+ "spotify",
162
+ "vlc",
163
+ "gimp",
164
+ "inkscape",
165
+ "blender",
166
+ "obs",
167
+ "postman",
168
+ "insomnia",
169
+ "dbeaver",
147
170
  ]
148
171
 
149
- # Map process/service names to Ubuntu packages
172
+ # Map process/service names to Ubuntu packages or snap packages
173
+ # Format: "process_name": ("package_name", "install_type") where install_type is "apt" or "snap"
150
174
  APP_TO_PACKAGE_MAP = {
151
- "python": "python3",
152
- "python3": "python3",
153
- "pip": "python3-pip",
154
- "node": "nodejs",
155
- "npm": "npm",
156
- "yarn": "yarnpkg",
157
- "docker": "docker.io",
158
- "dockerd": "docker.io",
159
- "docker-compose": "docker-compose",
160
- "podman": "podman",
161
- "nginx": "nginx",
162
- "apache2": "apache2",
163
- "httpd": "apache2",
164
- "postgres": "postgresql",
165
- "postgresql": "postgresql",
166
- "mysql": "mysql-server",
167
- "mysqld": "mysql-server",
168
- "mongod": "mongodb",
169
- "mongodb": "mongodb",
170
- "redis-server": "redis-server",
171
- "redis": "redis-server",
172
- "vim": "vim",
173
- "nvim": "neovim",
174
- "emacs": "emacs",
175
- "firefox": "firefox",
176
- "chromium": "chromium-browser",
177
- "jupyter": "jupyter-notebook",
178
- "jupyter-lab": "jupyterlab",
179
- "gunicorn": "gunicorn",
180
- "uvicorn": "uvicorn",
181
- "tmux": "tmux",
182
- "screen": "screen",
183
- "git": "git",
184
- "curl": "curl",
185
- "wget": "wget",
186
- "ssh": "openssh-client",
187
- "sshd": "openssh-server",
188
- "go": "golang",
189
- "cargo": "cargo",
190
- "rustc": "rustc",
191
- "java": "default-jdk",
192
- "gradle": "gradle",
193
- "mvn": "maven",
175
+ "python": ("python3", "apt"),
176
+ "python3": ("python3", "apt"),
177
+ "pip": ("python3-pip", "apt"),
178
+ "node": ("nodejs", "apt"),
179
+ "npm": ("npm", "apt"),
180
+ "yarn": ("yarnpkg", "apt"),
181
+ "docker": ("docker.io", "apt"),
182
+ "dockerd": ("docker.io", "apt"),
183
+ "docker-compose": ("docker-compose", "apt"),
184
+ "podman": ("podman", "apt"),
185
+ "nginx": ("nginx", "apt"),
186
+ "apache2": ("apache2", "apt"),
187
+ "httpd": ("apache2", "apt"),
188
+ "postgres": ("postgresql", "apt"),
189
+ "postgresql": ("postgresql", "apt"),
190
+ "mysql": ("mysql-server", "apt"),
191
+ "mysqld": ("mysql-server", "apt"),
192
+ "mongod": ("mongodb", "apt"),
193
+ "mongodb": ("mongodb", "apt"),
194
+ "redis-server": ("redis-server", "apt"),
195
+ "redis": ("redis-server", "apt"),
196
+ "vim": ("vim", "apt"),
197
+ "nvim": ("neovim", "apt"),
198
+ "emacs": ("emacs", "apt"),
199
+ "firefox": ("firefox", "apt"),
200
+ "chromium": ("chromium-browser", "apt"),
201
+ "jupyter": ("jupyter-notebook", "apt"),
202
+ "jupyter-lab": ("jupyterlab", "apt"),
203
+ "gunicorn": ("gunicorn", "apt"),
204
+ "uvicorn": ("uvicorn", "apt"),
205
+ "tmux": ("tmux", "apt"),
206
+ "screen": ("screen", "apt"),
207
+ "git": ("git", "apt"),
208
+ "curl": ("curl", "apt"),
209
+ "wget": ("wget", "apt"),
210
+ "ssh": ("openssh-client", "apt"),
211
+ "sshd": ("openssh-server", "apt"),
212
+ "go": ("golang", "apt"),
213
+ "cargo": ("cargo", "apt"),
214
+ "rustc": ("rustc", "apt"),
215
+ "java": ("default-jdk", "apt"),
216
+ "gradle": ("gradle", "apt"),
217
+ "mvn": ("maven", "apt"),
218
+ # Popular desktop apps (snap packages)
219
+ "chrome": ("chromium", "snap"),
220
+ "google-chrome": ("chromium", "snap"),
221
+ "pycharm": ("pycharm-community", "snap"),
222
+ "idea": ("intellij-idea-community", "snap"),
223
+ "code": ("code", "snap"),
224
+ "vscode": ("code", "snap"),
225
+ "slack": ("slack", "snap"),
226
+ "discord": ("discord", "snap"),
227
+ "spotify": ("spotify", "snap"),
228
+ "vlc": ("vlc", "apt"),
229
+ "gimp": ("gimp", "apt"),
230
+ "inkscape": ("inkscape", "apt"),
231
+ "blender": ("blender", "apt"),
232
+ "obs": ("obs-studio", "apt"),
233
+ "telegram": ("telegram-desktop", "snap"),
234
+ "postman": ("postman", "snap"),
235
+ "insomnia": ("insomnia", "snap"),
236
+ "dbeaver": ("dbeaver-ce", "snap"),
237
+ "sublime": ("sublime-text", "snap"),
238
+ "atom": ("atom", "snap"),
239
+ }
240
+
241
+ # Map applications to their config/data directories for complete cloning
242
+ # These directories contain user settings, extensions, profiles, credentials
243
+ APP_DATA_DIRS = {
244
+ # Browsers - profiles, extensions, bookmarks, passwords
245
+ "chrome": [".config/google-chrome", ".config/chromium"],
246
+ "chromium": [".config/chromium"],
247
+ "firefox": [".mozilla/firefox", ".cache/mozilla/firefox"],
248
+
249
+ # IDEs and editors - settings, extensions, projects history
250
+ "code": [".config/Code", ".vscode", ".vscode-server"],
251
+ "vscode": [".config/Code", ".vscode", ".vscode-server"],
252
+ "pycharm": [".config/JetBrains", ".local/share/JetBrains", ".cache/JetBrains"],
253
+ "idea": [".config/JetBrains", ".local/share/JetBrains"],
254
+ "webstorm": [".config/JetBrains", ".local/share/JetBrains"],
255
+ "goland": [".config/JetBrains", ".local/share/JetBrains"],
256
+ "sublime": [".config/sublime-text", ".config/sublime-text-3"],
257
+ "atom": [".atom"],
258
+ "vim": [".vim", ".vimrc", ".config/nvim"],
259
+ "nvim": [".config/nvim", ".local/share/nvim"],
260
+ "emacs": [".emacs.d", ".emacs"],
261
+ "cursor": [".config/Cursor", ".cursor"],
262
+
263
+ # Development tools
264
+ "docker": [".docker"],
265
+ "git": [".gitconfig", ".git-credentials", ".config/git"],
266
+ "npm": [".npm", ".npmrc"],
267
+ "yarn": [".yarn", ".yarnrc"],
268
+ "pip": [".pip", ".config/pip"],
269
+ "cargo": [".cargo"],
270
+ "rustup": [".rustup"],
271
+ "go": [".go", "go"],
272
+ "gradle": [".gradle"],
273
+ "maven": [".m2"],
274
+
275
+ # Python environments
276
+ "python": [".pyenv", ".virtualenvs", ".local/share/virtualenvs"],
277
+ "python3": [".pyenv", ".virtualenvs", ".local/share/virtualenvs"],
278
+ "conda": [".conda", "anaconda3", "miniconda3"],
279
+
280
+ # Node.js
281
+ "node": [".nvm", ".node", ".npm"],
282
+
283
+ # Databases
284
+ "postgres": [".pgpass", ".psqlrc", ".psql_history"],
285
+ "mysql": [".my.cnf", ".mysql_history"],
286
+ "mongodb": [".mongorc.js", ".dbshell"],
287
+ "redis": [".rediscli_history"],
288
+
289
+ # Communication apps
290
+ "slack": [".config/Slack"],
291
+ "discord": [".config/discord"],
292
+ "telegram": [".local/share/TelegramDesktop"],
293
+ "teams": [".config/Microsoft/Microsoft Teams"],
294
+
295
+ # Other tools
296
+ "postman": [".config/Postman"],
297
+ "insomnia": [".config/Insomnia"],
298
+ "dbeaver": [".local/share/DBeaverData"],
299
+ "ssh": [".ssh"],
300
+ "gpg": [".gnupg"],
301
+ "aws": [".aws"],
302
+ "gcloud": [".config/gcloud"],
303
+ "kubectl": [".kube"],
304
+ "terraform": [".terraform.d"],
305
+ "ansible": [".ansible"],
306
+
307
+ # General app data
308
+ "spotify": [".config/spotify"],
309
+ "vlc": [".config/vlc"],
310
+ "gimp": [".config/GIMP", ".gimp-2.10"],
311
+ "obs": [".config/obs-studio"],
194
312
  }
195
313
 
196
314
  def __init__(self):
197
315
  self.user = pwd.getpwuid(os.getuid()).pw_name
198
316
  self.home = Path.home()
199
317
 
318
+ def detect_app_data_dirs(self, applications: list) -> list:
319
+ """Detect config/data directories for running applications.
320
+
321
+ Returns list of paths that contain user data needed by running apps.
322
+ """
323
+ app_data_paths = []
324
+ seen_paths = set()
325
+
326
+ for app in applications:
327
+ app_name = app.name.lower()
328
+
329
+ # Check each known app pattern
330
+ for pattern, dirs in self.APP_DATA_DIRS.items():
331
+ if pattern in app_name:
332
+ for dir_name in dirs:
333
+ full_path = self.home / dir_name
334
+ if full_path.exists() and str(full_path) not in seen_paths:
335
+ seen_paths.add(str(full_path))
336
+ # Calculate size
337
+ try:
338
+ size = self._get_dir_size(full_path, max_depth=2)
339
+ except:
340
+ size = 0
341
+ app_data_paths.append({
342
+ "path": str(full_path),
343
+ "app": app.name,
344
+ "type": "app_data",
345
+ "size_mb": round(size / 1024 / 1024, 1)
346
+ })
347
+
348
+ return app_data_paths
349
+
200
350
  def detect_all(self) -> SystemSnapshot:
201
351
  """Detect all services, applications and paths."""
202
352
  return SystemSnapshot(
@@ -437,29 +587,53 @@ class SystemDetector:
437
587
  pass
438
588
  return containers
439
589
 
440
- def suggest_packages_for_apps(self, applications: list) -> list:
441
- """Suggest Ubuntu packages based on detected applications."""
442
- packages = set()
590
+ def suggest_packages_for_apps(self, applications: list) -> dict:
591
+ """Suggest packages based on detected applications.
592
+
593
+ Returns:
594
+ dict with 'apt' and 'snap' keys containing lists of packages
595
+ """
596
+ apt_packages = set()
597
+ snap_packages = set()
598
+
443
599
  for app in applications:
444
600
  app_name = app.name.lower()
445
- # Check if app name matches any known mapping
446
- for key, package in self.APP_TO_PACKAGE_MAP.items():
601
+ for key, (package, install_type) in self.APP_TO_PACKAGE_MAP.items():
447
602
  if key in app_name:
448
- packages.add(package)
603
+ if install_type == "snap":
604
+ snap_packages.add(package)
605
+ else:
606
+ apt_packages.add(package)
449
607
  break
450
- return sorted(list(packages))
608
+
609
+ return {
610
+ "apt": sorted(list(apt_packages)),
611
+ "snap": sorted(list(snap_packages))
612
+ }
451
613
 
452
- def suggest_packages_for_services(self, services: list) -> list:
453
- """Suggest Ubuntu packages based on detected services."""
454
- packages = set()
614
+ def suggest_packages_for_services(self, services: list) -> dict:
615
+ """Suggest packages based on detected services.
616
+
617
+ Returns:
618
+ dict with 'apt' and 'snap' keys containing lists of packages
619
+ """
620
+ apt_packages = set()
621
+ snap_packages = set()
622
+
455
623
  for service in services:
456
624
  service_name = service.name.lower()
457
- # Check if service name matches any known mapping
458
- for key, package in self.APP_TO_PACKAGE_MAP.items():
625
+ for key, (package, install_type) in self.APP_TO_PACKAGE_MAP.items():
459
626
  if key in service_name:
460
- packages.add(package)
627
+ if install_type == "snap":
628
+ snap_packages.add(package)
629
+ else:
630
+ apt_packages.add(package)
461
631
  break
462
- return sorted(list(packages))
632
+
633
+ return {
634
+ "apt": sorted(list(apt_packages)),
635
+ "snap": sorted(list(snap_packages))
636
+ }
463
637
 
464
638
  def get_system_info(self) -> dict:
465
639
  """Get basic system information."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clonebox
3
- Version: 0.1.6
3
+ Version: 0.1.7
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
@@ -38,6 +38,7 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
38
38
  Dynamic: license-file
39
39
 
40
40
  # CloneBox 📦
41
+ ![img.png](img.png)
41
42
 
42
43
  ```commandline
43
44
  ╔═══════════════════════════════════════════════════════╗
@@ -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=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,,
@@ -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=mlGDWvBvi4UJjOpjG7rqJYzzxpmIzwhbv16CMYRFb7c,40583
4
- clonebox/cloner.py,sha256=MpHkm964NUdr0zEPu7G5ggRQPif-j3shlwM5jv8KWSU,25236
5
- clonebox/detector.py,sha256=rxqP7GxjBN1SjNo2L0m_URqX_FVBljqMr01g5nyESkc,14890
6
- clonebox-0.1.6.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
7
- clonebox-0.1.6.dist-info/METADATA,sha256=loeSNwXQo71C4Hiar2U-aSG1Mqqvx9y261VOH7h0uso,15562
8
- clonebox-0.1.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
9
- clonebox-0.1.6.dist-info/entry_points.txt,sha256=FES95Vi3btfViLEEoHdb8nikNxTqzaooi9ehZw9ZfWI,47
10
- clonebox-0.1.6.dist-info/top_level.txt,sha256=LdMo2cvCrEcRGH2M8JgQNVsCoszLV0xug6kx1JnaRjo,9
11
- clonebox-0.1.6.dist-info/RECORD,,