huaweicloud-devkit 1.1.0-next.2 → 1.1.0-next.21
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.
- package/README.md +64 -1
- package/README.zh-CN.md +63 -1
- package/package.json +3 -1
- package/plugins/huaweicloud-core/.claude-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.codex-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.cursor-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.hermes-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/.workbuddy-plugin/plugin.json +1 -1
- package/plugins/huaweicloud-core/hooks/huaweicloud-safety.py +46 -21
- package/plugins/huaweicloud-core/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/safety/rules/cloud-risk-rules.json +136 -24
- package/plugins/huaweicloud-core/skills/huawei-billing/SKILL.md +15 -14
- package/plugins/huaweicloud-core/skills/huawei-dew/SKILL.md +1 -1
- package/plugins/huaweicloud-core/skills/huawei-ecs/SKILL.md +5 -1
- package/plugins/huaweicloud-core/skills/huawei-ecs/references/hc-activity.md +27 -0
- package/plugins/huaweicloud-core/skills/huawei-iam/SKILL.md +11 -11
- package/plugins/huaweicloud-core/skills/huawei-rds/SKILL.md +6 -5
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +596 -93
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/framework-commands.md +0 -1
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/nginx-templates.md +8 -4
- package/plugins/huaweicloud-core/skills/huawei-smn-dms/SKILL.md +19 -12
- package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +52 -0
- package/plugins/huaweicloud-core/skills/huawei-vpc/SKILL.md +26 -26
- package/plugins/huaweicloud-core/skills/huawei-vpc/references/network.md +12 -6
- package/plugins/huaweicloud-core/skills/huawei-waf-aad/SKILL.md +7 -4
- package/plugins/huaweicloud-core/skills/huaweicloud-cli-and-auth/SKILL.md +7 -7
- package/plugins/huaweicloud-core/skills/huaweicloud-core/SKILL.md +2 -0
- package/plugins/huaweicloud-core/src/auth/agent-registration.mjs +8 -0
- package/plugins/huaweicloud-core/src/auth/credentials.mjs +30 -1
- package/plugins/huaweicloud-core/src/auth/service.mjs +33 -0
- package/plugins/huaweicloud-core/src/detect-framework.mjs +25 -3
- package/plugins/huaweicloud-core/src/hcloud-cli.mjs +67 -5
- package/plugins/huaweicloud-core/src/mcp-server.mjs +23 -0
- package/plugins/huaweicloud-core/src/sandbox/hdkitservice-api.mjs +11 -3
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +436 -21
- package/plugins/huaweicloud-core/src/search-market.mjs +1 -0
- package/plugins/huaweicloud-core/src/setup-cli.mjs +605 -50
- package/plugins/huaweicloud-core/src/tools.mjs +193 -18
- package/plugins/huaweicloud-core/src/ws-exec/ws-exec-client.js +29 -8
|
@@ -52,6 +52,8 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
|
|
|
52
52
|
| `huaweicloud_sandbox_exec_one_shot` | One-shot execution (fresh connection; best for long/heavy commands) |
|
|
53
53
|
| `huaweicloud_sandbox_upload_file` | Upload a local file into the sandbox (chunked base64 write + md5 verify) |
|
|
54
54
|
| `huaweicloud_sandbox_upload_project` | Upload a local project directory to sandbox (HTTP tunnel, tar.gz + extract) |
|
|
55
|
+
| `huaweicloud_sandbox_deploy_nginx` | Deploy nginx config with permissions fix and reload in one call |
|
|
56
|
+
| `huaweicloud_sandbox_deploy_check` | Run deployment completeness check (nginx, DevBridge, URL, QR if needed) |
|
|
55
57
|
| `huaweicloud_sandbox_close_session` | Close a persistent terminal session |
|
|
56
58
|
|
|
57
59
|
### Tool Selection Guide
|
|
@@ -63,6 +65,8 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
|
|
|
63
65
|
| `curl`, health checks, quick tests | Either — `exec_one_shot` preferred | Stateless, fast |
|
|
64
66
|
| Server startup (background) | `exec_with_session` | Need to `nohup ... &` then check output in same session |
|
|
65
67
|
| Deployment scripts | `exec_one_shot+shot` | Long script, fresh connection avoids session timeouts |
|
|
68
|
+
| nginx configuration | `deploy_nginx` | Auto-generates correct template + permissions + reload |
|
|
69
|
+
| Deployment completeness check | `deploy_check` | Verifies nginx, DevBridge, URL, QR before reporting success |
|
|
66
70
|
| Single file upload (<1MB) | `upload_file` | Base64 chunked, reliable for small files |
|
|
67
71
|
| Project directory upload (>1MB) | `upload_project` | HTTP tunnel, much faster than base64 for multi-file projects |
|
|
68
72
|
|
|
@@ -72,6 +76,14 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
|
|
|
72
76
|
{ "timeout_ms": 300000 }
|
|
73
77
|
```
|
|
74
78
|
|
|
79
|
+
**Session recovery**: if `exec_with_session` returns `session is not ready`, the WebSocket connection has dropped. Do NOT retry the same session — fall back to `exec_one_shot` for that command instead. To recover state (cd, env vars), reconstruct them explicitly in the one-shot command.
|
|
80
|
+
|
|
81
|
+
**Timeout recovery**: if `exec_one_shot` returns a timeout error, check whether partial output is available before declaring failure:
|
|
82
|
+
|
|
83
|
+
- For build commands: check `tail -30 /tmp/build.log` — the build may have completed but the tee pipe didn't flush before timeout
|
|
84
|
+
- For long scripts: split into independent `exec_one_shot` calls (max 5 sub-commands per call, 15s timeout per call)
|
|
85
|
+
- Do NOT retry the same composite command — split and retry individual steps
|
|
86
|
+
|
|
75
87
|
## Workflow
|
|
76
88
|
|
|
77
89
|
Setup is a **plugin-side preflight** — the developer should be asked a question only once, when the agreement actually needs signing:
|
|
@@ -83,11 +95,35 @@ Setup is a **plugin-side preflight** — the developer should be asked a questio
|
|
|
83
95
|
2. **Real-name verification only** (`HDKIT_NOT_REALNAME`): tell the developer once, "Huawei Cloud requires real-name verification before using the sandbox — please complete it in the Huawei Cloud console (实名认证)." and stop — do not retry `connect` in a loop
|
|
84
96
|
3. **Sign agreement only** (`HDKIT_NOT_AGREEMENT`): **STOP and do NOT sign on your own.** Ask the developer: "Huawei Cloud sandbox requires signing the latest developer service agreement. May I sign it for you?" Then **wait for the developer to explicitly agree** (e.g. "签署" / "确认" / "sign it"). Only after explicit consent call `huaweicloud_sandbox_sign_agreement` and return its result (`signed`/`signedCount`) to the developer. **Never sign a legal agreement on the developer's behalf without their explicit, unambiguous consent.** Do not expose the underlying sandbox/DevBridge service as a separate entity the developer must understand or sign up for
|
|
85
97
|
4. **Both missing** (`HDKIT_NOT_REALNAME_AND_AGREEMENT`): present **both** requirements together in one message — the real-name verification steps (console, step 2) **and** the agreement-signing request (step 3, wait for explicit consent) — so the developer can complete both at once
|
|
86
|
-
5. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`
|
|
87
|
-
6. **
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
5. **Connect**: `huaweicloud_sandbox_connect` — returns `session_id`, `dev_stage_id`, `connection_id`, `connection_address`. The `source` parameter identifies the calling agent (valid values: `CLI`, `WEB`, `VSCODE`, `WEBVNC`, `WEBPTY`, `WEBIDE`, `CURSOR`, etc. — case-sensitive, all uppercase). The `git` parameter (with `repo_url`, `repo_name`, `target_path`) is accepted but does NOT auto-clone the repository — always clone manually.
|
|
99
|
+
6. **Cleanup previous deployments** (after first connect to a sandbox): nginx configs, DevBridge tunnels, and stale web processes from previous deployments can cause port conflicts and quota errors. Run cleanup immediately after connect:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# Kill stale Node.js web processes from previous deployments
|
|
103
|
+
pkill -9 -f "next-server" 2>/dev/null || true
|
|
104
|
+
pkill -9 -f "next start" 2>/dev/null || true
|
|
105
|
+
pkill -9 -f "nuxt" 2>/dev/null || true
|
|
106
|
+
sleep 1
|
|
107
|
+
# Remove stale nginx configs from previous deployments
|
|
108
|
+
sudo rm -f /etc/nginx/conf.d/*.conf /etc/nginx/conf.d/*.conf.bak 2>/dev/null
|
|
109
|
+
# Remove stale DevBridge tunnels
|
|
110
|
+
devbridge delete-all 2>/dev/null || true
|
|
111
|
+
# Reload nginx to apply
|
|
112
|
+
sudo nginx -s reload 2>/dev/null || true
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
7. **Inject credentials** (optional): `huaweicloud_sandbox_credentials` — enables cloud API access from sandbox. After injection, credentials are also written to `/tmp/hw_creds.sh` for shell access:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Source credentials in any sandbox shell before using hcloud/devbridge:
|
|
119
|
+
source /tmp/hw_creds.sh 2>/dev/null && echo "Credentials loaded"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If `source /tmp/hw_creds.sh` returns empty, the credentials injection may not have completed — re-run `huaweicloud_sandbox_credentials`.
|
|
123
|
+
|
|
124
|
+
8. **Execute commands**: use `huaweicloud_sandbox_exec_with_session` for interactive work (cd, env setup), or `huaweicloud_sandbox_exec_one_shot` for long-running builds/installs. See [Tool Selection Guide](#tool-selection-guide).
|
|
125
|
+
9. **Upload project** (if deploying local code): `huaweicloud_sandbox_upload_project` to transfer a local project directory into `/workspace/<name>` — uses HTTP tunnel, much faster than base64 for multi-file projects. Use `huaweicloud_sandbox_upload_file` for single files.
|
|
126
|
+
10. **Expose the deployed app and return the public URL**: set up a public access link for the app port and give the URL to the developer as the final result (see "Expose the deployed app (public URL)") — this step uses an internal tunnel mechanism that must never be mentioned to the developer
|
|
91
127
|
|
|
92
128
|
## File Transfer (local → sandbox)
|
|
93
129
|
|
|
@@ -102,14 +138,27 @@ Setup is a **plugin-side preflight** — the developer should be asked a questio
|
|
|
102
138
|
"local_dir": "/path/to/local/project",
|
|
103
139
|
"remote_dir": "/workspace",
|
|
104
140
|
"extract": true,
|
|
105
|
-
"exclude": [
|
|
141
|
+
"exclude": [
|
|
142
|
+
"**/node_modules",
|
|
143
|
+
".git",
|
|
144
|
+
"__pycache__",
|
|
145
|
+
".next",
|
|
146
|
+
".nuxt",
|
|
147
|
+
".output",
|
|
148
|
+
".turbo",
|
|
149
|
+
".cache",
|
|
150
|
+
".swc",
|
|
151
|
+
"dist",
|
|
152
|
+
"coverage",
|
|
153
|
+
"*.pyc"
|
|
154
|
+
]
|
|
106
155
|
}
|
|
107
156
|
```
|
|
108
157
|
|
|
109
158
|
- `local_dir` (required): local project directory
|
|
110
159
|
- `remote_dir` (optional, default `/workspace`): parent directory on sandbox
|
|
111
160
|
- `extract` (optional, default `true`): extract tar.gz after upload
|
|
112
|
-
- `exclude` (optional): patterns to exclude from archive
|
|
161
|
+
- `exclude` (optional): patterns to exclude from archive. **For web apps, always exclude dependency directories** (`node_modules`, `.next`, `.nuxt`, `.output`, `.turbo`, `.cache`) — these will be re-installed/built in the sandbox.
|
|
113
162
|
- Result includes `md5` and `md5Verified` for integrity check
|
|
114
163
|
|
|
115
164
|
### upload_file (for single files)
|
|
@@ -158,25 +207,65 @@ fi
|
|
|
158
207
|
export PATH=$PATH:$HOME/.huawei/bin # installer only writes ~/.bashrc; session shells do not re-source it
|
|
159
208
|
```
|
|
160
209
|
|
|
161
|
-
**Login** (non-interactive
|
|
210
|
+
**Login** (non-interactive; credentials from `huaweicloud_sandbox_credentials` are available via `/tmp/hw_creds.sh`). If `source /tmp/hw_creds.sh` returns empty, the credentials injection has expired (sandbox session reconnection resets them) — re-run `huaweicloud_sandbox_credentials` first:
|
|
162
211
|
|
|
163
212
|
```bash
|
|
164
|
-
|
|
213
|
+
source /tmp/hw_creds.sh 2>/dev/null
|
|
214
|
+
devbridge auth login --huaweicloud --access-key "$HW_ACCESS_KEY" --secret-key "$HW_SECRET_KEY"
|
|
165
215
|
```
|
|
166
216
|
|
|
167
217
|
- The `--huaweicloud` flag is required for AK/SK login; without it the CLI tries an interactive browser login, which fails in the sandbox.
|
|
168
|
-
-
|
|
218
|
+
- Credentials are stored in `/tmp/hw_creds.sh` (chmod 600) — source it before login, never echo the values.
|
|
219
|
+
- Verify with `devbridge auth status`. If `$HW_ACCESS_KEY` is empty, ensure `huaweicloud_sandbox_credentials` was called first.
|
|
169
220
|
|
|
170
221
|
**Expose** (run the web server and the tunnel in the background, then read the URL from the log; the app lives in the workspace mount, e.g. `/workspace/<repo-name>`):
|
|
171
222
|
|
|
172
223
|
```bash
|
|
173
|
-
|
|
174
|
-
|
|
224
|
+
# 0. Pre-cleanup: kill old processes and stale tunnels
|
|
225
|
+
pkill -f "devbridge host" 2>/dev/null || true
|
|
226
|
+
sleep 2
|
|
227
|
+
devbridge delete-all 2>/dev/null || true
|
|
228
|
+
|
|
229
|
+
# 1. Start tunnel
|
|
230
|
+
nohup devbridge host -p <port> -e 8 > /tmp/host.log 2>&1 &
|
|
231
|
+
sleep 10 && cat /tmp/host.log
|
|
232
|
+
|
|
233
|
+
# 2. Extract tunnel URL and health-check before returning
|
|
234
|
+
TUNNEL_URL=$(grep -oP 'Tunnel URL: \K.*' /tmp/host.log)
|
|
235
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$TUNNEL_URL" 2>/dev/null || echo "000")
|
|
236
|
+
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "304" ]; then
|
|
237
|
+
echo "Tunnel verified: $TUNNEL_URL (HTTP $HTTP_CODE)"
|
|
238
|
+
else
|
|
239
|
+
echo "WARN: Tunnel URL unreachable (HTTP $HTTP_CODE). Rebuilding tunnel..."
|
|
240
|
+
pkill -f "devbridge host" && sleep 2
|
|
241
|
+
devbridge delete-all 2>/dev/null || true
|
|
242
|
+
nohup devbridge host -p <port> -e 8 > /tmp/host.log 2>&1 &
|
|
243
|
+
sleep 10
|
|
244
|
+
TUNNEL_URL=$(grep -oP 'Tunnel URL: \K.*' /tmp/host.log)
|
|
245
|
+
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$TUNNEL_URL" 2>/dev/null || echo "000")
|
|
246
|
+
echo "Retry tunnel: $TUNNEL_URL (HTTP $HTTP_CODE)"
|
|
247
|
+
fi
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Never return a tunnel URL without verifying it first** — a stale URL (from a killed tunnel process) will silently fail. Always curl-check before giving the URL to the developer.
|
|
251
|
+
|
|
252
|
+
**Quota recovery**: if the tunnel creation fails with `10006: quota exceeded`:
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# Step A: List all tunnels (both active and stale)
|
|
256
|
+
devbridge list -j
|
|
257
|
+
# Step B: Remove all stale tunnels
|
|
258
|
+
devbridge delete-all
|
|
259
|
+
# Step C: Retry tunnel creation
|
|
260
|
+
nohup devbridge host -p <port> -e 8 > /tmp/host.log 2>&1 &
|
|
175
261
|
sleep 10 && cat /tmp/host.log
|
|
176
262
|
```
|
|
177
263
|
|
|
264
|
+
This eliminates the most common deployment failure — historical tunnels from previous sessions accumulating past the max=10 quota.
|
|
265
|
+
|
|
178
266
|
- The public URL has the form `https://<id>-<port>.cn-north-4-bridge.myhuaweicloud.com` (from the `Tunnel URL:` line).
|
|
179
267
|
- **Return this URL to the developer as the deployment result link.** Keep the host process running (do not close the session before handing over the URL).
|
|
268
|
+
- Tunnel `description` (`-d`) accepts only Chinese characters, letters, and digits (0-64). Symbols such as `-`/`_`/spaces are rejected (`Invalid tunnel description`).
|
|
180
269
|
- Internal docs: https://huaweicloud.github.io/devspace-devbridge/
|
|
181
270
|
|
|
182
271
|
**No local downgrade**: if the tunnel tooling cannot be installed in the sandbox, STOP and report a generic error ("无法生成访问地址") without technical detail. Never install it on the developer's local machine — a local install would defeat the purpose of sandbox deployment.
|
|
@@ -208,76 +297,270 @@ Follow the standard [Workflow](#workflow) steps 1-6 to connect to the sandbox, t
|
|
|
208
297
|
```json
|
|
209
298
|
{
|
|
210
299
|
"local_dir": "<projectPath>",
|
|
211
|
-
"remote_dir": "/workspace"
|
|
300
|
+
"remote_dir": "/workspace",
|
|
301
|
+
"exclude": [
|
|
302
|
+
"**/node_modules",
|
|
303
|
+
".git",
|
|
304
|
+
"__pycache__",
|
|
305
|
+
".next",
|
|
306
|
+
".nuxt",
|
|
307
|
+
".output",
|
|
308
|
+
".turbo",
|
|
309
|
+
".cache",
|
|
310
|
+
".swc",
|
|
311
|
+
"dist",
|
|
312
|
+
"coverage",
|
|
313
|
+
"*.pyc"
|
|
314
|
+
]
|
|
212
315
|
}
|
|
213
316
|
```
|
|
214
317
|
|
|
215
|
-
|
|
318
|
+
**Always exclude build artifacts and dependency directories** — they will be re-installed/built inside the sandbox:
|
|
319
|
+
|
|
320
|
+
| Pattern | Why excluded |
|
|
321
|
+
| ----------------- | ---------------------------------------------- |
|
|
322
|
+
| `**/node_modules` | Dependencies — reinstall in sandbox |
|
|
323
|
+
| `.git` | Version control — not needed for deployment |
|
|
324
|
+
| `__pycache__` | Python bytecode cache |
|
|
325
|
+
| `.next` | Next.js build output — rebuild in sandbox |
|
|
326
|
+
| `.nuxt` | Nuxt build cache — rebuild in sandbox |
|
|
327
|
+
| `.output` | Nuxt production output — rebuild in sandbox |
|
|
328
|
+
| `.turbo` | Turborepo cache — re-run in sandbox |
|
|
329
|
+
| `.cache` | Generic tool cache (Parcel, Storybook, etc.) |
|
|
330
|
+
| `.swc` | Taro/Webpack SWC cache — regenerate in sandbox |
|
|
331
|
+
| `dist` | Build output — rebuild in sandbox |
|
|
332
|
+
| `coverage` | Test coverage reports — not needed for deploy |
|
|
333
|
+
| `*.pyc` | Python compiled files |
|
|
334
|
+
|
|
335
|
+
**Post-upload permission fix**: after `upload_project` extracts the project, fix file permissions lost during transfer (native binaries from other platforms, .bin symlinks):
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
# Fix executable permissions on node_modules/.bin (lost during cross-platform transfer)
|
|
339
|
+
chmod -R +x /workspace/<dirname>/node_modules/.bin 2>/dev/null || true
|
|
340
|
+
# Fix world-read on all files (sandbox default umask may restrict)
|
|
341
|
+
chmod -R o+rX /workspace/<dirname> 2>/dev/null || true
|
|
342
|
+
```
|
|
216
343
|
|
|
217
344
|
### Step 3: Sandbox Environment Readiness
|
|
218
345
|
|
|
219
|
-
|
|
346
|
+
Install OS-level dependencies **before** uploading the project (independent of project code, can run in parallel if desired).
|
|
347
|
+
|
|
348
|
+
#### 3a: Detect OS and package manager
|
|
220
349
|
|
|
221
350
|
```bash
|
|
222
|
-
|
|
223
|
-
echo "
|
|
224
|
-
command -v
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
351
|
+
source /etc/os-release 2>/dev/null
|
|
352
|
+
echo "OS_DETECTED=${ID:-unknown}|${ID_LIKE:-}"
|
|
353
|
+
if command -v apt-get >/dev/null 2>&1; then echo "PKG_MGR=apt"; elif command -v yum >/dev/null 2>&1; then echo "PKG_MGR=yum"; elif command -v dnf >/dev/null 2>&1; then echo "PKG_MGR=dnf"; elif command -v apk >/dev/null 2>&1; then echo "PKG_MGR=apk"; else echo "PKG_MGR=unknown"; fi
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
Use the detected `PKG_MGR` for all package installations below.
|
|
357
|
+
|
|
358
|
+
**Architecture awareness**: the sandbox runs Linux aarch64 (ARM64). Native binaries built on x64 (Windows/macOS Intel) will not execute. Always install dependencies and build inside the sandbox. For projects with native addons (Taro `@swc/core`, Prisma, `esbuild`, `node-gyp`), local x64 pre-build + upload of `dist/` output is a viable alternative when sandbox builds fail.
|
|
359
|
+
|
|
360
|
+
**GitCode SSL**: if `git clone` from GitCode fails with SSL certificate errors, use a one-shot override (do NOT set it globally — that would disable cert verification for every repo):
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
git -c http.sslVerify=false clone <repo-url>
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Then retry the clone. This bypasses SSL verification only for this single clone.
|
|
367
|
+
|
|
368
|
+
#### 3b: Install nginx (before project upload)
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
# Use the detected PKG_MGR from step 3a
|
|
372
|
+
case "$PKG_MGR" in
|
|
373
|
+
apt) sudo apt-get update -qq && sudo apt-get install -y -qq nginx ;;
|
|
374
|
+
yum) sudo yum install -y nginx ;;
|
|
375
|
+
dnf) sudo dnf install -y nginx ;;
|
|
376
|
+
esac
|
|
377
|
+
sudo nginx -t && echo "nginx: ready"
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
If nginx cannot be installed, skip to Python HTTP server fallback (see `references/nginx-templates.md`).
|
|
381
|
+
|
|
382
|
+
#### 3c: Verify remaining tools
|
|
383
|
+
|
|
384
|
+
Before installing project dependencies, verify the sandbox has the required runtime tools. **Run each check as a separate `exec_one_shot` call with 15s timeout** — do not bundle all checks into one command. A single hung subcommand (e.g., `make --version` or `hugo version`) will timeout the entire check, blocking deployment:
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
Check 1: node --version (timeout: 15s)
|
|
388
|
+
Check 2: npm --version (timeout: 15s)
|
|
389
|
+
Check 3: nginx -v 2>&1 (timeout: 15s)
|
|
390
|
+
Check 4: git --version (timeout: 15s)
|
|
391
|
+
Check 5: python3 --version (timeout: 15s)
|
|
392
|
+
Check 6: curl --version | head -1 (timeout: 15s)
|
|
393
|
+
Check 7: wget --version | head -1 (timeout: 15s)
|
|
394
|
+
Check 8: make --version | head -1 (timeout: 15s)
|
|
395
|
+
Check 9: pnpm --version (timeout: 15s)
|
|
396
|
+
Check 10: yarn --version (timeout: 15s)
|
|
397
|
+
Check 11: hugo version (timeout: 15s)
|
|
398
|
+
Check 12: devbridge version (timeout: 15s)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
For each check, parse the output: if stdout contains `MISSING:` or the tool wasn't found, install it. **Skip framework-specific tools not needed for the current project** (e.g., skip Hugo for React apps).
|
|
402
|
+
|
|
403
|
+
**Install only missing tools** — parse the pre-flight output and install only tools reported as `MISSING`. Use OS-aware commands:
|
|
404
|
+
|
|
405
|
+
| Missing Tool | Install Command (apt) | Install Command (yum/dnf) |
|
|
406
|
+
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
|
|
407
|
+
| Node.js | Follow [Node.js in the sandbox](#nodejs-in-the-sandbox) | Same |
|
|
408
|
+
| nginx | `sudo apt-get update -qq && sudo apt-get install -y -qq nginx` | `sudo yum install -y nginx` |
|
|
409
|
+
| curl | `sudo apt-get update -qq && sudo apt-get install -y -qq curl` | `sudo yum install -y curl` |
|
|
410
|
+
| wget | `sudo apt-get update -qq && sudo apt-get install -y -qq wget` | `sudo yum install -y wget` |
|
|
411
|
+
| make | `sudo apt-get update -qq && sudo apt-get install -y -qq make` | `sudo yum install -y make` |
|
|
412
|
+
| pnpm | `npm i -g pnpm` | Same |
|
|
413
|
+
| yarn | `npm i -g yarn` | Same |
|
|
414
|
+
| Hugo | `curl -fsSL https://github.com/gohugoio/hugo/releases/download/v0.140.0/hugo_extended_0.140.0_linux-amd64.tar.gz -o /tmp/hugo.tar.gz && sudo tar -xzf /tmp/hugo.tar.gz -C /usr/local/bin hugo && rm /tmp/hugo.tar.gz` | Same |
|
|
415
|
+
| DevBridge | `curl -fsSL https://res-hd.hc-cdn.cn/sharedata/hdspace/devbridge/install.sh \| bash && export PATH=$PATH:$HOME/.huawei/bin` | Same |
|
|
262
416
|
|
|
263
417
|
**If Node.js is missing**, install it first — all build workflows depend on it. Stop and report to the developer if Node.js installation fails.
|
|
264
418
|
|
|
265
419
|
### Step 4: Install and Build
|
|
266
420
|
|
|
267
|
-
|
|
421
|
+
#### 4a: Inject Environment Variables
|
|
422
|
+
|
|
423
|
+
Before any project commands, parse `.env*` files and inject them into the shell environment. Prisma, Drizzle, and other ORM/database tools do NOT auto-read framework-level env files:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
cd /workspace/<dirname>
|
|
427
|
+
# Load env files if present (most specific first)
|
|
428
|
+
for f in .env.local .env.development.local .env.development .env; do
|
|
429
|
+
if [ -f "$f" ]; then
|
|
430
|
+
set -a && source "$f" 2>/dev/null; set +a
|
|
431
|
+
echo "Loaded env: $f"
|
|
432
|
+
fi
|
|
433
|
+
done
|
|
434
|
+
# Verify key variables for common tools
|
|
435
|
+
echo "DATABASE_URL=${DATABASE_URL:-<NOT SET>}"
|
|
436
|
+
echo "NODE_ENV=${NODE_ENV:-development}"
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
This must run via `exec_with_session` so the exported variables persist for subsequent build commands in the same session.
|
|
268
440
|
|
|
269
|
-
**
|
|
441
|
+
**Prisma / ORM compatibility**: Prisma CLI (`prisma generate`, `prisma db push`, `prisma migrate`) only reads `.env` by default, NOT `.env.local` or `.env.development`. If the project uses `.env.local` for `DATABASE_URL`, link it before any Prisma command:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
# Prisma requires .env (not .env.local) — symlink if needed
|
|
445
|
+
if [ -f .env.local ] && [ ! -f .env ]; then ln -sf .env.local .env 2>/dev/null || cp .env.local .env; fi
|
|
446
|
+
# Then re-source
|
|
447
|
+
set -a && source .env 2>/dev/null; set +a
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
#### 4b: Install Dependencies
|
|
451
|
+
|
|
452
|
+
Use `exec_one_shot` for install (no shared state needed). Skip if `node_modules` already exists:
|
|
270
453
|
|
|
271
454
|
```bash
|
|
272
455
|
cd /workspace/<dirname> && [ -d node_modules ] && echo "SKIP: node_modules exists" || <installCmd>
|
|
273
456
|
```
|
|
274
457
|
|
|
275
|
-
Wait for install to complete,
|
|
458
|
+
Wait for install to complete. For large projects on aarch64 sandboxes (1000+ packages), set `timeout_ms` to 180000 (3 min).
|
|
459
|
+
|
|
460
|
+
**Node version compatibility**: if `npm install` fails with native module errors (e.g., `rollup 4`, `@esbuild`, `node-gyp`), check the Node version:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
node -v
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Node v24+ uses musl-based binaries on some sandbox images, which may break native addons built for glibc. If native modules fail:
|
|
467
|
+
|
|
468
|
+
- Try `npm install --force` or `npm install --legacy-peer-deps`
|
|
469
|
+
- For rollup 4 projects, consider `npm install rollup@3` as fallback
|
|
470
|
+
- If webpack/rollup native addon errors persist, add `--ignore-scripts` then manually rebuild: `npm rebuild`
|
|
471
|
+
|
|
472
|
+
**Prisma / database initialization**: if `prisma/schema.prisma` exists in the project, initialize the database after install and before build. Prisma Client generation (`prisma generate`) is usually handled by `postinstall`, but `prisma db push` (SQLite) or `prisma migrate deploy` (PostgreSQL/MySQL) must be run manually:
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
cd /workspace/<dirname>
|
|
476
|
+
if [ -f prisma/schema.prisma ]; then
|
|
477
|
+
echo "Prisma schema detected — initializing database..."
|
|
478
|
+
npx prisma db push --skip-generate 2>/dev/null || npx prisma migrate deploy 2>/dev/null || echo "WARN: skip db init"
|
|
479
|
+
fi
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
> `--skip-generate` avoids redundant generation when `postinstall` already ran `prisma generate`. For SQLite, `DATABASE_URL="file:./dev.db"` must be set in `.env`/`.env.local` before this step.
|
|
483
|
+
|
|
484
|
+
#### 4c: Build
|
|
276
485
|
|
|
277
|
-
**
|
|
486
|
+
**Timeout strategy by framework type:**
|
|
487
|
+
|
|
488
|
+
| Type | timeout_ms | Rationale |
|
|
489
|
+
| ------------------------------ | --------------- | ------------------------------------- |
|
|
490
|
+
| SPA / SSG | 300000 (5 min) | Vite/Webpack builds typically < 3 min |
|
|
491
|
+
| Cross-platform (Taro, uni-app) | 900000 (15 min) | Webpack5 H5 slow on aarch64, 7-8 min |
|
|
492
|
+
| SSR (Next.js, Nuxt) | 600000 (10 min) | Full-stack compilation + SSG pages |
|
|
493
|
+
| Monorepo | 600000 (10 min) | Multiple apps, shared packages |
|
|
494
|
+
| `null` (no build) | N/A | Skip |
|
|
495
|
+
|
|
496
|
+
**Build with `exec_one_shot`:**
|
|
278
497
|
|
|
279
498
|
```bash
|
|
280
|
-
cd /workspace/<dirname> && [ -d <outputDir> ] && echo "SKIP: <outputDir> exists" || <buildCmd>
|
|
499
|
+
cd /workspace/<dirname> && [ -d <outputDir> ] && echo "SKIP: <outputDir> exists" || (umask 022 && <buildCmd> 2>&1 | tee /tmp/build.log)
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Always pipe build output through `tee /tmp/build.log` — captures stderr+stdout so diagnostics are available even if the command times out.
|
|
503
|
+
|
|
504
|
+
**OutDir verification**: before building for the first time, check the project's actual output directory (not just the default from framework detection). Projects can override outDir in config (e.g., VitePress `outDir: '../dist'`):
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# Check for custom outDir in common config files
|
|
508
|
+
grep -r "outDir\|outputDir\|dest\|distDir" /workspace/<dirname>/.vitepress/config.* 2>/dev/null || true
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
If a custom outDir is found, use that instead of the framework-detected default for all subsequent checks.
|
|
512
|
+
|
|
513
|
+
**Post-build output verification**: after a successful build, verify the actual `index.html` location. Framework-returned `outputDir` may be inaccurate (e.g., uni-app v3 framework: `dist`, actual: `dist/build/h5`):
|
|
514
|
+
|
|
515
|
+
```bash
|
|
516
|
+
# Find the real index.html after build
|
|
517
|
+
REAL_INDEX=$(find /workspace/<dirname>/<outputDir> -name "index.html" -type f 2>/dev/null | head -1)
|
|
518
|
+
if [ -n "$REAL_INDEX" ] && [ -f "$REAL_INDEX" ]; then
|
|
519
|
+
REAL_OUTDIR=$(dirname "$REAL_INDEX")
|
|
520
|
+
echo "Actual output dir: $REAL_OUTDIR"
|
|
521
|
+
# Use REAL_OUTDIR for nginx config instead of framework-reported outputDir
|
|
522
|
+
fi
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
If `REAL_OUTDIR` differs from `<outputDir>`, use `REAL_OUTDIR` for all subsequent steps (nginx config, port check, etc.).
|
|
526
|
+
|
|
527
|
+
**Post-timeout recovery**: if `exec_one_shot` returns a timeout error (Request timed out), do NOT fail immediately. First dump any captured build log, then check the output directory:
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
# If timeout occurred, show captured output and verify build
|
|
531
|
+
if timeout_error; then
|
|
532
|
+
echo "=== Build log (tail) ==="
|
|
533
|
+
tail -30 /tmp/build.log 2>/dev/null
|
|
534
|
+
echo "=== Checking output ==="
|
|
535
|
+
if [ -d <outputDir> ] && [ "$(ls -A <outputDir> 2>/dev/null)" ]; then
|
|
536
|
+
# Verify at least one key output file exists (not just empty dir from broken build)
|
|
537
|
+
if [ -f <outputDir>/index.html ] || [ -f <outputDir>/server.js ] || [ -f <outputDir>/app.js ]; then
|
|
538
|
+
echo "Build output detected despite timeout — continuing with deployment"
|
|
539
|
+
else
|
|
540
|
+
echo "ERROR: Output directory exists but missing expected files (index.html/server.js). Build may have failed silently."
|
|
541
|
+
echo "Full log: /tmp/build.log"
|
|
542
|
+
exit 1
|
|
543
|
+
fi
|
|
544
|
+
else
|
|
545
|
+
echo "ERROR: Build did not complete. Output directory empty or missing."
|
|
546
|
+
echo "Full log: /tmp/build.log"
|
|
547
|
+
exit 1
|
|
548
|
+
fi
|
|
549
|
+
fi
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
For SSR frameworks, also verify the server entry point exists: `test -f <outputDir>/server.js || test -f node_modules/next/dist/server/next-server.js`.
|
|
553
|
+
|
|
554
|
+
**Build progress visibility**: for very large builds, touch a marker file before starting and use `exec_with_session` to poll intermediate logs:
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
# Before build:
|
|
558
|
+
touch /tmp/build-start && echo "Build started at $(date)"
|
|
559
|
+
|
|
560
|
+
# During build via exec_with_session (separate call for polling):
|
|
561
|
+
cat .next/trace 2>/dev/null | tail -5 # Next.js build trace
|
|
562
|
+
# or
|
|
563
|
+
tail -5 /tmp/build.log 2>/dev/null
|
|
281
564
|
```
|
|
282
565
|
|
|
283
566
|
- `cd /workspace/<dirname>/<subAppPath>` for Monorepo sub-apps.
|
|
@@ -285,38 +568,250 @@ cd /workspace/<dirname> && [ -d <outputDir> ] && echo "SKIP: <outputDir> exists"
|
|
|
285
568
|
- For Hugo/static sites where `installCmd` is `null`, skip install entirely.
|
|
286
569
|
- For static sites where `buildCmd` is `null`, skip build entirely.
|
|
287
570
|
|
|
288
|
-
|
|
571
|
+
#### 4c-aux: Build Failure Response
|
|
572
|
+
|
|
573
|
+
Build failures fall into two categories. Handle them differently:
|
|
574
|
+
|
|
575
|
+
| Failure Type | Behavior |
|
|
576
|
+
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
577
|
+
| **Timeout** (timed out) | Check `/tmp/build.log` tail and output directory — build may have completed but `tee` pipe didn't flush. See Post-timeout recovery above. |
|
|
578
|
+
| **Non-zero exit code** | **STOP immediately.** The build engine explicitly rejected the output. Do NOT retry, modify source, or tweak env vars. Follow the procedure below. |
|
|
579
|
+
|
|
580
|
+
**When a build exits with non-zero exit code:**
|
|
581
|
+
|
|
582
|
+
1. **STOP** — do NOT retry, do NOT modify source code, do NOT change environment variables
|
|
583
|
+
2. **Extract the error** from `/tmp/build.log`:
|
|
584
|
+
```bash
|
|
585
|
+
tail -30 /tmp/build.log
|
|
586
|
+
```
|
|
587
|
+
3. **Present the failure to the developer** with:
|
|
588
|
+
- The app name and build command that failed
|
|
589
|
+
- The key error message (last meaningful lines from the build log)
|
|
590
|
+
- A brief diagnosis of the likely cause
|
|
591
|
+
4. **Offer fix options** (1-3 choices) and **wait for the developer to choose** before applying any fix:
|
|
592
|
+
- Never modify project source files (configs, scripts, etc.) without explicit approval
|
|
593
|
+
- If the fix requires editing source code, tell the developer what to change and where
|
|
594
|
+
5. **After the developer selects a fix**, apply it, then restart only the failed build — do NOT rebuild already-succeeded apps
|
|
595
|
+
|
|
596
|
+
| Rule | Rationale |
|
|
597
|
+
| ---------------------- | -------------------------------------------------------------- |
|
|
598
|
+
| No blind retry | Retrying without diagnosis wastes time and obscures real error |
|
|
599
|
+
| No silent source edits | Modifying project files without consent destroys trust |
|
|
600
|
+
| Developer decides fix | Different projects have different fix preferences |
|
|
601
|
+
| Only rebuild failed | Avoid redundant work in monorepo deployments |
|
|
602
|
+
|
|
603
|
+
**Monorepo-specific**: if one sub-app build fails while others succeed, report the failure immediately — do NOT delay until all builds complete. Continue other builds in parallel if possible, but notify the developer as soon as a failure is detected.
|
|
604
|
+
|
|
605
|
+
#### 4d: Fix Build Output Permissions
|
|
606
|
+
|
|
607
|
+
After a successful build, fix directory traverse permissions on the build output. Build tools (webpack, vite, uni-app) may create directories with restrictive permissions that block nginx from traversing to `index.html`. **Always resolve symlinks first** — `chmod` does not follow symlinks on Linux:
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
# Fix directory execute permissions for nginx traverse
|
|
611
|
+
REAL_OUTDIR="<outputDir>" # use actual output dir from post-build verification
|
|
612
|
+
PROJECT_ROOT="/workspace/<dirname>"
|
|
613
|
+
|
|
614
|
+
# Resolve symlinks — monorepo sub-apps may be symlinked
|
|
615
|
+
REAL_ROOT=$(readlink -f "$PROJECT_ROOT" 2>/dev/null || echo "$PROJECT_ROOT")
|
|
616
|
+
REL_OUTDIR=$(echo "$REAL_OUTDIR" | sed "s|$PROJECT_ROOT/||")
|
|
617
|
+
REAL_OUTDIR="${REAL_ROOT}/${REL_OUTDIR}"
|
|
618
|
+
|
|
619
|
+
# Fix permissions on resolved real paths
|
|
620
|
+
find "$REAL_ROOT" -path "*/${REAL_OUTDIR}" -prune -o -type d -exec chmod o+x {} \; 2>/dev/null || true
|
|
621
|
+
chmod -R o+rX "$REAL_ROOT" 2>/dev/null || true
|
|
622
|
+
chmod -R +x "$REAL_ROOT/node_modules/.bin" 2>/dev/null || true
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
This prevents the most common deployment failure: nginx 500 with `stat() ... Permission denied` caused by missing `o+x` on intermediate directories.
|
|
626
|
+
|
|
627
|
+
#### 4e: Write Deployment Fingerprint
|
|
628
|
+
|
|
629
|
+
After a successful build, write a deployment fingerprint into the output directory. This enables `deploy_check` to verify the nginx-served content belongs to the current deployment (not a stale process from an earlier session):
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
echo "deployed-$(date +%s)-<dirname>" > /workspace/<dirname>/<outputDir>/.deploy_fingerprint
|
|
633
|
+
chmod o+r /workspace/<dirname>/<outputDir>/.deploy_fingerprint 2>/dev/null || true
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
> For SSR proxy type, nginx does not serve static files from `.next` — the fingerprint check will gracefully SKIP in `deploy_check`. The fingerprint is still written as a deployment marker for debugging.
|
|
637
|
+
|
|
638
|
+
### Step 5: Port Availability Check
|
|
639
|
+
|
|
640
|
+
**Before configuring nginx or starting the app**, verify the target ports are free. Port conflicts from previous deployments cause silent failures:
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
# Check ports from framework detection
|
|
644
|
+
check_port() {
|
|
645
|
+
PORT=$1
|
|
646
|
+
# Prefer lsof (most portable), fallback to netstat, then ss
|
|
647
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
648
|
+
PID=$(lsof -ti :$PORT 2>/dev/null)
|
|
649
|
+
if [ -n "$PID" ]; then
|
|
650
|
+
echo "PORT_IN_USE:$PORT (PID=$PID)"
|
|
651
|
+
kill -9 $PID 2>/dev/null && echo "Killed PID $PID on port $PORT"
|
|
652
|
+
else
|
|
653
|
+
echo "PORT_FREE:$PORT"
|
|
654
|
+
fi
|
|
655
|
+
elif command -v netstat >/dev/null 2>&1; then
|
|
656
|
+
PID=$(netstat -tlnp 2>/dev/null | grep ":$PORT " | awk '{print $NF}' | sed 's|/.*||')
|
|
657
|
+
if [ -n "$PID" ] && [ "$PID" != "-" ]; then
|
|
658
|
+
echo "PORT_IN_USE:$PORT (PID=$PID)"
|
|
659
|
+
kill -9 $PID 2>/dev/null && echo "Killed PID $PID on port $PORT"
|
|
660
|
+
else
|
|
661
|
+
echo "PORT_FREE:$PORT"
|
|
662
|
+
fi
|
|
663
|
+
else
|
|
664
|
+
# Last resort: ss (iproute2)
|
|
665
|
+
PID=$(ss -tlnp 2>/dev/null | grep ":$PORT " | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)
|
|
666
|
+
if [ -n "$PID" ]; then
|
|
667
|
+
echo "PORT_IN_USE:$PORT (PID=$PID)"
|
|
668
|
+
kill -9 $PID 2>/dev/null && echo "Killed PID $PID on port $PORT"
|
|
669
|
+
else
|
|
670
|
+
echo "PORT_FREE:$PORT"
|
|
671
|
+
fi
|
|
672
|
+
fi
|
|
673
|
+
}
|
|
674
|
+
check_port <port>
|
|
675
|
+
# For SSR, also check the Node port
|
|
676
|
+
check_port <nodePort>
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
| Scenario | Port | Action if occupied |
|
|
680
|
+
| ---------------------- | ------------------------------------ | -------------------------------------- |
|
|
681
|
+
| SPA/SSG/Cross-platform | nginx port (from `framework.detect`) | Kill old process, then configure nginx |
|
|
682
|
+
| SSR | nginx public port + Node app port | Kill old processes on both ports |
|
|
683
|
+
|
|
684
|
+
If the port cannot be freed (different user/process), increment to the next available port: `<port>+1`, update all subsequent nginx config and DevBridge references accordingly.
|
|
685
|
+
|
|
686
|
+
#### Configure Nginx
|
|
289
687
|
|
|
290
|
-
|
|
688
|
+
Use `huaweicloud_sandbox_deploy_nginx` to write the correct template, fix directory permissions, and reload nginx — all in one call:
|
|
291
689
|
|
|
292
|
-
|
|
690
|
+
```json
|
|
691
|
+
{
|
|
692
|
+
"nginx_type": "<nginxType>",
|
|
693
|
+
"port": <port>,
|
|
694
|
+
"project": "<dirname>",
|
|
695
|
+
"output_dir": "<outputDir>",
|
|
696
|
+
"node_port": <nodePort>,
|
|
697
|
+
"public_port": <publicPort>
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
- `nginx_type` — `spa` (SPA/SSG/cross-platform), `proxy` (SSR), or `static` (Hugo/Hexo) from `detect_framework`
|
|
702
|
+
- `port` — listen port from framework detection
|
|
703
|
+
- `project` — project dir name under `/workspace`
|
|
704
|
+
- `output_dir` — build output dir relative to `/workspace/<project>`
|
|
705
|
+
- `node_port` — Node.js app port for SSR proxy. Defaults to `<port> + 1` if omitted, and the result includes `nodePort` so you know which port to bind the Node process to.
|
|
706
|
+
- `public_port` — public listen port for SSR proxy (optional, defaults to `port`)
|
|
293
707
|
|
|
294
|
-
|
|
708
|
+
The tool automatically handles:
|
|
295
709
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
710
|
+
- Writing the correct nginx template (SPA try_files, SSR reverse proxy, or static)
|
|
711
|
+
- Fixing `o+x` directory traverse permissions on the project path
|
|
712
|
+
- Reloading nginx
|
|
713
|
+
|
|
714
|
+
If the tool returns `ok: false`, nginx may not be installed — fall back to Python HTTP server (see `references/nginx-templates.md`).
|
|
715
|
+
|
|
716
|
+
**Verify nginx is serving** — curl-check the app before proceeding to DevBridge:
|
|
717
|
+
|
|
718
|
+
```bash
|
|
719
|
+
curl -s -o /dev/null -w "nginx status: %{http_code}\n" http://localhost:<port>
|
|
720
|
+
```
|
|
301
721
|
|
|
302
|
-
|
|
722
|
+
If the status code is not 2xx/3xx:
|
|
303
723
|
|
|
304
|
-
|
|
724
|
+
- **403** — run `chmod -R o+rX /workspace/<project>/<outputDir>` and re-test
|
|
725
|
+
- **000 (connection refused)** — nginx not listening: check `sudo nginx -t` for config errors
|
|
726
|
+
- **Other** — check nginx error log: `sudo tail -20 /var/log/nginx/error.log`
|
|
305
727
|
|
|
306
|
-
|
|
728
|
+
> If `curl` is unavailable, check port via `lsof -i :<port>` or `netstat -tlnp | grep :<port>`
|
|
729
|
+
|
|
730
|
+
### Step 6: Start the App [REQUIRED]
|
|
307
731
|
|
|
308
732
|
- **Static (SPA/SSG/cross-platform)**: nginx is already serving. Skip.
|
|
309
|
-
- **SSR**:
|
|
733
|
+
- **SSR**: `PORT=<nodePort>` prefix is REQUIRED before `<serveCmd>`. nginx `proxy_pass` targets `<nodePort>`, not `<port>` — the two must differ. `deployNginx` returns `nodePort` in its result (defaults to `<port> + 1` for proxy type).
|
|
734
|
+
|
|
735
|
+
**Runtime environment variables**: SSR apps often need `DATABASE_URL`, `NEXTAUTH_URL`, `NEXTAUTH_SECRET`, etc. at runtime. Before starting, verify env vars from Step 4a are still loaded, and re-source `.env` if the shell session was reset:
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
cd /workspace/<dirname>
|
|
739
|
+
# Re-load env vars (SSR apps need them at runtime, not just build time)
|
|
740
|
+
if [ -f .env ]; then set -a && source .env 2>/dev/null; set +a; echo "Loaded .env"; fi
|
|
741
|
+
if [ -f .env.local ]; then set -a && source .env.local 2>/dev/null; set +a; echo "Loaded .env.local"; fi
|
|
742
|
+
# Verify critical vars
|
|
743
|
+
echo "DATABASE_URL=${DATABASE_URL:-<NOT SET>}"
|
|
744
|
+
echo "NEXTAUTH_URL=${NEXTAUTH_URL:-<NOT SET>}"
|
|
745
|
+
```
|
|
310
746
|
|
|
311
|
-
|
|
747
|
+
Then start with `PORT=<nodePort> <serveCmd>` via `exec_with_session` to run the Node process in background.
|
|
748
|
+
|
|
749
|
+
### Step 7: Expose via DevBridge [REQUIRED — deployment incomplete without this]
|
|
312
750
|
|
|
313
751
|
Follow the standard [Expose the deployed app](#expose-the-deployed-app-public-url) procedure. The app is already running on the detected port — only DevBridge tunnel setup is needed.
|
|
314
752
|
|
|
315
753
|
Use `exec_with_session` to background DevBridge. For SSR, DevBridge tunnels the nginx public port (not the Node port directly).
|
|
316
754
|
|
|
317
|
-
|
|
755
|
+
**Pre-flight**: always run `devbridge delete-all` before creating a new tunnel to prevent `10006: quota exceeded` from accumulated stale tunnels. If you still get quota error, list tunnels with `devbridge list -j`, delete stale ones, and retry.
|
|
756
|
+
|
|
757
|
+
Extract the tunnel URL from DevBridge output. The public URL has the form `https://<id>-<port>.cn-north-4-bridge.myhuaweicloud.com`. **Return this URL to the developer as the deployment result.**
|
|
758
|
+
|
|
759
|
+
#### Cross-platform H5 QR code
|
|
760
|
+
|
|
761
|
+
**If `detect_framework` returned `type: "cross-platform"` (Taro, uni-app)**, after exposing the tunnel, generate a QR code for mobile scanning:
|
|
762
|
+
|
|
763
|
+
```bash
|
|
764
|
+
TUNNEL_URL="<extracted-tunnel-url>"
|
|
765
|
+
|
|
766
|
+
# Generate QR inside nginx-served output directory so it is accessible via the tunnel URL
|
|
767
|
+
curl -s "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$TUNNEL_URL")" -o /workspace/<dirname>/<outputDir>/qr.png
|
|
768
|
+
chmod o+r /workspace/<dirname>/<outputDir>/qr.png
|
|
769
|
+
echo "QR code URL: ${TUNNEL_URL}/qr.png"
|
|
770
|
+
echo "Desktop URL: $TUNNEL_URL"
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
**Do NOT use `qrencode -t ANSI256`** — terminal ANSI/ASCII QR codes have low precision and phone cameras cannot scan them. Also, never save the QR image outside the nginx root (e.g., `/workspace/qr.png`) — it must be inside the output directory so it is served by nginx alongside the app.
|
|
774
|
+
|
|
775
|
+
**After generating the QR code**, return both URLs to the developer:
|
|
776
|
+
|
|
777
|
+
```
|
|
778
|
+
桌面访问: <tunnel-url>
|
|
779
|
+
手机扫码: <tunnel-url>/qr.png
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
#### Deployment Completion Check [REQUIRED]
|
|
783
|
+
|
|
784
|
+
**Before reporting success, call `huaweicloud_sandbox_deploy_check`** to verify the deployment is complete:
|
|
785
|
+
|
|
786
|
+
```json
|
|
787
|
+
{
|
|
788
|
+
"port": <port>,
|
|
789
|
+
"project": "<dirname>",
|
|
790
|
+
"output_dir": "<outputDir>",
|
|
791
|
+
"framework_type": "<type>"
|
|
792
|
+
}
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
The tool checks:
|
|
796
|
+
|
|
797
|
+
- **nginx_serving** — nginx responds with 2xx/3xx on the app port
|
|
798
|
+
- **output_dir** — build output directory exists and is non-empty
|
|
799
|
+
- **devbridge_tunnel** — DevBridge tunnel is active
|
|
800
|
+
- **tunnel_url_accessible** — tunnel URL returns 200/304
|
|
801
|
+
- **qr_code** (cross-platform only) — QR image exists in output dir
|
|
802
|
+
|
|
803
|
+
Returns `complete: true/false`, `score`, and `nextStep` to fix missing items.
|
|
804
|
+
|
|
805
|
+
**If `complete` is false, follow `nextStep` to resolve before reporting success.** Do not return a deployment URL until `complete: true`.
|
|
806
|
+
|
|
807
|
+
| Framework Type | Must Return |
|
|
808
|
+
| --------------------------- | ------------------------- |
|
|
809
|
+
| `cross-platform` | Desktop URL + QR code URL |
|
|
810
|
+
| `spa` / `ssg` / `static` | Desktop URL |
|
|
811
|
+
| `ssr` | Desktop URL |
|
|
812
|
+
| `monorepo` (cross-platform) | Desktop URL + QR code URL |
|
|
318
813
|
|
|
319
|
-
|
|
814
|
+
**If the framework is cross-platform and the QR code was not generated, the deployment is incomplete — go back and generate it before reporting success.**
|
|
320
815
|
|
|
321
816
|
## References
|
|
322
817
|
|
|
@@ -325,23 +820,31 @@ Extract the tunnel URL from DevBridge output and return it to the developer. For
|
|
|
325
820
|
|
|
326
821
|
## Critical Warnings
|
|
327
822
|
|
|
328
|
-
| Trap | Why
|
|
329
|
-
| ------------------------------------ |
|
|
330
|
-
| Agreement required first | `sandbox_connect` fails if the agreement isn't signed; the `sandbox_check_user` preflight detects this, so surface it to the developer only when signing is needed
|
|
331
|
-
| Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect
|
|
332
|
-
| Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL
|
|
333
|
-
| Login needs `--huaweicloud` | `devbridge auth login --access-key/--secret-key` without `--huaweicloud` falls back to interactive browser login, which fails in the sandbox
|
|
334
|
-
| CLI PATH | The installer only writes `~/.bashrc`; run `export PATH=$PATH:$HOME/.huawei/bin` in the session before using `devbridge`
|
|
335
|
-
| Never install tunnel tooling locally | If the sandbox cannot install it, report a generic error and stop — installing on the developer's machine defeats sandbox deployment
|
|
336
|
-
| Return the deployment URL | Always hand the public URL from the host log to the developer as the final result
|
|
337
|
-
|
|
|
338
|
-
|
|
|
339
|
-
|
|
|
340
|
-
|
|
|
341
|
-
|
|
|
342
|
-
|
|
|
343
|
-
|
|
|
344
|
-
|
|
|
823
|
+
| Trap | Why |
|
|
824
|
+
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
825
|
+
| Agreement required first | `sandbox_connect` fails if the agreement isn't signed; the `sandbox_check_user` preflight detects this, so surface it to the developer only when signing is needed |
|
|
826
|
+
| Real-name required | `sandbox_connect` fails if `realnameVerified=false`; tell the developer once and stop, don't loop on connect |
|
|
827
|
+
| Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
|
|
828
|
+
| Login needs `--huaweicloud` | `devbridge auth login --access-key/--secret-key` without `--huaweicloud` falls back to interactive browser login, which fails in the sandbox |
|
|
829
|
+
| CLI PATH | The installer only writes `~/.bashrc`; run `export PATH=$PATH:$HOME/.huawei/bin` in the session before using `devbridge` |
|
|
830
|
+
| Never install tunnel tooling locally | If the sandbox cannot install it, report a generic error and stop — installing on the developer's machine defeats sandbox deployment |
|
|
831
|
+
| Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
|
|
832
|
+
| Deploy is not just nginx | Configuring nginx does NOT complete the deployment. Steps 7 (DevBridge expose) and deploy_check are REQUIRED — `deploy_nginx` returns `nextStep: expose_via_devbridge` as a reminder. Do not stop after nginx. |
|
|
833
|
+
| Call deploy_check before success | Always call `huaweicloud_sandbox_deploy_check` before reporting deployment success. A green nginx status does not mean the tunnel is accessible — verify end-to-end with the tool. |
|
|
834
|
+
| Session state persists | `exec_with_session` preserves `cd`, env vars, aliases between calls |
|
|
835
|
+
| Long commands prefer one-shot | `exec_one_shot` creates a fresh connection per call — more stable for builds, installs, and scripts >30s. See [Tool Selection Guide](#tool-selection-guide). |
|
|
836
|
+
| SSR nginx/Node ports must differ | nginx `proxy_pass` targets `<nodePort>`, not `<port>`. `deploy_nginx` auto-defaults `nodePort` to `<port>+1` — always start the Node process with `PORT=<nodePort>` to match. Same-port = EADDRINUSE. |
|
|
837
|
+
| HTTP 200 ≠ correct content | A green HTTP check does not guarantee the right project is serving — old processes from a previous session bound to the same port will still return 200. `deploy_check` verifies the deployment fingerprint to catch this. |
|
|
838
|
+
| Destructive commands blocked | `rm -rf /`, `mkfs`, `dd if=`, fork bombs are denied by safety policy |
|
|
839
|
+
| Workspace ID = dev_stage_id | Use `dev_stage_id` from `sandbox_connect` as `workspace_id` for terminal exec |
|
|
840
|
+
| Projects live in `/workspace` | Clone/install project code under `/workspace/<repo-name>` (filesystem-root workspace mount, not `$HOME/workspace`), never in `/tmp` — ephemeral locations lose the project when the sandbox session restarts |
|
|
841
|
+
| Upload project for local code | Use `sandbox_upload_project` to transfer local projects — packages as tar.gz, uploads via HTTP tunnel, extracts on sandbox. Much faster than base64 for multi-file projects |
|
|
842
|
+
| Upload file for single files | Use `sandbox_upload_file` for individual files — base64 chunked, reliable for small files (<1MB) |
|
|
843
|
+
| Node.js >= 22 required | Sandbox terminal uses built-in WebSocket (globalThis.WebSocket); if Node.js is missing, install it from the Huawei Cloud mirror (see "Node.js in the sandbox") |
|
|
844
|
+
| Sandbox restart kills processes | After sandbox restarts, all user processes (nginx, Node.js, Python servers) are stopped. Re-run startup commands and verify ports are listening before proceeding. |
|
|
845
|
+
| Cross-platform binaries incompatible | The sandbox runs Linux. Native binaries built on Windows/macOS (e.g., Prisma client, `node_modules/.prisma/`, platform-specific native addons) will not execute. Always install and build dependencies inside the sandbox, not locally. |
|
|
846
|
+
| Cross-platform needs QR code | When `detect_framework` returns `type: "cross-platform"` (Taro, uni-app), generating a QR code image is **mandatory** — the deployment is incomplete without it. Check the Deployment Completion Check table in Step 7. |
|
|
847
|
+
| Build fails do NOT auto-fix | When a build exits with non-zero exit code, STOP and present the error + fix options to the developer. Do not silently retry, modify configs, or change source files without explicit approval. See 4c-aux. |
|
|
345
848
|
|
|
346
849
|
## Node.js in the sandbox
|
|
347
850
|
|