Skip to content
Open
24 changes: 4 additions & 20 deletions cli/src/api/cvms.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
safeGetCvmList,
safeGetCvmInfo,
safeGetCvmComposeFile,
type Client,
type CvmInfoDetailV20260121,
} from "@phala/cloud";
Expand All @@ -20,7 +19,6 @@ import type {
GetCvmNetworkResponse,
TeepodResponse,
PubkeyResponse,
CvmComposeConfigResponse,
UpgradeResponse,
} from "./types";
import inquirer from "inquirer";
Expand All @@ -45,22 +43,6 @@ export async function getCvmByAppId(
return result.data;
}

/**
* Get CVM compose configuration
*/
export async function getCvmComposeConfig(
cvmId: string,
): Promise<CvmComposeConfigResponse> {
const client = await getClient();
const result = await safeGetCvmComposeFile(client, { id: cvmId });

if (!result.success) {
throw new Error(result.error.message);
}

return result.data as CvmComposeConfigResponse;
}

/**
* Get CVM network information
* @param appId App ID (with or without app_ prefix)
Expand Down Expand Up @@ -168,11 +150,13 @@ export interface ResizeCvmPayload {
}

/**
* Replicate a CVM
* Replicate a CVM instance
* @param appId App ID (with or without app_ prefix)
* @param vmUuid Source CVM UUID (with or without dashes)
*/
export async function replicateCvm(
appId: string,
vmUuid: string,
payload: {
teepod_id?: number;
encrypted_env?: string;
Expand All @@ -181,7 +165,7 @@ export async function replicateCvm(
const client = await getClient();
const cleanAppId = appId.replace(/^app_/, "");
const response = await client.post<ReplicateCvmResponse>(
`cvms/app_${cleanAppId}/replicas`,
`apps/${cleanAppId}/cvms/${vmUuid}/replicas`,
payload,
);
return replicateCvmResponseSchema.parse(response);
Expand Down
28 changes: 11 additions & 17 deletions cli/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,22 @@ export const replicateCvmResponseSchema = z.object({
id: z.number(),
name: z.string(),
}),
user_id: z.number().nullable(),
user_id: z.number().nullable().optional(),
app_id: z.string(),
vm_uuid: z.string().nullable(),
instance_id: z.string().nullable(),
app_url: z.string().nullable(),
base_image: z.string().nullable(),
vm_uuid: z.string().nullable().optional(),
instance_id: z.string().nullable().optional(),
app_url: z.string().nullable().optional(),
base_image: z.string().nullable().optional(),
vcpu: z.number(),
memory: z.number(),
disk_size: z.number(),
manifest_version: z.number().nullable(),
version: z.string().nullable(),
runner: z.string().nullable(),
docker_compose_file: z.string().nullable(),
features: z.array(z.string()).nullable(),
manifest_version: z.number().nullable().optional(),
version: z.string().nullable().optional(),
runner: z.string().nullable().optional(),
docker_compose_file: z.string().nullable().optional(),
features: z.array(z.string()).nullable().optional(),
created_at: z.string(),
encrypted_env_pubkey: z.string().nullable(),
encrypted_env_pubkey: z.string().nullable().optional(),
});

export type ReplicateCvmResponse = z.infer<typeof replicateCvmResponseSchema>;
Expand Down Expand Up @@ -309,9 +309,3 @@ export interface AvailableNodesResponse {
nodes: TEEPod[];
kms_list?: KmsListItem[];
}

// CVM Compose Config Response
export interface CvmComposeConfigResponse {
env_pubkey: string;
[key: string]: unknown;
}
47 changes: 4 additions & 43 deletions cli/src/commands/allow-devices/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import chalk from "chalk";
import inquirer from "inquirer";
import {
type Chain,
type PublicClient,
type WalletClient,
createPublicClient,
createWalletClient,
http,
} from "viem";
import { privateKeyToAccount, nonceManager } from "viem/accounts";
import {
safeGetCvmInfo,
safeGetAppDeviceAllowlist,
Expand Down Expand Up @@ -163,24 +154,6 @@ function resolvePrivateKey(input: { privateKey?: string }): `0x${string}` {
return (key.startsWith("0x") ? key : `0x${key}`) as `0x${string}`;
}

function createSharedClients(
chain: Chain,
privateKey: `0x${string}`,
rpcUrl?: string,
) {
const account = privateKeyToAccount(privateKey, { nonceManager });
const publicClient = createPublicClient({
chain,
transport: http(rpcUrl),
}) as unknown as PublicClient;
const walletClient = createWalletClient({
account,
chain,
transport: http(rpcUrl),
}) as unknown as WalletClient;
return { publicClient, walletClient };
}

async function resolveDeviceIdOrNodeName(
deviceInput: string,
): Promise<`0x${string}`> {
Expand Down Expand Up @@ -429,12 +402,6 @@ async function runAdd(
return 1;
}

const { publicClient, walletClient } = createSharedClients(
chain,
privateKey,
input.rpcUrl,
);

const results: {
deviceId: string;
txHash: string;
Expand All @@ -444,10 +411,10 @@ async function runAdd(
for (const deviceId of deviceIds) {
const result = await safeAddDevice({
chain,
rpcUrl: input.rpcUrl,
appAddress: appContractAddress,
deviceId,
walletClient,
publicClient,
privateKey,
skipPrerequisiteChecks: true,
});

Expand Down Expand Up @@ -574,12 +541,6 @@ async function runRemove(
return 1;
}

const { publicClient, walletClient } = createSharedClients(
chain,
privateKey,
input.rpcUrl,
);

const results: {
deviceId: string;
txHash: string;
Expand All @@ -589,10 +550,10 @@ async function runRemove(
for (const deviceId of deviceIds) {
const result = await safeRemoveDevice({
chain,
rpcUrl: input.rpcUrl,
appAddress: appContractAddress,
deviceId,
walletClient,
publicClient,
privateKey,
skipPrerequisiteChecks: true,
});

Expand Down
79 changes: 74 additions & 5 deletions cli/src/commands/cvms/replicate/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ export const cvmsReplicateCommandMeta: CommandMeta = {
arguments: [cvmIdArgument],
options: [
{
name: "teepod-id",
description: "TEEPod ID for replica",
name: "node-id",
description: "Node ID for replica",
type: "string",
target: "teepodId",
target: "nodeId",
},
{
name: "compose-hash",
description:
"Explicit compose hash to replicate. Required when the source app has multiple live instances",
type: "string",
target: "composeHash",
},
{
name: "env-file",
Expand All @@ -21,20 +28,82 @@ export const cvmsReplicateCommandMeta: CommandMeta = {
type: "string",
target: "envFile",
},
{
name: "private-key",
description: "Private key for signing transactions.",
type: "string",
target: "privateKey",
group: "advanced",
},
{
name: "rpc-url",
description: "RPC URL for the blockchain.",
type: "string",
target: "rpcUrl",
group: "advanced",
},
{
name: "prepare-only",
description:
"Only prepare the replica (generate commit token) without performing on-chain operations.",
type: "boolean",
target: "prepareOnly",
group: "advanced",
},
{
name: "commit",
description:
"Commit a previously prepared replica using a commit token. Requires --token and --compose-hash.",
type: "boolean",
target: "commit",
group: "advanced",
},
{
name: "token",
description: "Commit token from a prepare-only replica request",
type: "string",
target: "token",
group: "advanced",
},
{
name: "transaction-hash",
description:
"Transaction hash proving on-chain compose hash registration. Use already-registered for state-only verification.",
type: "string",
target: "transactionHash",
group: "advanced",
},
interactiveOption,
],
examples: [
{
name: "Replicate a CVM",
value: "phala cvms replicate 1234 --teepod-id 5",
value: "phala cvms replicate 1234 --node-id 5",
},
{
name: "Prepare a replica for multisig approval",
value:
"phala cvms replicate 1234 --node-id 5 --compose-hash <hash> --prepare-only",
},
{
name: "Commit a prepared replica",
value:
"phala cvms replicate 1234 --commit --token <token> --compose-hash <hash> --transaction-hash <tx-hash>",
},
],
};

export const cvmsReplicateCommandSchema = z.object({
cvmId: z.string().optional(),
teepodId: z.string().optional(),
nodeId: z.string().optional(),
composeHash: z.string().optional(),
envFile: z.string().optional(),
privateKey: z.string().optional(),
rpcUrl: z.string().optional(),
prepareOnly: z.boolean().default(false),
commit: z.boolean().default(false),
token: z.string().optional(),
transactionHash: z.string().optional(),
interactive: z.boolean().default(false),
});

Expand Down
Loading
Loading