Files
elizaos--eliza/packages/scripts/cloud/admin/sync-api-dev-vars.ts
T
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

282 lines
9.1 KiB
TypeScript

#!/usr/bin/env bun
// Drives cloud admin cloud admin sync api dev vars automation with explicit environment and CI invariants.
import crypto from "node:crypto";
import { mkdirSync, writeFileSync } from "node:fs";
import path from "node:path";
import { getLocalPGliteDatabaseUrl } from "../../../cloud/shared/src/db/database-url";
import {
generateJwtSigningKeys,
isPlaceholderValue,
parseEnvFile,
} from "./local-dev-helpers";
const repoRoot = path.resolve(import.meta.dir, "..", "..", "..", "..");
const cloudRoot = path.join(repoRoot, "packages", "cloud", "shared");
const apiDir = path.join(repoRoot, "packages", "cloud", "api");
const outputPath = path.join(apiDir, ".dev.vars");
const envExamplePath = path.join(cloudRoot, ".env.example");
const localAppUrl =
process.env.ELIZA_CLOUD_LOCAL_APP_URL ?? "http://localhost:3000";
const localApiUrl =
process.env.ELIZA_CLOUD_LOCAL_API_URL ?? "http://localhost:8787";
function quoteDevVarValue(value: string): string {
return JSON.stringify(value);
}
function mergeRealEnvValues(
target: Record<string, string>,
source: Record<string, string>,
): void {
for (const [key, value] of Object.entries(source)) {
if (isRealValue(value)) {
target[key] = value;
}
}
}
function isRealValue(value: string | undefined): value is string {
return (
typeof value === "string" &&
value.trim().length > 0 &&
!isPlaceholderValue(value)
);
}
function ensureLocalSharedSecret(key: string): void {
if (isRealValue(env[key])) return;
if (isRealValue(process.env[key])) {
env[key] = process.env[key];
return;
}
if (isRealValue(existingDevVars[key])) {
env[key] = existingDevVars[key];
return;
}
env[key] = crypto.randomBytes(32).toString("hex");
}
function mirrorEnvValue(targetKey: string, sourceKeys: string[]): void {
if (isRealValue(env[targetKey])) return;
for (const sourceKey of sourceKeys) {
if (isRealValue(env[sourceKey])) {
env[targetKey] = env[sourceKey];
return;
}
}
}
function mirrorDerivedValue(
targetKey: string,
derive: () => string | undefined,
): void {
if (isRealValue(env[targetKey])) return;
const value = derive();
if (isRealValue(value)) {
env[targetKey] = value;
}
}
function joinUrl(
baseUrl: string | undefined,
pathname: string,
): string | undefined {
if (!isRealValue(baseUrl)) return undefined;
try {
const url = new URL(baseUrl);
url.pathname = `${url.pathname.replace(/\/+$/, "")}/${pathname.replace(/^\/+/, "")}`;
url.search = "";
url.hash = "";
return url.toString();
} catch {
return undefined;
}
}
const exampleEnv = parseEnvFile(envExamplePath);
const existingDevVars = parseEnvFile(outputPath);
const sourceEnvFiles = [
parseEnvFile(path.join(cloudRoot, ".env")),
parseEnvFile(path.join(cloudRoot, ".env.local")),
];
const env: Record<string, string> = {};
const providerOverrideKeys = new Set([
"OPENROUTER_API_KEY",
"OPENAI_API_KEY",
"OPENAI_BASE_URL",
"ANTHROPIC_API_KEY",
// Cerebras is the cloud's DEFAULT text provider — a shell-exported key must
// reach the booted worker (e.g. the creator-monetization e2e real-LLM lane),
// same as the other providers below.
"CEREBRAS_API_KEY",
"GROQ_API_KEY",
"AI_GATEWAY_API_KEY",
"AIGATEWAY_API_KEY",
"AI_GATEWAY_BASE_URL",
]);
const preserveProviderEnv = process.env.PRESERVE_E2E_PROVIDER_ENV === "1";
for (const sourceEnv of sourceEnvFiles) {
mergeRealEnvValues(env, sourceEnv);
}
const knownEnvKeys = new Set([
...Object.keys(exampleEnv),
...Object.keys(env),
...providerOverrideKeys,
]);
for (const key of knownEnvKeys) {
const value = process.env[key];
if (isRealValue(value)) {
env[key] = value;
} else if (
preserveProviderEnv &&
providerOverrideKeys.has(key) &&
value !== undefined
) {
env[key] = value;
}
}
for (const key of [
"CRON_SECRET",
"INTERNAL_SECRET",
"GATEWAY_INTERNAL_SECRET",
"AGENT_SERVER_SHARED_SECRET",
"AGENT_TEST_BOOTSTRAP_ADMIN",
"PAYOUT_STATUS_SKIP_LIVE_BALANCE",
"PAYOUT_STATUS_ASSUME_OPERATIONAL",
// Payout wallet config — the redemption/payout worker reads these to report
// which networks are operational and to sign on-chain transfers.
"EVM_PAYOUT_PRIVATE_KEY",
"EVM_PAYOUT_WALLET_ADDRESS",
"SOLANA_PAYOUT_PRIVATE_KEY",
"SOLANA_PAYOUT_WALLET_ADDRESS",
]) {
const value = process.env[key];
if (isRealValue(value)) {
env[key] = value;
}
}
env.NODE_ENV = "development";
env.ENVIRONMENT = "local";
env.NEXT_PUBLIC_APP_URL = localAppUrl;
env.NEXT_PUBLIC_API_URL = localApiUrl;
env.ELIZA_CLOUD_URL = localAppUrl;
env.CACHE_ENABLED = env.CACHE_ENABLED || "true";
// `auto` picks Upstash REST when KV_REST_API_URL/_TOKEN are set, otherwise
// falls back to embedded Wadis (WASM Redis) for fully-offline local dev.
env.CACHE_BACKEND = env.CACHE_BACKEND || "auto";
env.REDIS_RATE_LIMITING = env.REDIS_RATE_LIMITING || "false";
env.FORCE_REDIS_EVENTS = env.FORCE_REDIS_EVENTS || "false";
// Local dev uses embedded PGlite (in-process Postgres); cloud uses Neon.
env.DATABASE_URL = env.DATABASE_URL || getLocalPGliteDatabaseUrl(process.env);
env.PAYOUT_TESTNET =
process.env.ELIZA_CLOUD_LOCAL_ENABLE_MAINNET_PAYOUTS === "1"
? env.PAYOUT_TESTNET || "false"
: "true";
env.JWT_SIGNING_KEY_ID = env.JWT_SIGNING_KEY_ID || "local-dev";
for (const key of [
"INTERNAL_SECRET",
"GATEWAY_INTERNAL_SECRET",
"AGENT_SERVER_SHARED_SECRET",
"ELIZA_APP_JWT_SECRET",
"ELIZA_API_TOKEN",
]) {
ensureLocalSharedSecret(key);
}
mirrorEnvValue("ELIZAOS_CLOUD_API_KEY", ["ELIZA_CLOUD_API_KEY"]);
mirrorEnvValue("ELIZA_CLOUD_API_KEY", ["ELIZAOS_CLOUD_API_KEY"]);
mirrorEnvValue("STEWARD_JWT_SECRET", ["STEWARD_SESSION_SECRET"]);
mirrorEnvValue("STEWARD_API_KEY", ["STEWARD_TENANT_API_KEY"]);
mirrorEnvValue("DISCORD_API_TOKEN", ["DISCORD_BOT_TOKEN"]);
mirrorEnvValue("DISCORD_APPLICATION_ID", ["DISCORD_CLIENT_ID"]);
mirrorEnvValue("DISCORD_TEST_CHANNEL_ID", ["DISCORD_CHANNEL_ID"]);
mirrorEnvValue("TELEGRAM_BOT_TOKEN", ["ELIZA_APP_TELEGRAM_BOT_TOKEN"]);
mirrorEnvValue("TELEGRAM_WEBHOOK_SECRET", [
"ELIZA_APP_TELEGRAM_WEBHOOK_SECRET",
]);
mirrorEnvValue("WHATSAPP_ACCESS_TOKEN", ["ELIZA_APP_WHATSAPP_ACCESS_TOKEN"]);
mirrorEnvValue("WHATSAPP_TOKEN", [
"WHATSAPP_ACCESS_TOKEN",
"ELIZA_APP_WHATSAPP_ACCESS_TOKEN",
]);
mirrorEnvValue("WHATSAPP_PHONE_NUMBER_ID", [
"ELIZA_APP_WHATSAPP_PHONE_NUMBER_ID",
]);
mirrorEnvValue("WHATSAPP_APP_SECRET", ["ELIZA_APP_WHATSAPP_APP_SECRET"]);
mirrorEnvValue("WHATSAPP_VERIFY_TOKEN", ["ELIZA_APP_WHATSAPP_VERIFY_TOKEN"]);
mirrorEnvValue("WHATSAPP_BUSINESS_PHONE", ["ELIZA_APP_WHATSAPP_PHONE_NUMBER"]);
mirrorEnvValue("WHATSAPP_PHONE_NUMBER", ["ELIZA_APP_WHATSAPP_PHONE_NUMBER"]);
mirrorEnvValue("BLOOIO_API_KEY", ["ELIZA_APP_BLOOIO_API_KEY"]);
mirrorEnvValue("BLOOIO_WEBHOOK_SECRET", ["ELIZA_APP_BLOOIO_WEBHOOK_SECRET"]);
mirrorEnvValue("BLOOIO_FROM_NUMBER", ["ELIZA_APP_BLOOIO_PHONE_NUMBER"]);
mirrorEnvValue("TWILIO_ACCOUNT_SID", ["ELIZA_APP_TWILIO_ACCOUNT_SID"]);
mirrorEnvValue("TWILIO_AUTH_TOKEN", ["ELIZA_APP_TWILIO_AUTH_TOKEN"]);
mirrorEnvValue("TWILIO_PHONE_NUMBER", ["ELIZA_APP_TWILIO_PHONE_NUMBER"]);
mirrorEnvValue("FAL_API_KEY", ["FAL_KEY"]);
mirrorEnvValue("GOOGLE_GENERATIVE_AI_API_KEY", ["GOOGLE_API_KEY"]);
mirrorEnvValue("GOOGLE_API_KEY", ["GOOGLE_GENERATIVE_AI_API_KEY"]);
mirrorEnvValue("UPSTASH_REDIS_REST_URL", ["KV_REST_API_URL"]);
mirrorEnvValue("UPSTASH_REDIS_REST_TOKEN", ["KV_REST_API_TOKEN"]);
mirrorEnvValue("X_BEARER_TOKEN", ["TWITTER_BEARER_TOKEN"]);
mirrorEnvValue("GITHUB_TOKEN", ["GIT_ACCESS_TOKEN"]);
mirrorEnvValue("LINEAR_OAUTH_CLIENT_ID", ["LINEAR_CLIENT_ID"]);
mirrorEnvValue("LINEAR_OAUTH_CLIENT_SECRET", ["LINEAR_CLIENT_SECRET"]);
mirrorDerivedValue("GOOGLE_REDIRECT_URI", () =>
joinUrl(env.NEXT_PUBLIC_API_URL, "/api/connectors/google/oauth/callback"),
);
if (process.env.PLAYWRIGHT_TEST_AUTH) {
env.PLAYWRIGHT_TEST_AUTH = process.env.PLAYWRIGHT_TEST_AUTH;
}
if (isRealValue(process.env.PLAYWRIGHT_TEST_AUTH_SECRET)) {
env.PLAYWRIGHT_TEST_AUTH_SECRET = process.env.PLAYWRIGHT_TEST_AUTH_SECRET;
}
if (process.env.PLAYWRIGHT_TEST_AUTH === "true") {
ensureLocalSharedSecret("PLAYWRIGHT_TEST_AUTH_SECRET");
const testDatabaseUrl =
process.env.TEST_DATABASE_URL ||
process.env.DATABASE_URL ||
env.TEST_DATABASE_URL ||
env.DATABASE_URL ||
getLocalPGliteDatabaseUrl(process.env);
env.TEST_DATABASE_URL = testDatabaseUrl;
env.DATABASE_URL = testDatabaseUrl;
env.CACHE_ENABLED = "false";
env.RATE_LIMIT_DISABLED = "true";
}
if (!env.JWT_SIGNING_PRIVATE_KEY || !env.JWT_SIGNING_PUBLIC_KEY) {
Object.assign(env, generateJwtSigningKeys());
}
mkdirSync(apiDir, { recursive: true });
const entries = Object.entries(env)
.filter(([key]) => /^[A-Z0-9_]+$/.test(key))
.sort(([a], [b]) => a.localeCompare(b));
const content = [
"# Generated by packages/scripts/cloud/admin/sync-api-dev-vars.ts.",
"# Local only. Do not commit.",
"# Reads real values from .env and .env.local, drops placeholders, and generates local JWT keys when needed.",
...entries.map(([key, value]) => `${key}=${quoteDevVarValue(value)}`),
"",
].join("\n");
writeFileSync(outputPath, content, "utf8");
console.log(
`[sync-api-dev-vars] wrote packages/cloud/api/.dev.vars (${entries.length} keys)`,
);