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.
- {wasm_cli-0.15.3/src/wasm_cli.egg-info → wasm_cli-0.15.4}/PKG-INFO +1 -1
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/pyproject.toml +1 -1
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/setup.py +1 -1
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/__init__.py +1 -1
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/cert.py +1 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/webapp.py +6 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/interactive.py +105 -7
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/parser.py +11 -1
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/base.py +27 -7
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/cert_manager.py +13 -5
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/apache/proxy.conf.j2 +6 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/apache/static.conf.j2 +6 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/nginx/advanced.conf.j2 +2 -2
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/nginx/proxy.conf.j2 +2 -2
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/nginx/static.conf.j2 +2 -2
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/domain.py +22 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4/src/wasm_cli.egg-info}/PKG-INFO +1 -1
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/LICENSE +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/MANIFEST.in +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/README.md +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/docs/MONITOR.md +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/docs/OBS_SETUP.md +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/docs/assets/logo.png +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/docs/assets/logo_bg.png +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/man/wasm.1 +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/setup.cfg +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/__main__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/backup.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/config.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/db.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/env.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/health.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/monitor.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/service.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/setup.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/site.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/store.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/version.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/cli/commands/web.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/completions/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/completions/_wasm +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/completions/wasm.bash +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/completions/wasm.fish +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/config.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/dependencies.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/exceptions.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/logger.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/store.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/update_checker.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/core/utils.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/docker_compose.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/env_manager.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/nginx_config.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/package_manager.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/path_resolver.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/prisma.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/turbo.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/workspace.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/monorepo.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/nextjs.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/nodejs.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/python.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/registry.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/static.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/deployers/vite.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/main.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/apache_manager.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/backup_manager.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/backup_scheduler.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/base_manager.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/base.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/mongodb.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/mysql.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/postgres.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/redis.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/database/registry.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/nginx_manager.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/service_manager.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/managers/source_manager.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/ai_analyzer.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/email_notifier.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/process_monitor.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/monitor/threat_store.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/nginx/monorepo.conf.j2 +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/systemd/app.service.j2 +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/systemd/backup-service.j2 +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/systemd/backup-timer.j2 +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/templates/systemd/docker-compose.service.j2 +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/port.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/source.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/validators/ssh.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/apps.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/auth.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/backups.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/certs.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/config.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/databases.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/jobs.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/monitor.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/router.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/services.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/sites.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/api/system.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/auth.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/jobs.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/server.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/css/main.css +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/index.html +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/cards.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/jobs.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/metrics.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/skeleton.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/api.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/dialogs.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/notifications.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/router.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/search.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/shortcuts.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/theme.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/ui.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/websocket.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/main.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/apps.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/backups.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/certs.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/config.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/dashboard.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/databases.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/jobs.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/logs.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/monitor.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/services.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/sites.js +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/login.html +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/logo.png +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/static/logo.webp +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/websockets/__init__.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm/web/websockets/router.py +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/SOURCES.txt +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/dependency_links.txt +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/entry_points.txt +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/requires.txt +0 -0
- {wasm_cli-0.15.3 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wasm-cli"
|
|
7
|
-
version = "0.15.
|
|
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.
|
|
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",
|
|
@@ -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.
|
|
127
|
-
elif action
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
180
|
-
|
|
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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|