dsh-cloud 0.2.0__tar.gz → 0.2.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.
Files changed (24) hide show
  1. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/PKG-INFO +1 -1
  2. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/pyproject.toml +1 -1
  3. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/cli.py +44 -9
  4. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/release-manifest.json +3 -3
  5. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/i18n/en.json +467 -462
  6. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/i18n/zh.json +467 -462
  7. dsh_cloud-0.2.2/src/dsh_cloud_cli/wizard.py +203 -0
  8. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/LICENSE +0 -0
  9. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/README.md +0 -0
  10. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/build_backend.py +0 -0
  11. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/__init__.py +0 -0
  12. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/__main__.py +0 -0
  13. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/Caddyfile +0 -0
  14. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/compose.build.yml +0 -0
  15. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/compose.postgres.yml +0 -0
  16. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/models.curated.txt +0 -0
  17. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/models.json +0 -0
  18. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/pricing.cny.json +0 -0
  19. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/pricing.eur.json +0 -0
  20. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/pricing.gbp.json +0 -0
  21. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/pricing.hkd.json +0 -0
  22. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/pricing.jpy.json +0 -0
  23. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/config/pricing.usd.json +0 -0
  24. {dsh_cloud-0.2.0 → dsh_cloud-0.2.2}/src/dsh_cloud_cli/templates/docker-compose.yml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dsh-cloud
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Safe self-host installer and lifecycle CLI for DSH Cloud Community Edition
5
5
  Requires-Python: >=3.11
6
6
  License-Expression: LicenseRef-DSH-Cloud-Community-1.0
@@ -5,7 +5,7 @@ backend-path = ["."]
5
5
 
6
6
  [project]
7
7
  name = "dsh-cloud"
8
- version = "0.2.0"
8
+ version = "0.2.2"
9
9
  description = "Safe self-host installer and lifecycle CLI for DSH Cloud Community Edition"
10
10
  requires-python = ">=3.11"
11
11
  dependencies = []
@@ -5,8 +5,12 @@ import os
5
5
  import re
6
6
  import secrets
7
7
  import shutil
8
+ import re
9
+ import shutil
8
10
  import subprocess
9
11
  import sys
12
+
13
+ from .wizard import apply_answers, command_prefix, next_steps, prompt_answers, should_run_wizard
10
14
  from pathlib import Path
11
15
 
12
16
 
@@ -173,7 +177,7 @@ def template_roots() -> tuple[Path, Path]:
173
177
  return root / "deploy/selfhost", root / "server/config"
174
178
 
175
179
 
176
- def generated_env(parsed: dict, manifest: dict) -> str:
180
+ def generated_env(parsed: dict, manifest: dict, answers: dict | None = None) -> str:
177
181
  options = parsed["options"]
178
182
  mode = str(options.get("mode", "trial"))
179
183
  trial = mode == "trial"
@@ -184,12 +188,17 @@ def generated_env(parsed: dict, manifest: dict) -> str:
184
188
  values = [
185
189
  ("COMPOSE_PROJECT_NAME", str(options.get("projectName", "dsh-selfhost"))),
186
190
  ("DOMAIN", domain), ("SITE_SCHEME", "http" if trial else "https"),
187
- ("PUBLIC_BASE", public_base), ("BIND_ADDRESS", "127.0.0.1" if trial else "0.0.0.0"),
191
+ # 键序与 src/wizard.mjs 的那份保持一致 —— 两个安装器的 .env 逐字节相同,
192
+ # 由 tests/distribution 的 parity 用例钉着。
193
+ ("PUBLIC_BASE", public_base),
188
194
  ("DSH_SITE", TRIAL_CADDY_SITE if trial else f"https://{domain}"),
195
+ ("BIND_ADDRESS", "127.0.0.1" if trial else "0.0.0.0"),
189
196
  ("HTTP_PORT", "8787" if trial else "80"), ("HTTPS_PORT", "8443" if trial else "443"),
190
197
  ("DHC_DEV", "1" if trial else "0"), ("DHC_CONFIG_DIR", "./config"),
191
198
  ("PRICING_FILE", "pricing.cny.json"), ("AUTH_SECRET_FILE", "/run/secrets/auth_secret"),
192
199
  ("UPSTREAM_BASE_URL", "https://api.deepseek.com/v1"), ("UPSTREAM_API_KEY", ""),
200
+ # 联网搜索: 留空则 web_search 不可用。zhipu 走 open.bigmodel.cn。
201
+ ("SEARCH_PROVIDER", "zhipu"), ("ZHIPU_SEARCH_API_KEY", ""),
193
202
  ("ADMIN_EMAILS", str(options.get("adminEmail", ""))),
194
203
  ("MAIL_SMTP_HOST", ""), ("MAIL_SMTP_USER", ""), ("MAIL_SMTP_PASS", ""), ("MAIL_FROM", ""),
195
204
  ("GOOGLE_LOGIN_CLIENT_ID", ""), ("GOOGLE_LOGIN_CLIENT_SECRET", ""),
@@ -197,13 +206,21 @@ def generated_env(parsed: dict, manifest: dict) -> str:
197
206
  ("DHC_SERVER_IMAGE", manifest["images"]["server"]),
198
207
  ("CADDY_IMAGE", manifest["baseImages"]["caddy"]),
199
208
  ("POSTGRES_IMAGE", manifest["baseImages"]["postgres"]),
209
+ # 云工作台。自部署给了域名就默认开 —— 那是招牌功能, 装完没有它等于交付了
210
+ # 半个东西。还差一条 DNS 记录 (work.<域名> 指向本机), 装完面板会说。试用
211
+ # 模式必定关: localhost 与 work.localhost 是不同 host, 会话 cookie 过不去。
212
+ ("WORK_ENABLED", "0" if trial else "1"),
213
+ ("WORK_DOMAIN", "" if trial else f"work.{domain}"),
214
+ ("COOKIE_DOMAIN", "" if trial else f".{domain}"),
215
+ ("COMPOSE_PROFILES", "" if trial else "work"),
200
216
  ("SOCKET_PROXY_IMAGE", manifest["baseImages"]["socketProxy"]),
201
217
  ("WORK_IMAGE", manifest["images"]["workspace"]),
202
218
  ]
219
+ values = apply_answers(values, answers or {})
203
220
  return "".join(f"{key}={value}\n" for key, value in values)
204
221
 
205
222
 
206
- def initialize(parsed: dict, manifest: dict, deployment_plan: dict | None = None) -> Path:
223
+ def initialize(parsed: dict, manifest: dict, deployment_plan: dict | None = None, answers: dict | None = None) -> Path:
207
224
  deployment_plan = deployment_plan or plan(parsed, manifest)
208
225
  target = target_of(parsed)
209
226
  if target.exists():
@@ -226,7 +243,7 @@ def initialize(parsed: dict, manifest: dict, deployment_plan: dict | None = None
226
243
  shutil.copytree(config, target / "config")
227
244
  secret = os.environ.get("DSH_CLOUD_TEST_RANDOM_HEX", secrets.token_hex(32))
228
245
  atomic_write(target / "secrets/auth_secret", f"{secret}\n", 0o600)
229
- atomic_write(target / ".env", generated_env(parsed, manifest), 0o600)
246
+ atomic_write(target / ".env", generated_env(parsed, manifest, answers), 0o600)
230
247
  atomic_write(target / ".gitignore", ".env\nsecrets/\n.dsh-cloud/lock\n")
231
248
  state = {
232
249
  "schemaVersion": 1, "version": manifest["version"], "stackSchema": manifest["stackSchema"],
@@ -307,14 +324,19 @@ def execute(parsed: dict) -> dict:
307
324
  if parsed.get("special") == "version":
308
325
  return {"text": manifest["version"] + "\n"}
309
326
  value = plan(parsed, manifest)
327
+ state = Path(value["directory"]) / ".dsh-cloud/state.json"
328
+ fresh_init = not state.is_file()
329
+ # 只有全新部署才问; 已初始化的目录再问一遍等于诱导用户覆盖自己的配置。
330
+ answers: dict = {}
331
+ if should_run_wizard(parsed, is_tty=sys.stdin.isatty() and sys.stdout.isatty(), fresh_init=fresh_init):
332
+ answers = prompt_answers(version=manifest["version"], mode=str(parsed["options"].get("mode", "trial")))
310
333
  if parsed["command"] == "init":
311
334
  if parsed["options"].get("dryRun"):
312
335
  return {"json": value}
313
- initialize(parsed, manifest, value)
336
+ initialize(parsed, manifest, value, answers)
314
337
  return {"json": {**value, "initialized": True}}
315
- state = Path(value["directory"]) / ".dsh-cloud/state.json"
316
- if parsed["command"] == "start" and not state.is_file() and not parsed["options"].get("dryRun"):
317
- initialize(parsed, manifest, value)
338
+ if parsed["command"] == "start" and fresh_init and not parsed["options"].get("dryRun"):
339
+ initialize(parsed, manifest, value, answers)
318
340
  if state.is_file():
319
341
  restore_deployment(value, state, parsed)
320
342
  if parsed["options"].get("dryRun"):
@@ -347,7 +369,20 @@ def execute(parsed: dict) -> dict:
347
369
  if result.stderr.strip():
348
370
  response["composeError"] = result.stderr.strip()
349
371
  return {"json": response}
350
- return {"text": value["url"] + "\n" if parsed["command"] in {"start", "up"} else ""}
372
+ if parsed["command"] not in {"start", "up"}:
373
+ return {"text": ""}
374
+ # 人在终端前就给完整指引; 管道/CI 里保持裸 URL, 免得打断既有脚本的解析。
375
+ if not sys.stdout.isatty():
376
+ return {"text": value["url"] + "\n"}
377
+ env_text = (Path(value["directory"]) / ".env").read_text(encoding="utf-8")
378
+ has_key = bool(re.search(r"^UPSTREAM_API_KEY=.+$", env_text, re.MULTILINE))
379
+ prefix = command_prefix(resolved=shutil.which("dsh-cloud") or "", entry=sys.argv[0])
380
+ return {"text": next_steps(url=value["url"], directory=value["directory"],
381
+ has_upstream_key=has_key, project_name=value["projectName"],
382
+ prefix=prefix,
383
+ work_domain=(f"work.{parsed['options'].get('domain')}"
384
+ if value["mode"] == "selfhost" else ""),
385
+ dev_mail=value["mode"] != "selfhost")}
351
386
 
352
387
 
353
388
  def main(argv: list[str] | None = None) -> int:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "license": "LicenseRef-DSH-Cloud-Community-1.0",
5
5
  "stackSchema": 1,
6
6
  "databaseSchema": 1,
@@ -8,8 +8,8 @@
8
8
  "minUpgradeFrom": "0.1.0",
9
9
  "harnessRuntime": "0.1.0-rc.8",
10
10
  "images": {
11
- "server": "ghcr.io/agentsdanceai/dsh-cloud-server:0.2.0",
12
- "workspace": "ghcr.io/agentsdanceai/dsh-cloud-workspace:0.2.0"
11
+ "server": "ghcr.io/agentsdanceai/dsh-cloud-server:0.2.2",
12
+ "workspace": "ghcr.io/agentsdanceai/dsh-cloud-workspace:0.2.2"
13
13
  },
14
14
  "baseImages": {
15
15
  "python": "python:3.11-slim-bookworm@sha256:2e32f7d302adc1c37428355c1e646897c0c53f4fd60b6a551245fb90ee129f91",