wasm-cli 0.15.3__tar.gz → 0.15.4__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 (155) hide show
  1. {wasm_cli-0.15.3/src/wasm_cli.egg-info → wasm_cli-0.15.4}/PKG-INFO +1 -1
  2. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/pyproject.toml +1 -1
  3. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/setup.py +1 -1
  4. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/__init__.py +1 -1
  5. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/cert.py +1 -0
  6. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/webapp.py +6 -0
  7. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/interactive.py +105 -7
  8. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/parser.py +11 -1
  9. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/base.py +27 -7
  10. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/cert_manager.py +13 -5
  11. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/apache/proxy.conf.j2 +6 -0
  12. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/apache/static.conf.j2 +6 -0
  13. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/nginx/advanced.conf.j2 +2 -2
  14. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/nginx/proxy.conf.j2 +2 -2
  15. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/nginx/static.conf.j2 +2 -2
  16. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/domain.py +22 -0
  17. {wasm_cli-0.15.3 → wasm_cli-0.15.4/src/wasm_cli.egg-info}/PKG-INFO +1 -1
  18. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/LICENSE +0 -0
  19. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/MANIFEST.in +0 -0
  20. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/README.md +0 -0
  21. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/docs/MONITOR.md +0 -0
  22. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/docs/OBS_SETUP.md +0 -0
  23. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/docs/assets/logo.png +0 -0
  24. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/docs/assets/logo_bg.png +0 -0
  25. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/man/wasm.1 +0 -0
  26. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/setup.cfg +0 -0
  27. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/__main__.py +0 -0
  28. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/__init__.py +0 -0
  29. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/__init__.py +0 -0
  30. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/backup.py +0 -0
  31. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/config.py +0 -0
  32. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/db.py +0 -0
  33. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/env.py +0 -0
  34. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/health.py +0 -0
  35. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/monitor.py +0 -0
  36. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/service.py +0 -0
  37. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/setup.py +0 -0
  38. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/site.py +0 -0
  39. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/store.py +0 -0
  40. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/version.py +0 -0
  41. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/web.py +0 -0
  42. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/completions/__init__.py +0 -0
  43. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/completions/_wasm +0 -0
  44. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/completions/wasm.bash +0 -0
  45. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/completions/wasm.fish +0 -0
  46. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/__init__.py +0 -0
  47. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/config.py +0 -0
  48. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/dependencies.py +0 -0
  49. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/exceptions.py +0 -0
  50. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/logger.py +0 -0
  51. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/store.py +0 -0
  52. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/update_checker.py +0 -0
  53. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/utils.py +0 -0
  54. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/__init__.py +0 -0
  55. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/docker_compose.py +0 -0
  56. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/__init__.py +0 -0
  57. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/env_manager.py +0 -0
  58. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/nginx_config.py +0 -0
  59. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/package_manager.py +0 -0
  60. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/path_resolver.py +0 -0
  61. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/prisma.py +0 -0
  62. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/turbo.py +0 -0
  63. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/workspace.py +0 -0
  64. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/monorepo.py +0 -0
  65. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/nextjs.py +0 -0
  66. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/nodejs.py +0 -0
  67. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/python.py +0 -0
  68. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/registry.py +0 -0
  69. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/static.py +0 -0
  70. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/vite.py +0 -0
  71. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/main.py +0 -0
  72. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/__init__.py +0 -0
  73. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/apache_manager.py +0 -0
  74. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/backup_manager.py +0 -0
  75. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/backup_scheduler.py +0 -0
  76. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/base_manager.py +0 -0
  77. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/__init__.py +0 -0
  78. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/base.py +0 -0
  79. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/mongodb.py +0 -0
  80. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/mysql.py +0 -0
  81. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/postgres.py +0 -0
  82. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/redis.py +0 -0
  83. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/registry.py +0 -0
  84. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/nginx_manager.py +0 -0
  85. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/service_manager.py +0 -0
  86. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/source_manager.py +0 -0
  87. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/__init__.py +0 -0
  88. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/ai_analyzer.py +0 -0
  89. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/email_notifier.py +0 -0
  90. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/process_monitor.py +0 -0
  91. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/threat_store.py +0 -0
  92. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/__init__.py +0 -0
  93. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/nginx/monorepo.conf.j2 +0 -0
  94. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/systemd/app.service.j2 +0 -0
  95. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/systemd/backup-service.j2 +0 -0
  96. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/systemd/backup-timer.j2 +0 -0
  97. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/systemd/docker-compose.service.j2 +0 -0
  98. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/__init__.py +0 -0
  99. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/port.py +0 -0
  100. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/source.py +0 -0
  101. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/ssh.py +0 -0
  102. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/__init__.py +0 -0
  103. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/__init__.py +0 -0
  104. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/apps.py +0 -0
  105. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/auth.py +0 -0
  106. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/backups.py +0 -0
  107. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/certs.py +0 -0
  108. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/config.py +0 -0
  109. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/databases.py +0 -0
  110. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/jobs.py +0 -0
  111. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/monitor.py +0 -0
  112. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/router.py +0 -0
  113. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/services.py +0 -0
  114. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/sites.py +0 -0
  115. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/system.py +0 -0
  116. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/auth.py +0 -0
  117. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/jobs.py +0 -0
  118. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/server.py +0 -0
  119. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/css/main.css +0 -0
  120. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/index.html +0 -0
  121. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/cards.js +0 -0
  122. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/jobs.js +0 -0
  123. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/metrics.js +0 -0
  124. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/skeleton.js +0 -0
  125. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/api.js +0 -0
  126. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/dialogs.js +0 -0
  127. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/notifications.js +0 -0
  128. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/router.js +0 -0
  129. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/search.js +0 -0
  130. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/shortcuts.js +0 -0
  131. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/theme.js +0 -0
  132. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/ui.js +0 -0
  133. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/websocket.js +0 -0
  134. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/main.js +0 -0
  135. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/apps.js +0 -0
  136. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/backups.js +0 -0
  137. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/certs.js +0 -0
  138. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/config.js +0 -0
  139. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/dashboard.js +0 -0
  140. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/databases.js +0 -0
  141. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/jobs.js +0 -0
  142. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/logs.js +0 -0
  143. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/monitor.js +0 -0
  144. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/services.js +0 -0
  145. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/sites.js +0 -0
  146. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/login.html +0 -0
  147. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/logo.png +0 -0
  148. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/logo.webp +0 -0
  149. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/websockets/__init__.py +0 -0
  150. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/websockets/router.py +0 -0
  151. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/SOURCES.txt +0 -0
  152. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/dependency_links.txt +0 -0
  153. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/entry_points.txt +0 -0
  154. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/requires.txt +0 -0
  155. {wasm_cli-0.15.3 → wasm_cli-0.15.4}/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.15.3
3
+ Version: 0.15.4
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.15.3"
7
+ version = "0.15.4"
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.15.3",
12
+ version="0.15.4",
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.15.3"
11
+ __version__ = "0.15.4"
12
12
  __author__ = "Yago López Prado"
13
13
  __license__ = "WASM-NCSAL"
14
14
 
@@ -85,6 +85,7 @@ def _handle_create(args: Namespace) -> int:
85
85
  apache=args.apache,
86
86
  dry_run=args.dry_run,
87
87
  additional_domains=additional_domains,
88
+ expand=getattr(args, "expand", False),
88
89
  )
89
90
 
90
91
  if args.dry_run:
@@ -201,6 +201,11 @@ def _handle_create(args: Namespace) -> int:
201
201
  logger.key_value("Port", str(port))
202
202
  logger.key_value("Package Manager", package_manager)
203
203
  logger.key_value("SSL", "Yes" if not args.no_ssl else "No")
204
+ include_www = getattr(args, "www", False)
205
+ if not args.no_ssl and include_www:
206
+ from wasm.validators.domain import should_include_www
207
+ if should_include_www(domain):
208
+ logger.key_value("WWW", f"www.{domain} included")
204
209
  logger.blank()
205
210
 
206
211
  # Handle monorepo deployments specially
@@ -224,6 +229,7 @@ def _handle_create(args: Namespace) -> int:
224
229
  branch=args.branch,
225
230
  env_vars=env_vars,
226
231
  package_manager=package_manager,
232
+ include_www=getattr(args, "www", False),
227
233
  )
228
234
 
229
235
  # Run deployment
@@ -123,13 +123,16 @@ class InteractiveMode:
123
123
  return self._run_webapp_command("list")
124
124
  elif action == "logs":
125
125
  domain = self._prompt_domain("Enter application domain")
126
- return self._run_webapp_command("logs", domain)
127
- elif action in ["status", "update", "restart", "stop", "start"]:
126
+ return self._webapp_logs(domain)
127
+ elif action == "update":
128
+ domain = self._prompt_domain("Enter application domain")
129
+ return self._webapp_update(domain)
130
+ elif action in ["status", "restart", "stop", "start"]:
128
131
  domain = self._prompt_domain("Enter application domain")
129
132
  return self._run_webapp_command(action, domain)
130
133
  elif action == "delete":
131
134
  domain = self._prompt_domain("Enter application domain")
132
- return self._run_webapp_command("delete", domain, force=True)
135
+ return self._run_webapp_command("delete", domain, force=True, keep_files=False)
133
136
 
134
137
  return 0
135
138
 
@@ -216,7 +219,21 @@ class InteractiveMode:
216
219
  answers = inquirer.prompt(questions, theme=GreenPassion())
217
220
  if not answers:
218
221
  return 0
219
-
222
+
223
+ # Ask about www if SSL enabled and domain is a base domain
224
+ from wasm.validators.domain import should_include_www
225
+ include_www = False
226
+ if answers["ssl"] and should_include_www(answers["domain"]):
227
+ www_questions = [
228
+ inquirer.Confirm(
229
+ "include_www",
230
+ message=f"Include www.{answers['domain']} in certificate and web server config?",
231
+ default=True,
232
+ ),
233
+ ]
234
+ www_answers = inquirer.prompt(www_questions, theme=GreenPassion())
235
+ include_www = www_answers.get("include_www", True) if www_answers else False
236
+
220
237
  # Build arguments
221
238
  from argparse import Namespace
222
239
  args = Namespace(
@@ -230,11 +247,59 @@ class InteractiveMode:
230
247
  branch=answers["branch"] or None,
231
248
  no_ssl=not answers["ssl"],
232
249
  env_file=answers["env_file"] or None,
250
+ www=include_www,
233
251
  )
234
-
252
+
235
253
  from wasm.cli.commands.webapp import handle_webapp
236
254
  return handle_webapp(args)
237
255
 
256
+ def _webapp_logs(self, domain: str) -> int:
257
+ """Handle webapp logs flow with interactive prompts."""
258
+ questions = [
259
+ inquirer.Text(
260
+ "lines",
261
+ message="Number of log lines to show",
262
+ default="50",
263
+ validate=lambda _, x: x.isdigit() and int(x) > 0 or "Must be a positive number",
264
+ ),
265
+ inquirer.Confirm(
266
+ "follow",
267
+ message="Follow log output in real time?",
268
+ default=False,
269
+ ),
270
+ ]
271
+
272
+ answers = inquirer.prompt(questions, theme=GreenPassion())
273
+ if not answers:
274
+ return 0
275
+
276
+ return self._run_webapp_command(
277
+ "logs",
278
+ domain,
279
+ lines=int(answers["lines"]),
280
+ follow=answers["follow"],
281
+ )
282
+
283
+ def _webapp_update(self, domain: str) -> int:
284
+ """Handle webapp update flow with interactive prompts."""
285
+ questions = [
286
+ inquirer.Text(
287
+ "branch",
288
+ message="Git branch to update from (leave empty for default)",
289
+ default="",
290
+ ),
291
+ ]
292
+
293
+ answers = inquirer.prompt(questions, theme=GreenPassion())
294
+ if not answers:
295
+ return 0
296
+
297
+ return self._run_webapp_command(
298
+ "update",
299
+ domain,
300
+ branch=answers["branch"] or None,
301
+ )
302
+
238
303
  def _site_flow(self) -> int:
239
304
  """Handle site interactive flow."""
240
305
  questions = [
@@ -348,7 +413,10 @@ class InteractiveMode:
348
413
  return self._service_create()
349
414
  elif action == "list":
350
415
  return self._run_command("service", "list")
351
- elif action in ["status", "start", "stop", "restart", "logs", "delete"]:
416
+ elif action == "logs":
417
+ name = self._prompt_text("Enter service name")
418
+ return self._service_logs(name)
419
+ elif action in ["status", "start", "stop", "restart", "delete"]:
352
420
  name = self._prompt_text("Enter service name")
353
421
  if action == "delete":
354
422
  return self._run_command("service", action, name, force=True)
@@ -395,7 +463,7 @@ class InteractiveMode:
395
463
  verbose=self.verbose,
396
464
  action="create",
397
465
  name=answers["name"],
398
- command=answers["command"],
466
+ exec_command=answers["command"],
399
467
  directory=answers["directory"],
400
468
  user=answers["user"],
401
469
  description=answers["description"] or None,
@@ -404,6 +472,34 @@ class InteractiveMode:
404
472
  from wasm.cli.commands.service import handle_service
405
473
  return handle_service(args)
406
474
 
475
+ def _service_logs(self, name: str) -> int:
476
+ """Handle service logs flow with interactive prompts."""
477
+ questions = [
478
+ inquirer.Text(
479
+ "lines",
480
+ message="Number of log lines to show",
481
+ default="50",
482
+ validate=lambda _, x: x.isdigit() and int(x) > 0 or "Must be a positive number",
483
+ ),
484
+ inquirer.Confirm(
485
+ "follow",
486
+ message="Follow log output in real time?",
487
+ default=False,
488
+ ),
489
+ ]
490
+
491
+ answers = inquirer.prompt(questions, theme=GreenPassion())
492
+ if not answers:
493
+ return 0
494
+
495
+ return self._run_command(
496
+ "service",
497
+ "logs",
498
+ name,
499
+ lines=int(answers["lines"]),
500
+ follow=answers["follow"],
501
+ )
502
+
407
503
  def _cert_flow(self) -> int:
408
504
  """Handle cert interactive flow."""
409
505
  questions = [
@@ -437,6 +533,8 @@ class InteractiveMode:
437
533
  domain = self._prompt_domain("Enter domain name")
438
534
  if action == "delete":
439
535
  return self._run_command("cert", action, domain, force=True)
536
+ elif action == "revoke":
537
+ return self._run_command("cert", action, domain, delete=True)
440
538
  return self._run_command("cert", action, domain)
441
539
 
442
540
  return 0
@@ -206,6 +206,11 @@ def _add_webapp_commands(subparsers) -> None:
206
206
  action="store_true",
207
207
  help="Skip SSL certificate configuration",
208
208
  )
209
+ create.add_argument(
210
+ "--www",
211
+ action="store_true",
212
+ help="Include www subdomain in SSL certificate and web server config",
213
+ )
209
214
  create.add_argument(
210
215
  "--env-file",
211
216
  help="Path to environment file",
@@ -659,7 +664,12 @@ def _add_cert_parser(subparsers) -> None:
659
664
  action="store_true",
660
665
  help="Test without obtaining",
661
666
  )
662
-
667
+ create.add_argument(
668
+ "--expand",
669
+ action="store_true",
670
+ help="Expand existing certificate to include additional domains",
671
+ )
672
+
663
673
  # cert list
664
674
  cert_sub.add_parser(
665
675
  "list",
@@ -95,6 +95,7 @@ class BaseDeployer(ABC):
95
95
  self.app_name: Optional[str] = None
96
96
  self.webserver: str = "nginx"
97
97
  self.ssl: bool = True
98
+ self.include_www: bool = False
98
99
  self.branch: Optional[str] = None
99
100
  self.env_vars: Dict[str, str] = {}
100
101
 
@@ -123,10 +124,11 @@ class BaseDeployer(ABC):
123
124
  env_vars: Optional[Dict[str, str]] = None,
124
125
  app_path: Optional[Path] = None,
125
126
  package_manager: PackageManager = "auto",
127
+ include_www: bool = False,
126
128
  ) -> None:
127
129
  """
128
130
  Configure the deployer.
129
-
131
+
130
132
  Args:
131
133
  domain: Target domain.
132
134
  source: Source URL or path.
@@ -137,12 +139,16 @@ class BaseDeployer(ABC):
137
139
  env_vars: Environment variables.
138
140
  app_path: Custom application path.
139
141
  package_manager: Package manager to use (npm/pnpm/bun/auto).
142
+ include_www: Include www subdomain in certificate and web server config.
140
143
  """
144
+ from wasm.validators.domain import should_include_www
145
+
141
146
  self.domain = domain
142
147
  self.source = source
143
148
  self.port = port or self.DEFAULT_PORT
144
149
  self.webserver = webserver
145
150
  self.ssl = ssl
151
+ self.include_www = include_www and should_include_www(domain)
146
152
  self.branch = branch
147
153
  self.env_vars = env_vars or {}
148
154
  self._package_manager = package_manager
@@ -410,16 +416,23 @@ class BaseDeployer(ABC):
410
416
  Returns:
411
417
  Context dictionary.
412
418
  """
419
+ server_names = self.domain
420
+ if self.include_www:
421
+ server_names = f"{self.domain} www.{self.domain}"
422
+
413
423
  if self._nginx_advanced_config is not None:
414
- return self._nginx_config_builder.build_context(
424
+ ctx = self._nginx_config_builder.build_context(
415
425
  self._nginx_advanced_config,
416
426
  self.domain,
417
427
  ssl=self.ssl,
418
428
  app_path=str(self.app_path),
419
429
  )
430
+ ctx["server_names"] = server_names
431
+ return ctx
420
432
 
421
433
  return {
422
434
  "domain": self.domain,
435
+ "server_names": server_names,
423
436
  "port": self.port,
424
437
  "app_path": str(self.app_path),
425
438
  "app_name": self.app_name,
@@ -1025,25 +1038,32 @@ class BaseDeployer(ABC):
1025
1038
  def obtain_certificate(self) -> bool:
1026
1039
  """
1027
1040
  Obtain SSL certificate.
1028
-
1041
+
1029
1042
  Returns:
1030
1043
  True if successful.
1031
1044
  """
1032
1045
  if not self.ssl:
1033
1046
  return True
1034
-
1047
+
1035
1048
  self.logger.substep(f"Domain: {self.domain}")
1036
-
1049
+
1050
+ additional_domains = None
1051
+ if self.include_www:
1052
+ www_domain = f"www.{self.domain}"
1053
+ additional_domains = [www_domain]
1054
+ self.logger.substep(f"Including: {www_domain}")
1055
+
1037
1056
  # Use nginx plugin if using nginx
1038
1057
  nginx = self.webserver == "nginx"
1039
1058
  apache = self.webserver == "apache"
1040
-
1059
+
1041
1060
  self.cert_manager.obtain(
1042
1061
  self.domain,
1043
1062
  nginx=nginx,
1044
1063
  apache=apache,
1064
+ additional_domains=additional_domains,
1045
1065
  )
1046
-
1066
+
1047
1067
  return True
1048
1068
 
1049
1069
  def start(self) -> bool:
@@ -155,10 +155,11 @@ class CertManager(BaseManager):
155
155
  apache: bool = False,
156
156
  dry_run: bool = False,
157
157
  additional_domains: Optional[List[str]] = None,
158
+ expand: bool = False,
158
159
  ) -> bool:
159
160
  """
160
161
  Obtain a new certificate.
161
-
162
+
162
163
  Args:
163
164
  domain: Primary domain name.
164
165
  email: Email for registration and recovery.
@@ -168,16 +169,19 @@ class CertManager(BaseManager):
168
169
  apache: Use apache plugin.
169
170
  dry_run: Test certificate issuance.
170
171
  additional_domains: Additional domains for the certificate.
171
-
172
+ expand: Expand existing certificate to include additional domains.
173
+
172
174
  Returns:
173
175
  True if certificate was obtained successfully.
174
-
176
+
175
177
  Raises:
176
178
  CertificateError: If certificate issuance fails.
177
179
  """
178
180
  if self.cert_exists(domain) and not dry_run:
179
- self.logger.warning(f"Certificate already exists for {domain}")
180
- return True
181
+ if not expand or not additional_domains:
182
+ self.logger.warning(f"Certificate already exists for {domain}")
183
+ return True
184
+ self.logger.info(f"Expanding certificate for {domain}")
181
185
 
182
186
  # Build command
183
187
  cmd = ["certbot", "certonly"]
@@ -244,6 +248,10 @@ class CertManager(BaseManager):
244
248
  for d in additional_domains:
245
249
  cmd.extend(["-d", d])
246
250
 
251
+ # Expand existing certificate
252
+ if expand and self.cert_exists(domain):
253
+ cmd.append("--expand")
254
+
247
255
  # Dry run
248
256
  if dry_run:
249
257
  cmd.append("--dry-run")
@@ -3,6 +3,9 @@
3
3
 
4
4
  <VirtualHost *:80>
5
5
  ServerName {{ domain }}
6
+ {% if server_names is defined and server_names != domain %}
7
+ ServerAlias www.{{ domain }}
8
+ {% endif %}
6
9
  ServerAdmin webmaster@{{ domain }}
7
10
 
8
11
  {% if ssl %}
@@ -14,6 +17,9 @@
14
17
 
15
18
  <VirtualHost *:443>
16
19
  ServerName {{ domain }}
20
+ {% if server_names is defined and server_names != domain %}
21
+ ServerAlias www.{{ domain }}
22
+ {% endif %}
17
23
  ServerAdmin webmaster@{{ domain }}
18
24
 
19
25
  # SSL Configuration
@@ -3,6 +3,9 @@
3
3
 
4
4
  <VirtualHost *:80>
5
5
  ServerName {{ domain }}
6
+ {% if server_names is defined and server_names != domain %}
7
+ ServerAlias www.{{ domain }}
8
+ {% endif %}
6
9
  ServerAdmin webmaster@{{ domain }}
7
10
 
8
11
  {% if ssl %}
@@ -14,6 +17,9 @@
14
17
 
15
18
  <VirtualHost *:443>
16
19
  ServerName {{ domain }}
20
+ {% if server_names is defined and server_names != domain %}
21
+ ServerAlias www.{{ domain }}
22
+ {% endif %}
17
23
  ServerAdmin webmaster@{{ domain }}
18
24
 
19
25
  # SSL Configuration
@@ -14,7 +14,7 @@ upstream {{ upstream.name }} {
14
14
  server {
15
15
  listen 80;
16
16
  listen [::]:80;
17
- server_name {{ domain }};
17
+ server_name {{ server_names | default(domain) }};
18
18
 
19
19
  # ACME challenge location for Let's Encrypt (webroot method)
20
20
  location /.well-known/acme-challenge/ {
@@ -32,7 +32,7 @@ server {
32
32
  server {
33
33
  listen 443 ssl http2;
34
34
  listen [::]:443 ssl http2;
35
- server_name {{ domain }};
35
+ server_name {{ server_names | default(domain) }};
36
36
 
37
37
  # SSL Configuration
38
38
  ssl_certificate {{ ssl_certificate }};
@@ -4,7 +4,7 @@
4
4
  server {
5
5
  listen 80;
6
6
  listen [::]:80;
7
- server_name {{ domain }};
7
+ server_name {{ server_names | default(domain) }};
8
8
 
9
9
  # ACME challenge location for Let's Encrypt (webroot method)
10
10
  location /.well-known/acme-challenge/ {
@@ -22,7 +22,7 @@ server {
22
22
  server {
23
23
  listen 443 ssl http2;
24
24
  listen [::]:443 ssl http2;
25
- server_name {{ domain }};
25
+ server_name {{ server_names | default(domain) }};
26
26
 
27
27
  # SSL Configuration
28
28
  ssl_certificate {{ ssl_certificate | default('/etc/letsencrypt/live/' + domain + '/fullchain.pem') }};
@@ -4,7 +4,7 @@
4
4
  server {
5
5
  listen 80;
6
6
  listen [::]:80;
7
- server_name {{ domain }};
7
+ server_name {{ server_names | default(domain) }};
8
8
 
9
9
  # ACME challenge location for Let's Encrypt (webroot method)
10
10
  location /.well-known/acme-challenge/ {
@@ -22,7 +22,7 @@ server {
22
22
  server {
23
23
  listen 443 ssl http2;
24
24
  listen [::]:443 ssl http2;
25
- server_name {{ domain }};
25
+ server_name {{ server_names | default(domain) }};
26
26
 
27
27
  # SSL Configuration
28
28
  ssl_certificate {{ ssl_certificate | default('/etc/letsencrypt/live/' + domain + '/fullchain.pem') }};
@@ -168,6 +168,28 @@ def get_domain_parts(domain: str) -> dict:
168
168
  }
169
169
 
170
170
 
171
+ def should_include_www(domain: str) -> bool:
172
+ """
173
+ Determine if www subdomain should be included for a domain.
174
+
175
+ Returns True for base domains like example.com.
176
+ Returns False for subdomains (api.example.com), www domains
177
+ (www.example.com), and single-label domains (localhost).
178
+
179
+ Args:
180
+ domain: Domain to check.
181
+
182
+ Returns:
183
+ True if www.domain should be included.
184
+ """
185
+ parts = domain.lower().strip().split(".")
186
+ if len(parts) != 2:
187
+ return False
188
+ if parts[0] == "www":
189
+ return False
190
+ return True
191
+
192
+
171
193
  def is_subdomain(domain: str) -> bool:
172
194
  """
173
195
  Check if a domain is a subdomain.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wasm-cli
3
- Version: 0.15.3
3
+ Version: 0.15.4
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>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes