wasm-cli 0.14.0__tar.gz → 0.14.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wasm_cli-0.14.0/src/wasm_cli.egg-info → wasm_cli-0.14.1}/PKG-INFO +1 -1
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/pyproject.toml +1 -1
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/setup.py +1 -1
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/__init__.py +1 -1
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/backup.py +18 -10
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/store.py +20 -4
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/webapp.py +4 -4
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/parser.py +5 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/completions/_wasm +31 -7
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/completions/wasm.bash +25 -1
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/completions/wasm.fish +24 -1
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/utils.py +18 -3
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/base.py +3 -3
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/backup_manager.py +108 -14
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/service_manager.py +91 -45
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/apps.py +6 -4
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/backups.py +37 -17
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/services.py +31 -25
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/websockets/router.py +3 -1
- {wasm_cli-0.14.0 → wasm_cli-0.14.1/src/wasm_cli.egg-info}/PKG-INFO +1 -1
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/LICENSE +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/MANIFEST.in +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/README.md +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/docs/MONITOR.md +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/docs/OBS_SETUP.md +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/docs/assets/logo.png +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/docs/assets/logo_bg.png +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/man/wasm.1 +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/setup.cfg +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/__main__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/cert.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/config.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/db.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/health.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/monitor.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/service.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/setup.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/site.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/version.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/commands/web.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/cli/interactive.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/completions/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/config.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/dependencies.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/exceptions.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/logger.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/store.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/core/update_checker.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/helpers/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/helpers/package_manager.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/helpers/path_resolver.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/helpers/prisma.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/nextjs.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/nodejs.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/python.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/registry.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/static.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/deployers/vite.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/main.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/apache_manager.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/base_manager.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/cert_manager.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/base.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/mongodb.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/mysql.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/postgres.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/redis.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/database/registry.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/nginx_manager.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/managers/source_manager.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/ai_analyzer.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/email_notifier.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/process_monitor.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/monitor/threat_store.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/apache/proxy.conf.j2 +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/apache/static.conf.j2 +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/nginx/proxy.conf.j2 +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/nginx/static.conf.j2 +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/templates/systemd/app.service.j2 +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/domain.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/port.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/source.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/validators/ssh.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/auth.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/certs.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/config.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/databases.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/jobs.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/monitor.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/router.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/sites.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/api/system.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/auth.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/jobs.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/server.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/css/main.css +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/index.html +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/components/cards.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/components/jobs.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/components/metrics.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/components/skeleton.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/api.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/dialogs.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/notifications.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/router.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/search.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/shortcuts.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/theme.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/ui.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/core/websocket.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/main.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/apps.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/backups.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/certs.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/config.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/dashboard.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/databases.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/jobs.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/logs.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/monitor.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/services.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/js/pages/sites.js +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/login.html +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/logo.png +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/static/logo.webp +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm/web/websockets/__init__.py +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm_cli.egg-info/SOURCES.txt +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm_cli.egg-info/dependency_links.txt +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm_cli.egg-info/entry_points.txt +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/src/wasm_cli.egg-info/requires.txt +0 -0
- {wasm_cli-0.14.0 → wasm_cli-0.14.1}/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.14.
|
|
7
|
+
version = "0.14.1"
|
|
8
8
|
description = "Web App System Management - Deploy and manage web applications on Linux servers"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "WASM-NCSAL" }
|
|
@@ -9,7 +9,7 @@ from setuptools import setup, find_packages
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name="wasm-cli",
|
|
12
|
-
version="0.14.
|
|
12
|
+
version="0.14.1",
|
|
13
13
|
description="Web App System Management - Deploy and manage web applications on Linux servers",
|
|
14
14
|
author="Yago López Prado",
|
|
15
15
|
author_email="yago.lopez.adeje@gmail.com",
|
|
@@ -105,6 +105,7 @@ def _backup_create(args: Namespace, verbose: bool) -> int:
|
|
|
105
105
|
include_env = not getattr(args, "no_env", False)
|
|
106
106
|
include_node_modules = getattr(args, "include_node_modules", False)
|
|
107
107
|
include_build = getattr(args, "include_build", False)
|
|
108
|
+
include_databases = getattr(args, "include_databases", False)
|
|
108
109
|
tags = getattr(args, "tags", None)
|
|
109
110
|
|
|
110
111
|
if not domain:
|
|
@@ -127,6 +128,7 @@ def _backup_create(args: Namespace, verbose: bool) -> int:
|
|
|
127
128
|
include_env=include_env,
|
|
128
129
|
include_node_modules=include_node_modules,
|
|
129
130
|
include_build=include_build,
|
|
131
|
+
include_databases=include_databases,
|
|
130
132
|
tags=tag_list,
|
|
131
133
|
)
|
|
132
134
|
|
|
@@ -135,7 +137,13 @@ def _backup_create(args: Namespace, verbose: bool) -> int:
|
|
|
135
137
|
logger.info(f" Size: {metadata.size_human}")
|
|
136
138
|
if metadata.git_commit:
|
|
137
139
|
logger.info(f" Commit: {metadata.git_commit} ({metadata.git_branch})")
|
|
138
|
-
|
|
140
|
+
if metadata.database_backups:
|
|
141
|
+
db_count = len(metadata.database_backups)
|
|
142
|
+
logger.info(f" Databases: {db_count} backed up")
|
|
143
|
+
for db_info in metadata.database_backups:
|
|
144
|
+
size_mb = db_info.get("size_bytes", 0) / (1024 * 1024)
|
|
145
|
+
logger.info(f" - {db_info['engine']}/{db_info['name']} ({size_mb:.1f} MB)")
|
|
146
|
+
|
|
139
147
|
return 0
|
|
140
148
|
|
|
141
149
|
except BackupError as e:
|
|
@@ -195,7 +203,7 @@ def _backup_list(args: Namespace, verbose: bool) -> int:
|
|
|
195
203
|
by_domain[backup.domain].append(backup)
|
|
196
204
|
|
|
197
205
|
for dom, dom_backups in by_domain.items():
|
|
198
|
-
logger.info(f"\n
|
|
206
|
+
logger.info(f"\n[{dom}]")
|
|
199
207
|
_print_backup_table(dom_backups, logger, indent=True)
|
|
200
208
|
|
|
201
209
|
return 0
|
|
@@ -226,7 +234,7 @@ def _print_backup_table(backups, logger, indent: bool = False):
|
|
|
226
234
|
desc_str = f" - {backup.description}"
|
|
227
235
|
|
|
228
236
|
logger.info(
|
|
229
|
-
f"{prefix}
|
|
237
|
+
f"{prefix}- {backup.id}: {backup.size_human}, "
|
|
230
238
|
f"{backup.age}{commit_str}{tags_str}{desc_str}"
|
|
231
239
|
)
|
|
232
240
|
|
|
@@ -359,17 +367,17 @@ def _backup_verify(args: Namespace, verbose: bool) -> int:
|
|
|
359
367
|
|
|
360
368
|
if result["valid"]:
|
|
361
369
|
logger.success("Backup is valid")
|
|
362
|
-
if result.get("
|
|
363
|
-
logger.info("
|
|
364
|
-
if result.get("
|
|
365
|
-
logger.info(f"
|
|
370
|
+
if result.get("checksum_ok"):
|
|
371
|
+
logger.info(" [OK] Checksum verified")
|
|
372
|
+
if result.get("files_ok"):
|
|
373
|
+
logger.info(f" [OK] Archive valid ({result.get('file_count', '?')} files)")
|
|
366
374
|
else:
|
|
367
375
|
logger.error("Backup is invalid")
|
|
368
376
|
for err in result["errors"]:
|
|
369
|
-
logger.error(f"
|
|
370
|
-
|
|
377
|
+
logger.error(f" [ERROR] {err}")
|
|
378
|
+
|
|
371
379
|
for warn in result.get("warnings", []):
|
|
372
|
-
logger.warning(f"
|
|
380
|
+
logger.warning(f" [WARN] {warn}")
|
|
373
381
|
|
|
374
382
|
return 0 if result["valid"] else 1
|
|
375
383
|
|
|
@@ -313,13 +313,29 @@ def _store_import(args: Namespace, verbose: bool) -> int:
|
|
|
313
313
|
else:
|
|
314
314
|
logger.step(2, 3, "No Apache sites found")
|
|
315
315
|
|
|
316
|
-
# 3. Import from systemd services (wasm-*
|
|
316
|
+
# 3. Import from systemd services (both legacy wasm-* and new format)
|
|
317
317
|
logger.step(3, 3, "Scanning systemd services")
|
|
318
318
|
if SYSTEMD_DIR.exists():
|
|
319
|
-
|
|
319
|
+
# Find all potential WASM service files
|
|
320
|
+
service_files = list(SYSTEMD_DIR.glob("wasm-*.service"))
|
|
321
|
+
# Also check for services matching domain pattern (new format)
|
|
322
|
+
for sf in SYSTEMD_DIR.glob("*.service"):
|
|
323
|
+
name = sf.stem
|
|
324
|
+
# Skip if already found as wasm-* or if it's a system service
|
|
325
|
+
if name.startswith("wasm-") or not "-" in name:
|
|
326
|
+
continue
|
|
327
|
+
# Check if it looks like a domain-based name (has hyphen, no @ or other special chars)
|
|
328
|
+
if "@" not in name and name.count("-") >= 1:
|
|
329
|
+
service_files.append(sf)
|
|
330
|
+
|
|
331
|
+
for service_file in service_files:
|
|
320
332
|
# Extract app name from service file name
|
|
321
|
-
service_name = service_file.stem
|
|
322
|
-
|
|
333
|
+
service_name = service_file.stem
|
|
334
|
+
# Handle both legacy (wasm-example-com) and new format (example-com)
|
|
335
|
+
if service_name.startswith("wasm-"):
|
|
336
|
+
app_name = service_name[5:] # Remove wasm- prefix
|
|
337
|
+
else:
|
|
338
|
+
app_name = service_name
|
|
323
339
|
|
|
324
340
|
if store.get_service(app_name):
|
|
325
341
|
continue
|
|
@@ -639,7 +639,7 @@ def _handle_delete(args: Namespace) -> int:
|
|
|
639
639
|
try:
|
|
640
640
|
status = service_manager.status(app_name)
|
|
641
641
|
if status.get("exists"):
|
|
642
|
-
logger.key_value("Stop and remove service",
|
|
642
|
+
logger.key_value("Stop and remove service", app_name)
|
|
643
643
|
except Exception:
|
|
644
644
|
pass
|
|
645
645
|
|
|
@@ -734,9 +734,9 @@ def _handle_logs(args: Namespace) -> int:
|
|
|
734
734
|
domain = validate_domain(args.domain)
|
|
735
735
|
app_name = domain_to_app_name(domain)
|
|
736
736
|
|
|
737
|
-
# Get service name (
|
|
738
|
-
service_name =
|
|
739
|
-
|
|
737
|
+
# Get resolved service name (handles both legacy wasm-* and new format)
|
|
738
|
+
service_name = service_manager._resolve_service_name(app_name)
|
|
739
|
+
|
|
740
740
|
if args.follow:
|
|
741
741
|
# Use journalctl directly for follow mode
|
|
742
742
|
import subprocess
|
|
@@ -908,6 +908,11 @@ def _add_backup_parser(subparsers) -> None:
|
|
|
908
908
|
action="store_true",
|
|
909
909
|
help="Include build artifacts (.next, dist, build)",
|
|
910
910
|
)
|
|
911
|
+
create.add_argument(
|
|
912
|
+
"--include-databases", "--include-db",
|
|
913
|
+
action="store_true",
|
|
914
|
+
help="Include associated database dumps in backup",
|
|
915
|
+
)
|
|
911
916
|
create.add_argument(
|
|
912
917
|
"--tags", "-t",
|
|
913
918
|
help="Comma-separated tags for the backup",
|
|
@@ -14,14 +14,38 @@
|
|
|
14
14
|
|
|
15
15
|
# Helper functions
|
|
16
16
|
_wasm_apps() {
|
|
17
|
-
local apps_dir="/var/www/apps"
|
|
18
17
|
local apps=()
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
|
|
19
|
+
# Try to get domains from SQLite store (fast and accurate)
|
|
20
|
+
local db_path="/var/lib/wasm/wasm.db"
|
|
21
|
+
if [[ ! -f "$db_path" ]]; then
|
|
22
|
+
db_path="$HOME/.local/share/wasm/wasm.db"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if [[ -f "$db_path" ]] && command -v sqlite3 &>/dev/null; then
|
|
26
|
+
apps=(${(f)"$(sqlite3 "$db_path" "SELECT domain FROM apps" 2>/dev/null)"})
|
|
27
|
+
else
|
|
28
|
+
# Fallback: list app directories and convert names to domains
|
|
29
|
+
local apps_dir="/var/www/apps"
|
|
30
|
+
if [[ -d "$apps_dir" ]]; then
|
|
31
|
+
for app in "$apps_dir"/*/; do
|
|
32
|
+
if [[ -d "$app" ]]; then
|
|
33
|
+
local name
|
|
34
|
+
name=$(basename "$app")
|
|
35
|
+
# Convert directory name to domain format
|
|
36
|
+
# Legacy: wasm-example-com -> example.com
|
|
37
|
+
# New: example-com -> example.com
|
|
38
|
+
if [[ "$name" == wasm-* ]]; then
|
|
39
|
+
apps+=("${${name#wasm-}//\-/.}")
|
|
40
|
+
elif [[ "$name" == *-* ]]; then
|
|
41
|
+
# New format: convert hyphens to dots
|
|
42
|
+
apps+=("${name//\-/.}")
|
|
43
|
+
else
|
|
44
|
+
apps+=("$name")
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
done 2>/dev/null
|
|
48
|
+
fi
|
|
25
49
|
fi
|
|
26
50
|
_describe -t apps 'deployed applications' apps
|
|
27
51
|
}
|
|
@@ -16,11 +16,35 @@
|
|
|
16
16
|
|
|
17
17
|
# Helper function to get list of deployed apps (domains)
|
|
18
18
|
_wasm_get_apps() {
|
|
19
|
+
# Try to get domains from SQLite store (fast and accurate)
|
|
20
|
+
local db_path="/var/lib/wasm/wasm.db"
|
|
21
|
+
if [[ ! -f "$db_path" ]]; then
|
|
22
|
+
db_path="$HOME/.local/share/wasm/wasm.db"
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if [[ -f "$db_path" ]] && command -v sqlite3 &>/dev/null; then
|
|
26
|
+
sqlite3 "$db_path" "SELECT domain FROM apps" 2>/dev/null
|
|
27
|
+
return
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Fallback: list app directories and convert names to domains
|
|
19
31
|
local apps_dir="/var/www/apps"
|
|
20
32
|
if [[ -d "$apps_dir" ]]; then
|
|
21
33
|
for app in "$apps_dir"/*/; do
|
|
22
34
|
if [[ -d "$app" ]]; then
|
|
23
|
-
|
|
35
|
+
local name
|
|
36
|
+
name=$(basename "$app")
|
|
37
|
+
# Convert directory name to domain format
|
|
38
|
+
# Legacy: wasm-example-com -> example.com
|
|
39
|
+
# New: example-com -> example.com
|
|
40
|
+
if [[ "$name" == wasm-* ]]; then
|
|
41
|
+
echo "${name#wasm-}" | tr '-' '.'
|
|
42
|
+
elif [[ "$name" == *-* ]]; then
|
|
43
|
+
# New format: convert hyphens to dots
|
|
44
|
+
echo "$name" | tr '-' '.'
|
|
45
|
+
else
|
|
46
|
+
echo "$name"
|
|
47
|
+
fi
|
|
24
48
|
fi
|
|
25
49
|
done 2>/dev/null
|
|
26
50
|
fi
|
|
@@ -14,11 +14,34 @@ complete -c wasm -f
|
|
|
14
14
|
|
|
15
15
|
# Helper functions
|
|
16
16
|
function __wasm_get_apps
|
|
17
|
+
# Try to get domains from SQLite store (fast and accurate)
|
|
18
|
+
set -l db_path "/var/lib/wasm/wasm.db"
|
|
19
|
+
if not test -f "$db_path"
|
|
20
|
+
set db_path "$HOME/.local/share/wasm/wasm.db"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
if test -f "$db_path"; and command -v sqlite3 >/dev/null
|
|
24
|
+
sqlite3 "$db_path" "SELECT domain FROM apps" 2>/dev/null
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Fallback: list app directories and convert names to domains
|
|
17
29
|
set -l apps_dir "/var/www/apps"
|
|
18
30
|
if test -d "$apps_dir"
|
|
19
31
|
for app in $apps_dir/*/
|
|
20
32
|
if test -d "$app"
|
|
21
|
-
basename "$app"
|
|
33
|
+
set -l name (basename "$app")
|
|
34
|
+
# Convert directory name to domain format
|
|
35
|
+
# Legacy: wasm-example-com -> example.com
|
|
36
|
+
# New: example-com -> example.com
|
|
37
|
+
if string match -q 'wasm-*' "$name"
|
|
38
|
+
string replace 'wasm-' '' "$name" | string replace -a '-' '.'
|
|
39
|
+
else if string match -q '*-*' "$name"
|
|
40
|
+
# New format: convert hyphens to dots
|
|
41
|
+
string replace -a '-' '.' "$name"
|
|
42
|
+
else
|
|
43
|
+
echo "$name"
|
|
44
|
+
end
|
|
22
45
|
end
|
|
23
46
|
end 2>/dev/null
|
|
24
47
|
end
|
|
@@ -219,12 +219,27 @@ def sanitize_name(name: str) -> str:
|
|
|
219
219
|
def domain_to_app_name(domain: str) -> str:
|
|
220
220
|
"""
|
|
221
221
|
Convert a domain to an application name.
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
Args:
|
|
224
224
|
domain: Domain name (e.g., "myapp.example.com").
|
|
225
|
-
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Application name (e.g., "myapp-example-com").
|
|
228
|
+
"""
|
|
229
|
+
return sanitize_name(domain)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def legacy_app_name(domain: str) -> str:
|
|
233
|
+
"""
|
|
234
|
+
Get legacy app name format (with wasm- prefix).
|
|
235
|
+
|
|
236
|
+
Used for backwards compatibility with apps created before v0.14.1.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
domain: Domain name (e.g., "myapp.example.com").
|
|
240
|
+
|
|
226
241
|
Returns:
|
|
227
|
-
|
|
242
|
+
Legacy application name (e.g., "wasm-myapp-example-com").
|
|
228
243
|
"""
|
|
229
244
|
return f"wasm-{sanitize_name(domain)}"
|
|
230
245
|
|
|
@@ -899,9 +899,9 @@ class BaseDeployer(ABC):
|
|
|
899
899
|
app = self.store.get_app(self.domain)
|
|
900
900
|
app_id = app.id if app else None
|
|
901
901
|
|
|
902
|
-
# Service name
|
|
902
|
+
# Service name (no prefix for new format)
|
|
903
903
|
service_name = self.app_name
|
|
904
|
-
service_file = SYSTEMD_DIR / f"
|
|
904
|
+
service_file = SYSTEMD_DIR / f"{self.app_name}.service"
|
|
905
905
|
|
|
906
906
|
existing_service = self.store.get_service(service_name)
|
|
907
907
|
|
|
@@ -1142,7 +1142,7 @@ class BaseDeployer(ABC):
|
|
|
1142
1142
|
# Basic info
|
|
1143
1143
|
protocol = "https" if ssl_obtained else "http"
|
|
1144
1144
|
self.logger.key_value("URL", f"{protocol}://{self.domain}")
|
|
1145
|
-
self.logger.key_value("Service",
|
|
1145
|
+
self.logger.key_value("Service", self.app_name)
|
|
1146
1146
|
self.logger.key_value("Port", str(self.port))
|
|
1147
1147
|
self.logger.key_value("App Path", str(self.app_path))
|
|
1148
1148
|
|
|
@@ -46,6 +46,9 @@ class BackupMetadata:
|
|
|
46
46
|
description: str
|
|
47
47
|
includes_env: bool
|
|
48
48
|
includes_node_modules: bool
|
|
49
|
+
includes_build: bool = False
|
|
50
|
+
includes_databases: bool = False
|
|
51
|
+
database_backups: List[Dict[str, Any]] = field(default_factory=list)
|
|
49
52
|
git_commit: Optional[str] = None
|
|
50
53
|
git_branch: Optional[str] = None
|
|
51
54
|
checksum: Optional[str] = None
|
|
@@ -64,6 +67,9 @@ class BackupMetadata:
|
|
|
64
67
|
"description": self.description,
|
|
65
68
|
"includes_env": self.includes_env,
|
|
66
69
|
"includes_node_modules": self.includes_node_modules,
|
|
70
|
+
"includes_build": self.includes_build,
|
|
71
|
+
"includes_databases": self.includes_databases,
|
|
72
|
+
"database_backups": self.database_backups,
|
|
67
73
|
"git_commit": self.git_commit,
|
|
68
74
|
"git_branch": self.git_branch,
|
|
69
75
|
"checksum": self.checksum,
|
|
@@ -84,6 +90,9 @@ class BackupMetadata:
|
|
|
84
90
|
description=data.get("description", ""),
|
|
85
91
|
includes_env=data.get("includes_env", False),
|
|
86
92
|
includes_node_modules=data.get("includes_node_modules", False),
|
|
93
|
+
includes_build=data.get("includes_build", False),
|
|
94
|
+
includes_databases=data.get("includes_databases", False),
|
|
95
|
+
database_backups=data.get("database_backups", []),
|
|
87
96
|
git_commit=data.get("git_commit"),
|
|
88
97
|
git_branch=data.get("git_branch"),
|
|
89
98
|
checksum=data.get("checksum"),
|
|
@@ -269,24 +278,26 @@ class BackupManager:
|
|
|
269
278
|
include_env: bool = True,
|
|
270
279
|
include_node_modules: bool = False,
|
|
271
280
|
include_build: bool = False,
|
|
281
|
+
include_databases: bool = False,
|
|
272
282
|
tags: Optional[List[str]] = None,
|
|
273
283
|
pre_backup_hook: Optional[str] = None,
|
|
274
284
|
) -> BackupMetadata:
|
|
275
285
|
"""
|
|
276
286
|
Create a backup of an application.
|
|
277
|
-
|
|
287
|
+
|
|
278
288
|
Args:
|
|
279
289
|
domain: Domain name of the application.
|
|
280
290
|
description: Optional description for the backup.
|
|
281
291
|
include_env: Include .env files in backup.
|
|
282
292
|
include_node_modules: Include node_modules (large!).
|
|
283
293
|
include_build: Include build artifacts.
|
|
294
|
+
include_databases: Include associated database dumps.
|
|
284
295
|
tags: Optional tags for the backup.
|
|
285
296
|
pre_backup_hook: Optional command to run before backup.
|
|
286
|
-
|
|
297
|
+
|
|
287
298
|
Returns:
|
|
288
299
|
BackupMetadata for the created backup.
|
|
289
|
-
|
|
300
|
+
|
|
290
301
|
Raises:
|
|
291
302
|
BackupError: If backup fails.
|
|
292
303
|
"""
|
|
@@ -378,6 +389,11 @@ class BackupManager:
|
|
|
378
389
|
result = run_command_sudo(["sha256sum", str(backup_file)])
|
|
379
390
|
checksum = result.stdout.split()[0] if result.success else None
|
|
380
391
|
|
|
392
|
+
# Backup associated databases if requested
|
|
393
|
+
database_backups = []
|
|
394
|
+
if include_databases:
|
|
395
|
+
database_backups = self._backup_databases(domain)
|
|
396
|
+
|
|
381
397
|
# Create metadata
|
|
382
398
|
metadata = BackupMetadata(
|
|
383
399
|
id=backup_id,
|
|
@@ -390,6 +406,9 @@ class BackupManager:
|
|
|
390
406
|
description=description,
|
|
391
407
|
includes_env=include_env,
|
|
392
408
|
includes_node_modules=include_node_modules,
|
|
409
|
+
includes_build=include_build,
|
|
410
|
+
includes_databases=include_databases,
|
|
411
|
+
database_backups=database_backups,
|
|
393
412
|
git_commit=git_commit,
|
|
394
413
|
git_branch=git_branch,
|
|
395
414
|
checksum=checksum,
|
|
@@ -415,29 +434,33 @@ class BackupManager:
|
|
|
415
434
|
def list_backups(
|
|
416
435
|
self,
|
|
417
436
|
domain: Optional[str] = None,
|
|
437
|
+
app_name: Optional[str] = None,
|
|
418
438
|
tags: Optional[List[str]] = None,
|
|
419
439
|
limit: Optional[int] = None,
|
|
420
440
|
) -> List[BackupMetadata]:
|
|
421
441
|
"""
|
|
422
442
|
List backups for an application or all applications.
|
|
423
|
-
|
|
443
|
+
|
|
424
444
|
Args:
|
|
425
445
|
domain: Filter by domain (None for all).
|
|
446
|
+
app_name: Filter by app name directly (alternative to domain).
|
|
426
447
|
tags: Filter by tags.
|
|
427
448
|
limit: Maximum number of backups to return.
|
|
428
|
-
|
|
449
|
+
|
|
429
450
|
Returns:
|
|
430
451
|
List of BackupMetadata objects.
|
|
431
452
|
"""
|
|
432
453
|
backups = []
|
|
433
|
-
|
|
454
|
+
|
|
434
455
|
if not self.backup_dir.exists():
|
|
435
456
|
return backups
|
|
436
|
-
|
|
457
|
+
|
|
437
458
|
# Determine which directories to scan
|
|
438
|
-
if
|
|
439
|
-
app_name = domain_to_app_name(domain)
|
|
459
|
+
if app_name:
|
|
440
460
|
dirs_to_scan = [self._get_app_backup_dir(app_name)]
|
|
461
|
+
elif domain:
|
|
462
|
+
resolved_app_name = domain_to_app_name(domain)
|
|
463
|
+
dirs_to_scan = [self._get_app_backup_dir(resolved_app_name)]
|
|
441
464
|
else:
|
|
442
465
|
dirs_to_scan = [d for d in self.backup_dir.iterdir() if d.is_dir()]
|
|
443
466
|
|
|
@@ -739,11 +762,11 @@ class BackupManager:
|
|
|
739
762
|
def _rotate_backups(self, app_name: str) -> None:
|
|
740
763
|
"""
|
|
741
764
|
Rotate old backups to keep only the most recent ones.
|
|
742
|
-
|
|
765
|
+
|
|
743
766
|
Args:
|
|
744
767
|
app_name: Application name.
|
|
745
768
|
"""
|
|
746
|
-
backups = self.list_backups(
|
|
769
|
+
backups = self.list_backups(app_name=app_name)
|
|
747
770
|
|
|
748
771
|
if len(backups) > self.max_backups:
|
|
749
772
|
# Delete oldest backups
|
|
@@ -753,7 +776,78 @@ class BackupManager:
|
|
|
753
776
|
self.logger.debug(f"Rotated old backup: {backup.id}")
|
|
754
777
|
except Exception as e:
|
|
755
778
|
self.logger.warning(f"Failed to rotate backup {backup.id}: {e}")
|
|
756
|
-
|
|
779
|
+
|
|
780
|
+
def _backup_databases(self, domain: str) -> List[Dict[str, Any]]:
|
|
781
|
+
"""
|
|
782
|
+
Backup databases associated with an application.
|
|
783
|
+
|
|
784
|
+
Args:
|
|
785
|
+
domain: Domain name of the application.
|
|
786
|
+
|
|
787
|
+
Returns:
|
|
788
|
+
List of database backup info dictionaries.
|
|
789
|
+
"""
|
|
790
|
+
database_backups = []
|
|
791
|
+
|
|
792
|
+
try:
|
|
793
|
+
from wasm.core.store import WASMStore
|
|
794
|
+
from wasm.managers.database.registry import DatabaseRegistry
|
|
795
|
+
except ImportError as e:
|
|
796
|
+
self.logger.warning(f"Database backup not available: {e}")
|
|
797
|
+
return database_backups
|
|
798
|
+
|
|
799
|
+
try:
|
|
800
|
+
store = WASMStore()
|
|
801
|
+
app = store.get_app_by_domain(domain)
|
|
802
|
+
|
|
803
|
+
if not app or not app.id:
|
|
804
|
+
self.logger.debug(f"No app found in store for {domain}")
|
|
805
|
+
return database_backups
|
|
806
|
+
|
|
807
|
+
databases = store.list_databases(app_id=app.id)
|
|
808
|
+
|
|
809
|
+
if not databases:
|
|
810
|
+
self.logger.debug(f"No databases associated with {domain}")
|
|
811
|
+
return database_backups
|
|
812
|
+
|
|
813
|
+
self.logger.info(f"Backing up {len(databases)} database(s) for {domain}")
|
|
814
|
+
|
|
815
|
+
for db in databases:
|
|
816
|
+
try:
|
|
817
|
+
manager = DatabaseRegistry.get(db.engine, verbose=self.verbose)
|
|
818
|
+
|
|
819
|
+
if not manager:
|
|
820
|
+
self.logger.warning(f"No manager available for {db.engine}")
|
|
821
|
+
continue
|
|
822
|
+
|
|
823
|
+
if not manager.is_installed():
|
|
824
|
+
self.logger.warning(
|
|
825
|
+
f"{db.engine} not installed, skipping {db.name}"
|
|
826
|
+
)
|
|
827
|
+
continue
|
|
828
|
+
|
|
829
|
+
backup_info = manager.backup(database=db.name, compress=True)
|
|
830
|
+
|
|
831
|
+
database_backups.append({
|
|
832
|
+
"engine": db.engine,
|
|
833
|
+
"name": db.name,
|
|
834
|
+
"backup_path": str(backup_info.path),
|
|
835
|
+
"size_bytes": backup_info.size,
|
|
836
|
+
"created": backup_info.created.isoformat(),
|
|
837
|
+
})
|
|
838
|
+
|
|
839
|
+
self.logger.info(f" Backed up {db.engine} database: {db.name}")
|
|
840
|
+
|
|
841
|
+
except Exception as e:
|
|
842
|
+
self.logger.warning(
|
|
843
|
+
f" Failed to backup {db.engine} '{db.name}': {e}"
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
except Exception as e:
|
|
847
|
+
self.logger.warning(f"Database backup failed: {e}")
|
|
848
|
+
|
|
849
|
+
return database_backups
|
|
850
|
+
|
|
757
851
|
def verify(self, backup_id: str) -> Dict[str, Any]:
|
|
758
852
|
"""
|
|
759
853
|
Verify a backup's integrity.
|
|
@@ -795,7 +889,7 @@ class BackupManager:
|
|
|
795
889
|
results["valid"] = False
|
|
796
890
|
results["errors"].append("Checksum mismatch")
|
|
797
891
|
else:
|
|
798
|
-
results["
|
|
892
|
+
results["checksum_ok"] = True
|
|
799
893
|
else:
|
|
800
894
|
results["warnings"].append("Could not verify checksum")
|
|
801
895
|
else:
|
|
@@ -807,7 +901,7 @@ class BackupManager:
|
|
|
807
901
|
results["valid"] = False
|
|
808
902
|
results["errors"].append("Archive is corrupted")
|
|
809
903
|
else:
|
|
810
|
-
results["
|
|
904
|
+
results["files_ok"] = True
|
|
811
905
|
results["file_count"] = len(result.stdout.strip().split("\n"))
|
|
812
906
|
|
|
813
907
|
return results
|