148 lines
4.7 KiB
JavaScript
148 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const platformRoot = path.resolve(scriptDir, "../..");
|
|
const monorepoRoot = path.resolve(platformRoot, "..");
|
|
|
|
const prodHosts = new Set([
|
|
"ai-hub.nodedc.ru",
|
|
"hub.nodedc.ru",
|
|
"engine.nodedc.ru",
|
|
"ops.nodedc.ru",
|
|
"id.nodedc.ru",
|
|
"ops-agents.nodedc.ru",
|
|
]);
|
|
|
|
const relayKeysAllowedInLocal = new Set([
|
|
"AI_WORKSPACE_HUB_PUBLIC_URL",
|
|
"AI_WORKSPACE_HUB_INTERNAL_URL",
|
|
"NDC_AI_WORKSPACE_HUB_URL",
|
|
"NDC_AI_WORKSPACE_HUB_HTTP_URL",
|
|
"AI_WORKSPACE_ASSISTANT_ACTION_GATEWAY_URL",
|
|
"NDC_AI_WORKSPACE_ASSISTANT_ACTION_GATEWAY_URL",
|
|
]);
|
|
|
|
const localOnlyFiles = [
|
|
{
|
|
file: path.join(platformRoot, "infra", ".env.example"),
|
|
required: true,
|
|
requiredKeys: ["NODEDC_ENV", "NODEDC_INTERNAL_ACCESS_TOKEN", "AI_WORKSPACE_HUB_PUBLIC_URL", "AI_WORKSPACE_HUB_INTERNAL_URL", "AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ENABLED", "AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ID"],
|
|
},
|
|
{
|
|
file: path.join(platformRoot, "infra", ".env"),
|
|
required: false,
|
|
requiredKeys: ["NODEDC_ENV", "NODEDC_INTERNAL_ACCESS_TOKEN", "AI_WORKSPACE_HUB_PUBLIC_URL", "AI_WORKSPACE_HUB_INTERNAL_URL", "AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ENABLED", "AI_WORKSPACE_ASSISTANT_ACTION_RELAY_ID"],
|
|
},
|
|
{
|
|
file: path.join(monorepoRoot, "NODEDC_ENGINE_INFRA", "nodedc-source", "server", ".env.local"),
|
|
required: false,
|
|
requiredKeys: [
|
|
"NODEDC_ENV",
|
|
"NODEDC_LAUNCHER_INTERNAL_URL",
|
|
"NODEDC_LAUNCHER_ORIGIN",
|
|
"AUTHENTIK_PUBLIC_BASE_URL",
|
|
"NODEDC_AI_WORKSPACE_ASSISTANT_URL",
|
|
],
|
|
},
|
|
];
|
|
|
|
const failures = [];
|
|
const warnings = [];
|
|
const passes = [];
|
|
|
|
function parseEnvFile(file) {
|
|
const env = new Map();
|
|
const raw = fs.readFileSync(file, "utf8");
|
|
raw.split(/\r?\n/).forEach((line, index) => {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith("#")) return;
|
|
const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(trimmed);
|
|
if (!match) return;
|
|
let value = match[2].trim();
|
|
if (
|
|
(value.startsWith('"') && value.endsWith('"')) ||
|
|
(value.startsWith("'") && value.endsWith("'"))
|
|
) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
env.set(match[1], { value, line: index + 1 });
|
|
});
|
|
return env;
|
|
}
|
|
|
|
function urlHost(value) {
|
|
try {
|
|
return new URL(value).hostname.toLowerCase();
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function isTunnelOrPrivateRelay(value) {
|
|
const host = urlHost(value);
|
|
return (
|
|
host.endsWith(".ts.net") ||
|
|
host.startsWith("100.") ||
|
|
host.startsWith("172.22.") ||
|
|
host.startsWith("192.168.") ||
|
|
host.startsWith("10.")
|
|
);
|
|
}
|
|
|
|
function checkLocalEnvFile({ file, required, requiredKeys }) {
|
|
if (!fs.existsSync(file)) {
|
|
if (required) failures.push(`${file}: missing required local env file`);
|
|
else warnings.push(`${file}: not present, skipped`);
|
|
return;
|
|
}
|
|
|
|
const env = parseEnvFile(file);
|
|
const profile = env.get("NODEDC_ENV")?.value || "local";
|
|
if (profile !== "local" && profile !== "tunnel-local-e2e") {
|
|
failures.push(`${file}: expected NODEDC_ENV=local or tunnel-local-e2e, got ${profile}`);
|
|
}
|
|
|
|
requiredKeys.forEach((key) => {
|
|
if (!env.get(key)?.value) failures.push(`${file}: missing required ${key}`);
|
|
});
|
|
|
|
for (const [key, entry] of env.entries()) {
|
|
const value = entry.value;
|
|
if (!/^https?:\/\//i.test(value) && !/^wss?:\/\//i.test(value)) continue;
|
|
const host = urlHost(value);
|
|
if (!host) continue;
|
|
|
|
if (prodHosts.has(host) && !relayKeysAllowedInLocal.has(key)) {
|
|
failures.push(`${file}:${entry.line}: ${key} points local profile at prod host ${host}`);
|
|
}
|
|
|
|
if (isTunnelOrPrivateRelay(value) && profile !== "tunnel-local-e2e") {
|
|
failures.push(`${file}:${entry.line}: ${key} uses tunnel/private host ${host}; set NODEDC_ENV=tunnel-local-e2e explicitly or use a normal local/public-relay URL`);
|
|
}
|
|
}
|
|
|
|
const hubInternal = env.get("AI_WORKSPACE_HUB_INTERNAL_URL")?.value || "";
|
|
if (hubInternal && profile === "local") {
|
|
const host = urlHost(hubInternal);
|
|
const allowed = host === "ai-hub.nodedc.ru" || host === "127.0.0.1" || host === "localhost";
|
|
if (!allowed) {
|
|
failures.push(`${file}: AI_WORKSPACE_HUB_INTERNAL_URL must be loopback or deployed relay in local profile, got ${hubInternal}`);
|
|
}
|
|
}
|
|
|
|
passes.push(`${file}: checked`);
|
|
}
|
|
|
|
localOnlyFiles.forEach(checkLocalEnvFile);
|
|
|
|
passes.forEach((item) => console.log(`PASS ${item}`));
|
|
warnings.forEach((item) => console.warn(`WARN ${item}`));
|
|
failures.forEach((item) => console.error(`FAIL ${item}`));
|
|
|
|
console.log(`summary: passed=${passes.length} warnings=${warnings.length} failed=${failures.length}`);
|
|
|
|
if (failures.length) process.exit(1);
|