import fs from "node:fs";
|
import crypto from "node:crypto";
|
import { spawnSync } from "node:child_process";
|
|
const CLIENT = "D:/mysql-8.0.40-winx64/bin/mysql.exe";
|
const EXPECTED_CLIENT_BYTES = 7127104;
|
const EXPECTED_CLIENT_SHA256 = "5231b429b025fabb90e7b09f40309011c7ff8194380c626d400b0e136ad498be";
|
const EXPECTED_VERSION_SIGNATURE = "Ver 8.0.40 for Win64 on x86_64 (MySQL Community Server - GPL)";
|
const mode = process.argv[2];
|
const baselinePath = process.argv[3];
|
const sqlPath = process.argv[4];
|
const outputPath = process.argv[5];
|
const preflightPath = process.argv[6];
|
|
if (!["preflight", "raw"].includes(mode)) throw new Error("mode must be preflight or raw");
|
if (!baselinePath || !sqlPath || !outputPath) throw new Error("missing nonsecret path argument");
|
if (!fs.existsSync(baselinePath)) throw new Error("EXECUTION_BASELINE_MISSING");
|
if (!fs.existsSync(sqlPath)) throw new Error("SQL_INPUT_MISSING");
|
if (fs.existsSync(outputPath)) throw new Error("OUTPUT_ALREADY_EXISTS_NO_OVERWRITE");
|
|
function parseCsv(text) {
|
const rows = [];
|
let row = [], cell = "", quoted = false;
|
for (let i = 0; i < text.length; i++) {
|
const ch = text[i];
|
if (quoted) {
|
if (ch === '"' && text[i + 1] === '"') { cell += '"'; i++; }
|
else if (ch === '"') quoted = false;
|
else cell += ch;
|
} else if (ch === '"') quoted = true;
|
else if (ch === ",") { row.push(cell); cell = ""; }
|
else if (ch === "\n") { row.push(cell.replace(/\r$/, "")); if (row.some((value) => value !== "")) rows.push(row); row = []; cell = ""; }
|
else cell += ch;
|
}
|
if (cell !== "" || row.length) { row.push(cell.replace(/\r$/, "")); rows.push(row); }
|
const headers = rows[0].map((value) => value.replace(/^\uFEFF/, ""));
|
return rows.slice(1).map((values) => Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ""])));
|
}
|
|
if (mode === "raw") {
|
if (!preflightPath || !fs.existsSync(preflightPath)) throw new Error("PREFLIGHT_OUTPUT_MISSING");
|
const rows = parseCsv(fs.readFileSync(preflightPath, "utf8"));
|
const gateFields = ["column_present", "type_compatible", "source_user_gate_pass", "dialect_gate_pass", "read_only_gate_pass", "engine_gate_pass", "currentity_gate_pass"];
|
if (rows.length !== 11 || rows.some((row) => gateFields.some((field) => row[field] !== "1"))) throw new Error("PREFLIGHT_11_ROWS_OR_GATE_FAILURE");
|
}
|
|
if (!fs.existsSync(CLIENT)) throw new Error("HELD_BY_CLIENT_IDENTITY_DRIFT_PATH");
|
const clientStat = fs.statSync(CLIENT);
|
if (!clientStat.isFile() || clientStat.size !== EXPECTED_CLIENT_BYTES) throw new Error("HELD_BY_CLIENT_IDENTITY_DRIFT_BYTES");
|
const clientSha256 = crypto.createHash("sha256").update(fs.readFileSync(CLIENT)).digest("hex");
|
if (clientSha256 !== EXPECTED_CLIENT_SHA256) throw new Error("HELD_BY_CLIENT_IDENTITY_DRIFT_SHA256");
|
const versionEnv = {};
|
for (const key of Object.keys(process.env)) {
|
if (key !== "KLINE_SOURCE_MYSQL_PASSWORD" && key !== "MYSQL_PWD") versionEnv[key] = process.env[key];
|
}
|
const versionResult = spawnSync(CLIENT, ["--version"], { encoding: "utf8", env: versionEnv, windowsHide: true });
|
if (versionResult.error || versionResult.status !== 0 || !(versionResult.stdout ?? "").includes(EXPECTED_VERSION_SIGNATURE)) throw new Error("HELD_BY_CLIENT_IDENTITY_DRIFT_VERSION");
|
|
const args = ["--protocol=TCP", "--host=317w7246e5.vicp.fun", "--port=50176", "--user=root", "--database=trading_xuntou", "--default-character-set=utf8mb4", "--batch", "--raw", "--column-names", "--connect-timeout=10", "--init-command=SET SESSION TRANSACTION READ ONLY"];
|
if (args.some((arg) => /^--password/i.test(arg))) throw new Error("SECRET_OPTION_IN_ARGUMENTS");
|
const sql = fs.readFileSync(sqlPath, "utf8");
|
let sourceSecret = "";
|
let childEnv = null;
|
let result;
|
try {
|
sourceSecret = process.env.KLINE_SOURCE_MYSQL_PASSWORD ?? "";
|
if (!sourceSecret) throw new Error("HELD_BY_MISSING_SECURE_CREDENTIAL");
|
childEnv = {};
|
for (const key of Object.keys(process.env)) {
|
if (key !== "KLINE_SOURCE_MYSQL_PASSWORD" && key !== "MYSQL_PWD") childEnv[key] = process.env[key];
|
}
|
childEnv.MYSQL_PWD = sourceSecret;
|
if (args.some((arg) => arg === sourceSecret)) throw new Error("SECRET_VALUE_IN_ARGUMENTS");
|
result = spawnSync(CLIENT, args, { input: sql, encoding: "utf8", env: childEnv, maxBuffer: 512 * 1024 * 1024, windowsHide: true });
|
} finally {
|
if (childEnv) {
|
delete childEnv.MYSQL_PWD;
|
delete childEnv.KLINE_SOURCE_MYSQL_PASSWORD;
|
}
|
sourceSecret = "";
|
}
|
|
if (!result || result.error || result.status !== 0) {
|
const stderrHash = crypto.createHash("sha256").update(result?.stderr ?? "").digest("hex");
|
throw new Error("MYSQL_READONLY_EXECUTION_FAILED stderr_sha256=" + stderrHash);
|
}
|
const lines = result.stdout.replace(/\r\n/g, "\n").replace(/\n$/, "").split("\n");
|
if (lines.length < 2) throw new Error("MYSQL_TABULAR_OUTPUT_EMPTY");
|
const width = lines[0].split("\t").length;
|
const matrix = lines.map((line) => line.split("\t"));
|
if (matrix.some((row) => row.length !== width)) throw new Error("MYSQL_TABULAR_COLUMN_COUNT_MISMATCH");
|
const csvCell = (value) => /[",\r\n]/.test(value) ? '"' + value.replaceAll('"', '""') + '"' : value;
|
const csv = "\uFEFF" + matrix.map((row) => row.map(csvCell).join(",")).join("\r\n") + "\r\n";
|
fs.writeFileSync(outputPath, csv, "utf8");
|
console.log(JSON.stringify({ mode, rows: matrix.length - 1, columns: width, output_sha256: crypto.createHash("sha256").update(Buffer.from(csv, "utf8")).digest("hex"), client_identity_verified: true, secret_persisted: false }));
|