@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.
@@ -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
- // Merge CLI options with provider config defaults
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
- let deployCmd = flyConfig
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
- * Redeploy an existing seller app on Fly.io.
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, config, image, dryRun } = options;
121
- const flyConfig = config || this.providerConfig?.default_config;
122
- const targetImage = image || this.providerConfig?.default_image;
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 redeploy app: ${app}`];
125
- if (targetImage)
126
- lines.push(` Image: ${targetImage}`);
127
- lines.push(` Config: ${flyConfig || "fly.toml (auto from CWD)"}`);
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
- let cmd = `${this.flyctl} deploy --app ${app} --now --yes`;
134
- if (flyConfig)
135
- cmd += ` --config ${flyConfig}`;
136
- if (targetImage)
137
- cmd += ` --image ${targetImage}`;
138
- console.log(`[Fly.io] Redeploying ${app}...`);
139
- execSync(cmd, { stdio: "inherit" });
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":"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"}
1
+ {"version":3,"file":"server-cmd.js","sourceRoot":"","sources":["../../src/server-cmd.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenbuddy/tb-admin",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Remote admin CLI for TokenBuddy seller apps",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -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
- .action(async () => {
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 ups of data.upstreams || []) {
353
- for (const model of ups.models || []) {
354
- table.push([
355
- model.id,
356
- `${model.inputPriceMicrosPer1m} micros`,
357
- `${model.outputPriceMicrosPer1m} micros`,
358
- model.streaming ? "Yes" : "No"
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
- .option("--image <image>", "Docker image name")
769
- .option("--config <path>", "fly.toml config file path")
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.config,
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("Redeploy an existing seller app on Fly.io (reads fly.toml from CWD)")
800
- .option("--config <path>", "fly.toml config file path")
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
@@ -1,3 +1,7 @@
1
+ /**
2
+ * admin CLI 调 wallet-bootstrap / seller 服务的轻量 HTTP 客户端。
3
+ * 内部封装 Bearer Token 鉴权、JSON 序列化、非 2xx 抛错的统一行为。
4
+ */
1
5
  export class AdminClient {
2
6
  private baseUrl: string;
3
7
  private token: string;
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
- // Merge CLI options with provider config defaults
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
- let deployCmd = flyConfig
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
- * Redeploy an existing seller app on Fly.io.
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, config, image, dryRun } = options;
160
- const flyConfig = config || this.providerConfig?.default_config;
161
- const targetImage = image || this.providerConfig?.default_image;
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 redeploy app: ${app}`];
165
- if (targetImage) lines.push(` Image: ${targetImage}`);
166
- lines.push(` Config: ${flyConfig || "fly.toml (auto from CWD)"}`);
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
- let cmd = `${this.flyctl} deploy --app ${app} --now --yes`;
175
- if (flyConfig) cmd += ` --config ${flyConfig}`;
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] Redeploying ${app}...`);
179
- execSync(cmd, { stdio: "inherit" });
180
- return `Successfully redeployed ${app}`;
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
  }