@tokenbuddy/tb-admin 1.0.12 → 1.0.14
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/dist/src/bootstrap-registry.d.ts +21 -0
- package/dist/src/bootstrap-registry.d.ts.map +1 -1
- package/dist/src/bootstrap-registry.js +14 -0
- package/dist/src/bootstrap-registry.js.map +1 -1
- package/dist/src/cli.d.ts +7 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +33 -20
- package/dist/src/cli.js.map +1 -1
- package/dist/src/client.d.ts +4 -0
- package/dist/src/client.d.ts.map +1 -1
- package/dist/src/client.js +4 -0
- package/dist/src/client.js.map +1 -1
- package/dist/src/config.d.ts +34 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +10 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/server-cmd.d.ts +19 -1
- package/dist/src/server-cmd.d.ts.map +1 -1
- package/dist/src/server-cmd.js +64 -25
- package/dist/src/server-cmd.js.map +1 -1
- package/package.json +1 -1
- package/src/bootstrap-registry.ts +21 -0
- package/src/cli.ts +34 -20
- package/src/client.ts +4 -0
- package/src/config.ts +34 -0
- package/src/index.ts +3 -0
- package/src/server-cmd.ts +77 -22
- package/tests/admin.test.ts +53 -1
package/dist/src/server-cmd.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
|
+
/**
|
|
3
|
+
* 检查 flyctl 是否在 PATH 中(或在指定路径)。
|
|
4
|
+
*
|
|
5
|
+
* @param flyctlPath 可选 flyctl 完整路径
|
|
6
|
+
* @returns 是否可用
|
|
7
|
+
*/
|
|
2
8
|
export function checkFlyctlInstalled(flyctlPath) {
|
|
3
9
|
try {
|
|
4
10
|
execSync(`which ${flyctlPath || "flyctl"}`, { stdio: "ignore" });
|
|
@@ -8,6 +14,35 @@ export function checkFlyctlInstalled(flyctlPath) {
|
|
|
8
14
|
return false;
|
|
9
15
|
}
|
|
10
16
|
}
|
|
17
|
+
export function parseFlyMachineIds(json, app) {
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = JSON.parse(json || "[]");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error(`fly machines list returned invalid JSON for ${app}`);
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(parsed)) {
|
|
26
|
+
throw new Error(`fly machines list returned an unexpected shape for ${app}`);
|
|
27
|
+
}
|
|
28
|
+
const ids = parsed
|
|
29
|
+
.map((item) => {
|
|
30
|
+
if (item && typeof item === "object" && "id" in item) {
|
|
31
|
+
const id = item.id;
|
|
32
|
+
return typeof id === "string" && id.length > 0 ? id : undefined;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
})
|
|
36
|
+
.filter((id) => Boolean(id));
|
|
37
|
+
if (ids.length === 0) {
|
|
38
|
+
throw new Error(`fly app ${app} has no machines to update`);
|
|
39
|
+
}
|
|
40
|
+
return ids;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Fly.io seller provider:把 admin CLI 的 `tb-admin server create` 命令转成 `flyctl` 调用。
|
|
44
|
+
* 支持 `dryRun` 模式:只打印会执行的命令而不实际修改 Fly 资源。
|
|
45
|
+
*/
|
|
11
46
|
export class FlyProvider {
|
|
12
47
|
providerConfig;
|
|
13
48
|
constructor(providerConfig) {
|
|
@@ -32,18 +67,14 @@ export class FlyProvider {
|
|
|
32
67
|
const { name, dryRun } = options;
|
|
33
68
|
// --app overrides the auto-prefixed name; allows bare "tbs-86d81e" or "tb-seller-foo"
|
|
34
69
|
const appName = options.app || `tb-seller-${name}`;
|
|
35
|
-
|
|
36
|
-
const targetImage = options.image
|
|
37
|
-
|| this.providerConfig?.default_image
|
|
38
|
-
|| "registry.fly.io/tb-seller:latest";
|
|
70
|
+
const targetImage = options.image;
|
|
39
71
|
const targetRegion = options.region
|
|
40
72
|
|| this.providerConfig?.default_region
|
|
41
73
|
|| "sin";
|
|
42
74
|
const operatorSecret = options.operatorSecret
|
|
43
75
|
|| this.providerConfig?.operator_secret
|
|
44
76
|
|| "";
|
|
45
|
-
const flyConfig = options.flyConfig
|
|
46
|
-
|| this.providerConfig?.default_config;
|
|
77
|
+
const flyConfig = options.flyConfig;
|
|
47
78
|
const volumeName = options.volumeName
|
|
48
79
|
|| this.providerConfig?.volume_name
|
|
49
80
|
|| "tb_seller_data";
|
|
@@ -52,6 +83,12 @@ export class FlyProvider {
|
|
|
52
83
|
|| 1;
|
|
53
84
|
const volumeSnapshotRetentionDays = options.volumeSnapshotRetentionDays;
|
|
54
85
|
const volumeId = options.volumeId;
|
|
86
|
+
if (!targetImage) {
|
|
87
|
+
throw new Error("seller create requires --image registry.fly.io/tb-seller:<v>");
|
|
88
|
+
}
|
|
89
|
+
if (!flyConfig) {
|
|
90
|
+
throw new Error("seller create requires --fly-config deploy/fly.io/fly.tb-seller.toml");
|
|
91
|
+
}
|
|
55
92
|
if (dryRun) {
|
|
56
93
|
const lines = [
|
|
57
94
|
`[DRY-RUN] Will create fly app: ${appName}`,
|
|
@@ -79,9 +116,7 @@ export class FlyProvider {
|
|
|
79
116
|
console.log(`[Fly.io] Setting secrets...`);
|
|
80
117
|
execSync(`${this.flyctl} secrets set ALLOW_MOCK=false OPERATOR_SECRET=${operatorSecret} --app ${appName}`, { stdio: "inherit" });
|
|
81
118
|
console.log(`[Fly.io] Deploying image ${targetImage}...`);
|
|
82
|
-
|
|
83
|
-
? `${this.flyctl} deploy -c ${flyConfig} --image ${targetImage} --region ${targetRegion} --app ${appName} --now`
|
|
84
|
-
: `${this.flyctl} deploy --image ${targetImage} --region ${targetRegion} --app ${appName} --now`;
|
|
119
|
+
const deployCmd = `${this.flyctl} deploy -c ${flyConfig} --image ${targetImage} --region ${targetRegion} --app ${appName} --now`;
|
|
85
120
|
execSync(deployCmd, { stdio: "inherit" });
|
|
86
121
|
return `Successfully deployed ${appName} on Fly.io`;
|
|
87
122
|
}
|
|
@@ -114,30 +149,34 @@ export class FlyProvider {
|
|
|
114
149
|
return execSync(`${this.flyctl} status --app ${appName}`, { encoding: "utf8" });
|
|
115
150
|
}
|
|
116
151
|
/**
|
|
117
|
-
*
|
|
152
|
+
* Update an existing seller app's Machines to a new image.
|
|
153
|
+
*
|
|
154
|
+
* This intentionally does not call `fly deploy --config`: existing seller
|
|
155
|
+
* deploys must not reconcile volumes, mounts, or service configuration.
|
|
118
156
|
*/
|
|
119
157
|
deploySeller(options) {
|
|
120
|
-
const { app,
|
|
121
|
-
const
|
|
122
|
-
|
|
158
|
+
const { app, image, dryRun } = options;
|
|
159
|
+
const targetImage = image;
|
|
160
|
+
if (!targetImage) {
|
|
161
|
+
throw new Error("seller deploy requires --image registry.fly.io/tb-seller:<v>");
|
|
162
|
+
}
|
|
123
163
|
if (dryRun) {
|
|
124
|
-
const lines = [`[DRY-RUN] Will
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
lines.push(
|
|
164
|
+
const lines = [`[DRY-RUN] Will update existing app machines: ${app}`];
|
|
165
|
+
lines.push(` Image: ${targetImage}`);
|
|
166
|
+
lines.push(" Config: unchanged");
|
|
167
|
+
lines.push(" Volumes: unchanged");
|
|
128
168
|
return lines.join("\n");
|
|
129
169
|
}
|
|
130
170
|
if (!checkFlyctlInstalled(this.flyctl)) {
|
|
131
171
|
throw new Error(`\`${this.flyctl}\` is not installed.`);
|
|
132
172
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return `Successfully redeployed ${app}`;
|
|
173
|
+
const machinesJson = execSync(`${this.flyctl} machines list --app ${app} --json`, { encoding: "utf8" });
|
|
174
|
+
const machineIds = parseFlyMachineIds(machinesJson, app);
|
|
175
|
+
console.log(`[Fly.io] Updating ${app} image on ${machineIds.length} machine(s)...`);
|
|
176
|
+
for (const machineId of machineIds) {
|
|
177
|
+
execSync(`${this.flyctl} machine update ${machineId} --app ${app} --image ${targetImage} --yes`, { stdio: "inherit" });
|
|
178
|
+
}
|
|
179
|
+
return `Successfully updated ${app} image`;
|
|
141
180
|
}
|
|
142
181
|
}
|
|
143
182
|
//# sourceMappingURL=server-cmd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-cmd.js","sourceRoot":"","sources":["../../src/server-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,MAAM,UAAU,oBAAoB,CAAC,UAAmB;IACtD,IAAI,CAAC;QACH,QAAQ,CAAC,SAAS,UAAU,IAAI,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;
|
|
1
|
+
{"version":3,"file":"server-cmd.js","sourceRoot":"","sources":["../../src/server-cmd.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* admin CLI 用的 seller 注册表条目结构。
|
|
5
|
+
* 与 wallet-bootstrap 的 `SellerRegistryEntry` 字段一致,但 `name` 是必填的(CLI 需要展示)。
|
|
6
|
+
*/
|
|
3
7
|
export interface SellerRegistryEntry {
|
|
4
8
|
id: string;
|
|
5
9
|
name: string;
|
|
@@ -15,6 +19,9 @@ export interface SellerRegistryEntry {
|
|
|
15
19
|
recommendedFor?: string[];
|
|
16
20
|
}
|
|
17
21
|
|
|
22
|
+
/**
|
|
23
|
+
* 完整 seller 注册表文档根结构。
|
|
24
|
+
*/
|
|
18
25
|
export interface SellerRegistryDocument {
|
|
19
26
|
version: number;
|
|
20
27
|
updatedAt?: string;
|
|
@@ -24,6 +31,13 @@ export interface SellerRegistryDocument {
|
|
|
24
31
|
sellers: SellerRegistryEntry[];
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
/**
|
|
35
|
+
* 校验 seller 注册表数据一致性。
|
|
36
|
+
* 校验项:`version >= 1`、sellers 非空、ID 唯一、URL `https://`、`supportedProtocols` / `paymentMethods` 非空、`defaultSeller` 命中。
|
|
37
|
+
*
|
|
38
|
+
* @param doc 待校验的注册表
|
|
39
|
+
* @throws Error 任何一条不满足
|
|
40
|
+
*/
|
|
27
41
|
export function validateRegistryDocument(doc: SellerRegistryDocument): void {
|
|
28
42
|
if (!doc.version || doc.version < 1) {
|
|
29
43
|
throw new Error("seller registry version must be >= 1");
|
|
@@ -60,6 +74,13 @@ export function validateRegistryDocument(doc: SellerRegistryDocument): void {
|
|
|
60
74
|
}
|
|
61
75
|
}
|
|
62
76
|
|
|
77
|
+
/**
|
|
78
|
+
* 从 JSON 文件加载 seller 注册表并校验。
|
|
79
|
+
*
|
|
80
|
+
* @param filePath 注册表 JSON 文件路径
|
|
81
|
+
* @returns 已校验的注册表
|
|
82
|
+
* @throws Error 文件读取 / 解析 / 校验失败
|
|
83
|
+
*/
|
|
63
84
|
export function loadRegistryFile(filePath: string): SellerRegistryDocument {
|
|
64
85
|
const content = fs.readFileSync(filePath, "utf8");
|
|
65
86
|
const doc = JSON.parse(content) as SellerRegistryDocument;
|
package/src/cli.ts
CHANGED
|
@@ -142,6 +142,20 @@ function loadYamlOrJsonFile(filePath: string): any {
|
|
|
142
142
|
return YAML.load(fs.readFileSync(filePath, "utf8"));
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
function requireUpstreamModels(data: any): any[] {
|
|
146
|
+
if (!Array.isArray(data?.models)) {
|
|
147
|
+
throw new Error("operator upstream summary must contain top-level models array");
|
|
148
|
+
}
|
|
149
|
+
return data.models;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 构造 admin commander program,绑定所有 `tb-admin` 子命令(profile / registry / seller / config / backup 等)。
|
|
154
|
+
* 顶层选项支持 `--url` / `--token` / `--profile` / `--config`,与 `ConfigManager` 协同解析。
|
|
155
|
+
*
|
|
156
|
+
* @param configManager 配置管理器
|
|
157
|
+
* @returns 配置完整的 commander Command 实例
|
|
158
|
+
*/
|
|
145
159
|
export function buildAdminCli(configManager: ConfigManager): Command {
|
|
146
160
|
const program = new Command();
|
|
147
161
|
program
|
|
@@ -343,26 +357,31 @@ export function buildAdminCli(configManager: ConfigManager): Command {
|
|
|
343
357
|
program
|
|
344
358
|
.command("models")
|
|
345
359
|
.description("List available upstream models")
|
|
346
|
-
.
|
|
360
|
+
.option("--json", "Print current upstream model summary as JSON")
|
|
361
|
+
.action(async (options) => {
|
|
347
362
|
try {
|
|
348
363
|
const client = getClient();
|
|
349
364
|
const data = await client.get("/operator/admin/upstreams");
|
|
365
|
+
const models = requireUpstreamModels(data);
|
|
366
|
+
if (options.json) {
|
|
367
|
+
console.log(JSON.stringify({ models }, null, 2));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
350
370
|
const table = new Table({ head: ["Model ID", "Input Price/1M", "Output Price/1M", "Streaming"] });
|
|
351
371
|
|
|
352
|
-
for (const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
]);
|
|
360
|
-
}
|
|
372
|
+
for (const model of models) {
|
|
373
|
+
table.push([
|
|
374
|
+
model.id,
|
|
375
|
+
`${model.inputPriceMicrosPer1m ?? "unknown"} micros`,
|
|
376
|
+
`${model.outputPriceMicrosPer1m ?? "unknown"} micros`,
|
|
377
|
+
model.streaming ? "Yes" : "No"
|
|
378
|
+
]);
|
|
361
379
|
}
|
|
362
380
|
console.log("=== Upstream Model Configurations ===");
|
|
363
381
|
console.log(table.toString());
|
|
364
382
|
} catch (err: any) {
|
|
365
383
|
console.error("Error:", err.message);
|
|
384
|
+
process.exit(1);
|
|
366
385
|
}
|
|
367
386
|
});
|
|
368
387
|
|
|
@@ -412,9 +431,6 @@ export function buildAdminCli(configManager: ConfigManager): Command {
|
|
|
412
431
|
console.log("Auto-refreshing upstream model catalog (upstream URL or key changed)...");
|
|
413
432
|
const refreshResp = await client.post("/operator/admin/upstreams/refresh", { autoModels: true });
|
|
414
433
|
console.log(` refreshed: ${refreshResp.refreshedModels ?? 0} models from upstream`);
|
|
415
|
-
if (refreshResp.upstreams && refreshResp.upstreams[0]?.error) {
|
|
416
|
-
throw new Error(`upstream refresh failed: ${refreshResp.upstreams[0].error}`);
|
|
417
|
-
}
|
|
418
434
|
}
|
|
419
435
|
|
|
420
436
|
const document = await getSellerConfig(client);
|
|
@@ -765,8 +781,8 @@ export function buildAdminCli(configManager: ConfigManager): Command {
|
|
|
765
781
|
.description("Deploy a new machines instance on Fly.io")
|
|
766
782
|
.option("--app <app>", "Fly.io app name (bypasses tb-seller- prefix; use tbs-<random> format)")
|
|
767
783
|
.option("--region <region>", "Fly region (default: sin)", "sin")
|
|
768
|
-
.
|
|
769
|
-
.
|
|
784
|
+
.requiredOption("--image <image>", "Published Docker image, for example registry.fly.io/tb-seller:<v>")
|
|
785
|
+
.requiredOption("--fly-config <path>", "Fly.io config file path, for example deploy/fly.io/fly.tb-seller.toml")
|
|
770
786
|
.option("--volume-name <name>", "Persistent volume name")
|
|
771
787
|
.option("--volume-size-gb <gb>", "Persistent volume size in GB", (v) => parseInt(v, 10))
|
|
772
788
|
.option("--volume-id <id>", "Attach existing volume by ID (skips volume creation)")
|
|
@@ -780,7 +796,7 @@ export function buildAdminCli(configManager: ConfigManager): Command {
|
|
|
780
796
|
app: options.app,
|
|
781
797
|
region: options.region,
|
|
782
798
|
image: options.image,
|
|
783
|
-
flyConfig: options.
|
|
799
|
+
flyConfig: options.flyConfig,
|
|
784
800
|
volumeName: options.volumeName,
|
|
785
801
|
volumeSizeGb: options.volumeSizeGb,
|
|
786
802
|
volumeId: options.volumeId,
|
|
@@ -796,15 +812,13 @@ export function buildAdminCli(configManager: ConfigManager): Command {
|
|
|
796
812
|
|
|
797
813
|
sellerCmd
|
|
798
814
|
.command("deploy <app>")
|
|
799
|
-
.description("
|
|
800
|
-
.
|
|
801
|
-
.option("--image <image>", "Docker image name")
|
|
815
|
+
.description("Update an existing seller app's Machines to an explicit image without changing Fly.io config or volumes")
|
|
816
|
+
.requiredOption("--image <image>", "Published Docker image, for example registry.fly.io/tb-seller:<v>")
|
|
802
817
|
.option("--dry-run", "Dry run")
|
|
803
818
|
.action((app, options) => {
|
|
804
819
|
try {
|
|
805
820
|
const res = flyProvider.deploySeller({
|
|
806
821
|
app,
|
|
807
|
-
config: options.config,
|
|
808
822
|
image: options.image,
|
|
809
823
|
dryRun: options.dryRun
|
|
810
824
|
});
|
package/src/client.ts
CHANGED
package/src/config.ts
CHANGED
|
@@ -3,36 +3,70 @@ import * as path from "path";
|
|
|
3
3
|
import * as os from "os";
|
|
4
4
|
import TOML from "@iarna/toml";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* admin CLI 单个 profile:远程 wallet-bootstrap / seller 服务的 URL + 鉴权 token。
|
|
8
|
+
*/
|
|
6
9
|
export interface AdminProfile {
|
|
10
|
+
/** 服务 base URL(如 `https://bootstrap.example.com`) */
|
|
7
11
|
url: string;
|
|
12
|
+
/** 鉴权 token(脱敏写入配置文件) */
|
|
8
13
|
token: string;
|
|
9
14
|
}
|
|
10
15
|
|
|
16
|
+
/**
|
|
17
|
+
* seller provider(如 Fly.io)的配置。
|
|
18
|
+
* 字段命名采用 snake_case 匹配 TOML 习惯。
|
|
19
|
+
*/
|
|
11
20
|
export interface SellerProviderConfig {
|
|
21
|
+
/** Fly.io API token */
|
|
12
22
|
token?: string;
|
|
23
|
+
/** flyctl 可执行路径(默认从 PATH 解析) */
|
|
13
24
|
flyctl_path?: string;
|
|
25
|
+
/** 默认 seller app 名称(创建新 seller 时复用) */
|
|
14
26
|
default_app?: string;
|
|
27
|
+
/** 默认部署 region(如 `sin` / `nrt`) */
|
|
15
28
|
default_region?: string;
|
|
29
|
+
/** 默认 Docker 镜像 */
|
|
16
30
|
default_image?: string;
|
|
31
|
+
/** 默认 seller config YAML 路径 */
|
|
17
32
|
default_config?: string;
|
|
33
|
+
/** seller operator 接口的 secret(写盘 / 重启用) */
|
|
18
34
|
operator_secret?: string;
|
|
35
|
+
/** 持久化 volume 名 */
|
|
19
36
|
volume_name?: string;
|
|
37
|
+
/** volume 容量(GB) */
|
|
20
38
|
volume_size_gb?: number;
|
|
39
|
+
/** 已存在的 volume ID(attach 用) */
|
|
21
40
|
volume_id?: string;
|
|
41
|
+
/** volume snapshot 保留天数 */
|
|
22
42
|
volume_snapshot_retention_days?: number;
|
|
23
43
|
}
|
|
24
44
|
|
|
45
|
+
/**
|
|
46
|
+
* admin CLI 配置根结构。
|
|
47
|
+
* `default_profile` 缺省时使用第一个 profile。
|
|
48
|
+
*/
|
|
25
49
|
export interface AdminConfig {
|
|
26
50
|
default_profile?: string;
|
|
27
51
|
profiles: { [name: string]: AdminProfile };
|
|
28
52
|
seller_providers?: { [name: string]: SellerProviderConfig };
|
|
29
53
|
}
|
|
30
54
|
|
|
55
|
+
/**
|
|
56
|
+
* 解析 admin CLI 的默认配置路径:`~/.config/tokenbuddy/admin.toml`。
|
|
57
|
+
* 兼容旧版 `~/.tokenbuddy/admin.json`(legacy JSON),由 `ConfigManager` 自动识别。
|
|
58
|
+
*
|
|
59
|
+
* @returns 配置文件绝对路径
|
|
60
|
+
*/
|
|
31
61
|
export function getDefaultConfigPath(): string {
|
|
32
62
|
const home = os.homedir();
|
|
33
63
|
return path.join(home, ".config", "tokenbuddy", "admin.toml");
|
|
34
64
|
}
|
|
35
65
|
|
|
66
|
+
/**
|
|
67
|
+
* admin CLI 配置的读写管理器(TOML 优先,legacy JSON 兼容)。
|
|
68
|
+
* 写盘时自动 chmod 0600(best effort)保护含 token 的配置文件。
|
|
69
|
+
*/
|
|
36
70
|
export class ConfigManager {
|
|
37
71
|
private configPath: string;
|
|
38
72
|
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ConfigManager } from "./config.js";
|
|
2
2
|
import { buildAdminCli } from "./cli.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* admin CLI 入口:构造 `ConfigManager`、绑定 commander program、同步解析 argv。
|
|
6
|
+
*/
|
|
4
7
|
export function run() {
|
|
5
8
|
const configManager = new ConfigManager();
|
|
6
9
|
const program = buildAdminCli(configManager);
|
package/src/server-cmd.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { execSync } from "child_process";
|
|
2
2
|
import { SellerProviderConfig } from "./config.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* 检查 flyctl 是否在 PATH 中(或在指定路径)。
|
|
6
|
+
*
|
|
7
|
+
* @param flyctlPath 可选 flyctl 完整路径
|
|
8
|
+
* @returns 是否可用
|
|
9
|
+
*/
|
|
4
10
|
export function checkFlyctlInstalled(flyctlPath?: string): boolean {
|
|
5
11
|
try {
|
|
6
12
|
execSync(`which ${flyctlPath || "flyctl"}`, { stdio: "ignore" });
|
|
@@ -10,6 +16,39 @@ export function checkFlyctlInstalled(flyctlPath?: string): boolean {
|
|
|
10
16
|
}
|
|
11
17
|
}
|
|
12
18
|
|
|
19
|
+
export function parseFlyMachineIds(json: string, app: string): string[] {
|
|
20
|
+
let parsed: unknown;
|
|
21
|
+
try {
|
|
22
|
+
parsed = JSON.parse(json || "[]");
|
|
23
|
+
} catch {
|
|
24
|
+
throw new Error(`fly machines list returned invalid JSON for ${app}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!Array.isArray(parsed)) {
|
|
28
|
+
throw new Error(`fly machines list returned an unexpected shape for ${app}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const ids = parsed
|
|
32
|
+
.map((item) => {
|
|
33
|
+
if (item && typeof item === "object" && "id" in item) {
|
|
34
|
+
const id = (item as { id?: unknown }).id;
|
|
35
|
+
return typeof id === "string" && id.length > 0 ? id : undefined;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
})
|
|
39
|
+
.filter((id): id is string => Boolean(id));
|
|
40
|
+
|
|
41
|
+
if (ids.length === 0) {
|
|
42
|
+
throw new Error(`fly app ${app} has no machines to update`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return ids;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* `FlyProvider.createSeller` 的输入。
|
|
50
|
+
* 字段未提供时回退到 `SellerProviderConfig` 的默认值。
|
|
51
|
+
*/
|
|
13
52
|
export interface SellerCreateOptions {
|
|
14
53
|
name: string;
|
|
15
54
|
app?: string;
|
|
@@ -24,6 +63,10 @@ export interface SellerCreateOptions {
|
|
|
24
63
|
dryRun?: boolean;
|
|
25
64
|
}
|
|
26
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Fly.io seller provider:把 admin CLI 的 `tb-admin server create` 命令转成 `flyctl` 调用。
|
|
68
|
+
* 支持 `dryRun` 模式:只打印会执行的命令而不实际修改 Fly 资源。
|
|
69
|
+
*/
|
|
27
70
|
export class FlyProvider {
|
|
28
71
|
private providerConfig?: SellerProviderConfig;
|
|
29
72
|
|
|
@@ -53,18 +96,14 @@ export class FlyProvider {
|
|
|
53
96
|
// --app overrides the auto-prefixed name; allows bare "tbs-86d81e" or "tb-seller-foo"
|
|
54
97
|
const appName = options.app || `tb-seller-${name}`;
|
|
55
98
|
|
|
56
|
-
|
|
57
|
-
const targetImage = options.image
|
|
58
|
-
|| this.providerConfig?.default_image
|
|
59
|
-
|| "registry.fly.io/tb-seller:latest";
|
|
99
|
+
const targetImage = options.image;
|
|
60
100
|
const targetRegion = options.region
|
|
61
101
|
|| this.providerConfig?.default_region
|
|
62
102
|
|| "sin";
|
|
63
103
|
const operatorSecret = options.operatorSecret
|
|
64
104
|
|| this.providerConfig?.operator_secret
|
|
65
105
|
|| "";
|
|
66
|
-
const flyConfig = options.flyConfig
|
|
67
|
-
|| this.providerConfig?.default_config;
|
|
106
|
+
const flyConfig = options.flyConfig;
|
|
68
107
|
const volumeName = options.volumeName
|
|
69
108
|
|| this.providerConfig?.volume_name
|
|
70
109
|
|| "tb_seller_data";
|
|
@@ -74,6 +113,13 @@ export class FlyProvider {
|
|
|
74
113
|
const volumeSnapshotRetentionDays = options.volumeSnapshotRetentionDays;
|
|
75
114
|
const volumeId = options.volumeId;
|
|
76
115
|
|
|
116
|
+
if (!targetImage) {
|
|
117
|
+
throw new Error("seller create requires --image registry.fly.io/tb-seller:<v>");
|
|
118
|
+
}
|
|
119
|
+
if (!flyConfig) {
|
|
120
|
+
throw new Error("seller create requires --fly-config deploy/fly.io/fly.tb-seller.toml");
|
|
121
|
+
}
|
|
122
|
+
|
|
77
123
|
if (dryRun) {
|
|
78
124
|
const lines = [
|
|
79
125
|
`[DRY-RUN] Will create fly app: ${appName}`,
|
|
@@ -106,9 +152,7 @@ export class FlyProvider {
|
|
|
106
152
|
);
|
|
107
153
|
|
|
108
154
|
console.log(`[Fly.io] Deploying image ${targetImage}...`);
|
|
109
|
-
|
|
110
|
-
? `${this.flyctl} deploy -c ${flyConfig} --image ${targetImage} --region ${targetRegion} --app ${appName} --now`
|
|
111
|
-
: `${this.flyctl} deploy --image ${targetImage} --region ${targetRegion} --app ${appName} --now`;
|
|
155
|
+
const deployCmd = `${this.flyctl} deploy -c ${flyConfig} --image ${targetImage} --region ${targetRegion} --app ${appName} --now`;
|
|
112
156
|
execSync(deployCmd, { stdio: "inherit" });
|
|
113
157
|
|
|
114
158
|
return `Successfully deployed ${appName} on Fly.io`;
|
|
@@ -148,7 +192,10 @@ export class FlyProvider {
|
|
|
148
192
|
}
|
|
149
193
|
|
|
150
194
|
/**
|
|
151
|
-
*
|
|
195
|
+
* Update an existing seller app's Machines to a new image.
|
|
196
|
+
*
|
|
197
|
+
* This intentionally does not call `fly deploy --config`: existing seller
|
|
198
|
+
* deploys must not reconcile volumes, mounts, or service configuration.
|
|
152
199
|
*/
|
|
153
200
|
public deploySeller(options: {
|
|
154
201
|
app: string;
|
|
@@ -156,14 +203,18 @@ export class FlyProvider {
|
|
|
156
203
|
image?: string;
|
|
157
204
|
dryRun?: boolean;
|
|
158
205
|
}): string {
|
|
159
|
-
const { app,
|
|
160
|
-
const
|
|
161
|
-
|
|
206
|
+
const { app, image, dryRun } = options;
|
|
207
|
+
const targetImage = image;
|
|
208
|
+
|
|
209
|
+
if (!targetImage) {
|
|
210
|
+
throw new Error("seller deploy requires --image registry.fly.io/tb-seller:<v>");
|
|
211
|
+
}
|
|
162
212
|
|
|
163
213
|
if (dryRun) {
|
|
164
|
-
const lines = [`[DRY-RUN] Will
|
|
165
|
-
|
|
166
|
-
lines.push(
|
|
214
|
+
const lines = [`[DRY-RUN] Will update existing app machines: ${app}`];
|
|
215
|
+
lines.push(` Image: ${targetImage}`);
|
|
216
|
+
lines.push(" Config: unchanged");
|
|
217
|
+
lines.push(" Volumes: unchanged");
|
|
167
218
|
return lines.join("\n");
|
|
168
219
|
}
|
|
169
220
|
|
|
@@ -171,12 +222,16 @@ export class FlyProvider {
|
|
|
171
222
|
throw new Error(`\`${this.flyctl}\` is not installed.`);
|
|
172
223
|
}
|
|
173
224
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (targetImage) cmd += ` --image ${targetImage}`;
|
|
225
|
+
const machinesJson = execSync(`${this.flyctl} machines list --app ${app} --json`, { encoding: "utf8" });
|
|
226
|
+
const machineIds = parseFlyMachineIds(machinesJson, app);
|
|
177
227
|
|
|
178
|
-
console.log(`[Fly.io]
|
|
179
|
-
|
|
180
|
-
|
|
228
|
+
console.log(`[Fly.io] Updating ${app} image on ${machineIds.length} machine(s)...`);
|
|
229
|
+
for (const machineId of machineIds) {
|
|
230
|
+
execSync(
|
|
231
|
+
`${this.flyctl} machine update ${machineId} --app ${app} --image ${targetImage} --yes`,
|
|
232
|
+
{ stdio: "inherit" }
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return `Successfully updated ${app} image`;
|
|
181
236
|
}
|
|
182
237
|
}
|