wasm-cli 0.14.0__py3-none-any.whl → 0.14.1__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.
wasm/__init__.py CHANGED
@@ -8,7 +8,7 @@ WASM - Web App System Management
8
8
  A robust CLI tool for deploying and managing web applications on Linux servers.
9
9
  """
10
10
 
11
- __version__ = "0.14.0"
11
+ __version__ = "0.14.1"
12
12
  __author__ = "Yago López Prado"
13
13
  __license__ = "WASM-NCSAL"
14
14
 
@@ -105,6 +105,7 @@ def _backup_create(args: Namespace, verbose: bool) -> int:
105
105
  include_env = not getattr(args, "no_env", False)
106
106
  include_node_modules = getattr(args, "include_node_modules", False)
107
107
  include_build = getattr(args, "include_build", False)
108
+ include_databases = getattr(args, "include_databases", False)
108
109
  tags = getattr(args, "tags", None)
109
110
 
110
111
  if not domain:
@@ -127,6 +128,7 @@ def _backup_create(args: Namespace, verbose: bool) -> int:
127
128
  include_env=include_env,
128
129
  include_node_modules=include_node_modules,
129
130
  include_build=include_build,
131
+ include_databases=include_databases,
130
132
  tags=tag_list,
131
133
  )
132
134
 
@@ -135,7 +137,13 @@ def _backup_create(args: Namespace, verbose: bool) -> int:
135
137
  logger.info(f" Size: {metadata.size_human}")
136
138
  if metadata.git_commit:
137
139
  logger.info(f" Commit: {metadata.git_commit} ({metadata.git_branch})")
138
-
140
+ if metadata.database_backups:
141
+ db_count = len(metadata.database_backups)
142
+ logger.info(f" Databases: {db_count} backed up")
143
+ for db_info in metadata.database_backups:
144
+ size_mb = db_info.get("size_bytes", 0) / (1024 * 1024)
145
+ logger.info(f" - {db_info['engine']}/{db_info['name']} ({size_mb:.1f} MB)")
146
+
139
147
  return 0
140
148
 
141
149
  except BackupError as e:
@@ -195,7 +203,7 @@ def _backup_list(args: Namespace, verbose: bool) -> int:
195
203
  by_domain[backup.domain].append(backup)
196
204
 
197
205
  for dom, dom_backups in by_domain.items():
198
- logger.info(f"\n📦 {dom}")
206
+ logger.info(f"\n[{dom}]")
199
207
  _print_backup_table(dom_backups, logger, indent=True)
200
208
 
201
209
  return 0
@@ -226,7 +234,7 @@ def _print_backup_table(backups, logger, indent: bool = False):
226
234
  desc_str = f" - {backup.description}"
227
235
 
228
236
  logger.info(
229
- f"{prefix} {backup.id}: {backup.size_human}, "
237
+ f"{prefix}- {backup.id}: {backup.size_human}, "
230
238
  f"{backup.age}{commit_str}{tags_str}{desc_str}"
231
239
  )
232
240
 
@@ -359,17 +367,17 @@ def _backup_verify(args: Namespace, verbose: bool) -> int:
359
367
 
360
368
  if result["valid"]:
361
369
  logger.success("Backup is valid")
362
- if result.get("checksum_verified"):
363
- logger.info(" Checksum verified")
364
- if result.get("archive_valid"):
365
- logger.info(f" Archive valid ({result.get('file_count', '?')} files)")
370
+ if result.get("checksum_ok"):
371
+ logger.info(" [OK] Checksum verified")
372
+ if result.get("files_ok"):
373
+ logger.info(f" [OK] Archive valid ({result.get('file_count', '?')} files)")
366
374
  else:
367
375
  logger.error("Backup is invalid")
368
376
  for err in result["errors"]:
369
- logger.error(f" {err}")
370
-
377
+ logger.error(f" [ERROR] {err}")
378
+
371
379
  for warn in result.get("warnings", []):
372
- logger.warning(f" {warn}")
380
+ logger.warning(f" [WARN] {warn}")
373
381
 
374
382
  return 0 if result["valid"] else 1
375
383
 
@@ -313,13 +313,29 @@ def _store_import(args: Namespace, verbose: bool) -> int:
313
313
  else:
314
314
  logger.step(2, 3, "No Apache sites found")
315
315
 
316
- # 3. Import from systemd services (wasm-* prefix)
316
+ # 3. Import from systemd services (both legacy wasm-* and new format)
317
317
  logger.step(3, 3, "Scanning systemd services")
318
318
  if SYSTEMD_DIR.exists():
319
- for service_file in SYSTEMD_DIR.glob("wasm-*.service"):
319
+ # Find all potential WASM service files
320
+ service_files = list(SYSTEMD_DIR.glob("wasm-*.service"))
321
+ # Also check for services matching domain pattern (new format)
322
+ for sf in SYSTEMD_DIR.glob("*.service"):
323
+ name = sf.stem
324
+ # Skip if already found as wasm-* or if it's a system service
325
+ if name.startswith("wasm-") or not "-" in name:
326
+ continue
327
+ # Check if it looks like a domain-based name (has hyphen, no @ or other special chars)
328
+ if "@" not in name and name.count("-") >= 1:
329
+ service_files.append(sf)
330
+
331
+ for service_file in service_files:
320
332
  # Extract app name from service file name
321
- service_name = service_file.stem # wasm-example-com
322
- app_name = service_name[5:] # example-com (remove wasm- prefix)
333
+ service_name = service_file.stem
334
+ # Handle both legacy (wasm-example-com) and new format (example-com)
335
+ if service_name.startswith("wasm-"):
336
+ app_name = service_name[5:] # Remove wasm- prefix
337
+ else:
338
+ app_name = service_name
323
339
 
324
340
  if store.get_service(app_name):
325
341
  continue
@@ -639,7 +639,7 @@ def _handle_delete(args: Namespace) -> int:
639
639
  try:
640
640
  status = service_manager.status(app_name)
641
641
  if status.get("exists"):
642
- logger.key_value("Stop and remove service", f"wasm-{app_name}")
642
+ logger.key_value("Stop and remove service", app_name)
643
643
  except Exception:
644
644
  pass
645
645
 
@@ -734,9 +734,9 @@ def _handle_logs(args: Namespace) -> int:
734
734
  domain = validate_domain(args.domain)
735
735
  app_name = domain_to_app_name(domain)
736
736
 
737
- # Get service name (ServiceManager adds prefix internally)
738
- service_name = f"wasm-{app_name}"
739
-
737
+ # Get resolved service name (handles both legacy wasm-* and new format)
738
+ service_name = service_manager._resolve_service_name(app_name)
739
+
740
740
  if args.follow:
741
741
  # Use journalctl directly for follow mode
742
742
  import subprocess
wasm/cli/parser.py CHANGED
@@ -908,6 +908,11 @@ def _add_backup_parser(subparsers) -> None:
908
908
  action="store_true",
909
909
  help="Include build artifacts (.next, dist, build)",
910
910
  )
911
+ create.add_argument(
912
+ "--include-databases", "--include-db",
913
+ action="store_true",
914
+ help="Include associated database dumps in backup",
915
+ )
911
916
  create.add_argument(
912
917
  "--tags", "-t",
913
918
  help="Comma-separated tags for the backup",
wasm/completions/_wasm CHANGED
@@ -14,14 +14,38 @@
14
14
 
15
15
  # Helper functions
16
16
  _wasm_apps() {
17
- local apps_dir="/var/www/apps"
18
17
  local apps=()
19
- if [[ -d "$apps_dir" ]]; then
20
- for app in "$apps_dir"/*/; do
21
- if [[ -d "$app" ]]; then
22
- apps+=("$(basename "$app")")
23
- fi
24
- done 2>/dev/null
18
+
19
+ # Try to get domains from SQLite store (fast and accurate)
20
+ local db_path="/var/lib/wasm/wasm.db"
21
+ if [[ ! -f "$db_path" ]]; then
22
+ db_path="$HOME/.local/share/wasm/wasm.db"
23
+ fi
24
+
25
+ if [[ -f "$db_path" ]] && command -v sqlite3 &>/dev/null; then
26
+ apps=(${(f)"$(sqlite3 "$db_path" "SELECT domain FROM apps" 2>/dev/null)"})
27
+ else
28
+ # Fallback: list app directories and convert names to domains
29
+ local apps_dir="/var/www/apps"
30
+ if [[ -d "$apps_dir" ]]; then
31
+ for app in "$apps_dir"/*/; do
32
+ if [[ -d "$app" ]]; then
33
+ local name
34
+ name=$(basename "$app")
35
+ # Convert directory name to domain format
36
+ # Legacy: wasm-example-com -> example.com
37
+ # New: example-com -> example.com
38
+ if [[ "$name" == wasm-* ]]; then
39
+ apps+=("${${name#wasm-}//\-/.}")
40
+ elif [[ "$name" == *-* ]]; then
41
+ # New format: convert hyphens to dots
42
+ apps+=("${name//\-/.}")
43
+ else
44
+ apps+=("$name")
45
+ fi
46
+ fi
47
+ done 2>/dev/null
48
+ fi
25
49
  fi
26
50
  _describe -t apps 'deployed applications' apps
27
51
  }
@@ -16,11 +16,35 @@
16
16
 
17
17
  # Helper function to get list of deployed apps (domains)
18
18
  _wasm_get_apps() {
19
+ # Try to get domains from SQLite store (fast and accurate)
20
+ local db_path="/var/lib/wasm/wasm.db"
21
+ if [[ ! -f "$db_path" ]]; then
22
+ db_path="$HOME/.local/share/wasm/wasm.db"
23
+ fi
24
+
25
+ if [[ -f "$db_path" ]] && command -v sqlite3 &>/dev/null; then
26
+ sqlite3 "$db_path" "SELECT domain FROM apps" 2>/dev/null
27
+ return
28
+ fi
29
+
30
+ # Fallback: list app directories and convert names to domains
19
31
  local apps_dir="/var/www/apps"
20
32
  if [[ -d "$apps_dir" ]]; then
21
33
  for app in "$apps_dir"/*/; do
22
34
  if [[ -d "$app" ]]; then
23
- basename "$app"
35
+ local name
36
+ name=$(basename "$app")
37
+ # Convert directory name to domain format
38
+ # Legacy: wasm-example-com -> example.com
39
+ # New: example-com -> example.com
40
+ if [[ "$name" == wasm-* ]]; then
41
+ echo "${name#wasm-}" | tr '-' '.'
42
+ elif [[ "$name" == *-* ]]; then
43
+ # New format: convert hyphens to dots
44
+ echo "$name" | tr '-' '.'
45
+ else
46
+ echo "$name"
47
+ fi
24
48
  fi
25
49
  done 2>/dev/null
26
50
  fi
@@ -14,11 +14,34 @@ complete -c wasm -f
14
14
 
15
15
  # Helper functions
16
16
  function __wasm_get_apps
17
+ # Try to get domains from SQLite store (fast and accurate)
18
+ set -l db_path "/var/lib/wasm/wasm.db"
19
+ if not test -f "$db_path"
20
+ set db_path "$HOME/.local/share/wasm/wasm.db"
21
+ end
22
+
23
+ if test -f "$db_path"; and command -v sqlite3 >/dev/null
24
+ sqlite3 "$db_path" "SELECT domain FROM apps" 2>/dev/null
25
+ return
26
+ end
27
+
28
+ # Fallback: list app directories and convert names to domains
17
29
  set -l apps_dir "/var/www/apps"
18
30
  if test -d "$apps_dir"
19
31
  for app in $apps_dir/*/
20
32
  if test -d "$app"
21
- basename "$app"
33
+ set -l name (basename "$app")
34
+ # Convert directory name to domain format
35
+ # Legacy: wasm-example-com -> example.com
36
+ # New: example-com -> example.com
37
+ if string match -q 'wasm-*' "$name"
38
+ string replace 'wasm-' '' "$name" | string replace -a '-' '.'
39
+ else if string match -q '*-*' "$name"
40
+ # New format: convert hyphens to dots
41
+ string replace -a '-' '.' "$name"
42
+ else
43
+ echo "$name"
44
+ end
22
45
  end
23
46
  end 2>/dev/null
24
47
  end
wasm/core/utils.py CHANGED
@@ -219,12 +219,27 @@ def sanitize_name(name: str) -> str:
219
219
  def domain_to_app_name(domain: str) -> str:
220
220
  """
221
221
  Convert a domain to an application name.
222
-
222
+
223
223
  Args:
224
224
  domain: Domain name (e.g., "myapp.example.com").
225
-
225
+
226
+ Returns:
227
+ Application name (e.g., "myapp-example-com").
228
+ """
229
+ return sanitize_name(domain)
230
+
231
+
232
+ def legacy_app_name(domain: str) -> str:
233
+ """
234
+ Get legacy app name format (with wasm- prefix).
235
+
236
+ Used for backwards compatibility with apps created before v0.14.1.
237
+
238
+ Args:
239
+ domain: Domain name (e.g., "myapp.example.com").
240
+
226
241
  Returns:
227
- Application name (e.g., "wasm-myapp-example-com").
242
+ Legacy application name (e.g., "wasm-myapp-example-com").
228
243
  """
229
244
  return f"wasm-{sanitize_name(domain)}"
230
245
 
wasm/deployers/base.py CHANGED
@@ -899,9 +899,9 @@ class BaseDeployer(ABC):
899
899
  app = self.store.get_app(self.domain)
900
900
  app_id = app.id if app else None
901
901
 
902
- # Service name without prefix (store handles that)
902
+ # Service name (no prefix for new format)
903
903
  service_name = self.app_name
904
- service_file = SYSTEMD_DIR / f"wasm-{self.app_name}.service"
904
+ service_file = SYSTEMD_DIR / f"{self.app_name}.service"
905
905
 
906
906
  existing_service = self.store.get_service(service_name)
907
907
 
@@ -1142,7 +1142,7 @@ class BaseDeployer(ABC):
1142
1142
  # Basic info
1143
1143
  protocol = "https" if ssl_obtained else "http"
1144
1144
  self.logger.key_value("URL", f"{protocol}://{self.domain}")
1145
- self.logger.key_value("Service", f"wasm-{self.app_name}")
1145
+ self.logger.key_value("Service", self.app_name)
1146
1146
  self.logger.key_value("Port", str(self.port))
1147
1147
  self.logger.key_value("App Path", str(self.app_path))
1148
1148
 
@@ -46,6 +46,9 @@ class BackupMetadata:
46
46
  description: str
47
47
  includes_env: bool
48
48
  includes_node_modules: bool
49
+ includes_build: bool = False
50
+ includes_databases: bool = False
51
+ database_backups: List[Dict[str, Any]] = field(default_factory=list)
49
52
  git_commit: Optional[str] = None
50
53
  git_branch: Optional[str] = None
51
54
  checksum: Optional[str] = None
@@ -64,6 +67,9 @@ class BackupMetadata:
64
67
  "description": self.description,
65
68
  "includes_env": self.includes_env,
66
69
  "includes_node_modules": self.includes_node_modules,
70
+ "includes_build": self.includes_build,
71
+ "includes_databases": self.includes_databases,
72
+ "database_backups": self.database_backups,
67
73
  "git_commit": self.git_commit,
68
74
  "git_branch": self.git_branch,
69
75
  "checksum": self.checksum,
@@ -84,6 +90,9 @@ class BackupMetadata:
84
90
  description=data.get("description", ""),
85
91
  includes_env=data.get("includes_env", False),
86
92
  includes_node_modules=data.get("includes_node_modules", False),
93
+ includes_build=data.get("includes_build", False),
94
+ includes_databases=data.get("includes_databases", False),
95
+ database_backups=data.get("database_backups", []),
87
96
  git_commit=data.get("git_commit"),
88
97
  git_branch=data.get("git_branch"),
89
98
  checksum=data.get("checksum"),
@@ -269,24 +278,26 @@ class BackupManager:
269
278
  include_env: bool = True,
270
279
  include_node_modules: bool = False,
271
280
  include_build: bool = False,
281
+ include_databases: bool = False,
272
282
  tags: Optional[List[str]] = None,
273
283
  pre_backup_hook: Optional[str] = None,
274
284
  ) -> BackupMetadata:
275
285
  """
276
286
  Create a backup of an application.
277
-
287
+
278
288
  Args:
279
289
  domain: Domain name of the application.
280
290
  description: Optional description for the backup.
281
291
  include_env: Include .env files in backup.
282
292
  include_node_modules: Include node_modules (large!).
283
293
  include_build: Include build artifacts.
294
+ include_databases: Include associated database dumps.
284
295
  tags: Optional tags for the backup.
285
296
  pre_backup_hook: Optional command to run before backup.
286
-
297
+
287
298
  Returns:
288
299
  BackupMetadata for the created backup.
289
-
300
+
290
301
  Raises:
291
302
  BackupError: If backup fails.
292
303
  """
@@ -378,6 +389,11 @@ class BackupManager:
378
389
  result = run_command_sudo(["sha256sum", str(backup_file)])
379
390
  checksum = result.stdout.split()[0] if result.success else None
380
391
 
392
+ # Backup associated databases if requested
393
+ database_backups = []
394
+ if include_databases:
395
+ database_backups = self._backup_databases(domain)
396
+
381
397
  # Create metadata
382
398
  metadata = BackupMetadata(
383
399
  id=backup_id,
@@ -390,6 +406,9 @@ class BackupManager:
390
406
  description=description,
391
407
  includes_env=include_env,
392
408
  includes_node_modules=include_node_modules,
409
+ includes_build=include_build,
410
+ includes_databases=include_databases,
411
+ database_backups=database_backups,
393
412
  git_commit=git_commit,
394
413
  git_branch=git_branch,
395
414
  checksum=checksum,
@@ -415,29 +434,33 @@ class BackupManager:
415
434
  def list_backups(
416
435
  self,
417
436
  domain: Optional[str] = None,
437
+ app_name: Optional[str] = None,
418
438
  tags: Optional[List[str]] = None,
419
439
  limit: Optional[int] = None,
420
440
  ) -> List[BackupMetadata]:
421
441
  """
422
442
  List backups for an application or all applications.
423
-
443
+
424
444
  Args:
425
445
  domain: Filter by domain (None for all).
446
+ app_name: Filter by app name directly (alternative to domain).
426
447
  tags: Filter by tags.
427
448
  limit: Maximum number of backups to return.
428
-
449
+
429
450
  Returns:
430
451
  List of BackupMetadata objects.
431
452
  """
432
453
  backups = []
433
-
454
+
434
455
  if not self.backup_dir.exists():
435
456
  return backups
436
-
457
+
437
458
  # Determine which directories to scan
438
- if domain:
439
- app_name = domain_to_app_name(domain)
459
+ if app_name:
440
460
  dirs_to_scan = [self._get_app_backup_dir(app_name)]
461
+ elif domain:
462
+ resolved_app_name = domain_to_app_name(domain)
463
+ dirs_to_scan = [self._get_app_backup_dir(resolved_app_name)]
441
464
  else:
442
465
  dirs_to_scan = [d for d in self.backup_dir.iterdir() if d.is_dir()]
443
466
 
@@ -739,11 +762,11 @@ class BackupManager:
739
762
  def _rotate_backups(self, app_name: str) -> None:
740
763
  """
741
764
  Rotate old backups to keep only the most recent ones.
742
-
765
+
743
766
  Args:
744
767
  app_name: Application name.
745
768
  """
746
- backups = self.list_backups(domain=app_name.replace("-", ".").replace("wasm-", ""))
769
+ backups = self.list_backups(app_name=app_name)
747
770
 
748
771
  if len(backups) > self.max_backups:
749
772
  # Delete oldest backups
@@ -753,7 +776,78 @@ class BackupManager:
753
776
  self.logger.debug(f"Rotated old backup: {backup.id}")
754
777
  except Exception as e:
755
778
  self.logger.warning(f"Failed to rotate backup {backup.id}: {e}")
756
-
779
+
780
+ def _backup_databases(self, domain: str) -> List[Dict[str, Any]]:
781
+ """
782
+ Backup databases associated with an application.
783
+
784
+ Args:
785
+ domain: Domain name of the application.
786
+
787
+ Returns:
788
+ List of database backup info dictionaries.
789
+ """
790
+ database_backups = []
791
+
792
+ try:
793
+ from wasm.core.store import WASMStore
794
+ from wasm.managers.database.registry import DatabaseRegistry
795
+ except ImportError as e:
796
+ self.logger.warning(f"Database backup not available: {e}")
797
+ return database_backups
798
+
799
+ try:
800
+ store = WASMStore()
801
+ app = store.get_app_by_domain(domain)
802
+
803
+ if not app or not app.id:
804
+ self.logger.debug(f"No app found in store for {domain}")
805
+ return database_backups
806
+
807
+ databases = store.list_databases(app_id=app.id)
808
+
809
+ if not databases:
810
+ self.logger.debug(f"No databases associated with {domain}")
811
+ return database_backups
812
+
813
+ self.logger.info(f"Backing up {len(databases)} database(s) for {domain}")
814
+
815
+ for db in databases:
816
+ try:
817
+ manager = DatabaseRegistry.get(db.engine, verbose=self.verbose)
818
+
819
+ if not manager:
820
+ self.logger.warning(f"No manager available for {db.engine}")
821
+ continue
822
+
823
+ if not manager.is_installed():
824
+ self.logger.warning(
825
+ f"{db.engine} not installed, skipping {db.name}"
826
+ )
827
+ continue
828
+
829
+ backup_info = manager.backup(database=db.name, compress=True)
830
+
831
+ database_backups.append({
832
+ "engine": db.engine,
833
+ "name": db.name,
834
+ "backup_path": str(backup_info.path),
835
+ "size_bytes": backup_info.size,
836
+ "created": backup_info.created.isoformat(),
837
+ })
838
+
839
+ self.logger.info(f" Backed up {db.engine} database: {db.name}")
840
+
841
+ except Exception as e:
842
+ self.logger.warning(
843
+ f" Failed to backup {db.engine} '{db.name}': {e}"
844
+ )
845
+
846
+ except Exception as e:
847
+ self.logger.warning(f"Database backup failed: {e}")
848
+
849
+ return database_backups
850
+
757
851
  def verify(self, backup_id: str) -> Dict[str, Any]:
758
852
  """
759
853
  Verify a backup's integrity.
@@ -795,7 +889,7 @@ class BackupManager:
795
889
  results["valid"] = False
796
890
  results["errors"].append("Checksum mismatch")
797
891
  else:
798
- results["checksum_verified"] = True
892
+ results["checksum_ok"] = True
799
893
  else:
800
894
  results["warnings"].append("Could not verify checksum")
801
895
  else:
@@ -807,7 +901,7 @@ class BackupManager:
807
901
  results["valid"] = False
808
902
  results["errors"].append("Archive is corrupted")
809
903
  else:
810
- results["archive_valid"] = True
904
+ results["files_ok"] = True
811
905
  results["file_count"] = len(result.stdout.strip().split("\n"))
812
906
 
813
907
  return results
@@ -18,18 +18,19 @@ from wasm.managers.base_manager import BaseManager
18
18
  class ServiceManager(BaseManager):
19
19
  """
20
20
  Manager for systemd services.
21
-
21
+
22
22
  Handles creating, starting, stopping, and managing systemd services.
23
23
  """
24
-
24
+
25
25
  SYSTEMD_DIR = SYSTEMD_DIR
26
- SERVICE_PREFIX = "wasm-"
27
-
26
+ SERVICE_PREFIX = "" # New services don't use prefix
27
+ LEGACY_PREFIX = "wasm-" # For backwards compatibility
28
+
28
29
  def __init__(self, verbose: bool = False):
29
30
  """Initialize service manager."""
30
31
  super().__init__(verbose=verbose)
31
32
  self.store = get_store()
32
-
33
+
33
34
  try:
34
35
  self.jinja_env = Environment(
35
36
  loader=PackageLoader("wasm", "templates/systemd"),
@@ -38,12 +39,12 @@ class ServiceManager(BaseManager):
38
39
  )
39
40
  except Exception:
40
41
  self.jinja_env = None
41
-
42
+
42
43
  def is_installed(self) -> bool:
43
44
  """Check if systemd is available."""
44
45
  result = self._run(["which", "systemctl"])
45
46
  return result.success
46
-
47
+
47
48
  def get_version(self) -> Optional[str]:
48
49
  """Get systemd version."""
49
50
  result = self._run(["systemctl", "--version"])
@@ -52,28 +53,58 @@ class ServiceManager(BaseManager):
52
53
  if match:
53
54
  return match.group(1)
54
55
  return None
55
-
56
+
56
57
  def _get_service_name(self, name: str) -> str:
57
58
  """
58
- Get full service name with prefix.
59
-
59
+ Get service name for new services (no prefix).
60
+
60
61
  Args:
61
62
  name: Base service name.
62
-
63
+
63
64
  Returns:
64
- Full service name.
65
+ Service name without prefix.
65
66
  """
66
- if name.startswith(self.SERVICE_PREFIX):
67
- return name
68
- return f"{self.SERVICE_PREFIX}{name}"
69
-
67
+ # Strip legacy prefix if present
68
+ if name.startswith(self.LEGACY_PREFIX):
69
+ return name[len(self.LEGACY_PREFIX):]
70
+ return name
71
+
72
+ def _resolve_service_name(self, name: str) -> str:
73
+ """
74
+ Resolve actual service name, checking both new and legacy formats.
75
+
76
+ For backwards compatibility, checks if legacy (wasm-*) service exists
77
+ and returns that if found. Otherwise returns the new format.
78
+
79
+ Args:
80
+ name: Base service name.
81
+
82
+ Returns:
83
+ Actual service name (may have legacy prefix if exists).
84
+ """
85
+ base_name = self._get_service_name(name)
86
+
87
+ # Check if legacy service exists
88
+ legacy_name = f"{self.LEGACY_PREFIX}{base_name}"
89
+ legacy_file = self.SYSTEMD_DIR / f"{legacy_name}.service"
90
+ if legacy_file.exists():
91
+ return legacy_name
92
+
93
+ # Check if new format exists
94
+ new_file = self.SYSTEMD_DIR / f"{base_name}.service"
95
+ if new_file.exists():
96
+ return base_name
97
+
98
+ # Neither exists, return new format for creation
99
+ return base_name
100
+
70
101
  def _get_service_file(self, name: str) -> Path:
71
102
  """
72
- Get service file path.
73
-
103
+ Get service file path for new services.
104
+
74
105
  Args:
75
106
  name: Service name.
76
-
107
+
77
108
  Returns:
78
109
  Path to service file.
79
110
  """
@@ -81,6 +112,21 @@ class ServiceManager(BaseManager):
81
112
  if not service_name.endswith(".service"):
82
113
  service_name = f"{service_name}.service"
83
114
  return self.SYSTEMD_DIR / service_name
115
+
116
+ def _resolve_service_file(self, name: str) -> Path:
117
+ """
118
+ Resolve service file path, checking both new and legacy formats.
119
+
120
+ Args:
121
+ name: Service name.
122
+
123
+ Returns:
124
+ Path to existing service file (or new format if none exists).
125
+ """
126
+ service_name = self._resolve_service_name(name)
127
+ if not service_name.endswith(".service"):
128
+ service_name = f"{service_name}.service"
129
+ return self.SYSTEMD_DIR / service_name
84
130
 
85
131
  def daemon_reload(self) -> bool:
86
132
  """
@@ -153,14 +199,14 @@ class ServiceManager(BaseManager):
153
199
  def get_status(self, name: str) -> Dict:
154
200
  """
155
201
  Get service status.
156
-
202
+
157
203
  Args:
158
204
  name: Service name.
159
-
205
+
160
206
  Returns:
161
207
  Dictionary with status information.
162
208
  """
163
- service_name = self._get_service_name(name)
209
+ service_name = self._resolve_service_name(name)
164
210
 
165
211
  # Check if active
166
212
  result = self._run(["systemctl", "is-active", service_name])
@@ -285,16 +331,16 @@ class ServiceManager(BaseManager):
285
331
  Returns:
286
332
  True if service was deleted.
287
333
  """
288
- service_name = self._get_service_name(name)
289
-
334
+ service_name = self._resolve_service_name(name)
335
+
290
336
  # Stop service if running
291
337
  self.stop(name)
292
-
338
+
293
339
  # Disable service
294
340
  self.disable(name)
295
-
341
+
296
342
  # Remove service file
297
- service_file = self._get_service_file(name)
343
+ service_file = self._resolve_service_file(name)
298
344
  if service_file.exists():
299
345
  if not remove_file(service_file, sudo=True):
300
346
  raise ServiceError(f"Failed to delete service: {service_name}")
@@ -314,14 +360,14 @@ class ServiceManager(BaseManager):
314
360
  def start(self, name: str) -> bool:
315
361
  """
316
362
  Start a service.
317
-
363
+
318
364
  Args:
319
365
  name: Service name.
320
-
366
+
321
367
  Returns:
322
368
  True if service started successfully.
323
369
  """
324
- service_name = self._get_service_name(name)
370
+ service_name = self._resolve_service_name(name)
325
371
  result = self._run_sudo(["systemctl", "start", service_name])
326
372
 
327
373
  if not result.success:
@@ -341,14 +387,14 @@ class ServiceManager(BaseManager):
341
387
  def stop(self, name: str) -> bool:
342
388
  """
343
389
  Stop a service.
344
-
390
+
345
391
  Args:
346
392
  name: Service name.
347
-
393
+
348
394
  Returns:
349
395
  True if service stopped successfully.
350
396
  """
351
- service_name = self._get_service_name(name)
397
+ service_name = self._resolve_service_name(name)
352
398
  result = self._run_sudo(["systemctl", "stop", service_name])
353
399
 
354
400
  # Update store status
@@ -362,14 +408,14 @@ class ServiceManager(BaseManager):
362
408
  def restart(self, name: str) -> bool:
363
409
  """
364
410
  Restart a service.
365
-
411
+
366
412
  Args:
367
413
  name: Service name.
368
-
414
+
369
415
  Returns:
370
416
  True if service restarted successfully.
371
417
  """
372
- service_name = self._get_service_name(name)
418
+ service_name = self._resolve_service_name(name)
373
419
  result = self._run_sudo(["systemctl", "restart", service_name])
374
420
 
375
421
  if not result.success:
@@ -389,14 +435,14 @@ class ServiceManager(BaseManager):
389
435
  def enable(self, name: str) -> bool:
390
436
  """
391
437
  Enable a service to start on boot.
392
-
438
+
393
439
  Args:
394
440
  name: Service name.
395
-
441
+
396
442
  Returns:
397
443
  True if service was enabled.
398
444
  """
399
- service_name = self._get_service_name(name)
445
+ service_name = self._resolve_service_name(name)
400
446
  result = self._run_sudo(["systemctl", "enable", service_name])
401
447
 
402
448
  # Update store
@@ -413,14 +459,14 @@ class ServiceManager(BaseManager):
413
459
  def disable(self, name: str) -> bool:
414
460
  """
415
461
  Disable a service from starting on boot.
416
-
462
+
417
463
  Args:
418
464
  name: Service name.
419
-
465
+
420
466
  Returns:
421
467
  True if service was disabled.
422
468
  """
423
- service_name = self._get_service_name(name)
469
+ service_name = self._resolve_service_name(name)
424
470
  result = self._run_sudo(["systemctl", "disable", service_name])
425
471
 
426
472
  # Update store
@@ -442,16 +488,16 @@ class ServiceManager(BaseManager):
442
488
  ) -> str:
443
489
  """
444
490
  Get service logs.
445
-
491
+
446
492
  Args:
447
493
  name: Service name.
448
494
  lines: Number of lines to return.
449
495
  follow: Follow log output (not supported in this method).
450
-
496
+
451
497
  Returns:
452
498
  Log output.
453
499
  """
454
- service_name = self._get_service_name(name)
500
+ service_name = self._resolve_service_name(name)
455
501
 
456
502
  cmd = ["journalctl", "-u", service_name, "-n", str(lines), "--no-pager"]
457
503
  result = self._run(cmd)
wasm/web/api/apps.py CHANGED
@@ -104,7 +104,7 @@ async def list_apps(
104
104
  # Get service from store by app_id
105
105
  service = store.get_service_by_app_id(app.id) if app.id else None
106
106
  if service:
107
- status = service_manager.get_status(service.name.replace("wasm-", ""))
107
+ status = service_manager.get_status(service.name)
108
108
  active = status.get("active", False)
109
109
  enabled = status.get("enabled", False)
110
110
  pid = status.get("pid")
@@ -158,7 +158,7 @@ async def get_app(
158
158
  service = store.get_service_by_app_id(app.id) if app.id else None
159
159
  if service:
160
160
  service_manager = ServiceManager(verbose=False)
161
- status = service_manager.get_status(service.name.replace("wasm-", ""))
161
+ status = service_manager.get_status(service.name)
162
162
  active = status.get("active", False)
163
163
  enabled = status.get("enabled", False)
164
164
  pid = status.get("pid")
@@ -356,9 +356,11 @@ async def get_app_logs(
356
356
  except Exception as e:
357
357
  raise HTTPException(status_code=400, detail=str(e))
358
358
 
359
+ from wasm.managers.service_manager import ServiceManager
359
360
  app_name = domain_to_app_name(validated_domain)
360
- service_name = f"wasm-{app_name}"
361
-
361
+ service_manager = ServiceManager(verbose=False)
362
+ service_name = service_manager._resolve_service_name(app_name)
363
+
362
364
  try:
363
365
  result = subprocess.run(
364
366
  ["journalctl", "-u", service_name, "-n", str(lines), "--no-pager"],
wasm/web/api/backups.py CHANGED
@@ -4,7 +4,7 @@ Backups API endpoints.
4
4
  Provides endpoints for managing application backups.
5
5
  """
6
6
 
7
- from typing import List, Optional
7
+ from typing import Any, Dict, List, Optional
8
8
  from pathlib import Path
9
9
 
10
10
  from fastapi import APIRouter, Request, HTTPException, Depends, Query
@@ -24,10 +24,16 @@ class BackupInfo(BaseModel):
24
24
  size: int
25
25
  size_human: str
26
26
  age: str
27
+ description: str = ""
27
28
  app_type: Optional[str] = None
29
+ includes_env: bool = False
30
+ includes_node_modules: bool = False
31
+ includes_build: bool = False
28
32
  has_database: bool = False
33
+ database_backups: List[Dict[str, Any]] = Field(default_factory=list)
29
34
  git_commit: Optional[str] = None
30
35
  git_branch: Optional[str] = None
36
+ tags: List[str] = Field(default_factory=list)
31
37
 
32
38
 
33
39
  class BackupListResponse(BaseModel):
@@ -48,8 +54,12 @@ class BackupStorageResponse(BaseModel):
48
54
  class CreateBackupRequest(BaseModel):
49
55
  """Request to create a new backup."""
50
56
  domain: str = Field(..., description="Domain of the app to backup")
51
- include_database: bool = Field(default=True, description="Include database in backup")
52
- compress: bool = Field(default=True, description="Compress the backup")
57
+ description: str = Field(default="", description="Description for the backup")
58
+ include_env: bool = Field(default=True, description="Include .env files")
59
+ include_node_modules: bool = Field(default=False, description="Include node_modules (large!)")
60
+ include_build: bool = Field(default=False, description="Include build artifacts")
61
+ include_database: bool = Field(default=False, description="Include database dumps")
62
+ tags: List[str] = Field(default_factory=list, description="Tags for the backup")
53
63
 
54
64
 
55
65
  class RestoreBackupRequest(BaseModel):
@@ -96,10 +106,16 @@ async def list_backups(
96
106
  size=backup.size_bytes,
97
107
  size_human=backup.size_human,
98
108
  age=backup.age,
109
+ description=backup.description,
99
110
  app_type=backup.app_type,
100
- has_database=False, # BackupMetadata doesn't track this
111
+ includes_env=backup.includes_env,
112
+ includes_node_modules=backup.includes_node_modules,
113
+ includes_build=backup.includes_build,
114
+ has_database=backup.includes_databases,
115
+ database_backups=backup.database_backups,
101
116
  git_commit=backup.git_commit,
102
- git_branch=backup.git_branch
117
+ git_branch=backup.git_branch,
118
+ tags=backup.tags
103
119
  ))
104
120
 
105
121
  return BackupListResponse(
@@ -178,10 +194,16 @@ async def get_backup(
178
194
  size=backup.size_bytes,
179
195
  size_human=backup.size_human,
180
196
  age=backup.age,
197
+ description=backup.description,
181
198
  app_type=backup.app_type,
182
- has_database=False, # BackupMetadata doesn't track this
199
+ includes_env=backup.includes_env,
200
+ includes_node_modules=backup.includes_node_modules,
201
+ includes_build=backup.includes_build,
202
+ has_database=backup.includes_databases,
203
+ database_backups=backup.database_backups,
183
204
  git_commit=backup.git_commit,
184
- git_branch=backup.git_branch
205
+ git_branch=backup.git_branch,
206
+ tags=backup.tags
185
207
  )
186
208
  except HTTPException:
187
209
  raise
@@ -214,6 +236,12 @@ async def create_backup(
214
236
 
215
237
  backup_meta = manager.create(
216
238
  domain=data.domain,
239
+ description=data.description,
240
+ include_env=data.include_env,
241
+ include_node_modules=data.include_node_modules,
242
+ include_build=data.include_build,
243
+ include_databases=data.include_database,
244
+ tags=data.tags,
217
245
  )
218
246
 
219
247
  return BackupActionResponse(
@@ -271,19 +299,11 @@ async def restore_backup(
271
299
 
272
300
  # Determine target domain
273
301
  target_domain = data.target_domain or backup.domain
274
-
275
- # Find or create target path
276
- from wasm.core.config import Config
277
- from wasm.core.utils import domain_to_app_name
278
-
279
- config = Config()
280
- app_name = domain_to_app_name(target_domain)
281
- target_path = config.apps_dir / app_name
282
-
302
+
283
303
  # Perform restore
284
304
  success = manager.restore(
285
305
  backup_id=backup_id,
286
- target_path=target_path
306
+ target_domain=target_domain
287
307
  )
288
308
 
289
309
  if success:
wasm/web/api/services.py CHANGED
@@ -71,11 +71,8 @@ async def list_services(
71
71
 
72
72
  result = []
73
73
  for svc in stored_services:
74
- if wasm_only and not svc.name.startswith("wasm-"):
75
- continue
76
-
77
- # Get live status from systemd
78
- live_status = service_manager.get_status(svc.name.replace("wasm-", ""))
74
+ # Get live status from systemd (ServiceManager resolves name automatically)
75
+ live_status = service_manager.get_status(svc.name)
79
76
 
80
77
  result.append(ServiceInfo(
81
78
  name=svc.name,
@@ -105,16 +102,16 @@ async def get_service(
105
102
  store = get_store()
106
103
  service_manager = ServiceManager(verbose=False)
107
104
 
108
- # Handle both wasm-prefixed and non-prefixed names
105
+ # Handle both prefixed and non-prefixed names for backwards compatibility
109
106
  svc = store.get_service(name)
110
107
  if not svc:
111
108
  svc = store.get_service(f"wasm-{name}")
112
-
109
+
113
110
  if not svc:
114
111
  raise HTTPException(status_code=404, detail=f"Service not found: {name}")
115
-
116
- # Get live status from systemd
117
- live_status = service_manager.get_status(svc.name.replace("wasm-", ""))
112
+
113
+ # Get live status from systemd (ServiceManager resolves name automatically)
114
+ live_status = service_manager.get_status(svc.name)
118
115
 
119
116
  return ServiceInfo(
120
117
  name=svc.name,
@@ -140,7 +137,7 @@ async def start_service(
140
137
  from wasm.managers.service_manager import ServiceManager
141
138
 
142
139
  service_manager = ServiceManager(verbose=False)
143
- app_name = name.replace("wasm-", "")
140
+ app_name = name
144
141
 
145
142
  status = service_manager.get_status(app_name)
146
143
  if not status["exists"]:
@@ -169,7 +166,7 @@ async def stop_service(
169
166
  from wasm.managers.service_manager import ServiceManager
170
167
 
171
168
  service_manager = ServiceManager(verbose=False)
172
- app_name = name.replace("wasm-", "")
169
+ app_name = name
173
170
 
174
171
  status = service_manager.get_status(app_name)
175
172
  if not status["exists"]:
@@ -198,7 +195,7 @@ async def restart_service(
198
195
  from wasm.managers.service_manager import ServiceManager
199
196
 
200
197
  service_manager = ServiceManager(verbose=False)
201
- app_name = name.replace("wasm-", "")
198
+ app_name = name
202
199
 
203
200
  status = service_manager.get_status(app_name)
204
201
  if not status["exists"]:
@@ -227,7 +224,7 @@ async def enable_service(
227
224
  from wasm.managers.service_manager import ServiceManager
228
225
 
229
226
  service_manager = ServiceManager(verbose=False)
230
- app_name = name.replace("wasm-", "")
227
+ app_name = name
231
228
 
232
229
  status = service_manager.get_status(app_name)
233
230
  if not status["exists"]:
@@ -256,7 +253,7 @@ async def disable_service(
256
253
  from wasm.managers.service_manager import ServiceManager
257
254
 
258
255
  service_manager = ServiceManager(verbose=False)
259
- app_name = name.replace("wasm-", "")
256
+ app_name = name
260
257
 
261
258
  status = service_manager.get_status(app_name)
262
259
  if not status["exists"]:
@@ -283,8 +280,10 @@ async def get_service_logs(
283
280
  """
284
281
  Get service logs from journalctl.
285
282
  """
286
- service_name = name if name.startswith("wasm-") else f"wasm-{name}"
287
-
283
+ from wasm.managers.service_manager import ServiceManager
284
+ service_manager = ServiceManager(verbose=False)
285
+ service_name = service_manager._resolve_service_name(name)
286
+
288
287
  try:
289
288
  result = subprocess.run(
290
289
  ["journalctl", "-u", service_name, "-n", str(lines), "--no-pager"],
@@ -315,8 +314,10 @@ async def get_service_config(
315
314
  Get the systemd unit file content for a service.
316
315
  """
317
316
  from pathlib import Path
318
-
319
- service_name = name if name.startswith("wasm-") else f"wasm-{name}"
317
+ from wasm.managers.service_manager import ServiceManager
318
+
319
+ service_manager = ServiceManager(verbose=False)
320
+ service_name = service_manager._resolve_service_name(name)
320
321
  service_path = Path(f"/etc/systemd/system/{service_name}.service")
321
322
 
322
323
  if not service_path.exists():
@@ -349,8 +350,10 @@ async def update_service_config(
349
350
  Update the systemd unit file content for a service.
350
351
  """
351
352
  from pathlib import Path
352
-
353
- service_name = name if name.startswith("wasm-") else f"wasm-{name}"
353
+ from wasm.managers.service_manager import ServiceManager
354
+
355
+ service_manager = ServiceManager(verbose=False)
356
+ service_name = service_manager._resolve_service_name(name)
354
357
  service_path = Path(f"/etc/systemd/system/{service_name}.service")
355
358
 
356
359
  if not service_path.exists():
@@ -382,8 +385,9 @@ async def create_service(
382
385
  Create a new systemd service.
383
386
  """
384
387
  from pathlib import Path
385
-
386
- service_name = data.name if data.name.startswith("wasm-") else f"wasm-{data.name}"
388
+
389
+ # New services don't use wasm- prefix
390
+ service_name = data.name
387
391
  service_path = Path(f"/etc/systemd/system/{service_name}.service")
388
392
 
389
393
  if service_path.exists():
@@ -455,8 +459,10 @@ async def delete_service(
455
459
  Delete a systemd service.
456
460
  """
457
461
  from pathlib import Path
458
-
459
- service_name = name if name.startswith("wasm-") else f"wasm-{name}"
462
+ from wasm.managers.service_manager import ServiceManager
463
+
464
+ service_manager = ServiceManager(verbose=False)
465
+ service_name = service_manager._resolve_service_name(name)
460
466
  service_path = Path(f"/etc/systemd/system/{service_name}.service")
461
467
 
462
468
  if not service_path.exists():
@@ -58,8 +58,10 @@ async def websocket_logs(
58
58
 
59
59
  await websocket.accept()
60
60
 
61
+ from wasm.managers.service_manager import ServiceManager
61
62
  app_name = domain_to_app_name(domain)
62
- service_name = f"wasm-{app_name}"
63
+ service_manager = ServiceManager(verbose=False)
64
+ service_name = service_manager._resolve_service_name(app_name)
63
65
 
64
66
  # Add to connections
65
67
  if domain not in _log_connections:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wasm-cli
3
- Version: 0.14.0
3
+ Version: 0.14.1
4
4
  Summary: Web App System Management - Deploy and manage web applications on Linux servers
5
5
  Author: Yago López Prado
6
6
  Author-email: Yago López Prado <yago.lopez.adeje@gmail.com>
@@ -1,11 +1,11 @@
1
- wasm/__init__.py,sha256=XYX4aBB2n0eMBQU_tbOYOYcbVRnMx9gMkLWcwQt8qAE,560
1
+ wasm/__init__.py,sha256=jVKeUvFzS2tI3X9vjQEbHKm1_UMzYpUHV1IEaRo_Rf4,560
2
2
  wasm/__main__.py,sha256=i3EgrkaduRUtc_1KtThBY6DHa9s9Uj22Ns9gjrsV75E,120
3
3
  wasm/main.py,sha256=4QqnXn6ToD1DY37WlAQ2j4uFrYqTgHLvV8TT-upXOrg,5095
4
4
  wasm/cli/__init__.py,sha256=KvBJMVlHhKRme-y2IoRwdag9bcp4soyNTgSZ01JL62Q,193
5
5
  wasm/cli/interactive.py,sha256=RkxgdfyWEVke9VuWGh0FjLCjsYJIQrGEjUVV3cX4NLU,20596
6
- wasm/cli/parser.py,sha256=7nHe8UrSK5qLx19Fob738W5HDEVThDcJcSyrNIUv2Dw,40965
6
+ wasm/cli/parser.py,sha256=Jy-YS4yxtJGCZkNi5yMXDBd-twNpZ2fU0lAQJ0AYWE8,41132
7
7
  wasm/cli/commands/__init__.py,sha256=_FGYr4xRKpRUAbRhA9hJxpoLJehj1t3bq_qOZ9ajtcM,525
8
- wasm/cli/commands/backup.py,sha256=clGkK7kyVeV-6cd8QO0NiAJziPpiI3Q4F_oII9Letxo,15195
8
+ wasm/cli/commands/backup.py,sha256=IfmDYgjyu6hN_I4ljXaGTHhUz0jroTWN-gc7MuSzPik,15661
9
9
  wasm/cli/commands/cert.py,sha256=12vpgnJ4gfqJ25QpG8XH0ZKcjVQP4VgqHk5fMHSmS1U,6550
10
10
  wasm/cli/commands/config.py,sha256=HIm1jNH5SV3-HiOKPpLK7yXoPzjRB0--1LOsqWC2OCg,2581
11
11
  wasm/cli/commands/db.py,sha256=f9tXgW98ZfURcBZ3A1-BRDYTxFatpt3nIR4TgwfNZsc,30575
@@ -14,14 +14,14 @@ wasm/cli/commands/monitor.py,sha256=G0dm3Zr_6dDZm-guJMfIGx0Ww47NVVs5Q9F5O2nCvZs,
14
14
  wasm/cli/commands/service.py,sha256=zQ20jkHE2aKpUPtEhQLTT13atUepxx-DLiIQmdrOdx8,5407
15
15
  wasm/cli/commands/setup.py,sha256=VhA1eD9niWCRpfBFTNmY2472NFPwJMN602k5s5XvGBY,38209
16
16
  wasm/cli/commands/site.py,sha256=Fi4ZWNlmUohLBPc2SJDxYIue1lYSsEHIkMDL9v6PW84,6213
17
- wasm/cli/commands/store.py,sha256=NUnlvUwIHedmRVjm1zuN4472zj55Cf8-Ph4fXP73fdE,16795
17
+ wasm/cli/commands/store.py,sha256=U3F7vQtXQDlf00igN1lEZMTmQRySnK5XgptuZHJqmVs,17569
18
18
  wasm/cli/commands/version.py,sha256=1gv-bnH0ur3Tlj0UoEdrCaQ6ZI_dA-TsHodgCdw1h1Y,3573
19
19
  wasm/cli/commands/web.py,sha256=aQW5kq5wt13pTLEi8Sg1uQAPX75qTo7XkMNVyfPUSHk,20364
20
- wasm/cli/commands/webapp.py,sha256=ESLmCBwjomFiMvkD5qVu82_dFAj1lXbFnOAGqq-ITys,25493
20
+ wasm/cli/commands/webapp.py,sha256=XvhzwqNR9S_mc_7K4zgs3yy-Vj_SdGZhxcgviP2U6bg,25521
21
21
  wasm/completions/__init__.py,sha256=EKNNDUKp8XRYcE56CJq3stkgVqUo9dbnKjCqj1mwd7U,716
22
- wasm/completions/_wasm,sha256=8au0Sahz8tibnnZQ6QEFYVWOqkTKgW0YfzUZFi1B33U,39396
23
- wasm/completions/wasm.bash,sha256=KrrfnVnl9Ik_0xBKuHDu4c343Tq_mc1lGPzNT6Eaadg,30281
24
- wasm/completions/wasm.fish,sha256=ew-MIDxMywNUfm99dajV-eDDwgl03CPNd-9mWP9Rh5I,34479
22
+ wasm/completions/_wasm,sha256=Hagz1H7K4MCsAhHZBg8G53cio7dpO5OoSZOnk3Tbn2U,40427
23
+ wasm/completions/wasm.bash,sha256=KNZbzUzRen7RONj6RNYSd6ysgza9vDwkn7q51zT4QKM,31229
24
+ wasm/completions/wasm.fish,sha256=lrQjB6XRMRBxusG8g_EYTA739Xe5mm1MaSIUYiCWegc,35448
25
25
  wasm/core/__init__.py,sha256=Sat72_6P3eqiWCHVU1GsZpwPhaXFNf8-t1Hfoxu5kpY,573
26
26
  wasm/core/config.py,sha256=3sA0GsRJzAt4C-0qGrMWM5AYrCi0PH8n_7WcBm00BPo,11635
27
27
  wasm/core/dependencies.py,sha256=q2qPt8unoYEM5IhjF9qb2DPn6chkIERvGoThwrHBWP0,21538
@@ -29,9 +29,9 @@ wasm/core/exceptions.py,sha256=G4NT13SsHwWgg3s9DEaq7gZbouvCIqlp5hpVvQaN9os,6084
29
29
  wasm/core/logger.py,sha256=-tuOxcLYLXQ7g8eA7OkYSN5iq_qGBZyNhGUeK4xwATI,11289
30
30
  wasm/core/store.py,sha256=SB9GiJMqdh2A01TkfsKDijXywH8yOKoXmtclKdoWaJU,37064
31
31
  wasm/core/update_checker.py,sha256=fBtKqbHpwErPOiVWadyv1MpQ7qGk974an0qgKynMXfk,11811
32
- wasm/core/utils.py,sha256=m-LJoWBlsnZgKiPOC_kk3PQiEGsVhr7g4ZlKbyUesoU,14338
32
+ wasm/core/utils.py,sha256=0_15_7ZsWzKWFvDTxuxNkBFpNVi1MHd85G1lRCgtD_0,14685
33
33
  wasm/deployers/__init__.py,sha256=Rl3Y7joRm7EH1-o0zxysYiYQKcVZv0Dyg29YvLQz4mk,258
34
- wasm/deployers/base.py,sha256=9T3MLSOBw6g2b5nMefRcnGaz45JQI4JVlq7aV7gDjGY,40912
34
+ wasm/deployers/base.py,sha256=KV1vklcx470pnp2zgMBc-ULx8EFgprXO9_9CWsnnXhA,40888
35
35
  wasm/deployers/nextjs.py,sha256=MLDAHFAGBxvIgBwuY_IyHEQkPnhEk6W_migYReHo-DA,4917
36
36
  wasm/deployers/nodejs.py,sha256=15II2Fyw9OZTX9RtFZzw3dzzc9PEBUyNDBVQ9eH-brI,4153
37
37
  wasm/deployers/python.py,sha256=A7RJwqXVQZgYg2C2zl-tU2meRNYdnPq-dSSaOe_20Ww,7891
@@ -44,11 +44,11 @@ wasm/deployers/helpers/path_resolver.py,sha256=HkJTGen1WK6OL3Argsa_rFkDw6SCykftd
44
44
  wasm/deployers/helpers/prisma.py,sha256=gzSuWwqkEpYBEidnA3pwvK7uqtqSSyYOZZKqyFycncA,4001
45
45
  wasm/managers/__init__.py,sha256=KjR8yntlqi88dldzda7Ube1QL0EjyzjOpDfUifKXhxI,633
46
46
  wasm/managers/apache_manager.py,sha256=KcDKe-gJ2NnP0VtVA0KTZyvmiptiWTtBNLvF6caOIiA,12263
47
- wasm/managers/backup_manager.py,sha256=OK0i69dVNd2s0HyyeCa-OmEQy00788Hb8_Acc1_8ljI,36023
47
+ wasm/managers/backup_manager.py,sha256=7KUHzpi2za38JJBwVi-7lOisgNvOtpW-jkc8GD7yqSU,39525
48
48
  wasm/managers/base_manager.py,sha256=dy9yLHjAZkTwWH2f8ZkeSVNADFT4qtw7WDEbvVfpo98,2948
49
49
  wasm/managers/cert_manager.py,sha256=fMZxbZJBFCa1Tpl4n8eizxRlhSSTHLNwrTI9nMk-O6w,15030
50
50
  wasm/managers/nginx_manager.py,sha256=tnThgOqIlMUEiaiCFAbNtUzNUq1uJ_GXdGBu1IP6oRo,11287
51
- wasm/managers/service_manager.py,sha256=AlHilDPRhKRZ8KiuJBjART-05tM5r1kpKnmkujy2wLo,14494
51
+ wasm/managers/service_manager.py,sha256=IBreQjTiKumE529h4ukTJA9QTp_9yJkckdBbuiVw1hg,15915
52
52
  wasm/managers/source_manager.py,sha256=glysx-YF_zkHMI3WUBIA_JR9aoPTW_j__bwg205YymM,20535
53
53
  wasm/managers/database/__init__.py,sha256=P_bfkT7LpjYnI2vEDoyM4SjtUTq4FDwLzvA-BhPnUDY,898
54
54
  wasm/managers/database/base.py,sha256=vcVMAkqfnOcasIfKNIRPeXEcOGygmPxnQl0ikd3k27Y,18215
@@ -78,16 +78,16 @@ wasm/web/auth.py,sha256=7QZDrhhFdsYcH62_eK9znpHO0N3-bvMvOOW3Q1msRl8,16467
78
78
  wasm/web/jobs.py,sha256=wajwWmfmOwRh97kKaZ-7ygt5aZiou-14RuadtwgQwVk,20625
79
79
  wasm/web/server.py,sha256=QkorvqKZageFKPYoXRyb73Nkn3pwk0vD1RKhsc6fX_g,13851
80
80
  wasm/web/api/__init__.py,sha256=sZw-PeWY0cYWYK2g_KM3FSJOAUoaR37ELbHM6yuqmmY,105
81
- wasm/web/api/apps.py,sha256=DSMSqYlf291DXS761ue1wbLhDFbPBDKKiyycQbrhSVA,13482
81
+ wasm/web/api/apps.py,sha256=leWamRrvJR1Fvqz-Vgf6xQx7Gu4TVaTDkW6BDjK4xTw,13578
82
82
  wasm/web/api/auth.py,sha256=ELFmDO9J_vzYIL8wOByKyaIbs_H0Gqzs_C0AGAjAosY,4134
83
- wasm/web/api/backups.py,sha256=oCbbwIWPj5o1hMFNp8GcGHbm_vaMTifdI238o7R_gtQ,10054
83
+ wasm/web/api/backups.py,sha256=I8MYy4v26VEaDczcK0dcnE_PjQpNgLl1ykoj4n0tYi8,11221
84
84
  wasm/web/api/certs.py,sha256=uJ349OgWDx4-67Y82hH6xmIxiezpwQjMVnDUAqs-jpE,10667
85
85
  wasm/web/api/config.py,sha256=aVimawBwntjravi464ax1w-8WXkcw2DpkhNaysnDuLg,10960
86
86
  wasm/web/api/databases.py,sha256=90u2736J9lnES54p3NoHmJnNEbMRRiGL6E9Gf9tN0n4,24272
87
87
  wasm/web/api/jobs.py,sha256=Z7rgt97yLuVuumxO_gYuPa5HPu0UdGBjdYtj6vbYEPQ,9283
88
88
  wasm/web/api/monitor.py,sha256=okptwHh6yP79EJJzj4wu-5TtNKxAWgQeBRMamihGlvo,20040
89
89
  wasm/web/api/router.py,sha256=tYX_9MBDpO2vgfxawvxBdC2p0cCCyZ6cr9-LgYq3DFA,1557
90
- wasm/web/api/services.py,sha256=lxVrPI4ib3Pdec0Na6azt1E3rM4EABHmg5OlhRcNxvg,14370
90
+ wasm/web/api/services.py,sha256=QPiytz1o7mJGlNwiiWduU0GseqgRtgjAEj_GVxseu7A,14619
91
91
  wasm/web/api/sites.py,sha256=DbQLtHknCqih9jIvxLWXf122ZT6cTFxioe49o5C4kp0,14310
92
92
  wasm/web/api/system.py,sha256=91A0W1LQCvmg662cAnOGKWwwL5CJAQKaKSaca8D1meM,13914
93
93
  wasm/web/static/index.html,sha256=JOL5_5XnOb3vH3BvuBu748H5Co7O0Z6mZffH4lvInQA,128832
@@ -121,11 +121,11 @@ wasm/web/static/js/pages/monitor.js,sha256=A7V3WuimKjLVN4Ug3t9of7u88T8688iUl1WDW
121
121
  wasm/web/static/js/pages/services.js,sha256=TT1s48dtQ4genq0-XFeCN2HSzTJHpHbAZs5ctYqOfN4,11027
122
122
  wasm/web/static/js/pages/sites.js,sha256=Xxh8y0mefOi5kF8knu9s1iHlq9CueG5a7Cx9Eb202So,8822
123
123
  wasm/web/websockets/__init__.py,sha256=GC-bLXm91pGA037ZeUKEsATKDf7ZuygDhxl6wd9hrOo,118
124
- wasm/web/websockets/router.py,sha256=NwXNt-7JzpwUdus2EwXXzfv8gQzrIQ7McJywtbz6KAs,20754
125
- wasm_cli-0.14.0.data/data/share/man/man1/wasm.1,sha256=Q4ML-26TFS5lCnKEUPD2Rgaof80i4g4uvLtqncGD9is,10619
126
- wasm_cli-0.14.0.dist-info/licenses/LICENSE,sha256=5PYGsNFEQYrzS8bN6_g4TrW-O999_ToZb4strA3xO4g,4083
127
- wasm_cli-0.14.0.dist-info/METADATA,sha256=pE9W3eoeRb4DIXTzx72gTlXbab9_bo0-wAhk2SJb6Vk,15444
128
- wasm_cli-0.14.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
129
- wasm_cli-0.14.0.dist-info/entry_points.txt,sha256=oe0IjfmSyedc--7xWlRsA4xKpZmNCl6aCi8WoaSPtdI,39
130
- wasm_cli-0.14.0.dist-info/top_level.txt,sha256=ICXMW6pzfO638yao6kQxfeSg3vPXusFOPJAY1LOu5-8,5
131
- wasm_cli-0.14.0.dist-info/RECORD,,
124
+ wasm/web/websockets/router.py,sha256=ujaOxcZqNBKZW8CDRKeyn6H5MIBpn9fbq8sOsZRYNeU,20896
125
+ wasm_cli-0.14.1.data/data/share/man/man1/wasm.1,sha256=Q4ML-26TFS5lCnKEUPD2Rgaof80i4g4uvLtqncGD9is,10619
126
+ wasm_cli-0.14.1.dist-info/licenses/LICENSE,sha256=5PYGsNFEQYrzS8bN6_g4TrW-O999_ToZb4strA3xO4g,4083
127
+ wasm_cli-0.14.1.dist-info/METADATA,sha256=HnbSNxhYlW1VHQFvUSd0Csh1ifbAUGtLHSiPxKn7xBY,15444
128
+ wasm_cli-0.14.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
129
+ wasm_cli-0.14.1.dist-info/entry_points.txt,sha256=oe0IjfmSyedc--7xWlRsA4xKpZmNCl6aCi8WoaSPtdI,39
130
+ wasm_cli-0.14.1.dist-info/top_level.txt,sha256=ICXMW6pzfO638yao6kQxfeSg3vPXusFOPJAY1LOu5-8,5
131
+ wasm_cli-0.14.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5