wasm-cli 1.5.1__tar.gz → 1.6.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-1.5.1/src/wasm_cli.egg-info → wasm_cli-1.6.1}/PKG-INFO +1 -1
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/docs/superpowers/plans/2026-08-13-wasm-competitive-parity-roadmap.md +32 -0
- wasm_cli-1.6.1/docs/superpowers/specs/2026-08-14-wasm-panel-design-v2.md +127 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/pyproject.toml +1 -1
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/setup.py +1 -1
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/__init__.py +1 -1
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/env.py +4 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/base.py +36 -0
- wasm_cli-1.6.1/src/wasm/deployers/helpers/permissions.py +74 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/monorepo.py +16 -27
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/app.css +406 -214
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/panel.js +18 -15
- wasm_cli-1.6.1/src/wasm/web/static/vendor/fonts/geist-mono-latin-400-normal.woff2 +0 -0
- wasm_cli-1.6.1/src/wasm/web/static/vendor/fonts/geist-mono-latin-500-normal.woff2 +0 -0
- wasm_cli-1.6.1/src/wasm/web/static/vendor/fonts/geist-sans-latin-400-normal.woff2 +0 -0
- wasm_cli-1.6.1/src/wasm/web/static/vendor/fonts/geist-sans-latin-500-normal.woff2 +0 -0
- wasm_cli-1.6.1/src/wasm/web/static/vendor/fonts/geist-sans-latin-600-normal.woff2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/_macros.html +7 -13
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/db_engine.html +2 -2
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/deploy_rows.html +8 -5
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/settings_section.html +1 -1
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/activity.html +2 -2
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/app.html +10 -10
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/backups.html +1 -1
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/certificates.html +1 -1
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/dashboard.html +8 -6
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/databases.html +3 -3
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/deployment_detail.html +2 -2
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/settings.html +4 -4
- {wasm_cli-1.5.1 → wasm_cli-1.6.1/src/wasm_cli.egg-info}/PKG-INFO +1 -1
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm_cli.egg-info/SOURCES.txt +7 -7
- wasm_cli-1.5.1/src/wasm/web/static/vendor/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- wasm_cli-1.5.1/src/wasm/web/static/vendor/fonts/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- wasm_cli-1.5.1/src/wasm/web/static/vendor/fonts/ibm-plex-sans-condensed-latin-500-normal.woff2 +0 -0
- wasm_cli-1.5.1/src/wasm/web/static/vendor/fonts/ibm-plex-sans-condensed-latin-600-normal.woff2 +0 -0
- wasm_cli-1.5.1/src/wasm/web/static/vendor/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- wasm_cli-1.5.1/src/wasm/web/static/vendor/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- wasm_cli-1.5.1/src/wasm/web/static/vendor/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/LICENSE +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/MANIFEST.in +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/README.md +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/docs/MONITOR.md +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/docs/OBS_SETUP.md +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/docs/assets/logo.png +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/docs/assets/logo_bg.png +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/docs/superpowers/specs/2026-08-12-wasm-panel-design-direction.md +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/docs/superpowers/specs/2026-08-12-wasm-v1-refactor-design.md +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/docs/superpowers/specs/2026-08-13-wasm-competitive-parity-design.md +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/man/wasm.1 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/setup.cfg +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/__main__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/app.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/backup.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/cert.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/config.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/db.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/health.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/monitor.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/service.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/setup.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/site.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/store.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/version.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/web.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/commands/webapp.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/interactive.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/panel_links.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/cli/prompts.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/completions/README.md +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/completions/_wasm +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/completions/wasm.bash +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/completions/wasm.fish +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/app_state.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/config.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/dependencies.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/exceptions.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/fs.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/logger.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/net.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/notifier.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/runner.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/store.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/totp.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/update_checker.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/core/utils.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/auto.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/docker_compose.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/env_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/health.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/nginx_config.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/package_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/path_resolver.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/preflight.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/prisma.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/registration.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/summary.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/turbo.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/helpers/workspace.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/interface.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/nextjs.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/nodejs.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/pipeline.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/python.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/recorder.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/registry.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/static.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/deployers/vite.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/main.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/apache_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/backup_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/backup_scheduler.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/base_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/cert_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/cron_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/database/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/database/base.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/database/mongodb.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/database/mysql.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/database/postgres.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/database/redis.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/database/registry.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/nginx_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/service_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/source_manager.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/managers/webserver.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/monitor/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/monitor/email_notifier.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/monitor/metrics.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/monitor/models.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/monitor/observation_store.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/monitor/process_monitor.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/monitor/signals.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/monitor/timeseries.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/apache/proxy.conf.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/apache/static.conf.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/nginx/advanced.conf.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/nginx/monorepo.conf.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/nginx/proxy.conf.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/nginx/static.conf.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/systemd/_escape.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/systemd/app.service.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/systemd/backup-service.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/systemd/backup-timer.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/systemd/cron-service.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/systemd/cron-timer.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/templates/systemd/docker-compose.service.j2 +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/validators/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/validators/domain.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/validators/environment.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/validators/names.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/validators/port.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/validators/source.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/validators/ssh.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/apps.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/auth.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/backup_schedules.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/backups.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/certs.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/config.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/cron.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/databases.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/deps.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/hooks.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/jobs.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/metrics.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/monitor.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/router.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/services.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/sites.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/api/system.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/auth.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/events.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/jobs.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/metrics_collector.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/pydantic_compat.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/server.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/logo.png +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/logo.webp +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/vendor/htmx.min.js +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/vendor/qrcodegen.js +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/vendor/sse.js +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/vendor/uplot.min.css +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/vendor/uplot.min.js +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/vendor/xterm.css +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/static/vendor/xterm.js +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/base.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/api_tokens.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/app_charts.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/app_env.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/backup_schedule_section.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/cron_runs.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/cron_section.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/db_console.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/deploy_form_check.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/deploy_form_options.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/deploy_recent.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/deploy_rollback.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/infra_service_editor.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/infra_service_form.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/infra_site_editor.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/infra_site_form.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/machine.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/notif_channels.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/sessions.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/settings_editor.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/settings_form_backups.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/settings_form_notifications.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/settings_form_paths.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/settings_form_ssl.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/settings_form_web.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/settings_form_webserver.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/totp.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/fragments/webhook_section.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/login.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/cron.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/deploy.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/deployments.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/failure.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/missing.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/resources.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/service_editor.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/service_new.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/site_editor.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/templates/pages/site_new.html +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/app_charts.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/backup_schedules.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/context.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/cron.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/databases.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/deploy_form.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/deployments.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/infrastructure.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/machine.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/rendering.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/resources.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/router.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/views/settings_editor.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/websockets/__init__.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm/web/websockets/router.py +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm_cli.egg-info/dependency_links.txt +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm_cli.egg-info/entry_points.txt +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm_cli.egg-info/requires.txt +0 -0
- {wasm_cli-1.5.1 → wasm_cli-1.6.1}/src/wasm_cli.egg-info/top_level.txt +0 -0
|
@@ -516,3 +516,35 @@ La API entera ya existe (`api/databases.py`). UI con tabs por engine:
|
|
|
516
516
|
web, preview deploys, multiusuario, one-click, multi-server) sigue vigente.
|
|
517
517
|
Decisión de publicación: tags v1.3.0/v1.4.0/v1.5.0 sin crear; cuando el dueño quiera
|
|
518
518
|
publicar, seguir el proceso del spec de release (tag + push dispara PyPI/OBS).
|
|
519
|
+
- 2026-08-14 (publicación): **v1.5.0 publicada** (solo ese tag; 1.3/1.4 quedan como
|
|
520
|
+
hitos internos — los números publicados no necesitan ser contiguos y esos árboles
|
|
521
|
+
tenían man pages sin regenerar). **Rota en Ubuntu 24.04 al arrancar el panel**:
|
|
522
|
+
noble empaqueta pydantic v1 y tres ficheros nuevos usaban `field_validator` (v2).
|
|
523
|
+
**v1.5.1 publicada con el fix**: `web/pydantic_compat.py` (puente v1/v2), guardias
|
|
524
|
+
AST en test_architecture contra API exclusiva de una major de pydantic, job CI
|
|
525
|
+
`pydantic-v1` (tests web con 1.10 pineado) y paso de release que importa la capa web
|
|
526
|
+
dentro del contenedor deb/rpm con las deps de la distro. Lección permanente: los
|
|
527
|
+
tests con deps de pip NO validan las deps de distro; toda API nueva de una librería
|
|
528
|
+
empaquetada debe contrastarse con la versión de noble/bookworm/Fedora (tabla en
|
|
529
|
+
CLAUDE.md) y el gate del contenedor es el chokepoint que lo garantiza.
|
|
530
|
+
- 2026-08-14 (rediseño): el dueño rechazó la estética austera de la v1 tras usarla
|
|
531
|
+
("pobre, asimétrica, componentes horribles"). Nueva dirección en
|
|
532
|
+
`docs/superpowers/specs/2026-08-14-wasm-panel-design-v2.md` (Geist, acento índigo,
|
|
533
|
+
elevación, esqueleto de página común, pills en vez de raíl) implementada entera y
|
|
534
|
+
**publicada como v1.6.0** (Release y CI verdes). Verificación visual con capturas
|
|
535
|
+
reales (scripts/panel_screenshots.py, 16 pantallas ambos temas + móvil) revisadas
|
|
536
|
+
en dos rondas por el implementador y una por el orquestador. El spec de diseño de
|
|
537
|
+
2026-08-12 queda superado en lo estético; sus restricciones técnicas (CSP, sin
|
|
538
|
+
build, vendor con checksum, color de estado + acento) siguen vigentes.
|
|
539
|
+
- 2026-08-27 (v1.6.1): **EACCES al arrancar toda app no-monorepo**: el deploy corre
|
|
540
|
+
como root y dejaba `/var/www/apps/{app}` propiedad de root mientras la unidad corre
|
|
541
|
+
como `service_user` (www-data); pnpm escribe un temporal `_tmp_*` en el cwd en cada
|
|
542
|
+
`pnpm run`, así que el servicio moría en bucle de reinicios (visto en producción en
|
|
543
|
+
bodas.arennalabs.com). El chown existía solo en el deployer monorepo — regla 3 rota:
|
|
544
|
+
la implementación nunca llegó al pipeline común. Fix en v1.6.1:
|
|
545
|
+
`deployers/helpers/permissions.py::hand_over_tree` como implementación única, paso
|
|
546
|
+
"Setting permissions" en el pipeline base tras el build (afecta a nextjs, nodejs,
|
|
547
|
+
python y vite; static define su propia lista y docker-compose no usa el pipeline —
|
|
548
|
+
a propósito: un `chown -R` sobre bind mounts rompería los datos de contenedores), y
|
|
549
|
+
los `.env` vuelven a 0600 tras el `chmod -R`. Remediación en servidores ya
|
|
550
|
+
desplegados: `chown -R www-data:www-data /var/www/apps/{app}` y restart del unit.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Panel WASM - Dirección de diseño v2
|
|
2
|
+
|
|
3
|
+
Fecha: 2026-08-14
|
|
4
|
+
Sustituye a: `2026-08-12-wasm-panel-design-direction.md` (el dueño rechazó esa dirección
|
|
5
|
+
tras verla en producción: "pobre, asimétrica, componentes horribles"). Las restricciones
|
|
6
|
+
técnicas de aquel documento siguen vigentes; la estética no.
|
|
7
|
+
|
|
8
|
+
## El veredicto que origina esto
|
|
9
|
+
|
|
10
|
+
La v1 apostó por un instrumento austero: todo acromático, cero acento, tipografía
|
|
11
|
+
condensada, raíles de 3px. Intelectualmente coherente; en pantalla lee como wireframe.
|
|
12
|
+
El dueño construye una alternativa a Vercel/Coolify y exige que el panel aguante el
|
|
13
|
+
careo visual con esos productos. Esa es la nueva vara.
|
|
14
|
+
|
|
15
|
+
## Dirección
|
|
16
|
+
|
|
17
|
+
**SaaS profesional de 2026**: neutros fríos limpios, un acento índigo con propósito,
|
|
18
|
+
tipografía Geist, tarjetas con elevación sutil, simetría por esqueleto común. El modo
|
|
19
|
+
oscuro fija la identidad (es donde vive un operador); el claro es igual de completo.
|
|
20
|
+
|
|
21
|
+
Lo que se conserva de la v1 porque es correcto: **el color semántico de estado**
|
|
22
|
+
(verde=corriendo, ámbar=en curso, rojo=fallo, gris=parado) como única fuente de color
|
|
23
|
+
además del acento; los errores del sistema verbatim en mono; los vacíos como invitación;
|
|
24
|
+
todo vendorizado, cero CDN, cero build step, CSP intacta.
|
|
25
|
+
|
|
26
|
+
## Tokens
|
|
27
|
+
|
|
28
|
+
### Color
|
|
29
|
+
|
|
30
|
+
El acento vive en el espacio azul-violeta, que el sistema de estados deja libre.
|
|
31
|
+
|
|
32
|
+
| Rol | Claro | Oscuro |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| Fondo | `#FAFAFA` | `#0B0C0E` |
|
|
35
|
+
| Fondo hundido | `#F4F4F5` | `#08090A` |
|
|
36
|
+
| Superficie | `#FFFFFF` | `#141519` |
|
|
37
|
+
| Superficie elevada | `#FFFFFF` + sombra | `#1A1C21` |
|
|
38
|
+
| Borde | `#E4E4E7` | `#26282E` |
|
|
39
|
+
| Borde fuerte | `#D4D4D8` | `#363940` |
|
|
40
|
+
| Texto | `#18181B` | `#EDEEF0` |
|
|
41
|
+
| Texto atenuado | `#52525B` | `#A1A5AD` |
|
|
42
|
+
| Texto débil | `#71717A` | `#7D828B` |
|
|
43
|
+
| **Acento** | `#5B5BD6` | `#7C7CF0` |
|
|
44
|
+
| Acento hover | `#4F4FC4` | `#8F8FF5` |
|
|
45
|
+
| Acento tenue (fondos) | `#EEEEFB` | `#232345` |
|
|
46
|
+
| Activo | `#16A34A` | `#4ADE80` |
|
|
47
|
+
| Activo fondo | `#EAF7EF` | `#132E1D` |
|
|
48
|
+
| Fallo | `#DC2626` | `#F87171` |
|
|
49
|
+
| Fallo fondo | `#FDECEC` | `#341518` |
|
|
50
|
+
| En curso | `#D97706` | `#FBBF24` |
|
|
51
|
+
| En curso fondo | `#FDF3E4` | `#33270F` |
|
|
52
|
+
| Parado | `#71717A` | `#8B8F98` |
|
|
53
|
+
| Parado fondo | `#F1F1F2` | `#222329` |
|
|
54
|
+
|
|
55
|
+
Todos los pares texto/fondo y estado/fondo cumplen AA; el contrato de estilo lo
|
|
56
|
+
verifica computándolo, así que cualquier ajuste posterior queda vigilado.
|
|
57
|
+
|
|
58
|
+
### Tipografía: Geist, dos voces
|
|
59
|
+
|
|
60
|
+
- **Geist Sans** (400/500/600) para todo el UI. Tracking ligeramente negativo en
|
|
61
|
+
títulos (−0.02em), pesos 600 para títulos, 500 para labels/botones, 400 para prosa.
|
|
62
|
+
- **Geist Mono** (400/500) para todo valor del sistema: rutas, units, puertos, logs,
|
|
63
|
+
timestamps, commits. Cifras tabulares en todo valor que cambie.
|
|
64
|
+
- Se retira IBM Plex entero, incluida la condensada (era la principal fuente de la
|
|
65
|
+
sensación "apretada"). Vendor: 5 woff2 de fontsource con checksum.
|
|
66
|
+
|
|
67
|
+
### Espaciado, radios, elevación
|
|
68
|
+
|
|
69
|
+
- Escala: 4/8/12/16/24/32/48/64. Radio: 6px (controles), 10px (tarjetas), 999px
|
|
70
|
+
(pills). Contenedor de página: max-width 1200px centrado, gutter 24px (16px móvil).
|
|
71
|
+
- Sombras (solo claro; el oscuro eleva por color de superficie):
|
|
72
|
+
`--shadow-xs: 0 1px 2px rgb(0 0 0 / 5%)`,
|
|
73
|
+
`--shadow-sm: 0 1px 3px rgb(0 0 0 / 7%), 0 1px 2px rgb(0 0 0 / 5%)`.
|
|
74
|
+
- Transiciones 140ms ease-out en hover/focus; `prefers-reduced-motion` respetado.
|
|
75
|
+
|
|
76
|
+
## Esqueleto común (la cura de la asimetría)
|
|
77
|
+
|
|
78
|
+
Toda página usa el mismo armazón, sin excepciones:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
┌─ cockpit bar (sticky, blur) ─────────────────────────────┐
|
|
82
|
+
├─ sidebar ─┬─ page ────────────────────────────────────────┤
|
|
83
|
+
│ │ header: h1 + descripción [acción 1ª] │
|
|
84
|
+
│ │ ── secciones con el mismo gap vertical ── │
|
|
85
|
+
│ │ card / tabla / form │
|
|
86
|
+
└───────────┴───────────────────────────────────────────────┘
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- Cabecera de página: título (xl/600) + una línea de descripción atenuada a la
|
|
90
|
+
izquierda; la acción primaria de la página a la derecha. Siempre.
|
|
91
|
+
- Secciones: título de sección (base/600) + gap uniforme (32px entre secciones,
|
|
92
|
+
16px título→contenido). Nada de espaciados ad-hoc por plantilla.
|
|
93
|
+
- Tarjetas: superficie + borde + radio 10 + padding 20/24; en claro además sombra xs.
|
|
94
|
+
- Tablas: cabecera en texto atenuado 500 (12px, sin condensada, sin mayúsculas
|
|
95
|
+
forzadas salvo eyebrows), filas 44px, hover superficie, divisores internos solo
|
|
96
|
+
horizontales, celdas numéricas alineadas a la derecha en tabular.
|
|
97
|
+
- Estado: pill con punto de color (`● Running`) sobre fondo tenue del estado.
|
|
98
|
+
El raíl de 3px desaparece.
|
|
99
|
+
|
|
100
|
+
## Componentes
|
|
101
|
+
|
|
102
|
+
- **Botones**: primario (acento, texto blanco, sombra xs), secundario (superficie +
|
|
103
|
+
borde), fantasma (sin borde, hover superficie), peligro (rojo solo como primario de
|
|
104
|
+
confirmación destructiva). Alturas 32/36, padding 12/16, radio 6, peso 500.
|
|
105
|
+
- **Formularios**: campos 36px, borde 1px, focus ring de 3px en acento tenue + borde
|
|
106
|
+
acento; labels 500 encima; hints atenuados debajo; errores en rojo con el mensaje
|
|
107
|
+
del sistema verbatim en mono dentro de un bloque, no inline chillón.
|
|
108
|
+
- **Cockpit bar** (la firma): sticky, fondo translúcido con `backdrop-filter: blur`,
|
|
109
|
+
hostname en mono + pill de estado global + sparkline de carga + memoria/disco como
|
|
110
|
+
micro-medidores + accesos (⌘K, tema). Es la única pieza con licencia para ser
|
|
111
|
+
distintiva; todo lo demás, disciplinado.
|
|
112
|
+
- **Sidebar**: grupos con eyebrow (11px/500/atenuado), item activo con fondo acento
|
|
113
|
+
tenue + texto acento, counts como pills mínimas.
|
|
114
|
+
- **Gráficas uPlot**: línea acento para métricas neutras (CPU/red), estados solo
|
|
115
|
+
cuando significan estado; ejes en Geist Mono 10px; marcas de deploy como líneas
|
|
116
|
+
discontinuas de estado.
|
|
117
|
+
- **Drawer de logs**: cabecera con pestaña, fondo hundido, xterm con el tema nuevo.
|
|
118
|
+
- **Login**: tarjeta centrada, logo, un campo, botón primario — la primera impresión
|
|
119
|
+
del producto, tratada como tal.
|
|
120
|
+
|
|
121
|
+
## Ejecución
|
|
122
|
+
|
|
123
|
+
Los nombres de clases y de variables CSS se conservan (los contratos de estilo y el
|
|
124
|
+
cliente vigilan por nombre); cambian los valores y el CSS de componentes. Los templates
|
|
125
|
+
solo se tocan para imponer el esqueleto común (cabeceras de página, secciones). Se
|
|
126
|
+
verifica con capturas reales en ambos temas revisadas por el implementador, más
|
|
127
|
+
`test_web_style_contract` (contraste AA recalculado) y `panel_browser_check` (22/22).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "wasm-cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.6.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="1.
|
|
12
|
+
version="1.6.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",
|
|
@@ -15,7 +15,7 @@ from importlib.metadata import version as _installed_version
|
|
|
15
15
|
#: as an OBS build directory. The single source of truth is the ``version``
|
|
16
16
|
#: field of pyproject.toml; ``scripts/release.py`` keeps this literal and the
|
|
17
17
|
#: distribution packaging files in step with it.
|
|
18
|
-
_FALLBACK_VERSION = "1.
|
|
18
|
+
_FALLBACK_VERSION = "1.6.1"
|
|
19
19
|
|
|
20
20
|
try:
|
|
21
21
|
__version__ = _installed_version("wasm-cli")
|
|
@@ -303,6 +303,10 @@ def configure(state: Context, domain: str) -> None:
|
|
|
303
303
|
@click.option(
|
|
304
304
|
"--unmask",
|
|
305
305
|
is_flag=True,
|
|
306
|
+
# Explicit, not implied: click 8.5 reports an unset flag default as a
|
|
307
|
+
# sentinel, and the guarantee that secrets stay masked must not depend on
|
|
308
|
+
# which click a distribution ships.
|
|
309
|
+
default=False,
|
|
306
310
|
help="Print secret values in clear instead of hiding them.",
|
|
307
311
|
)
|
|
308
312
|
@pass_context
|
|
@@ -42,6 +42,7 @@ from wasm.deployers.helpers import (
|
|
|
42
42
|
)
|
|
43
43
|
from wasm.deployers.helpers.health import failure_output, wait_until_healthy
|
|
44
44
|
from wasm.deployers.helpers.nginx_config import NginxAdvancedConfig
|
|
45
|
+
from wasm.deployers.helpers.permissions import hand_over_tree
|
|
45
46
|
from wasm.deployers.helpers.registration import StoreRegistrar
|
|
46
47
|
from wasm.deployers.helpers.summary import print_deployment_summary
|
|
47
48
|
from wasm.deployers.interface import AppDeployer, StepReporter, UpdateResult
|
|
@@ -1163,6 +1164,11 @@ class BaseDeployer(AppDeployer):
|
|
|
1163
1164
|
icon=Icons.BUILD,
|
|
1164
1165
|
run=self.build,
|
|
1165
1166
|
),
|
|
1167
|
+
DeployStep(
|
|
1168
|
+
title="Setting permissions",
|
|
1169
|
+
icon=Icons.LOCK,
|
|
1170
|
+
run=self._set_permissions,
|
|
1171
|
+
),
|
|
1166
1172
|
DeployStep(
|
|
1167
1173
|
title="Creating site configuration",
|
|
1168
1174
|
icon=Icons.GLOBE,
|
|
@@ -1235,6 +1241,36 @@ class BaseDeployer(AppDeployer):
|
|
|
1235
1241
|
if self.app_name:
|
|
1236
1242
|
self.store.update_service_status(self.app_name, active=True, enabled=True)
|
|
1237
1243
|
|
|
1244
|
+
def _set_permissions(self) -> None:
|
|
1245
|
+
"""
|
|
1246
|
+
Hand the deployed tree over to the account the service runs as.
|
|
1247
|
+
|
|
1248
|
+
The deployment ran as root, so without this step the service user
|
|
1249
|
+
cannot write into its own app directory and the unit fails at start
|
|
1250
|
+
with EACCES.
|
|
1251
|
+
"""
|
|
1252
|
+
hand_over_tree(
|
|
1253
|
+
self.app_path,
|
|
1254
|
+
user=self.config.service_user,
|
|
1255
|
+
group=self.config.service_group,
|
|
1256
|
+
runner=self.runner,
|
|
1257
|
+
fs=self.fs,
|
|
1258
|
+
logger=self.logger,
|
|
1259
|
+
env_files=self._env_files(),
|
|
1260
|
+
)
|
|
1261
|
+
|
|
1262
|
+
def _env_files(self) -> list[Path]:
|
|
1263
|
+
"""
|
|
1264
|
+
List the environment files whose modes must survive ``_set_permissions``.
|
|
1265
|
+
|
|
1266
|
+
Returns:
|
|
1267
|
+
The dotenv files at the application root. Subclasses that write
|
|
1268
|
+
environment files elsewhere override this.
|
|
1269
|
+
"""
|
|
1270
|
+
if not self.app_path.is_dir():
|
|
1271
|
+
return []
|
|
1272
|
+
return sorted(path for path in self.app_path.glob(".env*") if path.is_file())
|
|
1273
|
+
|
|
1238
1274
|
def _recorder(self) -> DeploymentRecorder:
|
|
1239
1275
|
"""
|
|
1240
1276
|
Build the recorder :meth:`deploy` and :meth:`update` write history with.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Copyright (c) 2024-2026 Yago Lopez Prado
|
|
2
|
+
# Licensed under WASM-NCSAL 1.0 (Commercial use prohibited)
|
|
3
|
+
# https://github.com/Perkybeet/wasm/blob/main/LICENSE
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Ownership hand-over for deployed trees.
|
|
7
|
+
|
|
8
|
+
The whole deployment runs as root, but the generated unit runs as the
|
|
9
|
+
configured service user. That user must be able to write into the app
|
|
10
|
+
directory: pnpm creates a temporary file in the project root on every
|
|
11
|
+
``pnpm run``, and Next.js writes into ``.next`` at runtime, so a root-owned
|
|
12
|
+
tree fails at start with EACCES and systemd restarts it forever.
|
|
13
|
+
|
|
14
|
+
This is the one implementation both the base pipeline and the monorepo
|
|
15
|
+
deployer use; it used to exist only in the monorepo deployer, which is why
|
|
16
|
+
every other app type shipped with the bug above.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from collections.abc import Iterable
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from wasm.core.fs import SECRET_MODE, FileSystem
|
|
25
|
+
from wasm.core.logger import Logger
|
|
26
|
+
from wasm.core.runner import CommandRunner
|
|
27
|
+
|
|
28
|
+
#: Deadline for the recursive chown and chmod over a deployed tree.
|
|
29
|
+
_PERMISSIONS_TIMEOUT = 60
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def hand_over_tree(
|
|
33
|
+
app_path: Path,
|
|
34
|
+
*,
|
|
35
|
+
user: str,
|
|
36
|
+
group: str,
|
|
37
|
+
runner: CommandRunner,
|
|
38
|
+
fs: FileSystem,
|
|
39
|
+
logger: Logger,
|
|
40
|
+
env_files: Iterable[Path] = (),
|
|
41
|
+
) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Make ``user`` the owner of the deployed tree, keeping secrets private.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
app_path: Root of the deployed application.
|
|
47
|
+
user: Account the service runs as.
|
|
48
|
+
group: Group the service runs as.
|
|
49
|
+
runner: Runner the chown and chmod execute through.
|
|
50
|
+
fs: Filesystem used to restore the mode of the secret files.
|
|
51
|
+
logger: Logger for the non-fatal failures.
|
|
52
|
+
env_files: Files holding secrets whose mode must come back to 0600
|
|
53
|
+
after the recursive chmod.
|
|
54
|
+
"""
|
|
55
|
+
result = runner.run(
|
|
56
|
+
["chown", "-R", f"{user}:{group}", str(app_path)],
|
|
57
|
+
timeout=_PERMISSIONS_TIMEOUT,
|
|
58
|
+
)
|
|
59
|
+
if not result.success:
|
|
60
|
+
logger.debug(f"chown failed: {result.stderr}")
|
|
61
|
+
|
|
62
|
+
# Directories need the execute bit and the built assets must stay readable
|
|
63
|
+
# by the web server, whatever umask the fetch and the build left behind.
|
|
64
|
+
runner.run(
|
|
65
|
+
["chmod", "-R", "u+rwX,g+rX,o+rX", str(app_path)],
|
|
66
|
+
timeout=_PERMISSIONS_TIMEOUT,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# That -R also put o+r on the .env files, which hold the secrets this
|
|
70
|
+
# deployment was given. The chown above has just made the service account
|
|
71
|
+
# their owner, so 0600 is readable by the application and by nobody else.
|
|
72
|
+
for env_file in env_files:
|
|
73
|
+
if env_file.exists():
|
|
74
|
+
fs.chmod(env_file, SECRET_MODE)
|
|
@@ -49,6 +49,7 @@ from wasm.deployers.helpers import (
|
|
|
49
49
|
TurboHelper,
|
|
50
50
|
WorkspaceHelper,
|
|
51
51
|
)
|
|
52
|
+
from wasm.deployers.helpers.permissions import hand_over_tree
|
|
52
53
|
from wasm.deployers.interface import AppDeployer, StepReporter, UpdateResult
|
|
53
54
|
from wasm.deployers.registry import DeployerRegistry
|
|
54
55
|
from wasm.managers.apache_manager import ApacheManager
|
|
@@ -948,34 +949,22 @@ class MonorepoDeployer(AppDeployer):
|
|
|
948
949
|
return False
|
|
949
950
|
|
|
950
951
|
def _set_permissions(self) -> None:
|
|
951
|
-
"""
|
|
952
|
-
|
|
953
|
-
service_group = self.config.service_group
|
|
954
|
-
|
|
955
|
-
try:
|
|
956
|
-
# Change ownership recursively
|
|
957
|
-
result = self.runner.run(
|
|
958
|
-
["chown", "-R", f"{service_user}:{service_group}", str(self.app_path)],
|
|
959
|
-
timeout=60,
|
|
960
|
-
)
|
|
961
|
-
if not result.success:
|
|
962
|
-
self.logger.debug(f"chown failed: {result.stderr}")
|
|
952
|
+
"""
|
|
953
|
+
Hand the deployed tree over to the account the services run as.
|
|
963
954
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
self.
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
if env_file.exists():
|
|
978
|
-
self.fs.chmod(env_file, SECRET_MODE)
|
|
955
|
+
The deployment ran as root, so without this step the workspace units
|
|
956
|
+
cannot write into their own app directory and fail at start with
|
|
957
|
+
EACCES.
|
|
958
|
+
"""
|
|
959
|
+
hand_over_tree(
|
|
960
|
+
self.app_path,
|
|
961
|
+
user=self.config.service_user,
|
|
962
|
+
group=self.config.service_group,
|
|
963
|
+
runner=self.runner,
|
|
964
|
+
fs=self.fs,
|
|
965
|
+
logger=self.logger,
|
|
966
|
+
env_files=self._env_files(),
|
|
967
|
+
)
|
|
979
968
|
|
|
980
969
|
def _env_files(self) -> list[Path]:
|
|
981
970
|
"""
|