wasm-cli 0.15.2__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.2/src/wasm_cli.egg-info → wasm_cli-0.15.4}/PKG-INFO +1 -1
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/pyproject.toml +1 -1
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/setup.py +1 -1
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/__init__.py +1 -1
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/cert.py +1 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/webapp.py +6 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/interactive.py +105 -7
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/parser.py +11 -1
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/base.py +27 -7
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/docker_compose.py +49 -26
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/env_manager.py +1 -1
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/cert_manager.py +13 -5
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/service_manager.py +12 -9
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/apache/proxy.conf.j2 +6 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/apache/static.conf.j2 +6 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/nginx/advanced.conf.j2 +2 -2
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/nginx/proxy.conf.j2 +2 -2
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/nginx/static.conf.j2 +2 -2
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/validators/domain.py +22 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4/src/wasm_cli.egg-info}/PKG-INFO +1 -1
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/LICENSE +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/MANIFEST.in +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/README.md +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/docs/MONITOR.md +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/docs/OBS_SETUP.md +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/docs/assets/logo.png +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/docs/assets/logo_bg.png +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/man/wasm.1 +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/setup.cfg +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/__main__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/backup.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/config.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/db.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/env.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/health.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/monitor.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/service.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/setup.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/site.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/store.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/version.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/cli/commands/web.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/completions/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/completions/_wasm +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/completions/wasm.bash +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/completions/wasm.fish +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/core/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/core/config.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/core/dependencies.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/core/exceptions.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/core/logger.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/core/store.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/core/update_checker.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/core/utils.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/nginx_config.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/package_manager.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/path_resolver.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/prisma.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/turbo.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/helpers/workspace.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/monorepo.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/nextjs.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/nodejs.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/python.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/registry.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/static.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/deployers/vite.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/main.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/apache_manager.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/backup_manager.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/backup_scheduler.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/base_manager.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/database/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/database/base.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/database/mongodb.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/database/mysql.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/database/postgres.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/database/redis.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/database/registry.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/nginx_manager.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/managers/source_manager.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/monitor/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/monitor/ai_analyzer.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/monitor/email_notifier.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/monitor/process_monitor.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/monitor/threat_store.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/nginx/monorepo.conf.j2 +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/systemd/app.service.j2 +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/systemd/backup-service.j2 +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/systemd/backup-timer.j2 +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/templates/systemd/docker-compose.service.j2 +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/validators/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/validators/port.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/validators/source.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/validators/ssh.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/apps.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/auth.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/backups.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/certs.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/config.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/databases.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/jobs.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/monitor.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/router.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/services.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/sites.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/api/system.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/auth.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/jobs.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/server.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/css/main.css +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/index.html +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/cards.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/jobs.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/metrics.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/components/skeleton.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/api.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/dialogs.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/notifications.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/router.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/search.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/shortcuts.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/theme.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/ui.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/core/websocket.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/main.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/apps.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/backups.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/certs.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/config.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/dashboard.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/databases.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/jobs.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/logs.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/monitor.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/services.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/js/pages/sites.js +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/login.html +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/logo.png +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/static/logo.webp +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/websockets/__init__.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm/web/websockets/router.py +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/SOURCES.txt +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/dependency_links.txt +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/entry_points.txt +0 -0
- {wasm_cli-0.15.2 → wasm_cli-0.15.4}/src/wasm_cli.egg-info/requires.txt +0 -0
- {wasm_cli-0.15.2 → 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:
|
|
@@ -216,11 +216,20 @@ class DockerComposeDeployer:
|
|
|
216
216
|
self.compose_profiles = compose_profiles or []
|
|
217
217
|
self.port = port
|
|
218
218
|
|
|
219
|
+
def _is_headless(self) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Check if the application has no web-facing services.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
True if no services expose ports (headless/worker mode).
|
|
225
|
+
"""
|
|
226
|
+
return not any(svc.is_web for svc in self.services)
|
|
227
|
+
|
|
219
228
|
def deploy(self) -> None:
|
|
220
229
|
"""
|
|
221
230
|
Execute the full deployment workflow.
|
|
222
231
|
|
|
223
|
-
|
|
232
|
+
For web-facing apps (services with ports):
|
|
224
233
|
1. Fetch source code
|
|
225
234
|
2. Discover compose file
|
|
226
235
|
3. Parse compose services
|
|
@@ -231,6 +240,9 @@ class DockerComposeDeployer:
|
|
|
231
240
|
8. Create systemd service
|
|
232
241
|
9. Start and verify
|
|
233
242
|
|
|
243
|
+
For headless/worker apps (no exposed ports):
|
|
244
|
+
Steps 6 and 7 are skipped automatically.
|
|
245
|
+
|
|
234
246
|
Raises:
|
|
235
247
|
DeploymentError: If any deployment step fails.
|
|
236
248
|
"""
|
|
@@ -246,17 +258,26 @@ class DockerComposeDeployer:
|
|
|
246
258
|
self.logger.step(3, total_steps, "Parsing compose services")
|
|
247
259
|
self._parse_compose_services()
|
|
248
260
|
|
|
261
|
+
headless = self._is_headless()
|
|
262
|
+
|
|
249
263
|
self.logger.step(4, total_steps, "Configuring environment")
|
|
250
264
|
self._configure_environment()
|
|
251
265
|
|
|
252
266
|
self.logger.step(5, total_steps, "Building Docker images")
|
|
253
267
|
self._build_images()
|
|
254
268
|
|
|
255
|
-
|
|
256
|
-
|
|
269
|
+
if headless:
|
|
270
|
+
self.logger.step(6, total_steps, "Skipping Nginx (headless worker)")
|
|
271
|
+
self.logger.substep("No services expose ports, web server not needed")
|
|
272
|
+
self.logger.step(7, total_steps, "Skipping SSL (headless worker)")
|
|
273
|
+
self.logger.substep("No web-facing services, certificate not needed")
|
|
274
|
+
self.ssl = False
|
|
275
|
+
else:
|
|
276
|
+
self.logger.step(6, total_steps, "Creating site configuration")
|
|
277
|
+
self._create_site()
|
|
257
278
|
|
|
258
|
-
|
|
259
|
-
|
|
279
|
+
self.logger.step(7, total_steps, "Obtaining SSL certificate")
|
|
280
|
+
self._obtain_certificate()
|
|
260
281
|
|
|
261
282
|
self.logger.step(8, total_steps, "Creating systemd service")
|
|
262
283
|
self._create_systemd_service()
|
|
@@ -273,7 +294,10 @@ class DockerComposeDeployer:
|
|
|
273
294
|
self.logger.key_value("Path", str(self.app_path))
|
|
274
295
|
self.logger.key_value("Compose File", str(self.compose_path.name))
|
|
275
296
|
self.logger.key_value("Services", str(len(self.services)))
|
|
276
|
-
|
|
297
|
+
if headless:
|
|
298
|
+
self.logger.key_value("Mode", "Headless worker (no web server)")
|
|
299
|
+
else:
|
|
300
|
+
self.logger.key_value("SSL", "Yes" if self.ssl else "No")
|
|
277
301
|
|
|
278
302
|
except Exception as e:
|
|
279
303
|
self.logger.error(f"Deployment failed: {e}")
|
|
@@ -285,7 +309,7 @@ class DockerComposeDeployer:
|
|
|
285
309
|
source_manager = SourceManager(verbose=self.verbose)
|
|
286
310
|
source_manager.fetch(
|
|
287
311
|
source=self.source,
|
|
288
|
-
|
|
312
|
+
destination=self.app_path,
|
|
289
313
|
branch=self.branch,
|
|
290
314
|
)
|
|
291
315
|
self.logger.substep(f"Source fetched to {self.app_path}")
|
|
@@ -407,7 +431,7 @@ class DockerComposeDeployer:
|
|
|
407
431
|
if no_cache:
|
|
408
432
|
cmd.append("--no-cache")
|
|
409
433
|
|
|
410
|
-
result = run_command(cmd, cwd=self.app_path, timeout=
|
|
434
|
+
result = run_command(cmd, cwd=self.app_path, timeout=600)
|
|
411
435
|
if not result.success:
|
|
412
436
|
raise DockerError(
|
|
413
437
|
"Failed to build Docker images",
|
|
@@ -561,14 +585,11 @@ class DockerComposeDeployer:
|
|
|
561
585
|
|
|
562
586
|
service_manager.create_service(
|
|
563
587
|
name=self.app_name,
|
|
588
|
+
working_directory=str(self.app_path),
|
|
589
|
+
description=f"Docker Compose app: {self.domain}",
|
|
590
|
+
environment=service_env,
|
|
564
591
|
template="docker-compose",
|
|
565
|
-
|
|
566
|
-
"name": self.app_name,
|
|
567
|
-
"description": f"Docker Compose app: {self.domain}",
|
|
568
|
-
"working_directory": str(self.app_path),
|
|
569
|
-
"environment": service_env,
|
|
570
|
-
"compose_file": compose_file_rel,
|
|
571
|
-
},
|
|
592
|
+
compose_file=compose_file_rel,
|
|
572
593
|
)
|
|
573
594
|
self.logger.substep(f"Created systemd service: {self.app_name}")
|
|
574
595
|
|
|
@@ -638,7 +659,8 @@ class DockerComposeDeployer:
|
|
|
638
659
|
|
|
639
660
|
def _register_app(self) -> None:
|
|
640
661
|
"""Register the application in the WASM store."""
|
|
641
|
-
|
|
662
|
+
headless = self._is_headless()
|
|
663
|
+
primary_port = 0 if headless else self._get_primary_port()
|
|
642
664
|
|
|
643
665
|
app = App(
|
|
644
666
|
domain=self.domain,
|
|
@@ -647,7 +669,7 @@ class DockerComposeDeployer:
|
|
|
647
669
|
branch=self.branch,
|
|
648
670
|
port=primary_port,
|
|
649
671
|
app_path=str(self.app_path),
|
|
650
|
-
webserver=self.webserver,
|
|
672
|
+
webserver="" if headless else self.webserver,
|
|
651
673
|
ssl_enabled=self.ssl,
|
|
652
674
|
status=AppStatus.RUNNING.value,
|
|
653
675
|
env_vars=self.env_vars,
|
|
@@ -676,14 +698,15 @@ class DockerComposeDeployer:
|
|
|
676
698
|
except Exception:
|
|
677
699
|
pass
|
|
678
700
|
|
|
679
|
-
# Remove nginx config
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
nginx.
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
701
|
+
# Remove nginx config (only if web-facing)
|
|
702
|
+
if not self._is_headless():
|
|
703
|
+
try:
|
|
704
|
+
nginx = NginxManager(verbose=self.verbose)
|
|
705
|
+
if nginx.site_exists(self.domain):
|
|
706
|
+
nginx.delete_site(self.domain)
|
|
707
|
+
nginx.reload()
|
|
708
|
+
except Exception:
|
|
709
|
+
pass
|
|
687
710
|
|
|
688
711
|
# Remove app directory
|
|
689
712
|
if self.app_path.exists():
|
|
@@ -791,7 +814,7 @@ class DockerComposeDeployer:
|
|
|
791
814
|
cmd.extend(["-f", str(self.compose_path)])
|
|
792
815
|
cmd.extend(["up", "-d", "--remove-orphans"])
|
|
793
816
|
|
|
794
|
-
result = run_command(cmd, cwd=self.app_path, timeout=
|
|
817
|
+
result = run_command(cmd, cwd=self.app_path, timeout=300)
|
|
795
818
|
if not result.success:
|
|
796
819
|
raise DockerError("Failed to update containers", result.stderr)
|
|
797
820
|
|
|
@@ -113,7 +113,7 @@ class EnvManager:
|
|
|
113
113
|
|
|
114
114
|
# Secret detection patterns
|
|
115
115
|
SECRET_PATTERNS = [
|
|
116
|
-
"PASSWORD", "SECRET", "TOKEN", "API_KEY", "PRIVATE_KEY",
|
|
116
|
+
"PASSWORD", "_PASS", "SECRET", "TOKEN", "API_KEY", "PRIVATE_KEY",
|
|
117
117
|
"ENCRYPTION_KEY", "SIGNING_KEY", "ACCESS_KEY", "SECRET_KEY",
|
|
118
118
|
"CLIENT_SECRET", "WEBHOOK_SECRET",
|
|
119
119
|
]
|
|
@@ -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")
|
|
@@ -238,41 +238,43 @@ class ServiceManager(BaseManager):
|
|
|
238
238
|
def create_service(
|
|
239
239
|
self,
|
|
240
240
|
name: str,
|
|
241
|
-
command: str,
|
|
242
|
-
working_directory: str,
|
|
241
|
+
command: str = "",
|
|
242
|
+
working_directory: str = "",
|
|
243
243
|
user: Optional[str] = None,
|
|
244
244
|
group: Optional[str] = None,
|
|
245
245
|
environment: Optional[Dict[str, str]] = None,
|
|
246
246
|
description: Optional[str] = None,
|
|
247
247
|
template: str = "app",
|
|
248
|
+
**kwargs,
|
|
248
249
|
) -> bool:
|
|
249
250
|
"""
|
|
250
251
|
Create a new systemd service.
|
|
251
|
-
|
|
252
|
+
|
|
252
253
|
Args:
|
|
253
254
|
name: Service name.
|
|
254
|
-
command: Command to execute.
|
|
255
|
+
command: Command to execute (not required for docker-compose template).
|
|
255
256
|
working_directory: Working directory for the service.
|
|
256
257
|
user: User to run service as.
|
|
257
258
|
group: Group to run service as.
|
|
258
259
|
environment: Environment variables.
|
|
259
260
|
description: Service description.
|
|
260
261
|
template: Template name.
|
|
261
|
-
|
|
262
|
+
**kwargs: Extra context variables passed to the template.
|
|
263
|
+
|
|
262
264
|
Returns:
|
|
263
265
|
True if service was created successfully.
|
|
264
|
-
|
|
266
|
+
|
|
265
267
|
Raises:
|
|
266
268
|
ServiceError: If creation fails.
|
|
267
269
|
"""
|
|
268
270
|
service_name = self._get_service_name(name)
|
|
269
|
-
|
|
271
|
+
|
|
270
272
|
if self.service_exists(name):
|
|
271
273
|
raise ServiceError(f"Service already exists: {service_name}")
|
|
272
|
-
|
|
274
|
+
|
|
273
275
|
if not self.jinja_env:
|
|
274
276
|
raise ServiceError("Template environment not initialized")
|
|
275
|
-
|
|
277
|
+
|
|
276
278
|
# Prepare context
|
|
277
279
|
ctx = {
|
|
278
280
|
"name": service_name,
|
|
@@ -283,6 +285,7 @@ class ServiceManager(BaseManager):
|
|
|
283
285
|
"group": group or self.config.service_group,
|
|
284
286
|
"environment": environment or {},
|
|
285
287
|
}
|
|
288
|
+
ctx.update(kwargs)
|
|
286
289
|
|
|
287
290
|
# Render template
|
|
288
291
|
try:
|
|
@@ -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
|