Ctrl K
ComunidadGuías…

electron/cua-linux-bundle.cjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
const { createHash } = require("node:crypto");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");

const STAGE_PREFIX = "openmausbot-cua-linux-x64-";
const LEGACY_STAGE_GRACE_MS = 10 * 60 * 1000;
const FILES = Object.freeze({
  "cua-driver": "83030028e7c9e3f4d753b3b27025135d44eb8d52f562cce0aaaf95777bc1b41f",
  "cua-cursor-theme": "d39efb960d207e7328d7a9dd48e3b6565f74be073f9c2ca8dcba86a4d0f28312",
});

function sha256(file, fileSystem = fs) {
  return createHash("sha256").update(fileSystem.readFileSync(file)).digest("hex");
}

function processIsAlive(processId) {
  try {
    process.kill(processId, 0);
    return true;
  } catch (error) {
    return error?.code === "EPERM";
  }
}

function processReferencesDirectory(directory, {
  fileSystem = fs,
  procRoot = "/proc",
} = {}) {
  let processes;
  try {
    processes = fileSystem.readdirSync(procRoot);
  } catch {
    // Cleanup is optional; deleting a stage that might still be in use is not.
    return true;
  }
  for (const processName of processes) {
    if (!/^\d+$/.test(processName)) continue;
    for (const linkName of ["exe", "cwd"]) {
      let target;
      try {
        target = fileSystem.readlinkSync(path.join(procRoot, processName, linkName));
      } catch {
        continue;
      }
      const liveTarget = target.endsWith(" (deleted)") ? target.slice(0, -10) : target;
      if (liveTarget === directory || liveTarget.startsWith(`${directory}${path.sep}`)) return true;
    }
  }
  return false;
}

function safeStageContents(directory, {
  currentUid = process.getuid?.(),
  fileSystem = fs,
  files = FILES,
} = {}) {
  let entries;
  try {
    entries = fileSystem.readdirSync(directory);
  } catch {
    return false;
  }
  for (const name of entries) {
    const expectedHash = files[name];
    if (!expectedHash) return false;
    const candidate = path.join(directory, name);
    let details;
    try {
      details = fileSystem.lstatSync(candidate);
      if (
        !details.isFile() ||
        details.isSymbolicLink() ||
        details.uid !== currentUid ||
        (details.mode & 0o777) !== 0o755 ||
        sha256(candidate, fileSystem) !== expectedHash
      ) {
        return false;
      }
    } catch {
      return false;
    }
  }
  return true;
}

function reapStaleAppImageCuaBundles({
  temporaryRoot = os.tmpdir(),
  currentUid = process.getuid?.(),
  fileSystem = fs,
  files = FILES,
  isDirectoryActive = (directory) => processReferencesDirectory(directory, { fileSystem }),
  isProcessAlive = processIsAlive,
  legacyGraceMs = LEGACY_STAGE_GRACE_MS,
  now = Date.now(),
} = {}) {
  if (!path.isAbsolute(temporaryRoot) || !Number.isInteger(currentUid)) return [];
  let names;
  try {
    names = fileSystem.readdirSync(temporaryRoot);
  } catch {
    return [];
  }
  const removed = [];
  const escapedPrefix = STAGE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const candidatePattern = new RegExp(`^${escapedPrefix}(?:(\\d+)-)?[A-Za-z0-9]{6}$`);
  for (const name of names) {
    const match = candidatePattern.exec(name);
    if (!match) continue;
    const directory = path.join(temporaryRoot, name);
    let details;
    try {
      details = fileSystem.lstatSync(directory);
    } catch {
      continue;
    }
    if (
      !details.isDirectory() ||
      details.isSymbolicLink() ||
      details.uid !== currentUid ||
      (details.mode & 0o777) !== 0o700
    ) {
      continue;
    }
    const ownerPid = match[1] ? Number(match[1]) : null;
    if (ownerPid !== null && isProcessAlive(ownerPid)) continue;
    if (ownerPid === null && now - details.mtimeMs < legacyGraceMs) continue;
    if (isDirectoryActive(directory)) continue;
    if (!safeStageContents(directory, { currentUid, fileSystem, files })) continue;
    try {
      fileSystem.rmSync(directory, { recursive: true, force: false });
      removed.push(directory);
    } catch {
    }
  }
  return removed;
}

function stageAppImageCuaBundle({
  resourcesPath,
  temporaryRoot = os.tmpdir(),
  fileSystem = fs,
  files = FILES,
  processId = process.pid,
} = {}) {
  if (
    !path.isAbsolute(resourcesPath) ||
    !path.isAbsolute(temporaryRoot) ||
    !Number.isSafeInteger(processId) ||
    processId <= 0
  ) {
    throw new Error("AppImage CUA staging paths must be absolute");
  }
  const sourceRoot = path.join(resourcesPath, "cua-linux-x64");
  const stageDirectory = fileSystem.mkdtempSync(
    path.join(temporaryRoot, `${STAGE_PREFIX}${processId}-`),
  );
  fileSystem.chmodSync(stageDirectory, 0o700);
  try {
    for (const [name, expectedHash] of Object.entries(files)) {
      const source = path.join(sourceRoot, name);
      const sourceDetails = fileSystem.lstatSync(source);
      if (!sourceDetails.isFile() || sourceDetails.isSymbolicLink()) {
        throw new Error(`AppImage CUA source must be a regular file: ${name}`);
      }
      const destination = path.join(stageDirectory, name);
      fileSystem.copyFileSync(source, destination, fs.constants.COPYFILE_EXCL);
      fileSystem.chmodSync(destination, 0o755);
      const destinationDetails = fileSystem.lstatSync(destination);
      if (
        !destinationDetails.isFile() ||
        destinationDetails.isSymbolicLink() ||
        (destinationDetails.mode & 0o777) !== 0o755 ||
        sha256(destination, fileSystem) !== expectedHash
      ) {
        throw new Error(`AppImage CUA staged file failed integrity validation: ${name}`);
      }
    }
    return Object.freeze({
      directory: stageDirectory,
      driverPath: path.join(stageDirectory, "cua-driver"),
    });
  } catch (error) {
    fileSystem.rmSync(stageDirectory, { recursive: true, force: true });
    throw error;
  }
}

function cleanupAppImageCuaBundle(stage, {
  temporaryRoot = os.tmpdir(),
  fileSystem = fs,
} = {}) {
  const directory = stage?.directory;
  if (
    !path.isAbsolute(directory ?? "") ||
    path.dirname(directory) !== path.resolve(temporaryRoot) ||
    !path.basename(directory).startsWith(STAGE_PREFIX)
  ) {
    throw new Error("refusing to clean an unexpected AppImage CUA stage");
  }
  const details = fileSystem.lstatSync(directory, { throwIfNoEntry: false });
  if (!details) return;
  if (!details.isDirectory() || details.isSymbolicLink()) {
    throw new Error("refusing to clean a replaced AppImage CUA stage");
  }
  fileSystem.rmSync(directory, { recursive: true, force: false });
}

module.exports = {
  FILES,
  LEGACY_STAGE_GRACE_MS,
  STAGE_PREFIX,
  cleanupAppImageCuaBundle,
  reapStaleAppImageCuaBundles,
  stageAppImageCuaBundle,
};