homeconsole-cli 0.0.1__tar.gz → 0.0.2__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.
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/PKG-INFO +30 -1
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/README.md +28 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/deploy.py +208 -11
- homeconsole_cli-0.0.2/hc/commands/env.py +588 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/install.py +5 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/remove.py +14 -0
- homeconsole_cli-0.0.2/hc/main.py +210 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/homeconsole_cli.egg-info/PKG-INFO +30 -1
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/homeconsole_cli.egg-info/SOURCES.txt +2 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/homeconsole_cli.egg-info/requires.txt +1 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/pyproject.toml +2 -1
- homeconsole_cli-0.0.2/tests/test_nav_cli.py +34 -0
- homeconsole_cli-0.0.1/hc/main.py +0 -105
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/__init__.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/api.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/capabilities.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/client.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/__init__.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/_client_helpers.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/_compose_helpers.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/auth.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/connect.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/core.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/logs.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/marketplace.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/module.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/ping.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/plugin.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/recovery/__init__.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/recovery/compose.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/recovery/config.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/recovery/core.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/recovery/db.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/recovery/mode.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/recovery/redis.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/recovery/ui.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/reset.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/search.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/secrets.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/setup.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/setup_wizard.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/status.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/commands/update.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/config.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/constants.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/core_ops.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/core_source.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/env_bootstrap.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/errors.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/marketplace_operation.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/native_core.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/repl.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/setup_runner.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/hc/shell.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/homeconsole_cli.egg-info/dependency_links.txt +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/homeconsole_cli.egg-info/entry_points.txt +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/homeconsole_cli.egg-info/top_level.txt +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/setup.cfg +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/tests/test_config_roundtrip.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/tests/test_core_ops.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/tests/test_env_bootstrap.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/tests/test_main_root_callback.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/tests/test_marketplace_operation_parse.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/tests/test_native_core_helpers.py +0 -0
- {homeconsole_cli-0.0.1 → homeconsole_cli-0.0.2}/tests/test_setup_runner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: homeconsole-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.2
|
|
4
4
|
Summary: HomeConsole CLI (hc) — управление платформой через HTTP API
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -10,6 +10,7 @@ Requires-Dist: httpx>=0.27
|
|
|
10
10
|
Requires-Dist: anyio>=4
|
|
11
11
|
Requires-Dist: prompt_toolkit>=3
|
|
12
12
|
Requires-Dist: tomlkit>=0.12
|
|
13
|
+
Requires-Dist: questionary>=2.0
|
|
13
14
|
Provides-Extra: dev
|
|
14
15
|
Requires-Dist: pytest>=8; extra == "dev"
|
|
15
16
|
|
|
@@ -31,6 +32,7 @@ pip install -e .
|
|
|
31
32
|
|
|
32
33
|
```bash
|
|
33
34
|
hc --help
|
|
35
|
+
hc nav
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
## Конфигурация
|
|
@@ -89,8 +91,35 @@ hc plugin list
|
|
|
89
91
|
- `hc deploy` (по умолчанию: build+push+rollout+wait) и `hc deploy ...` (тонкие подкоманды)
|
|
90
92
|
- `hc deploy platform` (локальный dev flow)
|
|
91
93
|
- `hc deploy platform --mode image --image ghcr.io/home-console/platform-home-console --tag latest` (image-only deploy)
|
|
94
|
+
- `hc deploy stack dev` / `hc deploy stack prod` (полный stack для dev/prod)
|
|
92
95
|
- `hc update core ...` (обновление core-runtime до нового image:tag)
|
|
93
96
|
- `hc shell`
|
|
97
|
+
- `hc nav [section ...]` (удобная навигация по командам: `hc nav deploy`, `hc nav deploy dev`)
|
|
98
|
+
|
|
99
|
+
### Навигация по командам (без запоминания)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
hc nav
|
|
103
|
+
hc nav deploy
|
|
104
|
+
hc nav deploy dev
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Подсказка: на любом уровне можно проваливаться дальше и смотреть доступные разделы.
|
|
108
|
+
|
|
109
|
+
### Быстрый dev up по профилям
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
hc deploy dev up --profile base
|
|
113
|
+
hc deploy dev up --profile platform
|
|
114
|
+
hc deploy dev up --profile cache
|
|
115
|
+
hc deploy dev up --profile db
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Профили:
|
|
119
|
+
- `base` = `core+proxy`
|
|
120
|
+
- `platform` = `core+proxy+platform`
|
|
121
|
+
- `cache` = `core+proxy+platform+cache`
|
|
122
|
+
- `db` = `core+proxy+platform+cache+db`
|
|
94
123
|
|
|
95
124
|
### Deploy “одной командой”
|
|
96
125
|
|
|
@@ -16,6 +16,7 @@ pip install -e .
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
hc --help
|
|
19
|
+
hc nav
|
|
19
20
|
```
|
|
20
21
|
|
|
21
22
|
## Конфигурация
|
|
@@ -74,8 +75,35 @@ hc plugin list
|
|
|
74
75
|
- `hc deploy` (по умолчанию: build+push+rollout+wait) и `hc deploy ...` (тонкие подкоманды)
|
|
75
76
|
- `hc deploy platform` (локальный dev flow)
|
|
76
77
|
- `hc deploy platform --mode image --image ghcr.io/home-console/platform-home-console --tag latest` (image-only deploy)
|
|
78
|
+
- `hc deploy stack dev` / `hc deploy stack prod` (полный stack для dev/prod)
|
|
77
79
|
- `hc update core ...` (обновление core-runtime до нового image:tag)
|
|
78
80
|
- `hc shell`
|
|
81
|
+
- `hc nav [section ...]` (удобная навигация по командам: `hc nav deploy`, `hc nav deploy dev`)
|
|
82
|
+
|
|
83
|
+
### Навигация по командам (без запоминания)
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
hc nav
|
|
87
|
+
hc nav deploy
|
|
88
|
+
hc nav deploy dev
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Подсказка: на любом уровне можно проваливаться дальше и смотреть доступные разделы.
|
|
92
|
+
|
|
93
|
+
### Быстрый dev up по профилям
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
hc deploy dev up --profile base
|
|
97
|
+
hc deploy dev up --profile platform
|
|
98
|
+
hc deploy dev up --profile cache
|
|
99
|
+
hc deploy dev up --profile db
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Профили:
|
|
103
|
+
- `base` = `core+proxy`
|
|
104
|
+
- `platform` = `core+proxy+platform`
|
|
105
|
+
- `cache` = `core+proxy+platform+cache`
|
|
106
|
+
- `db` = `core+proxy+platform+cache+db`
|
|
79
107
|
|
|
80
108
|
### Deploy “одной командой”
|
|
81
109
|
|
|
@@ -34,6 +34,22 @@ _MODE_HELP = (
|
|
|
34
34
|
"алиас image → dev-image)"
|
|
35
35
|
)
|
|
36
36
|
_DEPLOY_MODE_HELP = _MODE_HELP
|
|
37
|
+
_DEV_PROFILE_SERVICES: dict[str, list[str]] = {
|
|
38
|
+
"core+proxy": ["core-runtime", "caddy"],
|
|
39
|
+
"core+proxy+platform": ["core-runtime", "caddy", "platform-web"],
|
|
40
|
+
"core+proxy+platform+cache": ["core-runtime", "caddy", "platform-web", "redis"],
|
|
41
|
+
"core+proxy+platform+cache+db": ["core-runtime", "caddy", "platform-web", "redis", "postgres"],
|
|
42
|
+
}
|
|
43
|
+
_DEV_PROFILE_ALIASES: dict[str, str] = {
|
|
44
|
+
"base": "core+proxy",
|
|
45
|
+
"platform": "core+proxy+platform",
|
|
46
|
+
"cache": "core+proxy+platform+cache",
|
|
47
|
+
"db": "core+proxy+platform+cache+db",
|
|
48
|
+
}
|
|
49
|
+
_STACK_ENV_COMPOSE: dict[str, str] = {
|
|
50
|
+
"dev": "deploy/dev/docker-compose.image.yml",
|
|
51
|
+
"prod": "deploy/prod/docker-compose.image.yml",
|
|
52
|
+
}
|
|
37
53
|
|
|
38
54
|
|
|
39
55
|
def _find_repo_root() -> Path | None:
|
|
@@ -125,6 +141,34 @@ def _normalize_edge_health_path(p: str) -> str:
|
|
|
125
141
|
return v
|
|
126
142
|
|
|
127
143
|
|
|
144
|
+
def _resolve_dev_profile(profile: str) -> tuple[str, list[str]]:
|
|
145
|
+
raw = (profile or "").strip().lower()
|
|
146
|
+
resolved = _DEV_PROFILE_ALIASES.get(raw, raw)
|
|
147
|
+
if resolved not in _DEV_PROFILE_SERVICES:
|
|
148
|
+
choices = " | ".join(
|
|
149
|
+
[*sorted(_DEV_PROFILE_SERVICES.keys()), *sorted(_DEV_PROFILE_ALIASES.keys())]
|
|
150
|
+
)
|
|
151
|
+
raise InvalidModeError(
|
|
152
|
+
message=f"--profile {profile!r} недопустим.",
|
|
153
|
+
exit_code=2,
|
|
154
|
+
hint=f"Допустимые профили: {choices}",
|
|
155
|
+
)
|
|
156
|
+
return resolved, _DEV_PROFILE_SERVICES[resolved]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _resolve_stack_env(env: str | None, compose_rel: str) -> tuple[str, str]:
|
|
160
|
+
raw = (env or "prod").strip().lower()
|
|
161
|
+
if raw not in _STACK_ENV_COMPOSE:
|
|
162
|
+
raise InvalidModeError(
|
|
163
|
+
message=f"stack env {env!r} недопустим.",
|
|
164
|
+
exit_code=2,
|
|
165
|
+
hint="Допустимые: dev | prod",
|
|
166
|
+
)
|
|
167
|
+
default_compose = _STACK_ENV_COMPOSE[raw]
|
|
168
|
+
resolved_compose = compose_rel if compose_rel.strip() else default_compose
|
|
169
|
+
return raw, resolved_compose
|
|
170
|
+
|
|
171
|
+
|
|
128
172
|
def _wait_http_ok(
|
|
129
173
|
*,
|
|
130
174
|
url: str,
|
|
@@ -434,6 +478,7 @@ def register(app: typer.Typer) -> None:
|
|
|
434
478
|
),
|
|
435
479
|
quiet: bool = typer.Option(False, "--quiet", help="Минимальный вывод (только итог/ошибка)"),
|
|
436
480
|
json_out: bool = typer.Option(False, "--json", help="Машинный вывод в JSON"),
|
|
481
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Показать план деплоя без реального выполнения"),
|
|
437
482
|
) -> None:
|
|
438
483
|
"""
|
|
439
484
|
Если запущено как `hc deploy` без подкоманд — выполняет полный пайплайн:
|
|
@@ -469,6 +514,23 @@ def register(app: typer.Typer) -> None:
|
|
|
469
514
|
Panel.fit(f"{full}\nmode={resolved_mode}\ntarget={target}", title="hc deploy")
|
|
470
515
|
)
|
|
471
516
|
|
|
517
|
+
if dry_run:
|
|
518
|
+
from rich.table import Table as _Table
|
|
519
|
+
plan = _Table(title="Dry run — план деплоя")
|
|
520
|
+
plan.add_column("Шаг", style="bold")
|
|
521
|
+
plan.add_column("Действие")
|
|
522
|
+
if build:
|
|
523
|
+
plan.add_row("Build", f"docker build -t {full} <src>")
|
|
524
|
+
if push:
|
|
525
|
+
plan.add_row("Push", f"docker push {full}")
|
|
526
|
+
if rollout:
|
|
527
|
+
plan.add_row("Rollout", f"compose pull + up -d → {target}")
|
|
528
|
+
if rollout and wait:
|
|
529
|
+
plan.add_row("Wait healthy", f"{health_url} (timeout={timeout}s)")
|
|
530
|
+
console.print(plan)
|
|
531
|
+
console.print("[yellow]Dry run:[/yellow] деплой не выполнен")
|
|
532
|
+
raise typer.Exit(code=0)
|
|
533
|
+
|
|
472
534
|
if build:
|
|
473
535
|
src = _resolve_source(console)
|
|
474
536
|
t0 = _step_start(console, f"Build {full}", quiet=quiet or json_out)
|
|
@@ -591,6 +653,7 @@ def register(app: typer.Typer) -> None:
|
|
|
591
653
|
"--restart-remote/--no-restart-remote",
|
|
592
654
|
help="После remote sync перезапустить caddy (docker compose restart)",
|
|
593
655
|
),
|
|
656
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Показать план деплоя без реального выполнения"),
|
|
594
657
|
) -> None:
|
|
595
658
|
"""Локально собрать platform web или запустить platform image из GHCR."""
|
|
596
659
|
console = Console()
|
|
@@ -606,6 +669,31 @@ def register(app: typer.Typer) -> None:
|
|
|
606
669
|
else _resolve_platform_root(console)
|
|
607
670
|
)
|
|
608
671
|
|
|
672
|
+
if dry_run:
|
|
673
|
+
from rich.table import Table as _Table
|
|
674
|
+
plan = _Table(title="Dry run — план деплоя platform")
|
|
675
|
+
plan.add_column("Шаг", style="bold")
|
|
676
|
+
plan.add_column("Действие")
|
|
677
|
+
plan.add_row("Режим", resolved_mode)
|
|
678
|
+
plan.add_row("Platform root", str(platform_root))
|
|
679
|
+
if resolved_mode == "image":
|
|
680
|
+
plan.add_row("Image", f"{image}:{tag}")
|
|
681
|
+
if start:
|
|
682
|
+
plan.add_row("Start", "docker compose pull + up -d")
|
|
683
|
+
else:
|
|
684
|
+
if build:
|
|
685
|
+
plan.add_row("Build", "pnpm --filter=web build")
|
|
686
|
+
plan.add_row("Sync dist", str(platform_root / "apps" / "web" / "dist"))
|
|
687
|
+
if ssh:
|
|
688
|
+
plan.add_row("Remote sync", f"rsync dist → {ssh}:{path}/deploy/dev/frontend/")
|
|
689
|
+
if restart_remote:
|
|
690
|
+
plan.add_row("Restart caddy", f"docker compose restart caddy на {ssh}")
|
|
691
|
+
elif start:
|
|
692
|
+
plan.add_row("Start core", "bash deploy/dev/start.sh")
|
|
693
|
+
console.print(plan)
|
|
694
|
+
console.print("[yellow]Dry run:[/yellow] деплой не выполнен")
|
|
695
|
+
raise typer.Exit(code=0)
|
|
696
|
+
|
|
609
697
|
if resolved_mode == "image":
|
|
610
698
|
require_docker(console)
|
|
611
699
|
compose_file = platform_root / "docker-compose.image.yml"
|
|
@@ -702,6 +790,10 @@ def register(app: typer.Typer) -> None:
|
|
|
702
790
|
|
|
703
791
|
@deploy_app.command("stack")
|
|
704
792
|
def deploy_stack(
|
|
793
|
+
env: str = typer.Argument(
|
|
794
|
+
"prod",
|
|
795
|
+
help="Окружение стека: dev | prod (пример: `hc deploy stack dev`)",
|
|
796
|
+
),
|
|
705
797
|
core_image: str | None = typer.Option(
|
|
706
798
|
None, "--core-image", help="Core image без тега (по умолчанию из deploy.core_image)"
|
|
707
799
|
),
|
|
@@ -721,9 +813,9 @@ def register(app: typer.Typer) -> None:
|
|
|
721
813
|
help="remote path к core-runtime-service (по умолчанию из deploy.path)",
|
|
722
814
|
),
|
|
723
815
|
compose_rel: str = typer.Option(
|
|
724
|
-
"
|
|
816
|
+
"",
|
|
725
817
|
"--compose",
|
|
726
|
-
help="Путь к compose (
|
|
818
|
+
help="Путь к compose (если не указан: выбирается по env: dev/prod)",
|
|
727
819
|
),
|
|
728
820
|
core_runtime_url: str = typer.Option(
|
|
729
821
|
"http://core-runtime:8000",
|
|
@@ -788,6 +880,7 @@ def register(app: typer.Typer) -> None:
|
|
|
788
880
|
interval: float = typer.Option(1.0, "--interval", help="Интервал проверки healthy (сек)"),
|
|
789
881
|
quiet: bool = typer.Option(False, "--quiet", help="Минимальный вывод"),
|
|
790
882
|
json_out: bool = typer.Option(False, "--json", help="Машинный вывод в JSON"),
|
|
883
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Показать план деплоя без реального выполнения"),
|
|
791
884
|
) -> None:
|
|
792
885
|
"""
|
|
793
886
|
Деплой всего стека (core-runtime + platform-web) из image’ов.
|
|
@@ -800,6 +893,7 @@ def register(app: typer.Typer) -> None:
|
|
|
800
893
|
require_docker(console)
|
|
801
894
|
cfg = Config.load()
|
|
802
895
|
src = _resolve_source(console)
|
|
896
|
+
resolved_env, resolved_compose_rel = _resolve_stack_env(env, compose_rel)
|
|
803
897
|
|
|
804
898
|
resolved_core_image = (core_image or cfg.deploy.core_image).strip()
|
|
805
899
|
full_core = f"{resolved_core_image}:{core_tag}"
|
|
@@ -831,18 +925,41 @@ def register(app: typer.Typer) -> None:
|
|
|
831
925
|
if not quiet and not json_out:
|
|
832
926
|
console.print(
|
|
833
927
|
Panel.fit(
|
|
834
|
-
f"core={full_core}\nplatform={full_platform}\ncompose={
|
|
928
|
+
f"core={full_core}\nplatform={full_platform}\ncompose={resolved_compose_rel}\n"
|
|
929
|
+
f"env={resolved_env}\n"
|
|
835
930
|
f"target={'remote ' + resolved_ssh if resolved_ssh else 'local'}",
|
|
836
931
|
title="hc deploy stack",
|
|
837
932
|
)
|
|
838
933
|
)
|
|
839
934
|
|
|
935
|
+
if dry_run:
|
|
936
|
+
from rich.table import Table as _Table
|
|
937
|
+
target_str = f"remote {resolved_ssh}" if resolved_ssh else "local"
|
|
938
|
+
plan = _Table(title="Dry run — план деплоя stack")
|
|
939
|
+
plan.add_column("Шаг", style="bold")
|
|
940
|
+
plan.add_column("Действие")
|
|
941
|
+
plan.add_row("Target", target_str)
|
|
942
|
+
plan.add_row("Env", resolved_env)
|
|
943
|
+
plan.add_row("Core image", full_core)
|
|
944
|
+
plan.add_row("Platform image", full_platform)
|
|
945
|
+
plan.add_row("Compose", resolved_compose_rel)
|
|
946
|
+
if pull:
|
|
947
|
+
plan.add_row("Pull", "docker compose pull core-runtime platform-web edge")
|
|
948
|
+
plan.add_row("Rollout", "docker compose up -d")
|
|
949
|
+
if wait:
|
|
950
|
+
plan.add_row("Wait healthy", f"edge → core ({edge_health_path}, timeout={timeout}s)")
|
|
951
|
+
if (external_base_url or "").strip():
|
|
952
|
+
plan.add_row("External check", (external_base_url or "").strip())
|
|
953
|
+
console.print(plan)
|
|
954
|
+
console.print("[yellow]Dry run:[/yellow] деплой не выполнен")
|
|
955
|
+
raise typer.Exit(code=0)
|
|
956
|
+
|
|
840
957
|
if resolved_ssh:
|
|
841
958
|
if not resolved_path:
|
|
842
959
|
raise HcCliError(
|
|
843
960
|
message="для --ssh нужен --path",
|
|
844
961
|
exit_code=2,
|
|
845
|
-
hint="Пример: `hc deploy stack --ssh user@host --path /srv/core-runtime-service`",
|
|
962
|
+
hint=f"Пример: `hc deploy stack {resolved_env} --ssh user@host --path /srv/core-runtime-service`",
|
|
846
963
|
)
|
|
847
964
|
t0 = _step_start(
|
|
848
965
|
console, "Rollout stack remote (compose pull + up -d)", quiet=quiet or json_out
|
|
@@ -851,9 +968,9 @@ def register(app: typer.Typer) -> None:
|
|
|
851
968
|
remote = f"cd {shlex.quote(resolved_path)} && "
|
|
852
969
|
if pull:
|
|
853
970
|
remote += (
|
|
854
|
-
f"{exports} docker compose -f {shlex.quote(
|
|
971
|
+
f"{exports} docker compose -f {shlex.quote(resolved_compose_rel)} pull core-runtime platform-web edge && "
|
|
855
972
|
)
|
|
856
|
-
remote += f"{exports} docker compose -f {shlex.quote(
|
|
973
|
+
remote += f"{exports} docker compose -f {shlex.quote(resolved_compose_rel)} up -d"
|
|
857
974
|
_run(_ssh_cmd(resolved_ssh, remote))
|
|
858
975
|
dt = _step_ok(console, "Rollout", t0, quiet=quiet or json_out)
|
|
859
976
|
steps.append({"name": "rollout", "ok": True, "duration_s": dt})
|
|
@@ -868,9 +985,9 @@ def register(app: typer.Typer) -> None:
|
|
|
868
985
|
while time.time() < deadline:
|
|
869
986
|
chk = (
|
|
870
987
|
f"cd {shlex.quote(resolved_path)} && "
|
|
871
|
-
f"docker compose -f {shlex.quote(
|
|
988
|
+
f"docker compose -f {shlex.quote(resolved_compose_rel)} exec -T edge sh -lc "
|
|
872
989
|
f"{shlex.quote(f'curl -fsS http://127.0.0.1{edge_health_path} >/dev/null && echo edge_core_ok || echo edge_core_no')} && "
|
|
873
|
-
f"docker compose -f {shlex.quote(
|
|
990
|
+
f"docker compose -f {shlex.quote(resolved_compose_rel)} exec -T edge sh -lc "
|
|
874
991
|
f"{shlex.quote('wget -qO- http://127.0.0.1/ >/dev/null && echo edge_ui_ok || echo edge_ui_no')}"
|
|
875
992
|
)
|
|
876
993
|
p = subprocess.run(_ssh_cmd(resolved_ssh, chk), text=True, capture_output=True, check=False) # noqa: S603
|
|
@@ -892,12 +1009,12 @@ def register(app: typer.Typer) -> None:
|
|
|
892
1009
|
steps.append({"name": "wait", "ok": True, "duration_s": dt})
|
|
893
1010
|
else:
|
|
894
1011
|
# local: apply compose from repo source (core-runtime-service)
|
|
895
|
-
compose_file = src.path /
|
|
1012
|
+
compose_file = src.path / resolved_compose_rel
|
|
896
1013
|
if not compose_file.exists():
|
|
897
1014
|
raise HcCliError(
|
|
898
1015
|
message=f"Не найден compose файл: {compose_file}",
|
|
899
1016
|
exit_code=1,
|
|
900
|
-
hint="Проверь
|
|
1017
|
+
hint="Проверь `--compose` или используй `hc deploy stack dev|prod`.",
|
|
901
1018
|
)
|
|
902
1019
|
env = {**os.environ, **env_pairs}
|
|
903
1020
|
t0 = _step_start(console, "Rollout stack local (compose pull + up -d)", quiet=quiet or json_out)
|
|
@@ -1031,7 +1148,8 @@ def register(app: typer.Typer) -> None:
|
|
|
1031
1148
|
"command": "deploy.stack",
|
|
1032
1149
|
"core": full_core,
|
|
1033
1150
|
"platform": full_platform,
|
|
1034
|
-
"
|
|
1151
|
+
"env": resolved_env,
|
|
1152
|
+
"compose": resolved_compose_rel,
|
|
1035
1153
|
"target": f"remote {resolved_ssh}" if resolved_ssh else "local",
|
|
1036
1154
|
"wait": bool(wait),
|
|
1037
1155
|
"timeout_s": int(timeout),
|
|
@@ -1061,6 +1179,84 @@ def register(app: typer.Typer) -> None:
|
|
|
1061
1179
|
console.print(f"[dim]Подсказка:[/dim] {e.hint}")
|
|
1062
1180
|
raise typer.Exit(code=int(e.exit_code or 1))
|
|
1063
1181
|
|
|
1182
|
+
dev_app = typer.Typer(
|
|
1183
|
+
help="Dev stack shortcuts (up/down профили сервисов)",
|
|
1184
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
1185
|
+
no_args_is_help=True,
|
|
1186
|
+
)
|
|
1187
|
+
|
|
1188
|
+
@dev_app.command("up")
|
|
1189
|
+
def dev_up(
|
|
1190
|
+
profile: str = typer.Option(
|
|
1191
|
+
"core+proxy",
|
|
1192
|
+
"--profile",
|
|
1193
|
+
help=(
|
|
1194
|
+
"Профиль сервисов: core+proxy | core+proxy+platform | "
|
|
1195
|
+
"core+proxy+platform+cache | core+proxy+platform+cache+db "
|
|
1196
|
+
"(алиасы: base|platform|cache|db)"
|
|
1197
|
+
),
|
|
1198
|
+
),
|
|
1199
|
+
ssh: str | None = typer.Option(
|
|
1200
|
+
None, "--ssh", help="user@host для удалённого запуска (по умолчанию из deploy.ssh)"
|
|
1201
|
+
),
|
|
1202
|
+
path: str | None = typer.Option(
|
|
1203
|
+
None, "--path", help="remote path к core-runtime-service (для --ssh)"
|
|
1204
|
+
),
|
|
1205
|
+
pull: bool = typer.Option(
|
|
1206
|
+
False,
|
|
1207
|
+
"--pull/--no-pull",
|
|
1208
|
+
help="Перед up сделать docker compose pull для сервисов профиля",
|
|
1209
|
+
),
|
|
1210
|
+
) -> None:
|
|
1211
|
+
"""Поднять dev compose только с нужным набором сервисов. (Используй `hc env up` для hot-reload.)"""
|
|
1212
|
+
console = Console()
|
|
1213
|
+
try:
|
|
1214
|
+
require_docker(console)
|
|
1215
|
+
src = _resolve_source(console)
|
|
1216
|
+
cfg = Config.load()
|
|
1217
|
+
resolved_profile, services = _resolve_dev_profile(profile)
|
|
1218
|
+
resolved_ssh = ssh if ssh is not None else (cfg.deploy.ssh or None)
|
|
1219
|
+
resolved_path = path if path is not None else (cfg.deploy.path or None)
|
|
1220
|
+
|
|
1221
|
+
compose_rel = src.compose_rel("dev")
|
|
1222
|
+
service_list = " ".join(services)
|
|
1223
|
+
console.print(f"[cyan]→[/cyan] dev up profile=[bold]{resolved_profile}[/bold] services={service_list}")
|
|
1224
|
+
|
|
1225
|
+
if resolved_ssh:
|
|
1226
|
+
if not resolved_path:
|
|
1227
|
+
console.print("[red]Ошибка:[/red] для --ssh нужен --path")
|
|
1228
|
+
raise typer.Exit(code=2)
|
|
1229
|
+
pull_cmd = (
|
|
1230
|
+
f"docker compose -f {shlex.quote(compose_rel)} pull {' '.join(shlex.quote(s) for s in services)} && "
|
|
1231
|
+
if pull
|
|
1232
|
+
else ""
|
|
1233
|
+
)
|
|
1234
|
+
remote = (
|
|
1235
|
+
f"cd {shlex.quote(resolved_path)} && "
|
|
1236
|
+
f"{pull_cmd}"
|
|
1237
|
+
f"docker compose -f {shlex.quote(compose_rel)} up -d {' '.join(shlex.quote(s) for s in services)}"
|
|
1238
|
+
)
|
|
1239
|
+
_run(_ssh_cmd(resolved_ssh, remote))
|
|
1240
|
+
console.print("[green]✓[/green] remote dev profile started")
|
|
1241
|
+
return
|
|
1242
|
+
|
|
1243
|
+
project = compose_project_from_source(console, src, mode="dev")
|
|
1244
|
+
if pull:
|
|
1245
|
+
_run(
|
|
1246
|
+
["docker", "compose", "-f", str(project.compose_file), "pull", *services],
|
|
1247
|
+
cwd=project.cwd,
|
|
1248
|
+
)
|
|
1249
|
+
_run(
|
|
1250
|
+
["docker", "compose", "-f", str(project.compose_file), "up", "-d", *services],
|
|
1251
|
+
cwd=project.cwd,
|
|
1252
|
+
)
|
|
1253
|
+
console.print("[green]✓[/green] local dev profile started")
|
|
1254
|
+
except HcCliError as e:
|
|
1255
|
+
console.print(f"[red]Ошибка:[/red] {e.message}")
|
|
1256
|
+
if e.hint:
|
|
1257
|
+
console.print(f"[dim]Подсказка:[/dim] {e.hint}")
|
|
1258
|
+
raise typer.Exit(code=int(e.exit_code or 1))
|
|
1259
|
+
|
|
1064
1260
|
cfg_app = typer.Typer(
|
|
1065
1261
|
help="Дефолты для deploy (ssh/path/image/mode)",
|
|
1066
1262
|
context_settings={"help_option_names": ["-h", "--help"]},
|
|
@@ -1463,4 +1659,5 @@ def register(app: typer.Typer) -> None:
|
|
|
1463
1659
|
|
|
1464
1660
|
deploy_app.add_typer(cfg_app, name="config")
|
|
1465
1661
|
deploy_app.add_typer(core_app, name="core")
|
|
1662
|
+
deploy_app.add_typer(dev_app, name="dev")
|
|
1466
1663
|
app.add_typer(deploy_app, name="deploy")
|