wasm-cli 0.14.0__tar.gz → 0.14.1__tar.gz

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.
Files changed (142) hide show
  1. {wasm_cli-0.14.0/src/wasm_cli.egg-info → wasm_cli-0.14.1}/PKG-INFO +1 -1
  2. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/pyproject.toml +1 -1
  3. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/setup.py +1 -1
  4. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/__init__.py +1 -1
  5. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/backup.py +18 -10
  6. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/store.py +20 -4
  7. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/webapp.py +4 -4
  8. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/parser.py +5 -0
  9. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/completions/_wasm +31 -7
  10. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/completions/wasm.bash +25 -1
  11. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/completions/wasm.fish +24 -1
  12. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/utils.py +18 -3
  13. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/base.py +3 -3
  14. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/backup_manager.py +108 -14
  15. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/service_manager.py +91 -45
  16. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/apps.py +6 -4
  17. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/backups.py +37 -17
  18. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/services.py +31 -25
  19. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/websockets/router.py +3 -1
  20. {wasm_cli-0.14.0 → wasm_cli-0.14.1/src/wasm_cli.egg-info}/PKG-INFO +1 -1
  21. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/LICENSE +0 -0
  22. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/MANIFEST.in +0 -0
  23. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/README.md +0 -0
  24. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/docs/MONITOR.md +0 -0
  25. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/docs/OBS_SETUP.md +0 -0
  26. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/docs/assets/logo.png +0 -0
  27. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/docs/assets/logo_bg.png +0 -0
  28. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/man/wasm.1 +0 -0
  29. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/setup.cfg +0 -0
  30. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/__main__.py +0 -0
  31. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/__init__.py +0 -0
  32. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/__init__.py +0 -0
  33. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/cert.py +0 -0
  34. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/config.py +0 -0
  35. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/db.py +0 -0
  36. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/health.py +0 -0
  37. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/monitor.py +0 -0
  38. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/service.py +0 -0
  39. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/setup.py +0 -0
  40. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/site.py +0 -0
  41. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/version.py +0 -0
  42. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/web.py +0 -0
  43. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/interactive.py +0 -0
  44. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/completions/__init__.py +0 -0
  45. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/__init__.py +0 -0
  46. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/config.py +0 -0
  47. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/dependencies.py +0 -0
  48. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/exceptions.py +0 -0
  49. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/logger.py +0 -0
  50. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/store.py +0 -0
  51. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/update_checker.py +0 -0
  52. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/__init__.py +0 -0
  53. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/helpers/__init__.py +0 -0
  54. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/helpers/package_manager.py +0 -0
  55. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/helpers/path_resolver.py +0 -0
  56. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/helpers/prisma.py +0 -0
  57. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/nextjs.py +0 -0
  58. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/nodejs.py +0 -0
  59. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/python.py +0 -0
  60. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/registry.py +0 -0
  61. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/static.py +0 -0
  62. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/vite.py +0 -0
  63. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/main.py +0 -0
  64. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/__init__.py +0 -0
  65. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/apache_manager.py +0 -0
  66. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/base_manager.py +0 -0
  67. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/cert_manager.py +0 -0
  68. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/__init__.py +0 -0
  69. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/base.py +0 -0
  70. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/mongodb.py +0 -0
  71. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/mysql.py +0 -0
  72. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/postgres.py +0 -0
  73. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/redis.py +0 -0
  74. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/registry.py +0 -0
  75. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/nginx_manager.py +0 -0
  76. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/source_manager.py +0 -0
  77. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/__init__.py +0 -0
  78. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/ai_analyzer.py +0 -0
  79. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/email_notifier.py +0 -0
  80. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/process_monitor.py +0 -0
  81. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/threat_store.py +0 -0
  82. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/__init__.py +0 -0
  83. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/apache/proxy.conf.j2 +0 -0
  84. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/apache/static.conf.j2 +0 -0
  85. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/nginx/proxy.conf.j2 +0 -0
  86. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/nginx/static.conf.j2 +0 -0
  87. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/systemd/app.service.j2 +0 -0
  88. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/__init__.py +0 -0
  89. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/domain.py +0 -0
  90. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/port.py +0 -0
  91. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/source.py +0 -0
  92. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/ssh.py +0 -0
  93. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/__init__.py +0 -0
  94. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/__init__.py +0 -0
  95. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/auth.py +0 -0
  96. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/certs.py +0 -0
  97. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/config.py +0 -0
  98. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/databases.py +0 -0
  99. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/jobs.py +0 -0
  100. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/monitor.py +0 -0
  101. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/router.py +0 -0
  102. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/sites.py +0 -0
  103. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/system.py +0 -0
  104. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/auth.py +0 -0
  105. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/jobs.py +0 -0
  106. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/server.py +0 -0
  107. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/css/main.css +0 -0
  108. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/index.html +0 -0
  109. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/components/cards.js +0 -0
  110. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/components/jobs.js +0 -0
  111. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/components/metrics.js +0 -0
  112. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/components/skeleton.js +0 -0
  113. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/api.js +0 -0
  114. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/dialogs.js +0 -0
  115. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/notifications.js +0 -0
  116. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/router.js +0 -0
  117. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/search.js +0 -0
  118. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/shortcuts.js +0 -0
  119. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/theme.js +0 -0
  120. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/ui.js +0 -0
  121. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/websocket.js +0 -0
  122. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/main.js +0 -0
  123. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/apps.js +0 -0
  124. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/backups.js +0 -0
  125. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/certs.js +0 -0
  126. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/config.js +0 -0
  127. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/dashboard.js +0 -0
  128. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/databases.js +0 -0
  129. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/jobs.js +0 -0
  130. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/logs.js +0 -0
  131. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/monitor.js +0 -0
  132. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/services.js +0 -0
  133. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/sites.js +0 -0
  134. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/login.html +0 -0
  135. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/logo.png +0 -0
  136. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/logo.webp +0 -0
  137. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/websockets/__init__.py +0 -0
  138. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm_cli.egg-info/SOURCES.txt +0 -0
  139. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm_cli.egg-info/dependency_links.txt +0 -0
  140. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm_cli.egg-info/entry_points.txt +0 -0
  141. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm_cli.egg-info/requires.txt +0 -0
  142. {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm_cli.egg-info/top_level.txt +0 -0
@@ -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>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wasm-cli"
7
- version = "0.14.0"
7
+ version = "0.14.1"
8
8
  description = "Web App System Management - Deploy and manage web applications on Linux servers"
9
9
  readme = "README.md"
10
10
  license = { text = "WASM-NCSAL" }
@@ -9,7 +9,7 @@ from setuptools import setup, find_packages
9
9
 
10
10
  setup(
11
11
  name="wasm-cli",
12
- version="0.14.0",
12
+ version="0.14.1",
13
13
  description="Web App System Management - Deploy and manage web applications on Linux servers",
14
14
  author="Yago López Prado",
15
15
  author_email="yago.lopez.adeje@gmail.com",
@@ -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
@@ -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",
@@ -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
@@ -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
 
@@ -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