diff --git a/single-scripts/openslaw-secret.ts b/single-scripts/openslaw-secret.ts new file mode 100755 index 0000000..3f6cc3e --- /dev/null +++ b/single-scripts/openslaw-secret.ts @@ -0,0 +1,158 @@ +#!/usr/bin/env -S deno run -A + +import { + args, + execCommand, + exit, + fetchDataWithTimeout, + getEnv, + log, + stdinToArrayBuffer, +} from "https://script.hatter.ink/@50/deno-commons-mod.ts"; +import {parseArgs} from "jsr:@std/cli/parse-args"; + +// specification: https://docs.openclaw.ai/gateway/secrets + +// input: +// { "protocolVersion": 1, "provider": "vault", "ids": ["providers/openai/apiKey"] } + +// output: +// { "protocolVersion": 1, "values": { "providers/openai/apiKey": "" } } +// or with error: +// { +// "protocolVersion": 1, +// "values": {}, +// "errors": { "providers/openai/apiKey": { "message": "not found" } } +// } + +interface OpenClawSecretInput { + protocolVersion: number; + provider: string; + ids: string[]; +} + +interface OpenClawSecretOutput { + protocolVersion: number; + values: Record; + errors: Record>; +} + +interface GetSecretResponse { + status: number; + message: string; + data: { + created: number; + modified: number; + name: string; + creatorKeyId: string; + grantedKeyIds: string[]; + comment: string; + value: string; + version: number; + }; +} + +async function getSecretValueViaHttps(key: string): Promise { + const pkcs7Response = await fetchDataWithTimeout( + "http://100.100.100.200/latest/dynamic/instance-identity/pkcs7", + ); + if (pkcs7Response.status != 200) { + throw new Error("Get PKCS#7 failed: ${pkcs7Response.status}`)"); + } + const pkcs7 = await pkcs7Response.text(); + const httpSecretResponse = await fetchDataWithTimeout( + "https://hatter.ink/secret/get.json?name=" + + encodeURIComponent(key), + { + headers: { + "Authorization": `PKCS7 ${pkcs7}`, + }, + }, + ); + if (httpSecretResponse.status != 200) { + throw new Error(`Get secret failed: ${httpSecretResponse.status}`); + } + const secretResponse = await httpSecretResponse + .json() as GetSecretResponse; + if (secretResponse.status != 200) { + throw new Error(`Get secret failed: ${secretResponse.status}`); + } + return secretResponse.data.value; +} + +async function getSecretValueViaHatterCli(key: string): Promise { + const output = await execCommand("hatter", [ + "secret", + "get", + "--name", + key, + ]); + const secretResponse = output.getStdoutAsJson() as GetSecretResponse; + console.debug("secretResponse", secretResponse); + if (secretResponse.status != 200) { + throw new Error(`Get secret failed: ${secretResponse.status}`); + } + return secretResponse.data.value; +} + +async function getSecretValue( + isOnAlibabaCloud: boolean, + key: string, +): Promise { + if (isOnAlibabaCloud) { + return await getSecretValueViaHttps(key); + } else { + return await getSecretValueViaHatterCli(key); + } +} + +const flags = parseArgs(args(), { + boolean: ["help"], +}); + +if (flags.help) { + console.log( + "export RUN_ENV=ALIBABA_CLOUD or `echo ALIBABA_CLOUD > ~/.config/envs/RUN_ENV` runs on Alibaba Cloud", + ); + console.log( + 'echo \'{"protocolVersion": 1, "provider": "vault", "ids": ["providers/openai/apiKey"]}\' | openclaw-secret.ts', + ); + exit(0); +} + +// RUN_ENV values ALIBABA_CLOUD, LOCAL +const runEnv = getEnv("RUN_ENV"); +const isOnAlibabaCloud = runEnv == "ALIBABA_CLOUD"; + +log.debug("isOnAlibabaCloud", isOnAlibabaCloud); + +const input = new TextDecoder().decode(await stdinToArrayBuffer()); +const openClawInput = JSON.parse(input) as OpenClawSecretInput; + +const values = {} as Record; +const errors = {} as Record>; + +if (openClawInput.protocolVersion !== 1) { + console.error( + `Invalid OpenClaw protocol version: ${openClawInput.protocolVersion}`, + ); + exit(1); +} + +for (const id of openClawInput.ids) { + try { + values[id] = await getSecretValue(isOnAlibabaCloud, id); + } catch (e) { + errors[id] = { + message: e.message, + }; + } +} + +const output = { + protocolVersion: 1, + values, + errors, +} as OpenClawSecretOutput; + +console.log(JSON.stringify(output, null, 2));