huaweicloud-devkit 1.1.10 → 1.1.11
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -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/openclaw.plugin.json +1 -1
- package/plugins/huaweicloud-core/skills/huawei-obs/SKILL.md +17 -16
- package/plugins/huaweicloud-core/skills/huawei-obs/references/common-pitfalls.md +168 -1
- package/plugins/huaweicloud-core/skills/huawei-sandbox/SKILL.md +75 -13
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/fullstack-postgrest.md +149 -0
- package/plugins/huaweicloud-core/skills/huawei-sandbox/references/tunnel-persistence.md +121 -0
- package/plugins/huaweicloud-core/skills/huawei-voucher/SKILL.md +13 -9
- package/plugins/huaweicloud-core/src/safety-policy.mjs +6 -2
- package/plugins/huaweicloud-core/src/sandbox/hwlink-api.mjs +75 -12
- package/plugins/huaweicloud-core/src/sandbox/session-manager.mjs +396 -9
- package/plugins/huaweicloud-core/src/tools.mjs +258 -5
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/huaweicloud-devkit)
|
|
7
|
-
[](https://github.com/huaweicloud/huaweicloud-devkit)
|
|
8
8
|
|
|
9
9
|
**[中文](README.zh-CN.md) | English**
|
|
10
10
|
|
package/README.zh-CN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://github.com/huaweicloud/huaweicloud-devkit/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/huaweicloud-devkit)
|
|
7
|
-
[](https://github.com/huaweicloud/huaweicloud-devkit)
|
|
8
8
|
|
|
9
9
|
**中文 | [English](README.md)**
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"mcpName": "io.github.huaweicloud/huaweicloud-devkit",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.11",
|
|
5
5
|
"kooCliVersion": "7.3.12",
|
|
6
6
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
7
7
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.
|
|
20
|
+
"version": "1.1.11",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"description": "Agent toolkit that helps coding agents use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities safely and accurately.",
|
|
22
22
|
"skills": "./skills/",
|
|
23
|
-
"version": "1.1.
|
|
23
|
+
"version": "1.1.11",
|
|
24
24
|
"author": {
|
|
25
25
|
"name": "HuaweiCloud Mate",
|
|
26
26
|
"url": "https://github.com/huaweicloud"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"mcpServers": "./.mcp.json",
|
|
18
18
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
19
19
|
"skills": "./skills/",
|
|
20
|
-
"version": "1.1.
|
|
20
|
+
"version": "1.1.11",
|
|
21
21
|
"author": {
|
|
22
22
|
"name": "HuaweiCloud Mate",
|
|
23
23
|
"url": "https://github.com/huaweicloud"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"description": "Guide coding agents to use Huawei Cloud Skills, KooCLI, APIs, SDKs, and future MCP capabilities with safer execution and less context.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "HuaweiCloud Mate",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "huaweicloud-devkit",
|
|
3
3
|
"id": "huaweicloud-devkit",
|
|
4
4
|
"displayName": "HuaweiCloud DevKit",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.11",
|
|
6
6
|
"family": "bundle-plugin",
|
|
7
7
|
"bundleFormat": "codex",
|
|
8
8
|
"description": "Guide coding agents to use Huawei Cloud safely — KooCLI, APIs, SDKs, 28 MCP tools, skills, and safety guardrails.",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: huawei-obs
|
|
3
|
-
description: 'Use when creating, configuring, or managing OBS buckets and objects on Huawei Cloud. Covers bucket creation, lifecycle policies, versioning, static website hosting, CORS, access control (IAM/bucket policy/ACL), cross-region replication, event notifications, and presigned URLs. Triggers on: OBS, bucket, object storage, lifecycle, versioning, static website, CORS, presigned, replication. NOT for: EVS block storage (use huawei-ecs), SFS file storage, CBR backup (use huawei-cbr).'
|
|
3
|
+
description: 'Use when creating, configuring, or managing OBS buckets and objects on Huawei Cloud. Covers bucket creation, lifecycle policies, versioning, static website hosting, CORS, access control (IAM/bucket policy/ACL), cross-region replication, event notifications, and presigned URLs. Triggers on: OBS, bucket, object storage, lifecycle, versioning, static website, CORS, presigned, replication, 浏览器直传, browser direct upload, 预签名上传, 直传 OBS. NOT for: EVS block storage (use huawei-ecs), SFS file storage, CBR backup (use huawei-cbr).'
|
|
4
4
|
version: 1
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -62,21 +62,21 @@ hcloud OBS config -e=<endpoint> -i=<AK> -k=<SK> -t=token
|
|
|
62
62
|
|
|
63
63
|
## Common Workflows
|
|
64
64
|
|
|
65
|
-
| Task | Command
|
|
66
|
-
| ---------------------------- |
|
|
67
|
-
| Create bucket | `hcloud OBS mb obs://<bucket> -location=<region>`
|
|
68
|
-
| List buckets/objects | `hcloud OBS ls [obs://<bucket>]`
|
|
69
|
-
| Upload file | `hcloud OBS cp <file> obs://<bucket>/<key>`
|
|
70
|
-
| Upload directory (recursive) | `hcloud OBS cp <dir>/ obs://<bucket>/ -r -f -flat`
|
|
71
|
-
| Download object | `hcloud OBS cp obs://<bucket>/<key> <local-path>`
|
|
72
|
-
| Set bucket ACL | `hcloud OBS chattri obs://<bucket> -acl=public-read`
|
|
73
|
-
| Set object ACL | `hcloud OBS chattri obs://<bucket>/<key> -acl=public-read`
|
|
74
|
-
| Set lifecycle | `hcloud OBS lifecycle obs://<bucket> -method=put -localfile=<json>`
|
|
75
|
-
| Set bucket policy | `hcloud OBS bucketpolicy obs://<bucket> -method=put -localfile=<json>`
|
|
76
|
-
| Set CORS |
|
|
77
|
-
| Delete bucket | `hcloud OBS rm obs://<bucket> -r` (must be empty)
|
|
78
|
-
| Presigned URL | `hcloud OBS sign obs://<bucket>/<key> -e=<seconds>`
|
|
79
|
-
| Object metadata | `hcloud OBS stat obs://<bucket>/<key>`
|
|
65
|
+
| Task | Command |
|
|
66
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
67
|
+
| Create bucket | `hcloud OBS mb obs://<bucket> -location=<region>` |
|
|
68
|
+
| List buckets/objects | `hcloud OBS ls [obs://<bucket>]` |
|
|
69
|
+
| Upload file | `hcloud OBS cp <file> obs://<bucket>/<key>` |
|
|
70
|
+
| Upload directory (recursive) | `hcloud OBS cp <dir>/ obs://<bucket>/ -r -f -flat` |
|
|
71
|
+
| Download object | `hcloud OBS cp obs://<bucket>/<key> <local-path>` |
|
|
72
|
+
| Set bucket ACL | `hcloud OBS chattri obs://<bucket> -acl=public-read` |
|
|
73
|
+
| Set object ACL | `hcloud OBS chattri obs://<bucket>/<key> -acl=public-read` | Bucket ACL does NOT cascade — anonymous reads need both |
|
|
74
|
+
| Set lifecycle | `hcloud OBS lifecycle obs://<bucket> -method=put -localfile=<json>` |
|
|
75
|
+
| Set bucket policy | `hcloud OBS bucketpolicy obs://<bucket> -method=put -localfile=<json>` |
|
|
76
|
+
| Set CORS | `huaweicloud_obs_set_cors` (set/get/delete) — KooCLI OBS has no cors subcommand; the tool does AWS4-signed `PUT /?cors` internally. allowedMethod MUST include HEAD |
|
|
77
|
+
| Delete bucket | `hcloud OBS rm obs://<bucket> -r` (must be empty) |
|
|
78
|
+
| Presigned URL | `hcloud OBS sign obs://<bucket>/<key> -e=<seconds>` |
|
|
79
|
+
| Object metadata | `hcloud OBS stat obs://<bucket>/<key>` |
|
|
80
80
|
|
|
81
81
|
## Static Website Deployment Workflow
|
|
82
82
|
|
|
@@ -131,3 +131,4 @@ See `references/single-file-share.md` for the full workflow to host one file and
|
|
|
131
131
|
- Static website: references/static-website.md
|
|
132
132
|
- Single-file share: references/single-file-share.md
|
|
133
133
|
- Lifecycle: references/bucket-lifecycle.md
|
|
134
|
+
- Browser direct upload & CORS: references/common-pitfalls.md
|
|
@@ -69,7 +69,7 @@ KooCLI OBS **不支持** `SetBucketWebsite` 命令。配置静态网站托管需
|
|
|
69
69
|
|
|
70
70
|
**避坑方法**:
|
|
71
71
|
|
|
72
|
-
1. **验证网页行为必须用 GET 直连**(`curl -s -D - -o
|
|
72
|
+
1. **验证网页行为必须用 GET 直连**(`curl -s -D - -o /dev/null <url>`,Windows 下为 `-o NUL`;或浏览器)——HEAD 和代理层会给出假象
|
|
73
73
|
2. **官方解法**:绑定自定义域名接入 **CDN**,在"CDN 控制台 > 域名管理 > 高级设置 > HTTP header 配置"设置 `Content-Disposition: inline`(参见华为云 CDN 文档《如何在浏览器中在线预览 OBS 中的对象?》)
|
|
74
74
|
3. 跨云对照:阿里云 OSS 同样策略(Bucket 域名访问 HTML 强制下载,绑定自定义域名即可在线预览)
|
|
75
75
|
|
|
@@ -126,3 +126,170 @@ hcloud OBS chattri obs://<bucket>/index.html -acl=public-read
|
|
|
126
126
|
- SHOULD 开启访问日志
|
|
127
127
|
- 预签名 URL 最大 7 天有效期
|
|
128
128
|
- AK/SK 绝对不能写入 Bucket Policy
|
|
129
|
+
|
|
130
|
+
## 浏览器直传(BrowserJS SDK)
|
|
131
|
+
|
|
132
|
+
使用 OBS BrowserJS SDK(`esdk-obs-browserjs` 3.25.6)从浏览器直传 OBS 时的已知陷阱。**共享 AK/SK 的完整客户端代码严禁出现在浏览器端**——浏览器直传只允许「服务端生成预签名 URL / 临时凭证 + 浏览器携带凭证直传」模式。
|
|
133
|
+
|
|
134
|
+
### p01 · CORS AllowedMethod 缺 HEAD → 探测请求被吞
|
|
135
|
+
|
|
136
|
+
**现象**:浏览器端 `listObjects` 一律失败,DevTools 只显示 `Network Error`,无任何 OBS 错误体——因为没有 CORS 头,浏览器直接吞掉了响应。
|
|
137
|
+
|
|
138
|
+
**根因**:BrowserJS SDK 3.25.6 默认开启签名协商(`isSignatureNegotiation=!0`),在发起 listObjects 前会先向桶发一个 `HEAD ?apiversion` 探测请求。若 CORS 规则的 AllowedMethod 不含 `HEAD`,这个预检/探测请求返回 403 `AccessForbidden` 且**响应不带 CORS 头**,浏览器按同源策略丢弃整个响应,最终只呈现 `Network Error`——真实错误(AccessForbidden)被完全掩盖。
|
|
139
|
+
|
|
140
|
+
**修复**:CORS 规则的 AllowedMethod 必须包含 `HEAD`(连同 GET/PUT/POST/DELETE,完整五方法见下文「CORS 配置」章节的 JSON/XML 模板)。
|
|
141
|
+
|
|
142
|
+
### p02 · `signature:'v4'` 被静默忽略
|
|
143
|
+
|
|
144
|
+
**现象**:构造客户端时传 `signature:'v4'`,但生成的请求/预签名仍是 V2 签名,且无任何警告。
|
|
145
|
+
|
|
146
|
+
**根因**:3.25.6 的 `initFactory` 未消费 `signature` 配置项,静默降级为默认 V2。想要 V4 预签名,必须手动注入 `signatureContext`:
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
const client = new ObsClient({
|
|
150
|
+
access_key_id: '<AK>', // 临时凭证场景用 STS 临时 AK
|
|
151
|
+
secret_access_key: '<SK>',
|
|
152
|
+
server: 'https://obs.<region>.myhuaweicloud.com',
|
|
153
|
+
// signature: 'v4' 在 3.25.6 被静默忽略,不起作用 —— 见 p02
|
|
154
|
+
signatureContext: {
|
|
155
|
+
signature: 'v4',
|
|
156
|
+
headerPrefix: 'X-Amz-',
|
|
157
|
+
headerMetaPrefix: 'X-Amz-Meta-',
|
|
158
|
+
authPrefix: 'AWS4-HMAC-SHA256',
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### p03 · 预签名 URL 的 token 参数大小写陷阱
|
|
164
|
+
|
|
165
|
+
**现象**:用临时凭证(STS token)生成 V4 预签名 URL,浏览器访问返回 403 `AccessDenied`。
|
|
166
|
+
|
|
167
|
+
**根因**:SDK 3.25.6 拼预签名 query 时把安全令牌参数拼成混合大小写 `X-Amz-security-token`,而 OBS 服务端对 query 参数名**大小写敏感**,只认小写 `x-amz-security-token`,大小写不匹配 → 403 `AccessDenied`。
|
|
168
|
+
|
|
169
|
+
**修复**:不要依赖 SDK 自动拼接 token,改为在生成预签名 URL 时以**小写**参数名手动传入 QueryParams:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
const { signedUrl } = await client.createSignedUrl({
|
|
173
|
+
Method: 'PUT',
|
|
174
|
+
Bucket: '<bucket>',
|
|
175
|
+
Key: '<object-key>',
|
|
176
|
+
Expires: 3600,
|
|
177
|
+
Headers: { 'x-amz-acl': 'public-read' },
|
|
178
|
+
QueryParams: {
|
|
179
|
+
'x-amz-security-token': '<security-token>', // 必须小写!SDK 自动拼的是 X-Amz-security-token(见 p03)
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
// 浏览器侧直接 fetch(signedUrl, { method: 'PUT', body: file })
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### p04 · V4 头部签名在浏览器端不可用
|
|
186
|
+
|
|
187
|
+
**现象**:给浏览器端客户端配了 V4 signatureContext 后,listObjects / deleteObject 等头部鉴权请求全部 403。
|
|
188
|
+
|
|
189
|
+
**根因**:V4 头部签名依赖的派生/规范化逻辑在浏览器环境不完整(BrowserJS SDK 仅对**预签名 URL** 路径支持 V4)。
|
|
190
|
+
|
|
191
|
+
**修复**——双客户端分工:
|
|
192
|
+
|
|
193
|
+
- **列表/删除等 HTTP 头鉴权操作**:用 V2 头部签名客户端,并显式关闭签名协商(否则又踩 p01 的 HEAD 探测问题):
|
|
194
|
+
|
|
195
|
+
```js
|
|
196
|
+
const v2Client = new ObsClient({
|
|
197
|
+
access_key_id: '<AK>',
|
|
198
|
+
secret_access_key: '<SK>',
|
|
199
|
+
server: 'https://obs.<region>.myhuaweicloud.com',
|
|
200
|
+
is_signature_negotiation: false, // 关闭协商,跳过 HEAD ?apiversion 探测(见 p01/p04)
|
|
201
|
+
});
|
|
202
|
+
await v2Client.listObjects({ Bucket: '<bucket>' });
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- **展示/上传**:走 p02+p03 修正后的 V4 预签名 URL(`createSignedUrl`),浏览器侧用 `fetch` 直接 PUT/GET。
|
|
206
|
+
|
|
207
|
+
### p05 · OBS 错误响应缺 CORS 头 → 浏览器吞错
|
|
208
|
+
|
|
209
|
+
**现象**:任何 OBS 侧错误(AccessDenied、InvalidAccessKeyId、CORS 拒绝等)在浏览器里都只剩 `Network Error`,无法定位真实原因。
|
|
210
|
+
|
|
211
|
+
**根因**:预检失败时 OBS 的错误响应本身不带 CORS 头,浏览器同源策略直接丢弃响应体。这是浏览器行为,不是 OBS 不可用。
|
|
212
|
+
|
|
213
|
+
**诊断**(二选一,均可拿到真实状态码与错误体):
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# 方法①:curl 实测 OPTIONS 预检 —— 看 OBS 真实返回的状态码与错误体
|
|
217
|
+
curl -i -X OPTIONS 'https://<bucket>.obs.<region>.myhuaweicloud.com/<object-key>' \
|
|
218
|
+
-H 'Origin: https://<your-app-origin>' \
|
|
219
|
+
-H 'Access-Control-Request-Method: PUT' \
|
|
220
|
+
-H 'Access-Control-Request-Headers: content-type,x-amz-acl'
|
|
221
|
+
|
|
222
|
+
# 方法②:Node 环境(无 CORS 约束)复现同一请求 —— 用 node fetch 重放浏览器的完整请求
|
|
223
|
+
node -e "fetch('<same-url>', {method:'PUT', headers:{'Origin':'https://<your-app-origin>'}}).then(r=>r.text().then(t=>console.log(r.status,t)))"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
预检返回 403 `AccessForbidden` → CORS 规则缺方法/头部;返回 200 但带 `Access-Control-Allow-Origin: *` → CORS 已通,错误在别处(签名/ACL/策略)。
|
|
227
|
+
|
|
228
|
+
## CORS 配置(浏览器直传前置条件)
|
|
229
|
+
|
|
230
|
+
浏览器直传 OBS 前,桶必须先配置 CORS 规则,否则所有请求都会被预检拦截。
|
|
231
|
+
|
|
232
|
+
**KooCLI 缺口**:KooCLI 的 OBS 包装层未暴露 obsutil 的 `cors` 子命令——执行 `hcloud OBS cors` 报 `No such command`(`hcloud OBS help` 列表中也无 cors)。
|
|
233
|
+
|
|
234
|
+
**首选路径:MCP 工具 `huaweicloud_obs_set_cors`(#1003)**——内部 AWS4 签名调 OBS REST API(`PUT/GET/DELETE /?cors`),屏蔽签名细节。`action=set` 传 `rules` 数组(`allowedOrigin` / `allowedMethod[]` 必须含 HEAD / `allowedHeader[]` / `maxAgeSeconds`),工具在入参边界就拒绝缺 HEAD 的规则(p01 前置防线);set/get 成功返回体自带 `verify` 命令(OPTIONS 预检)。SDK/控制台仅作降级路径。
|
|
235
|
+
|
|
236
|
+
**降级路径 ①:REST API `PUT /?cors` + CORS XML**
|
|
237
|
+
|
|
238
|
+
向桶 endpoint 发 `PUT /?cors`,body 为 CORSConfiguration XML;请求需带 AWS4 签名(Authorization 头,可用 SDK/工具生成,或使用插件内 `huaweicloud_obs_set_website_config` 同款的 AWS4 签名方式)。`PUT /?cors` 使用的 AllowedMethod 必须覆盖浏览器直传全部五个方法:
|
|
239
|
+
|
|
240
|
+
```xml
|
|
241
|
+
<CORSConfiguration>
|
|
242
|
+
<CORSRule>
|
|
243
|
+
<AllowedOrigin>https://<your-app-origin></AllowedOrigin>
|
|
244
|
+
<AllowedMethod>GET</AllowedMethod>
|
|
245
|
+
<AllowedMethod>PUT</AllowedMethod>
|
|
246
|
+
<AllowedMethod>POST</AllowedMethod>
|
|
247
|
+
<AllowedMethod>DELETE</AllowedMethod>
|
|
248
|
+
<AllowedMethod>HEAD</AllowedMethod> <!-- 缺 HEAD 会触发 p01:SDK 的 HEAD ?apiversion 探测被拒 -->
|
|
249
|
+
<AllowedHeader>*</AllowedHeader>
|
|
250
|
+
<MaxAgeSeconds>3000</MaxAgeSeconds>
|
|
251
|
+
</CORSRule>
|
|
252
|
+
</CORSConfiguration>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**降级路径 ②:esdk-obs-python `setBucketCors`**
|
|
256
|
+
|
|
257
|
+
服务端 Python 环境最小示例(凭证从环境变量读取,勿硬编码):
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
import os
|
|
261
|
+
from obs import ObsClient
|
|
262
|
+
|
|
263
|
+
client = ObsClient(
|
|
264
|
+
access_key_id=os.environ['HUAWEICLOUD_SDK_AK'],
|
|
265
|
+
secret_access_key=os.environ['HUAWEICLOUD_SDK_SK'],
|
|
266
|
+
server='https://obs.<region>.myhuaweicloud.com',
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
client.setBucketCors(
|
|
270
|
+
'<bucket>',
|
|
271
|
+
[
|
|
272
|
+
{
|
|
273
|
+
'allowedOrigin': 'https://<your-app-origin>',
|
|
274
|
+
'allowedMethod': ['GET', 'PUT', 'POST', 'DELETE', 'HEAD'], # HEAD 必含,见 p01
|
|
275
|
+
'allowedHeader': ['*'],
|
|
276
|
+
'maxAgeSeconds': 3000,
|
|
277
|
+
}
|
|
278
|
+
],
|
|
279
|
+
)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
JSON 形态(供 MCP 工具/自研封装消费):
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
[
|
|
286
|
+
{
|
|
287
|
+
"allowedOrigin": "https://<your-app-origin>",
|
|
288
|
+
"allowedMethod": ["GET", "PUT", "POST", "DELETE", "HEAD"],
|
|
289
|
+
"allowedHeader": ["*"],
|
|
290
|
+
"maxAgeSeconds": 3000
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
配置完成后用 p05 的 curl OPTIONS 预检命令验证:预检应返回 200 且响应头含 `Access-Control-Allow-Origin`。走 `huaweicloud_obs_set_cors` 工具时,set/get 返回体的 `verify` 字段就是这条预检命令(含 `--noproxy '*'`,#1000 的注入代理陷阱同样适用于 OPTIONS 预检)。
|
|
@@ -63,6 +63,14 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
|
|
|
63
63
|
| `huaweicloud_sandbox_connect` | Connect to sandbox (one user one instance, reuses existing if available) |
|
|
64
64
|
| `huaweicloud_sandbox_credentials` | Inject temporary AK/SK into a running sandbox |
|
|
65
65
|
|
|
66
|
+
### Tunnel Persistence (public URL lifetime)
|
|
67
|
+
|
|
68
|
+
| Tool | Purpose |
|
|
69
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
70
|
+
| `huaweicloud_sandbox_expose_tunnel` | Expose a port → public tunnel URL (auth, pre-clean, health check; `expiry_hours` 1-720, def 72). **AUTO-enrolls into the keepalive watchdog** (merged into existing coverage; `keepalive: false` opts out) |
|
|
71
|
+
| `huaweicloud_sandbox_renew_tunnel` | Renew a tunnel lifetime (`update -e`, rolling window; returns absolute expiresAt) |
|
|
72
|
+
| `huaweicloud_sandbox_keepalive_watchdog` | Install/replace the 3-layer watchdog: host re-attach + daily 720h renewal + 5-min health check |
|
|
73
|
+
|
|
66
74
|
### Terminal Execution
|
|
67
75
|
|
|
68
76
|
| Tool | Purpose |
|
|
@@ -86,6 +94,8 @@ Domain expertise for Huawei Cloud Sandbox (DevStation) instances and workspace t
|
|
|
86
94
|
| Deployment scripts | `exec_one_shot+shot` | Long script, fresh connection avoids session timeouts |
|
|
87
95
|
| nginx configuration | `deploy_nginx` | Auto-generates correct template + permissions + reload |
|
|
88
96
|
| Deployment completeness check | `deploy_check` | Verifies nginx, DevBridge, URL, QR before reporting success |
|
|
97
|
+
| Tunnel lifetime renewal | `renew_tunnel` | Rolling-window renewal, same URL stays alive |
|
|
98
|
+
| URL must outlive the session | `keepalive_watchdog` | Unattended host re-attach + daily renewal + health check |
|
|
89
99
|
| Single file upload (<1MB) | `upload_file` | Base64 chunked, reliable for small files |
|
|
90
100
|
| Project directory upload (>1MB) | `upload_project` | HTTP tunnel, much faster than base64 for multi-file projects |
|
|
91
101
|
|
|
@@ -235,7 +245,7 @@ Expose a deployed web app to a public URL and return that URL to the developer.
|
|
|
235
245
|
**Check the version first.** Sandboxes created before Sep 2026 ship devbridge 0.1.13, whose hardcoded gateway (`cn-north-4-bridge.myhuaweicloud.com`) was migrated and now serves a 「服务已迁移」 placeholder page — 0.1.x can no longer connect:
|
|
236
246
|
|
|
237
247
|
```bash
|
|
238
|
-
export PATH="
|
|
248
|
+
export PATH="/workspace/.local/bin:$HOME/.huawei/bin:$PATH" # PREPEND — appending lets a stale image binary in /usr/local/bin win
|
|
239
249
|
devbridge version 2>/dev/null || echo "not installed"
|
|
240
250
|
```
|
|
241
251
|
|
|
@@ -256,39 +266,61 @@ DB_TARGET=$(grep -m1 'DEFAULT_VERSION=' /tmp/devbridge-install.sh | cut -d'"' -f
|
|
|
256
266
|
if ! bash /tmp/devbridge-install.sh -s -u "https://gitcode.com/CloudDeveloperDepartment/devbrige/releases/download/${DB_TARGET}" -v "${DB_TARGET}"; then
|
|
257
267
|
bash /tmp/devbridge-install.sh -s # fallback: GitHub (baked-in URL)
|
|
258
268
|
fi
|
|
259
|
-
export PATH="
|
|
269
|
+
export PATH="/workspace/.local/bin:$HOME/.huawei/bin:$PATH" # PREPEND (session shells do not re-source ~/.bashrc)
|
|
260
270
|
|
|
261
271
|
# Layer 3 (both installer paths failed): direct GitHub release tar.gz.
|
|
262
272
|
# Asset naming is fixed: devbridge_linux_arm64_<version>-release.tar.gz (the sandbox
|
|
263
273
|
# is Linux aarch64). If DB_TARGET is empty the installer download itself failed —
|
|
264
274
|
# resolve the latest tag from the GitHub releases API first.
|
|
275
|
+
# Install to /workspace/.local/bin — /workspace is the sandbox's DATA disk and
|
|
276
|
+
# survives reboots; ~/.huawei/bin lives on the SYSTEM disk and a reboot wipes it
|
|
277
|
+
# back to the image state (measured 2026-10-22: an overnight reboot reverted
|
|
278
|
+
# ~/.huawei/bin/devbridge 0.2.2 to the image's 0.1.13 — yesterday's success
|
|
279
|
+
# replayed as today's unexplained failure). (#999)
|
|
265
280
|
if ! command -v devbridge >/dev/null 2>&1; then
|
|
266
281
|
[ -z "$DB_TARGET" ] && DB_TARGET=$(curl -fsSL --http1.1 --retry 2 --max-time 60 \
|
|
267
282
|
https://api.github.com/repos/huaweicloud/devspace-devbridge/releases/latest | grep -m1 '"tag_name"' | cut -d'"' -f4)
|
|
268
|
-
mkdir -p
|
|
283
|
+
mkdir -p /workspace/.local/bin
|
|
269
284
|
curl -fsSL --http1.1 --retry 3 --retry-all-errors --max-time 180 \
|
|
270
285
|
"https://github.com/huaweicloud/devspace-devbridge/releases/download/${DB_TARGET}/devbridge_linux_arm64_${DB_TARGET}.tar.gz" \
|
|
271
286
|
-o /tmp/devbridge.tar.gz \
|
|
272
287
|
&& rm -rf /tmp/devbridge-pkg && mkdir -p /tmp/devbridge-pkg \
|
|
273
288
|
&& tar -xzf /tmp/devbridge.tar.gz -C /tmp/devbridge-pkg \
|
|
274
|
-
&& install -m 0755 /tmp/devbridge-pkg/devbridge
|
|
275
|
-
export PATH="
|
|
289
|
+
&& install -m 0755 /tmp/devbridge-pkg/devbridge /workspace/.local/bin/devbridge
|
|
290
|
+
export PATH="/workspace/.local/bin:$HOME/.huawei/bin:$PATH"
|
|
276
291
|
fi
|
|
277
292
|
devbridge version # must print 0.2.x-release
|
|
278
293
|
|
|
279
294
|
# Remove the stale image-installed binary so only ONE version remains.
|
|
280
|
-
# (Image sandboxes ship 0.1.x at /usr/local/bin/devbridge; the installer writes
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
if [ -
|
|
295
|
+
# (Image sandboxes ship 0.1.x at /usr/local/bin/devbridge; the installer writes
|
|
296
|
+
# /workspace/.local/bin. Compare against the explicit install path — never
|
|
297
|
+
# `command -v`, which can resolve to the stale binary itself when PATH order differs.)
|
|
298
|
+
if [ -f /usr/local/bin/devbridge ] && [ -x /workspace/.local/bin/devbridge ] && [ "$(readlink -f /usr/local/bin/devbridge)" != "$(readlink -f /workspace/.local/bin/devbridge)" ]; then
|
|
284
299
|
rm -f /usr/local/bin/devbridge && echo "stale /usr/local/bin/devbridge removed"
|
|
285
300
|
fi
|
|
301
|
+
# A leftover 0.2.x in ~/.huawei/bin (installed by older docs) is valid until the
|
|
302
|
+
# next reboot — deduplicate it against the /workspace copy so the version check
|
|
303
|
+
# above can never report the image's 0.1.x.
|
|
304
|
+
if [ -x "$HOME/.huawei/bin/devbridge" ] && [ -x /workspace/.local/bin/devbridge ] && [ "$(readlink -f "$HOME/.huawei/bin/devbridge")" != "$(readlink -f /workspace/.local/bin/devbridge)" ]; then
|
|
305
|
+
rm -f "$HOME/.huawei/bin/devbridge" && echo "stale ~/.huawei/bin/devbridge removed"
|
|
306
|
+
fi
|
|
286
307
|
```
|
|
287
308
|
|
|
288
309
|
- `-s` (silent) is **required** in the sandbox — without it the installer blocks reading `/dev/tty`.
|
|
289
310
|
- **Old tunnels do not survive the upgrade**: 0.1.x-created tunnels are not registered on the s2 gateway — always rebuild the tunnel after upgrading (the expose flow below does this via pre-cleanup).
|
|
290
311
|
- If `10006: quota exceeded` appears after upgrading, stale pre-upgrade tunnels still count against the quota — `devbridge delete-all` and retry.
|
|
291
312
|
|
|
313
|
+
### Sandbox filesystem persistence: what survives a reboot (#999)
|
|
314
|
+
|
|
315
|
+
| Path | Type | After a sandbox reboot |
|
|
316
|
+
| -------------------------------------------- | -------------- | ------------------------------------------------------------------------------------ |
|
|
317
|
+
| `/workspace` (incl. `/workspace/.local/bin`) | Data disk | **Survives** — install project code and the devbridge 0.2.x binary here |
|
|
318
|
+
| `$HOME` (incl. `~/.huawei/bin`) | System disk | **Rolled back to the image state** — an installed binary is lost, 0.1.13 returns |
|
|
319
|
+
| `/usr/local/bin` | System disk | Rolled back — the image-preinstalled 0.1.x returns |
|
|
320
|
+
| `/tmp` | Session-scoped | Cleared — `/tmp/hw_creds.sh`, `/tmp/hw_api_key`, `/tmp/tunnel-keepalive.sh` are gone |
|
|
321
|
+
|
|
322
|
+
Measured (2026-10-22): an overnight reboot reverted `~/.huawei/bin/devbridge` 0.2.2 to the image's 0.1.13 — **yesterday's successful operation replayed as today's unexplained failure**. Recovery after a reboot: re-run the Step 0 install (or just re-export the PATH to the surviving `/workspace/.local/bin`), re-run `huaweicloud_sandbox_credentials` to re-inject `/tmp` credentials, and re-run `huaweicloud_sandbox_keepalive_watchdog` to restore the keepalive loop.
|
|
323
|
+
|
|
292
324
|
### Step 1: Authenticate — verified waterfall, keyring first
|
|
293
325
|
|
|
294
326
|
Authentication tries three credential sources in order, each verified with a real login + `auth status` before use; a failure falls through to the next tier:
|
|
@@ -300,7 +332,7 @@ Authentication tries three credential sources in order, each verified with a rea
|
|
|
300
332
|
`huaweicloud_sandbox_expose_tunnel` runs this waterfall automatically and reports the winning mode (`API_KEY_KEYRING` / `AKSK_SUPPORTED` / `API_KEY_FILE`) or `NO_CREDENTIAL` with a non-sensitive per-tier `attempts` diagnostic. Manual equivalent:
|
|
301
333
|
|
|
302
334
|
```bash
|
|
303
|
-
export PATH="
|
|
335
|
+
export PATH="/workspace/.local/bin:$HOME/.huawei/bin:$PATH"
|
|
304
336
|
export HOME="${HOME:-/root}"
|
|
305
337
|
# Tier 1: platform-injected keyring key (stderr! prefix filter!)
|
|
306
338
|
export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-unix:path=/run/dbus-session}"
|
|
@@ -361,7 +393,10 @@ sleep 10 && cat /tmp/host.log
|
|
|
361
393
|
|
|
362
394
|
# 2. Extract tunnel URL and health-check before returning
|
|
363
395
|
TUNNEL_URL=$(grep -oP 'Tunnel URL: \K.*' /tmp/host.log)
|
|
364
|
-
|
|
396
|
+
# --noproxy is MANDATORY (#1000): the sandbox injects an HTTP proxy that does NOT
|
|
397
|
+
# route the tunnel domain — without it a working tunnel verifies as 502/404 and
|
|
398
|
+
# sends you chasing credentials/version/port.
|
|
399
|
+
HTTP_CODE=$(curl -s --noproxy '*' -o /dev/null -w "%{http_code}" --max-time 10 "$TUNNEL_URL" 2>/dev/null || echo "000")
|
|
365
400
|
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "304" ]; then
|
|
366
401
|
echo "Tunnel verified: $TUNNEL_URL (HTTP $HTTP_CODE)"
|
|
367
402
|
else
|
|
@@ -372,13 +407,15 @@ else
|
|
|
372
407
|
disown 2>/dev/null || true
|
|
373
408
|
sleep 10
|
|
374
409
|
TUNNEL_URL=$(grep -oP 'Tunnel URL: \K.*' /tmp/host.log)
|
|
375
|
-
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$TUNNEL_URL" 2>/dev/null || echo "000")
|
|
410
|
+
HTTP_CODE=$(curl -s --noproxy '*' -o /dev/null -w "%{http_code}" --max-time 10 "$TUNNEL_URL" 2>/dev/null || echo "000")
|
|
376
411
|
echo "Retry tunnel: $TUNNEL_URL (HTTP $HTTP_CODE)"
|
|
377
412
|
fi
|
|
378
413
|
```
|
|
379
414
|
|
|
380
415
|
**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. Note: a migrated gateway serves its placeholder page with **HTTP 200** — a bare status check is not enough when diagnosing "200 but wrong content".
|
|
381
416
|
|
|
417
|
+
**Proxy trap on verify commands (#1000)**: every curl against `*.hwtunnel.com` MUST pass `--noproxy '*'` — the sandbox injects an HTTP/HTTPS proxy that does not route the tunnel domain, so a proxied verify returns 502/404 fakes while the tunnel is actually serving. If a tunnel "fails" verification, re-test with `--noproxy '*'` BEFORE checking versions/credentials/ports (same trap on the SSH path: huawei-ecs skill references/windows-ssh.md §0.1).
|
|
418
|
+
|
|
382
419
|
**Quota recovery**: if the tunnel creation fails with `10006: quota exceeded`:
|
|
383
420
|
|
|
384
421
|
```bash
|
|
@@ -404,10 +441,35 @@ This eliminates the most common deployment failure — historical tunnels from p
|
|
|
404
441
|
|
|
405
442
|
**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.
|
|
406
443
|
|
|
444
|
+
### Step 3: Keep the URL alive (persistence & renewal)
|
|
445
|
+
|
|
446
|
+
**AUTO-ENROLLMENT**: `huaweicloud_sandbox_expose_tunnel` automatically enrolls every exposed tunnel into the keepalive watchdog (merged into existing coverage — exposing a second tunnel never drops the first one's protection; `keepalive: false` opts out). The developer's requirement — "every tunnel deployed to the sandbox must be keepalive-protected" — is satisfied by the tool itself, not by remembering to call the watchdog.
|
|
447
|
+
|
|
448
|
+
The tunnel URL is bound to the tunnel ID, not the host process — persistence is about **preventing expiry, never URL recovery**. `-e` accepts 1..720 hours (720 = the 30-day maximum); the expiry is a **rolling window** (renewal sets `expiration = now + hours` regardless of remaining time), so renewing before expiry keeps the same URL alive indefinitely.
|
|
449
|
+
|
|
450
|
+
- **Renew** (same URL, extends lifetime): `huaweicloud_sandbox_renew_tunnel` (or `devbridge update <id> -e 720`). A renewal counts as ok only when the update succeeds AND `show <id> -v` reports the absolute `tunnelExpiration` — the management plane can lose a tunnel record (`10002 tunnel not found`) while the data plane keeps serving.
|
|
451
|
+
- **Host process dies** → the watchdog re-attaches with `devbridge host <id>`; the URL does NOT change.
|
|
452
|
+
- **Manual install / replacement / repair** → `huaweicloud_sandbox_keepalive_watchdog`: installs a setsid-detached loop (host watchdog + daily 720h renewal + 5-min health check) at `/tmp/tunnel-keepalive.sh`.
|
|
453
|
+
|
|
454
|
+
Complete measured procedure and traps (OSC-escape port list, health-check URL, watchdog pgrep): see `references/tunnel-persistence.md`.
|
|
455
|
+
|
|
456
|
+
## Full-Stack: REST API over PostgreSQL (PostgREST)
|
|
457
|
+
|
|
458
|
+
When deploying a **full-stack workbench project** (前后端工作台) or asking for a database-backed REST API inside the sandbox (postgres, postgrest, 数据库, RESTful 接口, 工作台后端):
|
|
459
|
+
|
|
460
|
+
1. **PostgreSQL 16 from PGDG** — the OS repo only ships PG 13, and PostgREST 16.x requires PG ≥ 14. HCE is NOT el8-lib-compatible: hand-write the repo file (the el8 path is `rhel-8-aarch64` WITH a hyphen), install the RPMs one-per-call with `--nodeps`, and shim the sonames — including the REAL `libicu-60` package (ICU symlinks load but crash on versioned symbols).
|
|
461
|
+
2. **PostgREST `linux-static-aarch64`** — dynamic Ubuntu builds fail on HCE glibc. Create the schema and a read-only `api_user` role (the anon role gates all writes; POST/PATCH → 401).
|
|
462
|
+
3. **Nginx single-origin** — `/` serves the frontend, `/api/` reverse-proxies to PostgREST (no CORS, same origin). The frontend dir needs `chmod o+x` on the whole path chain or nginx returns 403.
|
|
463
|
+
4. **Expose + keep alive (automatic)** — `huaweicloud_sandbox_expose_tunnel` with the nginx port; it AUTO-enrolls the tunnel into the keepalive watchdog (merged into existing coverage). Prefer `expiry_hours: 720` for long-lived workbench deployments.
|
|
464
|
+
|
|
465
|
+
Complete measured procedure (with the full command chain): see `references/fullstack-postgrest.md`.
|
|
466
|
+
|
|
407
467
|
## Web Application Deployment
|
|
408
468
|
|
|
409
469
|
When deploying a web application to the sandbox, build the app inside the sandbox before exposing it. Source code is uploaded, dependencies installed, and the framework built in the sandbox environment.
|
|
410
470
|
|
|
471
|
+
**Backend present?** If the project includes a PostgreSQL + PostgREST backend (or the developer asks for one), set up the backend FIRST by following [Full-Stack: REST API over PostgreSQL](#full-stack-rest-api-over-postgresql-postgrest) — install PostgreSQL/PostgREST, create the schema, and point the frontend's nginx `/api/` location at PostgREST — then continue with the steps below. Every tunnel exposed afterwards is auto-enrolled into the keepalive watchdog.
|
|
472
|
+
|
|
411
473
|
**沙箱无 systemd(实测 #935 报告3 问题3)**:沙箱为无 systemd 的容器环境,`systemctl` 全部失效(`System has not been booted with systemd`)—— 不要用 `systemctl start/reload/restart nginx`。Nginx 配置变更直接使用 `nginx -t` 验证 + `nginx -s reload` 热加载,或直接执行 nginx 二进制(`sudo nginx` 启动 / `nginx -s stop` 停止)。`huaweicloud_sandbox_deploy_nginx` 工具返回的 nextStep 文案与该语义一致(配置写入后自动回验存在性与 `nginx -t`,通过后即生效,无需 systemctl)。
|
|
412
474
|
|
|
413
475
|
### Step 1: Detect Framework Locally
|
|
@@ -980,7 +1042,7 @@ Returns `complete: true/false`, `score`, and `nextStep` to fix missing items.
|
|
|
980
1042
|
| Never expose tunnel details | Do not mention "DevBridge"/"tunnel"/"devbridge" to the developer — say "正在生成访问地址..." and hand over only the URL |
|
|
981
1043
|
| devbridge auth: probe the build | `0.2.2-release` has two builds: image builds retain AK/SK login + env auto-read (fully automatic via `/tmp/hw_creds.sh`); release builds accept only API Key (`/tmp/hw_api_key`). Never assume — probe with `devbridge auth login --help 2>&1 \| grep -q -- '--access-key'` (Step 1 of "Expose the deployed app") |
|
|
982
1044
|
| devbridge 0.1.x is dead | Sandboxes created before Sep 2026 ship 0.1.13, which connects to a migrated gateway serving a 「服务已迁移」 placeholder with HTTP 200. Check `devbridge version` first and upgrade in place (Step 0) — old tunnels never survive the upgrade |
|
|
983
|
-
| CLI PATH | The installer only writes `~/.bashrc`; run `export PATH="
|
|
1045
|
+
| CLI PATH | The installer only writes `~/.bashrc`; run `export PATH="/workspace/.local/bin:$HOME/.huawei/bin:$PATH"` (prepend) in the session before using `devbridge` — appending lets a stale image binary in `/usr/local/bin` win. `/workspace/.local/bin` survives sandbox reboots; `~/.huawei/bin` is on the system disk and is wiped on reboot (#999) |
|
|
984
1046
|
| 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 |
|
|
985
1047
|
| Return the deployment URL | Always hand the public URL from the host log to the developer as the final result |
|
|
986
1048
|
| 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. |
|