@supacloud/admin 0.1.12 → 0.2.0
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 +21 -0
- package/dist/index.js +310 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,3 +30,24 @@ Project commands owned by this CLI:
|
|
|
30
30
|
- `project restore`
|
|
31
31
|
- `project restart`
|
|
32
32
|
- `project update_settings`
|
|
33
|
+
|
|
34
|
+
Gateway / Caddy commands (config is injected via the Caddy JSON Admin API; requires admin privileges):
|
|
35
|
+
|
|
36
|
+
- `gateway routes` — list custom gateway routes
|
|
37
|
+
- `gateway upsert_route` — create or replace a route
|
|
38
|
+
- `gateway update_route` — replace a route by id
|
|
39
|
+
- `gateway delete_route` — remove a route by id
|
|
40
|
+
- `gateway config` — update rate-limit tier, CORS origins, or JWT settings
|
|
41
|
+
- `gateway get_certificate` / `gateway update_certificate`
|
|
42
|
+
- `gateway issue_certificate` — issue or renew with lego
|
|
43
|
+
- `gateway deploy_certificate` — deploy an existing PEM cert/key pair
|
|
44
|
+
- `gateway rebuild` — rebuild all tenant gateway configs (`--clean` for a full rebuild)
|
|
45
|
+
- `gateway custom_hostname` / `gateway set_custom_hostname` / `gateway delete_custom_hostname` / `gateway verify_custom_hostname`
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
supacloud-admin gateway routes --ref abc123
|
|
49
|
+
supacloud-admin gateway upsert_route --ref abc123 --route_id webhook \
|
|
50
|
+
--hosts "api.example.com" --paths "/webhook/*" --upstream 10.0.0.5:8080
|
|
51
|
+
supacloud-admin gateway config --ref abc123 --rate_limit_tier pro
|
|
52
|
+
supacloud-admin gateway rebuild --ref abc123 --clean
|
|
53
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -34370,6 +34370,300 @@ function registerAdminProjectCliTools(server, http) {
|
|
|
34370
34370
|
});
|
|
34371
34371
|
}
|
|
34372
34372
|
|
|
34373
|
+
// src/shared/tools/gateway-tools.ts
|
|
34374
|
+
var stringArray = exports_external.preprocess((value) => {
|
|
34375
|
+
if (value === undefined || value === null)
|
|
34376
|
+
return;
|
|
34377
|
+
if (Array.isArray(value))
|
|
34378
|
+
return value;
|
|
34379
|
+
const text = String(value).trim();
|
|
34380
|
+
if (!text)
|
|
34381
|
+
return [];
|
|
34382
|
+
if (text.startsWith("[")) {
|
|
34383
|
+
try {
|
|
34384
|
+
return JSON.parse(text);
|
|
34385
|
+
} catch {
|
|
34386
|
+
return [text];
|
|
34387
|
+
}
|
|
34388
|
+
}
|
|
34389
|
+
return text.split(",").map((item) => item.trim()).filter(Boolean);
|
|
34390
|
+
}, exports_external.array(exports_external.string()).optional());
|
|
34391
|
+
var headersRecord = exports_external.preprocess((value) => {
|
|
34392
|
+
if (value === undefined || value === null)
|
|
34393
|
+
return;
|
|
34394
|
+
if (typeof value === "object" && !Array.isArray(value))
|
|
34395
|
+
return value;
|
|
34396
|
+
const text = String(value).trim();
|
|
34397
|
+
if (!text)
|
|
34398
|
+
return;
|
|
34399
|
+
try {
|
|
34400
|
+
const parsed = JSON.parse(text);
|
|
34401
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
34402
|
+
return parsed;
|
|
34403
|
+
} catch {}
|
|
34404
|
+
const out = {};
|
|
34405
|
+
for (const part of text.split(",")) {
|
|
34406
|
+
const idx = part.indexOf(":");
|
|
34407
|
+
if (idx > 0)
|
|
34408
|
+
out[part.slice(0, idx).trim()] = part.slice(idx + 1).trim();
|
|
34409
|
+
}
|
|
34410
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
34411
|
+
}, exports_external.record(exports_external.string(), exports_external.string()).optional());
|
|
34412
|
+
var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
34413
|
+
var simple2 = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
|
|
34414
|
+
function registerGatewayTools(server, http, options = {}) {
|
|
34415
|
+
const { projectRef } = options;
|
|
34416
|
+
server.tool("gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
|
|
34417
|
+
Actions: routes, upsert_route, update_route, delete_route, config, get_certificate, update_certificate, issue_certificate, deploy_certificate, rebuild, custom_hostname, set_custom_hostname, delete_custom_hostname, verify_custom_hostname`, {
|
|
34418
|
+
action: exports_external.enum([
|
|
34419
|
+
"routes",
|
|
34420
|
+
"upsert_route",
|
|
34421
|
+
"update_route",
|
|
34422
|
+
"delete_route",
|
|
34423
|
+
"config",
|
|
34424
|
+
"get_certificate",
|
|
34425
|
+
"update_certificate",
|
|
34426
|
+
"issue_certificate",
|
|
34427
|
+
"deploy_certificate",
|
|
34428
|
+
"rebuild",
|
|
34429
|
+
"custom_hostname",
|
|
34430
|
+
"set_custom_hostname",
|
|
34431
|
+
"delete_custom_hostname",
|
|
34432
|
+
"verify_custom_hostname"
|
|
34433
|
+
]).describe("Action"),
|
|
34434
|
+
ref: exports_external.string().optional().describe(projectRef ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
|
|
34435
|
+
route_id: exports_external.string().optional().describe("[upsert_route/update_route/delete_route] 路由 ID(字母/数字/_/-,1-64)"),
|
|
34436
|
+
hosts: stringArray.describe("[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
|
|
34437
|
+
paths: stringArray.describe("[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-20)"),
|
|
34438
|
+
upstream: exports_external.string().optional().describe("[upsert_route/update_route] 反代上游 host:port 或 http(s)://host[:port]"),
|
|
34439
|
+
upstream_tls_insecure_skip_verify: exports_external.boolean().optional().describe("[upsert_route/update_route] 上游 TLS 跳过校验"),
|
|
34440
|
+
static_root: exports_external.string().optional().describe("[upsert_route/update_route] 静态站点根目录(与 upstream 二选一)"),
|
|
34441
|
+
rewrite_uri: exports_external.string().optional().describe("[upsert_route/update_route] 重写 URI(以 / 开头)"),
|
|
34442
|
+
strip_prefix: exports_external.string().optional().describe("[upsert_route/update_route] 去除前缀"),
|
|
34443
|
+
headers: headersRecord.describe("[upsert_route/update_route] 自定义请求头,JSON 或 K:V,K2:V2"),
|
|
34444
|
+
cors: stringArray.describe("[upsert_route/update_route] 额外 CORS 源,逗号分隔"),
|
|
34445
|
+
priority: exports_external.number().optional().describe("[upsert_route/update_route] 路由优先级"),
|
|
34446
|
+
enabled: exports_external.boolean().optional().describe("[upsert_route/update_route] 是否启用"),
|
|
34447
|
+
rate_limit_tier: exports_external.enum(["free", "pro", "enterprise"]).optional().describe("[config] 限流档位"),
|
|
34448
|
+
cors_origins: exports_external.string().optional().describe("[config] CORS 源(逗号分隔)"),
|
|
34449
|
+
jwt_enabled: exports_external.boolean().optional().describe("[config] 是否启用 JWT"),
|
|
34450
|
+
jwt_secret: exports_external.string().optional().describe("[config] JWT 密钥"),
|
|
34451
|
+
cert_mode: exports_external.enum(["lego", "manual"]).optional().describe("[update_certificate] 证书模式"),
|
|
34452
|
+
challenge: exports_external.enum(["dns-01", "http-01"]).optional().describe("[update_certificate/issue_certificate] ACME challenge"),
|
|
34453
|
+
email: exports_external.string().optional().describe("[update_certificate/issue_certificate] ACME 邮箱"),
|
|
34454
|
+
dns_provider: exports_external.string().optional().describe("[update_certificate/issue_certificate] DNS 提供商"),
|
|
34455
|
+
dns_env: stringArray.describe("[update_certificate/issue_certificate] DNS 环境变量 KEY=VALUE 列表"),
|
|
34456
|
+
domains: stringArray.describe("[update_certificate/issue_certificate/deploy_certificate] 域名列表"),
|
|
34457
|
+
auto_renew: exports_external.boolean().optional().describe("[update_certificate/issue_certificate] 自动续期"),
|
|
34458
|
+
renew: exports_external.boolean().optional().describe("[issue_certificate] 仅续期已有证书"),
|
|
34459
|
+
cert: exports_external.string().optional().describe("[deploy_certificate] PEM 证书内容"),
|
|
34460
|
+
key: exports_external.string().optional().describe("[deploy_certificate] PEM 私钥内容"),
|
|
34461
|
+
clean: exports_external.boolean().optional().describe("[rebuild] 清理后全量重建"),
|
|
34462
|
+
custom_hostname: exports_external.string().optional().describe("[set_custom_hostname] 自定义域名")
|
|
34463
|
+
}, async (args) => {
|
|
34464
|
+
const resolveRef3 = (override) => {
|
|
34465
|
+
const ref2 = projectRef || override;
|
|
34466
|
+
if (!ref2)
|
|
34467
|
+
throw new Error("'ref' is required for this action");
|
|
34468
|
+
return ref2;
|
|
34469
|
+
};
|
|
34470
|
+
const need = (field, value) => {
|
|
34471
|
+
if (value === undefined || value === null || value === "")
|
|
34472
|
+
throw new Error(`'${field}' is required for '${args.action}'`);
|
|
34473
|
+
};
|
|
34474
|
+
const {
|
|
34475
|
+
action,
|
|
34476
|
+
ref,
|
|
34477
|
+
route_id,
|
|
34478
|
+
hosts,
|
|
34479
|
+
paths,
|
|
34480
|
+
upstream,
|
|
34481
|
+
upstream_tls_insecure_skip_verify,
|
|
34482
|
+
static_root,
|
|
34483
|
+
rewrite_uri,
|
|
34484
|
+
strip_prefix,
|
|
34485
|
+
headers,
|
|
34486
|
+
cors,
|
|
34487
|
+
priority,
|
|
34488
|
+
enabled,
|
|
34489
|
+
rate_limit_tier,
|
|
34490
|
+
cors_origins,
|
|
34491
|
+
jwt_enabled,
|
|
34492
|
+
jwt_secret,
|
|
34493
|
+
cert_mode,
|
|
34494
|
+
challenge,
|
|
34495
|
+
email: email3,
|
|
34496
|
+
dns_provider,
|
|
34497
|
+
dns_env,
|
|
34498
|
+
domains,
|
|
34499
|
+
auto_renew,
|
|
34500
|
+
renew,
|
|
34501
|
+
cert,
|
|
34502
|
+
key,
|
|
34503
|
+
clean,
|
|
34504
|
+
custom_hostname
|
|
34505
|
+
} = args;
|
|
34506
|
+
const projectRefValue = resolveRef3(ref);
|
|
34507
|
+
let text;
|
|
34508
|
+
switch (action) {
|
|
34509
|
+
case "routes":
|
|
34510
|
+
text = ok2(await http.get(`/v1/projects/${projectRefValue}/gateway/routes`));
|
|
34511
|
+
break;
|
|
34512
|
+
case "upsert_route": {
|
|
34513
|
+
need("route_id", route_id);
|
|
34514
|
+
need("hosts", hosts);
|
|
34515
|
+
need("paths", paths);
|
|
34516
|
+
const body = {
|
|
34517
|
+
id: route_id,
|
|
34518
|
+
hosts,
|
|
34519
|
+
path: paths.length === 1 ? paths[0] : paths
|
|
34520
|
+
};
|
|
34521
|
+
if (upstream !== undefined)
|
|
34522
|
+
body.upstream = upstream;
|
|
34523
|
+
if (upstream_tls_insecure_skip_verify !== undefined)
|
|
34524
|
+
body.upstream_tls_insecure_skip_verify = upstream_tls_insecure_skip_verify;
|
|
34525
|
+
if (static_root !== undefined)
|
|
34526
|
+
body.static_root = static_root;
|
|
34527
|
+
if (rewrite_uri !== undefined)
|
|
34528
|
+
body.rewrite_uri = rewrite_uri;
|
|
34529
|
+
if (strip_prefix !== undefined)
|
|
34530
|
+
body.strip_prefix = strip_prefix;
|
|
34531
|
+
if (headers !== undefined)
|
|
34532
|
+
body.headers = headers;
|
|
34533
|
+
if (cors !== undefined)
|
|
34534
|
+
body.cors = cors;
|
|
34535
|
+
if (priority !== undefined)
|
|
34536
|
+
body.priority = priority;
|
|
34537
|
+
if (enabled !== undefined)
|
|
34538
|
+
body.enabled = enabled;
|
|
34539
|
+
text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/routes`, body));
|
|
34540
|
+
break;
|
|
34541
|
+
}
|
|
34542
|
+
case "update_route": {
|
|
34543
|
+
need("route_id", route_id);
|
|
34544
|
+
need("hosts", hosts);
|
|
34545
|
+
need("paths", paths);
|
|
34546
|
+
const body = {
|
|
34547
|
+
hosts,
|
|
34548
|
+
path: paths.length === 1 ? paths[0] : paths
|
|
34549
|
+
};
|
|
34550
|
+
if (upstream !== undefined)
|
|
34551
|
+
body.upstream = upstream;
|
|
34552
|
+
if (upstream_tls_insecure_skip_verify !== undefined)
|
|
34553
|
+
body.upstream_tls_insecure_skip_verify = upstream_tls_insecure_skip_verify;
|
|
34554
|
+
if (static_root !== undefined)
|
|
34555
|
+
body.static_root = static_root;
|
|
34556
|
+
if (rewrite_uri !== undefined)
|
|
34557
|
+
body.rewrite_uri = rewrite_uri;
|
|
34558
|
+
if (strip_prefix !== undefined)
|
|
34559
|
+
body.strip_prefix = strip_prefix;
|
|
34560
|
+
if (headers !== undefined)
|
|
34561
|
+
body.headers = headers;
|
|
34562
|
+
if (cors !== undefined)
|
|
34563
|
+
body.cors = cors;
|
|
34564
|
+
if (priority !== undefined)
|
|
34565
|
+
body.priority = priority;
|
|
34566
|
+
if (enabled !== undefined)
|
|
34567
|
+
body.enabled = enabled;
|
|
34568
|
+
text = ok2(await http.put(`/v1/projects/${projectRefValue}/gateway/routes/${route_id}`, body));
|
|
34569
|
+
break;
|
|
34570
|
+
}
|
|
34571
|
+
case "delete_route": {
|
|
34572
|
+
need("route_id", route_id);
|
|
34573
|
+
text = ok2(await http.delete(`/v1/projects/${projectRefValue}/gateway/routes/${route_id}`));
|
|
34574
|
+
break;
|
|
34575
|
+
}
|
|
34576
|
+
case "config": {
|
|
34577
|
+
const body = {};
|
|
34578
|
+
if (rate_limit_tier !== undefined)
|
|
34579
|
+
body.rate_limit_tier = rate_limit_tier;
|
|
34580
|
+
if (cors_origins !== undefined)
|
|
34581
|
+
body.cors_origins = cors_origins;
|
|
34582
|
+
if (jwt_enabled !== undefined)
|
|
34583
|
+
body.jwt_enabled = jwt_enabled;
|
|
34584
|
+
if (jwt_secret !== undefined)
|
|
34585
|
+
body.jwt_secret = jwt_secret;
|
|
34586
|
+
if (Object.keys(body).length === 0)
|
|
34587
|
+
throw new Error("At least one of rate_limit_tier, cors_origins, jwt_enabled, jwt_secret is required");
|
|
34588
|
+
text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/config`, body));
|
|
34589
|
+
break;
|
|
34590
|
+
}
|
|
34591
|
+
case "get_certificate":
|
|
34592
|
+
text = ok2(await http.get(`/v1/projects/${projectRefValue}/gateway/certificate`));
|
|
34593
|
+
break;
|
|
34594
|
+
case "update_certificate": {
|
|
34595
|
+
const body = {};
|
|
34596
|
+
if (cert_mode !== undefined)
|
|
34597
|
+
body.mode = cert_mode;
|
|
34598
|
+
if (challenge !== undefined)
|
|
34599
|
+
body.challenge = challenge;
|
|
34600
|
+
if (email3 !== undefined)
|
|
34601
|
+
body.email = email3;
|
|
34602
|
+
if (dns_provider !== undefined)
|
|
34603
|
+
body.dns_provider = dns_provider;
|
|
34604
|
+
if (dns_env !== undefined)
|
|
34605
|
+
body.dns_env = dns_env;
|
|
34606
|
+
if (domains !== undefined)
|
|
34607
|
+
body.domains = domains;
|
|
34608
|
+
if (auto_renew !== undefined)
|
|
34609
|
+
body.auto_renew = auto_renew;
|
|
34610
|
+
text = ok2(await http.put(`/v1/projects/${projectRefValue}/gateway/certificate`, body));
|
|
34611
|
+
break;
|
|
34612
|
+
}
|
|
34613
|
+
case "issue_certificate": {
|
|
34614
|
+
const body = {};
|
|
34615
|
+
if (challenge !== undefined)
|
|
34616
|
+
body.challenge = challenge;
|
|
34617
|
+
if (email3 !== undefined)
|
|
34618
|
+
body.email = email3;
|
|
34619
|
+
if (dns_provider !== undefined)
|
|
34620
|
+
body.dns_provider = dns_provider;
|
|
34621
|
+
if (dns_env !== undefined)
|
|
34622
|
+
body.dns_env = dns_env;
|
|
34623
|
+
if (domains !== undefined)
|
|
34624
|
+
body.domains = domains;
|
|
34625
|
+
if (auto_renew !== undefined)
|
|
34626
|
+
body.auto_renew = auto_renew;
|
|
34627
|
+
if (renew !== undefined)
|
|
34628
|
+
body.renew = renew;
|
|
34629
|
+
text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/certificate/issue`, body));
|
|
34630
|
+
break;
|
|
34631
|
+
}
|
|
34632
|
+
case "deploy_certificate": {
|
|
34633
|
+
need("cert", cert);
|
|
34634
|
+
need("key", key);
|
|
34635
|
+
const body = { cert, key };
|
|
34636
|
+
if (domains !== undefined)
|
|
34637
|
+
body.domains = domains;
|
|
34638
|
+
text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/certificate/deploy`, body));
|
|
34639
|
+
break;
|
|
34640
|
+
}
|
|
34641
|
+
case "rebuild": {
|
|
34642
|
+
const query = clean ? "?clean=true" : "";
|
|
34643
|
+
text = ok2(await http.post(`/v1/projects/${projectRefValue}/gateway/rebuild-all${query}`));
|
|
34644
|
+
break;
|
|
34645
|
+
}
|
|
34646
|
+
case "custom_hostname":
|
|
34647
|
+
text = ok2(await http.get(`/v1/projects/${projectRefValue}/custom-hostname`));
|
|
34648
|
+
break;
|
|
34649
|
+
case "set_custom_hostname": {
|
|
34650
|
+
need("custom_hostname", custom_hostname);
|
|
34651
|
+
text = simple2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname`, { custom_hostname }), `Custom hostname ${custom_hostname} requested`);
|
|
34652
|
+
break;
|
|
34653
|
+
}
|
|
34654
|
+
case "delete_custom_hostname":
|
|
34655
|
+
text = simple2(await http.delete(`/v1/projects/${projectRefValue}/custom-hostname`), "Custom hostname removed");
|
|
34656
|
+
break;
|
|
34657
|
+
case "verify_custom_hostname":
|
|
34658
|
+
text = ok2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname/verify`));
|
|
34659
|
+
break;
|
|
34660
|
+
default:
|
|
34661
|
+
text = `❌ Unknown action: ${action}`;
|
|
34662
|
+
}
|
|
34663
|
+
return { content: [{ type: "text", text }] };
|
|
34664
|
+
});
|
|
34665
|
+
}
|
|
34666
|
+
|
|
34373
34667
|
// src/index.ts
|
|
34374
34668
|
var adminProjectActionSchema = exports_external.enum([
|
|
34375
34669
|
"list",
|
|
@@ -34456,6 +34750,10 @@ EXAMPLES
|
|
|
34456
34750
|
supacloud-admin project create --name my-app
|
|
34457
34751
|
supacloud-admin project list
|
|
34458
34752
|
supacloud-admin platform metrics
|
|
34753
|
+
supacloud-admin gateway routes --ref abc123
|
|
34754
|
+
supacloud-admin gateway upsert_route --ref abc123 --route_id webhook --hosts "api.example.com" --paths "/webhook/*" --upstream 10.0.0.5:8080
|
|
34755
|
+
supacloud-admin gateway config --ref abc123 --rate_limit_tier pro
|
|
34756
|
+
supacloud-admin gateway rebuild --ref abc123 --clean
|
|
34459
34757
|
`);
|
|
34460
34758
|
}
|
|
34461
34759
|
function createAdminTools() {
|
|
@@ -34514,6 +34812,17 @@ function createAdminTools() {
|
|
|
34514
34812
|
]
|
|
34515
34813
|
})
|
|
34516
34814
|
};
|
|
34815
|
+
tools.gateway = {
|
|
34816
|
+
schema: { action: exports_external.string() },
|
|
34817
|
+
callback: async () => ({
|
|
34818
|
+
content: [
|
|
34819
|
+
{
|
|
34820
|
+
type: "text",
|
|
34821
|
+
text: "⚠️ Gateway / Caddy commands require SUPACLOUD_API_URL and SUPACLOUD_API_TOKEN (admin privileges)."
|
|
34822
|
+
}
|
|
34823
|
+
]
|
|
34824
|
+
})
|
|
34825
|
+
};
|
|
34517
34826
|
};
|
|
34518
34827
|
registerAdminHelp();
|
|
34519
34828
|
if (context.host) {
|
|
@@ -34532,6 +34841,7 @@ function createAdminTools() {
|
|
|
34532
34841
|
token: context.apiToken
|
|
34533
34842
|
});
|
|
34534
34843
|
Object.assign(tools, captureTools((server) => registerAdminProjectCliTools(server, http)));
|
|
34844
|
+
Object.assign(tools, captureTools((server) => registerGatewayTools(server, http)));
|
|
34535
34845
|
const advancedTools = captureTools((server) => registerAdvancedTools(server, http));
|
|
34536
34846
|
if (advancedTools.platform) {
|
|
34537
34847
|
tools.platform = advancedTools.platform;
|