Ctrl K
ComunidadGuías…

src/services/local-n8n-supergrok-installer.js

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

Archivo original ↗Preguntar a KiBot
import { createHash, randomBytes as defaultRandomBytes, randomUUID } from "node:crypto";
import * as defaultFileSystem from "node:fs/promises";
import { homedir } from "node:os";
import { basename, dirname, isAbsolute, join, resolve } from "node:path";

import {
  LOCAL_N8N_SUPERGROK_ENDPOINT,
  LOCAL_N8N_SUPERGROK_HOSTNAME,
  LOCAL_N8N_SUPERGROK_TARGET,
  createLocalN8nSuperGrokComposeFile,
  createLocalN8nSuperGrokDockerfile,
  createLocalN8nSuperGrokDockerignore,
  normalizeLocalN8nSuperGrokPlan,
} from "../domain/local-n8n-supergrok.js";
import { validateDockerName } from "../domain/validation.js";
import { lockDownLocalPath, runLocalProcess, validateLocalDockerHost } from "../infrastructure/local-process.js";
import { getLocalProcessIdentity } from "../infrastructure/process-identity.js";
import { acquireLocalIntegrationLifecycleLock, settleLocalIntegrationLifecycleOperation } from "./local-integration-lifecycle-lock.js";

const COMPOSE_FILE = "docker-compose.yml";
const MARKER_FILE = ".managed-by-relmio.json";
const ROOT_MARKER = ".managed-by-relmio-root.json";
const PROJECT_PREFIX = "relmio-n8n-supergrok-oauth";
const SERVICE_NAME = "supergrok-oauth";
const MARKER_SCHEMA = 1;
const MAX_METADATA_BYTES = 1024 * 1024;
const MAX_CONTAINERS = 100;
const DOCKER_ENV = new Set(["DOCKER_HOST", "DOCKER_CONTEXT", "DOCKER_CONFIG", "DOCKER_TLS_VERIFY", "DOCKER_CERT_PATH", "BUILDKIT_HOST"]);
const VERIFIER = [
  'let b="";process.stdin.setEncoding("utf8");process.stdin.on("data",x=>{b+=x;if(b.length>512)process.exit(2)});',
  'process.stdin.on("end",async()=>{try{if(!/^[a-f0-9]{64}$/i.test(b.trim()))process.exit(2);',
  // Installation verifies the local service and bearer; model discovery requires
  // the separate, user-attended provider login and is not a bootstrap health check.
  'const h={Authorization:"Bearer "+b.trim()};const [a,c]=await Promise.all([fetch("http://127.0.0.1:14502/health"),fetch("http://127.0.0.1:14502/auth/verify",{headers:h})]);',
  'if(!a.ok||!c.ok)process.exit(1);process.stdout.write("ok")}catch{process.exit(1)}});',
].join("");

function missing(error) { return error?.code === "ENOENT"; }
function sha(value) { return createHash("sha256").update(value).digest("hex"); }
function validId(value) { return typeof value === "string" && /^[a-f0-9]{32}$/u.test(value); }
function validatePath(value) {
  if (typeof value !== "string" || !isAbsolute(value) || value.includes("\0")) throw new TypeError("The local SuperGrok install directory is invalid.");
  return resolve(value);
}
function assertInstallRoot(value) {
  const root = validatePath(value);
  if (basename(root) !== LOCAL_N8N_SUPERGROK_TARGET || basename(dirname(root)) !== "local" || basename(resolve(root, "..", "..")) !== ".relmio") throw new TypeError("The local SuperGrok install directory is invalid.");
  return root;
}
function assertNoDockerOverrides(env) {
  if (!env || typeof env !== "object" || Array.isArray(env)) throw new TypeError("The local process environment is invalid.");
  for (const [name, value] of Object.entries(env)) if (DOCKER_ENV.has(name.toUpperCase()) && value) throw new Error("Relmio local sidecars require the selected Docker context without a Docker environment override.");
}
async function lstat(fileSystem, path) { try { return await fileSystem.lstat(path); } catch (error) { if (missing(error)) return null; throw error; } }
function assertDirectory(entry) { if (!entry?.isDirectory?.() || entry.isSymbolicLink()) throw new Error("Relmio refuses an unsafe local managed directory."); }
async function writePrivate(fileSystem, path, data) {
  const old = await lstat(fileSystem, path);
  if (old && (!old.isFile?.() || old.isSymbolicLink())) throw new Error("Relmio refuses to replace an unsafe local managed file.");
  const temporary = `${path}.tmp-${randomUUID()}`;
  try { await fileSystem.writeFile(temporary, data, { flag: "wx", mode: 0o600 }); await fileSystem.chmod(temporary, 0o600); await fileSystem.rename(temporary, path); await fileSystem.chmod(path, 0o600); }
  catch (error) { try { await fileSystem.unlink(temporary); } catch {} throw error; }
}
async function writeExclusivePrivate(fileSystem, path, data) {
  const old = await lstat(fileSystem, path);
  if (old) throw new Error("Relmio refuses to replace an existing fresh local SuperGrok file.");
  try {
    await fileSystem.writeFile(path, data, { flag: "wx", mode: 0o600 });
    await fileSystem.chmod(path, 0o600);
  } catch (error) {
    if (error?.code === "EEXIST") throw new Error("A fresh local SuperGrok file appeared during installation. Nothing was changed.");
    throw error;
  }
}
async function ensurePrivateDirectory(fileSystem, path, platform, lockDownPath) {
  const entry = await lstat(fileSystem, path);
  if (entry) assertDirectory(entry); else await fileSystem.mkdir(path, { mode: 0o700 });
  await fileSystem.chmod(path, 0o700);
  if (platform === "win32") await lockDownPath(path, { platform });
}
async function directoryIdentity(fileSystem, path) {
  let entry;
  try { entry = await fileSystem.lstat(path, { bigint: true }); }
  catch (error) { if (missing(error)) entry = null; else throw error; }
  assertDirectory(entry);
  const exactIdentity = (value) => (
    (typeof value === "bigint" && value >= 0n) ||
    (Number.isSafeInteger(value) && value >= 0)
  );
  if (!exactIdentity(entry.dev) || !exactIdentity(entry.ino)) throw new Error("Relmio could not attest the fresh local SuperGrok directory.");
  return { dev: entry.dev, ino: entry.ino };
}

export async function resolveLocalN8nSuperGrokInstallRoot({ env = process.env, homeDirectory = homedir(), fileSystem = defaultFileSystem, platform = process.platform } = {}) {
  if (typeof platform !== "string" || !platform) throw new TypeError("The local platform is invalid.");
  const requested = typeof env.RELMIO_HOME === "string" && env.RELMIO_HOME.trim() ? env.RELMIO_HOME : join(homeDirectory, ".relmio");
  const home = validatePath(requested);
  if (basename(home) !== ".relmio") throw new TypeError("Relmio local storage path is invalid.");
  const parent = dirname(home);
  let canonical;
  try { canonical = await fileSystem.realpath(parent); } catch { throw new Error("The parent of the Relmio local storage directory is invalid."); }
  if (canonical !== resolve(parent)) throw new Error("Relmio refuses a local storage path with a symbolic-link ancestor.");
  return join(canonical, ".relmio", "local", LOCAL_N8N_SUPERGROK_TARGET);
}

export async function acquireLocalN8nSuperGrokChangeLock({ fileSystem = defaultFileSystem, getProcessIdentity = getLocalProcessIdentity, installRoot, lockDownPath = lockDownLocalPath, now = Date.now, platform = process.platform } = {}) {
  const root = assertInstallRoot(installRoot);
  return acquireLocalIntegrationLifecycleLock({ fileSystem, getProcessIdentity, lockDownPath, now, platform, lockPath: join(dirname(resolve(root, "..", "..")), ".relmio-local-n8n-supergrok-oauth.lock"), label: "local n8n SuperGrok sidecar operation lock" });
}

function composeArgs(projectName, rest) {
  if (!new RegExp(`^${PROJECT_PREFIX}-[a-f0-9]{32}$`, "u").test(projectName)) throw new TypeError("The local SuperGrok project identity is invalid.");
  return ["compose", "--project-name", projectName, "--file", COMPOSE_FILE, ...rest];
}
async function run(runProcess, spec, label) { const result = await runProcess(spec); if (result?.code !== 0) throw new Error(`${label} failed.`); return result; }
function json(text, label) { if (typeof text !== "string" || Buffer.byteLength(text) > MAX_METADATA_BYTES) throw new Error(`${label} returned invalid Docker metadata.`); try { return JSON.parse(text); } catch { throw new Error(`${label} returned invalid Docker metadata.`); } }
function lines(text, label) { if (!text.trim()) return []; return text.trim().split("\n").map((line) => json(line, label)); }
async function inspectContainer(runProcess, cwd, dockerHost, id) { return json((await run(runProcess, { file: "docker", args: ["container", "inspect", "--format", "{{json .}}", id], cwd, dockerHost }, "Docker container inspection")).stdout.trim(), "Docker container inspection"); }
async function inspectNetwork(runProcess, cwd, dockerHost, name) { return json((await run(runProcess, { file: "docker", args: ["network", "inspect", "--format", "{{json .}}", name], cwd, dockerHost }, "Docker network inspection")).stdout.trim(), "Docker network inspection"); }
function labelsOwned(labels, marker, service = false) { return labels && labels["com.docker.compose.project"] === marker.projectName && labels["io.relmio.managed"] === "true" && labels["io.relmio.target"] === LOCAL_N8N_SUPERGROK_TARGET && labels["io.relmio.install"] === marker.installId && (!service || labels["com.docker.compose.service"] === SERVICE_NAME); }
function validateMarker(marker) {
  if (!marker || marker.schemaVersion !== MARKER_SCHEMA || marker.kind !== "relmio-local-n8n-supergrok" || marker.target !== LOCAL_N8N_SUPERGROK_TARGET || !validId(marker.installId) || marker.projectName !== `${PROJECT_PREFIX}-${marker.installId}` || typeof marker.tokenSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(marker.tokenSha256) || typeof marker.configSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(marker.configSha256) || typeof marker.sourceSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(marker.sourceSha256) || typeof marker.imageId !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(marker.imageId)) throw new Error("The local SuperGrok managed marker is invalid.");
  validateLocalDockerHost(marker.dockerHost);
  normalizeLocalN8nSuperGrokPlan({
    kind: "n8n-supergrok", target: marker.target,
    label: "SuperGrok for n8n", endpoint: LOCAL_N8N_SUPERGROK_ENDPOINT,
    baseUrl: LOCAL_N8N_SUPERGROK_ENDPOINT, protocol: "openai-chat-completions",
    upstreamAuth: "provider-owned-oauth", dockerHost: marker.dockerHost,
    n8nContainerId: marker.n8nContainerId, n8nContainerName: marker.n8nContainerName,
    dockerNetworkId: marker.dockerNetworkId, networkName: marker.networkName,
    managedPath: "~/.relmio/local/n8n-supergrok-oauth", hostPublication: "none", experimental: true,
  });
  return marker;
}
async function inspectManaged(fileSystem, root) {
  const home = resolve(root, "..", ".."); const homeEntry = await lstat(fileSystem, home);
  if (!homeEntry) return { baseExists: false, marker: null };
  assertDirectory(homeEntry);
  const rootMarker = await lstat(fileSystem, join(home, ROOT_MARKER));
  if (!rootMarker?.isFile?.() || rootMarker.isSymbolicLink()) throw new Error("The Relmio local storage directory is not an owned managed root. Nothing was changed.");
  let parsed; try { parsed = json(await fileSystem.readFile(join(home, ROOT_MARKER), "utf8"), "Relmio root marker"); } catch { throw new Error("The Relmio local managed-root marker is invalid."); }
  if (parsed?.schemaVersion !== 1 || parsed?.kind !== "relmio-local-root") throw new Error("The Relmio local managed-root marker is invalid.");
  const localRoot = join(home, "local");
  const localEntry = await lstat(fileSystem, localRoot);
  if (!localEntry) return { baseExists: true, marker: null };
  assertDirectory(localEntry);
  const entry = await lstat(fileSystem, root); if (!entry) return { baseExists: true, marker: null }; assertDirectory(entry);
  const markerFile = await lstat(fileSystem, join(root, MARKER_FILE)); if (!markerFile?.isFile?.() || markerFile.isSymbolicLink()) throw new Error("The local SuperGrok directory is unmanaged. Nothing was overwritten.");
  try { return { baseExists: true, marker: validateMarker(json(await fileSystem.readFile(join(root, MARKER_FILE), "utf8"), "SuperGrok marker")) }; } catch { throw new Error("The local SuperGrok managed marker is invalid."); }
}
async function attestStoredManagedFiles(fileSystem, root, marker) {
  const names = ["Dockerfile", ".dockerignore", COMPOSE_FILE, "gateway.js", "chat.js", "session.js"];
  const contents = [];
  for (const name of names) {
    const entry = await lstat(fileSystem, join(root, name));
    if (!entry?.isFile?.() || entry.isSymbolicLink()) throw new Error("Relmio refuses an unsafe local SuperGrok managed file.");
    contents.push(await fileSystem.readFile(join(root, name), "utf8"));
  }
  if (sha(`${contents[0]}\n${contents[1]}\n${contents[2]}`) !== marker.configSha256 || sha(`${contents[3]}\n${contents[4]}\n${contents[5]}`) !== marker.sourceSha256) throw new Error("The local SuperGrok managed files changed. Nothing was changed.");
  const markerText = `${JSON.stringify(marker)}\n`;
  const markerEntry = await lstat(fileSystem, join(root, MARKER_FILE));
  if (!markerEntry?.isFile?.() || markerEntry.isSymbolicLink() || await fileSystem.readFile(join(root, MARKER_FILE), "utf8") !== markerText) throw new Error("The local SuperGrok managed marker changed. Nothing was changed.");
  const files = { Dockerfile: contents[0], ".dockerignore": contents[1], [COMPOSE_FILE]: contents[2], "gateway.js": contents[3], "chat.js": contents[4], "session.js": contents[5], [MARKER_FILE]: markerText };
  const rootIdentity = await directoryIdentity(fileSystem, root);
  await attestGeneratedFiles(fileSystem, root, files, rootIdentity);
  return { files, rootIdentity };
}
async function attestManagedFiles(fileSystem, root, marker) {
  const stored = await attestStoredManagedFiles(fileSystem, root, marker);
  const expectedDockerfile = createLocalN8nSuperGrokDockerfile({ installId: marker.installId });
  const expectedDockerignore = createLocalN8nSuperGrokDockerignore();
  const expectedCompose = createLocalN8nSuperGrokComposeFile({ installId: marker.installId, networkName: marker.networkName, tokenSha256: marker.tokenSha256 });
  if (stored.files.Dockerfile !== expectedDockerfile || stored.files[".dockerignore"] !== expectedDockerignore || stored.files[COMPOSE_FILE] !== expectedCompose) throw new Error("The local SuperGrok managed configuration changed. Nothing was changed.");
  return stored;
}
async function attestGeneratedFiles(fileSystem, root, files, expectedRootIdentity) {
  const actualRootIdentity = await directoryIdentity(fileSystem, root);
  if (actualRootIdentity.dev !== expectedRootIdentity.dev || actualRootIdentity.ino !== expectedRootIdentity.ino) {
    throw new Error("The fresh local SuperGrok directory changed. Nothing was changed.");
  }
  const expectedNames = Object.keys(files).sort();
  const actualNames = (await fileSystem.readdir(root)).sort();
  if (actualNames.length !== expectedNames.length || actualNames.some((name, index) => name !== expectedNames[index])) {
    throw new Error("The fresh local SuperGrok directory changed. Nothing was changed.");
  }
  for (const [name, expected] of Object.entries(files)) {
    const path = join(root, name);
    const entry = await lstat(fileSystem, path);
    if (!entry?.isFile?.() || entry.isSymbolicLink() || await fileSystem.readFile(path, "utf8") !== expected) {
      throw new Error("The generated local SuperGrok files changed. Nothing was changed.");
    }
  }
}
async function removeFreshGeneratedRoot(fileSystem, root, files, rootIdentity) {
  await attestGeneratedFiles(fileSystem, root, files, rootIdentity);
  const detached = join(dirname(root), `.${basename(root)}.cleanup-${randomUUID()}`);
  if (await lstat(fileSystem, detached)) throw new Error("Relmio could not reserve fresh local SuperGrok cleanup storage.");
  await fileSystem.rename(root, detached);
  try {
    await attestGeneratedFiles(fileSystem, detached, files, rootIdentity);
    await fileSystem.rm(detached, { recursive: true, force: false });
  } catch (error) {
    if (!(await lstat(fileSystem, root))) {
      try { await fileSystem.rename(detached, root); } catch {}
    }
    throw error;
  }
}
async function initialize(fileSystem, root, baseExists, platform, lockDownPath, freshRoot) {
  const home = resolve(root, "..", "..");
  if (!baseExists) { await fileSystem.mkdir(home, { mode: 0o700 }); await writePrivate(fileSystem, join(home, ROOT_MARKER), `${JSON.stringify({ schemaVersion: 1, kind: "relmio-local-root" })}\n`); }
  await ensurePrivateDirectory(fileSystem, home, platform, lockDownPath); await ensurePrivateDirectory(fileSystem, join(home, "local"), platform, lockDownPath);
  if (await lstat(fileSystem, root)) throw new Error("A fresh local SuperGrok directory appeared during installation. Nothing was changed.");
  try { await fileSystem.mkdir(root, { mode: 0o700 }); }
  catch (error) {
    if (error?.code === "EEXIST") throw new Error("A fresh local SuperGrok directory appeared during installation. Nothing was changed.");
    throw error;
  }
  freshRoot.created = true;
  freshRoot.identity = await directoryIdentity(fileSystem, root);
  await fileSystem.chmod(root, 0o700);
  if (platform === "win32") await lockDownPath(root, { platform });
}
async function resolveDockerHost(runProcess, cwd, env, platform) {
  assertNoDockerOverrides(env); const found = await runProcess({ file: "docker", args: ["context", "inspect", "--format", "{{json .Endpoints.docker.Host}}"], cwd });
  if (found.code !== 0) throw new Error("The selected Docker context could not be inspected.");
  let host; try { host = validateLocalDockerHost(json(found.stdout.trim(), "Docker context"), { platform }); } catch { throw new Error("The selected Docker context is not a local Docker daemon."); }
  return host;
}
async function attestPlan(plan, runProcess, cwd, marker) {
  const n8n = await inspectContainer(runProcess, cwd, plan.dockerHost, plan.n8nContainerId);
  if (n8n.Id !== plan.n8nContainerId || n8n.Name !== `/${plan.n8nContainerName}` || n8n.State?.Running !== true || n8n.NetworkSettings?.Networks?.[plan.networkName]?.NetworkID !== plan.dockerNetworkId) throw new Error("The selected n8n container changed. Create and confirm a fresh plan.");
  const network = await inspectNetwork(runProcess, cwd, plan.dockerHost, plan.networkName);
  if (network.Id !== plan.dockerNetworkId || network.Name !== plan.networkName || network.Driver !== "bridge" || network.Internal === true || !network.Containers?.[plan.n8nContainerId]) throw new Error("The selected n8n Docker network changed. Create and confirm a fresh plan.");
  const connected = Object.keys(network.Containers); if (connected.length > MAX_CONTAINERS) throw new Error("The selected n8n Docker network is too large to attest safely.");
  for (const id of connected) { const item = id === plan.n8nContainerId ? n8n : await inspectContainer(runProcess, cwd, plan.dockerHost, id); const state = item.NetworkSettings?.Networks?.[plan.networkName]; const aliases = state?.Aliases; const claimed = [item.Name?.slice(1), network.Containers[id]?.Name, ...(Array.isArray(aliases) ? aliases : [])].includes(LOCAL_N8N_SUPERGROK_HOSTNAME) || [item.Name?.slice(1), network.Containers[id]?.Name, ...(Array.isArray(aliases) ? aliases : [])].includes(SERVICE_NAME); if (claimed && !labelsOwned(item.Config?.Labels, marker, true)) throw new Error("The n8n-supergrok Docker network alias has a collision. Nothing was changed."); }
}
async function attestOwned(marker, runProcess, cwd, { state = "absent" } = {}) {
  if (!["absent", "exact", "recovery"].includes(state)) throw new TypeError("The local SuperGrok ownership state is invalid.");
  const filter = `label=com.docker.compose.project=${marker.projectName}`;
  const containers = lines((await run(runProcess, { file: "docker", args: ["ps", "--all", "--no-trunc", "--filter", filter, "--format", "{{json .}}"], cwd, dockerHost: marker.dockerHost }, "SuperGrok container ownership check")).stdout, "SuperGrok container ownership check");
  const volumes = lines((await run(runProcess, { file: "docker", args: ["volume", "ls", "--filter", filter, "--format", "{{json .}}"], cwd, dockerHost: marker.dockerHost }, "SuperGrok volume ownership check")).stdout, "SuperGrok volume ownership check");
  if (containers.length > MAX_CONTAINERS || volumes.length > MAX_CONTAINERS) throw new Error("The local SuperGrok ownership check failed closed.");
  const resourcesEmpty = containers.length === 0 && volumes.length === 0;
  const resourcesExact = containers.length === 1 && volumes.length === 1 && volumes[0]?.Name === `${marker.projectName}_grok-home`;
  const resourcesRecoverable = containers.length <= 1 && volumes.length <= 1 && (volumes.length === 0 || volumes[0]?.Name === `${marker.projectName}_grok-home`);
  if ((state === "absent" && !resourcesEmpty) || (state === "exact" && !resourcesExact) || (state === "recovery" && !resourcesRecoverable)) throw new Error("Unexpected Docker resources use the local SuperGrok project identity. Nothing was changed.");
  for (const row of containers) { const id = row?.ID; if (typeof id !== "string" || !/^[a-f0-9]{64}$/u.test(id)) throw new Error("The local SuperGrok Docker ownership metadata is invalid."); const container = await inspectContainer(runProcess, cwd, marker.dockerHost, id); if (!labelsOwned(container.Config?.Labels, marker, true) || container.Name !== `/${marker.projectName}-${SERVICE_NAME}-1`) throw new Error("A Docker resource uses this Relmio project identity without matching ownership. Nothing was changed."); }
  for (const row of volumes) { const name = validateDockerName(row?.Name); const labels = json((await run(runProcess, { file: "docker", args: ["volume", "inspect", "--format", "{{json .Labels}}", name], cwd, dockerHost: marker.dockerHost }, "SuperGrok volume ownership inspection")).stdout.trim(), "SuperGrok volume ownership inspection"); if (!labelsOwned(labels, marker)) throw new Error("A Docker resource uses this Relmio project identity without matching ownership. Nothing was changed."); }
  const image = `${marker.projectName}:local`; const imageResult = await runProcess({ file: "docker", args: ["image", "inspect", "--format", "{{json .}}", image], cwd, dockerHost: marker.dockerHost });
  if (imageResult.code !== 0) {
    if (state === "exact" || !resourcesEmpty) throw new Error("The local SuperGrok image is missing.");
    return true;
  }
  const imageMetadata = json(imageResult.stdout.trim(), "SuperGrok image ownership inspection");
  if (imageMetadata?.Id !== marker.imageId || !labelsOwned(imageMetadata?.Config?.Labels, marker)) throw new Error("A Docker image uses this Relmio project identity without matching ownership. Nothing was changed.");
  if (state === "absent") throw new Error("Unexpected Docker image uses the local SuperGrok project identity. Nothing was changed.");
  return true;
}
async function getOwnedImageId(marker, runProcess, cwd, { allowAbsent = false } = {}) {
  const result = await runProcess({ file: "docker", args: ["image", "inspect", "--format", "{{json .}}", `${marker.projectName}:local`], cwd, dockerHost: marker.dockerHost });
  if (result.code !== 0) {
    if (allowAbsent) return null;
    throw new Error("The local SuperGrok image is missing.");
  }
  const image = json(result.stdout.trim(), "SuperGrok image ownership inspection");
  if (!/^sha256:[a-f0-9]{64}$/u.test(image?.Id) || !labelsOwned(image?.Config?.Labels, marker)) throw new Error("A Docker image uses this Relmio project identity without matching ownership. Nothing was changed.");
  return image.Id;
}
function noPorts(text) { const rows = json(text, "SuperGrok published-port safety check"); const item = Array.isArray(rows) ? rows[0] : rows; if (!item || (Array.isArray(rows) && rows.length !== 1) || !Array.isArray(item.Publishers) || item.Publishers.some((p) => p?.PublishedPort !== 0 || p?.URL !== "")) throw new Error("Safety check failed: the local SuperGrok sidecar published an unexpected host port."); }
async function verify(marker, runProcess, cwd, clientKey) {
  const running = await run(runProcess, { file: "docker", args: composeArgs(marker.projectName, ["ps", "--status", "running", "--services", SERVICE_NAME]), cwd, dockerHost: marker.dockerHost }, "SuperGrok status check"); if (!running.stdout.split(/\s+/u).includes(SERVICE_NAME)) throw new Error("The local SuperGrok sidecar did not reach the running state.");
  noPorts((await run(runProcess, { file: "docker", args: composeArgs(marker.projectName, ["ps", "--format", "json", SERVICE_NAME]), cwd, dockerHost: marker.dockerHost }, "SuperGrok published-port check")).stdout);
  const id = (await run(runProcess, { file: "docker", args: composeArgs(marker.projectName, ["ps", "-q", SERVICE_NAME]), cwd, dockerHost: marker.dockerHost }, "SuperGrok identity check")).stdout.trim();
  if (!/^[a-f0-9]{64}$/u.test(id)) throw new Error("The local SuperGrok Docker identity could not be verified."); const sidecar = await inspectContainer(runProcess, cwd, marker.dockerHost, id); const network = sidecar.NetworkSettings?.Networks?.[marker.networkName]; const ports = sidecar.NetworkSettings?.Ports;
  const mounts = sidecar.Mounts;
  const host = sidecar.HostConfig;
  if (sidecar.Id !== id || sidecar.Name !== `/${marker.projectName}-${SERVICE_NAME}-1` || sidecar.Image !== marker.imageId || sidecar.Config?.Image !== `${marker.projectName}:local` || sidecar.State?.Running !== true || !labelsOwned(sidecar.Config?.Labels, marker, true) || Object.keys(sidecar.NetworkSettings?.Networks ?? {}).length !== 1 || network?.NetworkID !== marker.dockerNetworkId || !Array.isArray(network.Aliases) || !network.Aliases.includes(LOCAL_N8N_SUPERGROK_HOSTNAME) || !network.Aliases.includes(SERVICE_NAME) || !ports || Object.values(ports).some((value) => value !== null) || host?.ReadonlyRootfs !== true || !Array.isArray(host?.CapDrop) || !host.CapDrop.includes("ALL") || !Array.isArray(host?.SecurityOpt) || !host.SecurityOpt.includes("no-new-privileges:true") || (host.PortBindings && Object.keys(host.PortBindings).length !== 0) || !Array.isArray(mounts) || mounts.length !== 1 || mounts[0]?.Type !== "volume" || mounts[0]?.Name !== `${marker.projectName}_grok-home` || mounts[0]?.Destination !== "/home/node/.grok" || mounts[0]?.RW !== true) throw new Error("The local SuperGrok Docker identity could not be verified.");
  if (clientKey) { const result = await run(runProcess, { file: "docker", args: ["exec", "-i", id, "node", "-e", VERIFIER], cwd, dockerHost: marker.dockerHost, input: clientKey }, "SuperGrok private-network verification"); if (result.stdout.trim() !== "ok") throw new Error("The local SuperGrok private-network verification failed."); }
}
async function cleanup(marker, runProcess, cwd) { await run(runProcess, { file: "docker", args: composeArgs(marker.projectName, ["down", "--volumes", "--remove-orphans"]), cwd, dockerHost: marker.dockerHost }, "SuperGrok cleanup"); const filter = `label=com.docker.compose.project=${marker.projectName}`; for (const args of [["ps", "--all", "--filter", filter, "--format", "{{json .}}"], ["volume", "ls", "--filter", filter, "--format", "{{json .}}"]]) if (lines((await run(runProcess, { file: "docker", args, cwd, dockerHost: marker.dockerHost }, "SuperGrok cleanup verification")).stdout, "SuperGrok cleanup verification").length) throw new Error("Relmio could not confirm removal of its local SuperGrok resources. The managed directory was kept."); }
async function removeOwnedImage(marker, runProcess, cwd) {
  await attestOwned(marker, runProcess, cwd, { state: "recovery" });
  const image = `${marker.projectName}:local`;
  const listed = await runProcess({ file: "docker", args: ["image", "inspect", image], cwd, dockerHost: marker.dockerHost });
  if (listed.code !== 0) return;
  await run(runProcess, { file: "docker", args: ["image", "rm", image], cwd, dockerHost: marker.dockerHost }, "SuperGrok image removal");
  const absent = await runProcess({ file: "docker", args: ["image", "inspect", image], cwd, dockerHost: marker.dockerHost });
  if (absent.code === 0) throw new Error("Relmio could not confirm removal of its local SuperGrok image. The managed directory was kept.");
}
async function recoverOwnedProject(marker, runProcess, cwd) {
  await attestOwned(marker, runProcess, cwd, { state: "recovery" });
  await cleanup(marker, runProcess, cwd);
  await removeOwnedImage(marker, runProcess, cwd);
}
async function source(read, label) { const value = await read(); if (typeof value !== "string" || !value.trim() || Buffer.byteLength(value) > 512 * 1024) throw new Error(`The packaged SuperGrok ${label} source is invalid.`); return value; }
const defaultReadRuntime = () => defaultFileSystem.readFile(new URL("../supergrok/runtime.js", import.meta.url), "utf8");
const defaultReadChat = () => defaultFileSystem.readFile(new URL("../supergrok/chat.js", import.meta.url), "utf8");
const defaultReadSession = () => defaultFileSystem.readFile(new URL("../supergrok/session.js", import.meta.url), "utf8");

export async function installLocalN8nSuperGrok({ plan, confirmed }, dependencies = {}) {
  if (confirmed !== true) throw new Error("Confirm installation of the managed local n8n SuperGrok bridge.");
  const {
    fileSystem = defaultFileSystem, env = process.env, homeDirectory = homedir(),
    runProcess = runLocalProcess, platform = process.platform,
    lockDownPath = lockDownLocalPath, getProcessIdentity, lifecycleLockNow = Date.now,
    randomBytes = defaultRandomBytes, readRuntime = defaultReadRuntime,
    readChat = defaultReadChat, readSession = defaultReadSession,
  } = dependencies;
  assertNoDockerOverrides(env);
  const normalized = normalizeLocalN8nSuperGrokPlan(plan);
  const root = await resolveLocalN8nSuperGrokInstallRoot({ env, homeDirectory, fileSystem, platform });
  const release = await acquireLocalN8nSuperGrokChangeLock({ fileSystem, getProcessIdentity, installRoot: root, lockDownPath, now: lifecycleLockNow, platform });
  return settleLocalIntegrationLifecycleOperation({
    completionLabel: "Local n8n SuperGrok installation",
    releaseLock: release,
    operation: async () => {
      const managed = await inspectManaged(fileSystem, root);
      if (managed.marker) throw new Error("The managed local n8n SuperGrok bridge is already installed. Remove it before installing again.");
      const entropy = randomBytes(64);
      if (!Buffer.isBuffer(entropy) || entropy.length !== 64) throw new Error("Relmio could not create a strong SuperGrok identity.");
      const installId = entropy.subarray(0, 16).toString("hex");
      const clientKey = entropy.subarray(16, 48).toString("hex");
      const marker = {
        schemaVersion: MARKER_SCHEMA, kind: "relmio-local-n8n-supergrok", target: LOCAL_N8N_SUPERGROK_TARGET,
        installId, projectName: `${PROJECT_PREFIX}-${installId}`, dockerHost: normalized.dockerHost,
        n8nContainerId: normalized.n8nContainerId, n8nContainerName: normalized.n8nContainerName,
        dockerNetworkId: normalized.dockerNetworkId, networkName: normalized.networkName,
        tokenSha256: sha(clientKey), configSha256: "", sourceSha256: "", imageId: "",
      };
      const parent = dirname(resolve(root, "..", ".."));
      if ((await resolveDockerHost(runProcess, parent, env, platform)) !== normalized.dockerHost) throw new Error("The selected Docker context changed. Create and confirm a fresh plan.");
      await attestOwned(marker, runProcess, parent);
      await attestPlan(normalized, runProcess, parent, marker);
      const [runtime, chat, session] = await Promise.all([source(readRuntime, "runtime"), source(readChat, "chat"), source(readSession, "session")]);
      const dockerfile = createLocalN8nSuperGrokDockerfile({ installId });
      const dockerignore = createLocalN8nSuperGrokDockerignore();
      const compose = createLocalN8nSuperGrokComposeFile({ installId, networkName: normalized.networkName, tokenSha256: marker.tokenSha256 });
      marker.configSha256 = sha(`${dockerfile}\n${dockerignore}\n${compose}`);
      marker.sourceSha256 = sha(`${runtime}\n${chat}\n${session}`);
      const generatedFiles = { Dockerfile: dockerfile, ".dockerignore": dockerignore, "gateway.js": runtime, "chat.js": chat, "session.js": session, [COMPOSE_FILE]: compose };
      const freshRoot = { created: false, identity: null, files: {} };
      let buildAttempted = false;
      try {
        await initialize(fileSystem, root, managed.baseExists, platform, lockDownPath, freshRoot);
        for (const [name, data] of Object.entries(generatedFiles)) {
          await writeExclusivePrivate(fileSystem, join(root, name), data);
          freshRoot.files[name] = data;
        }
        await run(runProcess, { file: "docker", args: composeArgs(marker.projectName, ["config", "--quiet"]), cwd: root, dockerHost: marker.dockerHost }, "SuperGrok Compose validation");
        await attestPlan(normalized, runProcess, root, marker);
        await attestOwned(marker, runProcess, root);
        buildAttempted = true;
        await run(runProcess, { file: "docker", args: composeArgs(marker.projectName, ["build", SERVICE_NAME]), cwd: root, dockerHost: marker.dockerHost }, "SuperGrok image build");
        marker.imageId = await getOwnedImageId(marker, runProcess, root);
        const markerText = `${JSON.stringify(marker)}\n`;
        await writeExclusivePrivate(fileSystem, join(root, MARKER_FILE), markerText);
        freshRoot.files[MARKER_FILE] = markerText;
        await attestManagedFiles(fileSystem, root, marker);
        await run(runProcess, { file: "docker", args: composeArgs(marker.projectName, ["up", "-d", "--wait", "--wait-timeout", "90", "--no-deps", SERVICE_NAME]), cwd: root, dockerHost: marker.dockerHost }, "SuperGrok start");
        await attestOwned(marker, runProcess, root, { state: "exact" });
        await verify(marker, runProcess, root, clientKey);
        return { target: LOCAL_N8N_SUPERGROK_TARGET, endpoint: LOCAL_N8N_SUPERGROK_ENDPOINT, baseUrl: LOCAL_N8N_SUPERGROK_ENDPOINT, protocol: "openai-chat-completions", n8nContainerName: normalized.n8nContainerName, networkName: normalized.networkName, hostPublication: "none", clientKey, credentialShownOnce: true, deploymentMode: "installed" };
      } catch (error) {
        if (freshRoot.created && !buildAttempted) {
          try {
            // `docker compose config` only reads the generated file. Do not
            // run `down` after it rejects that same file; prove this fresh
            // project has no Docker resources, then remove the exact files.
            await attestGeneratedFiles(fileSystem, root, freshRoot.files, freshRoot.identity);
            await attestOwned(marker, runProcess, root, { state: "absent" });
            await removeFreshGeneratedRoot(fileSystem, root, freshRoot.files, freshRoot.identity);
          } catch { throw new Error("SuperGrok installation failed and automatic cleanup could not be confirmed. Inspect only the Relmio SuperGrok project before retrying."); }
        } else if (freshRoot.created) {
          try {
            // A failed build can still leave a complete labelled image. Capture
            // its attested identity before recovery so cleanup remains owned.
            if (!marker.imageId) {
              const imageId = await getOwnedImageId(marker, runProcess, root, { allowAbsent: true });
              if (imageId) {
                marker.imageId = imageId;
                const markerText = `${JSON.stringify(marker)}\n`;
                await writeExclusivePrivate(fileSystem, join(root, MARKER_FILE), markerText);
                freshRoot.files[MARKER_FILE] = markerText;
              }
            }
            await recoverOwnedProject(marker, runProcess, root);
            await removeFreshGeneratedRoot(fileSystem, root, freshRoot.files, freshRoot.identity);
          }
          catch { throw new Error("SuperGrok installation failed and automatic cleanup could not be confirmed. Inspect only the Relmio SuperGrok project before retrying."); }
        }
        throw error;
      }
    },
  });
}

export async function attestLocalN8nSuperGrokInstallation({ installDirectory }, dependencies = {}) {
  const { fileSystem = defaultFileSystem, runProcess = runLocalProcess, platform = process.platform } = dependencies;
  const root = assertInstallRoot(installDirectory);
  const managed = await inspectManaged(fileSystem, root);
  const marker = managed.marker;
  if (!marker) throw new Error("The managed local n8n SuperGrok bridge is not installed.");
  validateLocalDockerHost(marker.dockerHost, { platform });
  await attestManagedFiles(fileSystem, root, marker);
  await attestPlan(marker, runProcess, root, marker);
  await attestOwned(marker, runProcess, root, { state: "exact" });
  await verify(marker, runProcess, root);
  return { installDirectory: root, projectName: marker.projectName, dockerHost: marker.dockerHost, serviceName: SERVICE_NAME };
}
export async function getLocalN8nSuperGrokStatus(dependencies = {}) {
  const {
    fileSystem = defaultFileSystem,
    env = process.env,
    homeDirectory = homedir(),
    runProcess = runLocalProcess,
    platform = process.platform,
  } = dependencies;
  const absent = { target: LOCAL_N8N_SUPERGROK_TARGET, managed: false, state: "absent" };
  try {
    const root = await resolveLocalN8nSuperGrokInstallRoot({ env, homeDirectory, fileSystem, platform });
    if (!(await lstat(fileSystem, root))) return absent;
    const managed = await inspectManaged(fileSystem, root);
    if (!managed.marker) return absent;
    const marker = managed.marker;
    if ((await resolveDockerHost(runProcess, root, env, platform)) !== marker.dockerHost) {
      return { target: LOCAL_N8N_SUPERGROK_TARGET, managed: false, state: "unavailable" };
    }
    await attestStoredManagedFiles(fileSystem, root, marker);
    await attestOwned(marker, runProcess, root, { state: "recovery" });
    const snapshot = {
      target: LOCAL_N8N_SUPERGROK_TARGET,
      endpoint: LOCAL_N8N_SUPERGROK_ENDPOINT,
      auth: { configured: true, disclosure: "one-time" },
      canRemove: true,
    };
    try {
      await attestOwned(marker, runProcess, root, { state: "exact" });
      await verify(marker, runProcess, root);
    } catch {
      return { target: LOCAL_N8N_SUPERGROK_TARGET, managed: true, state: "partial", snapshot };
    }
    return {
      target: LOCAL_N8N_SUPERGROK_TARGET,
      managed: true,
      state: "healthy",
      snapshot,
    };
  } catch {
    return { target: LOCAL_N8N_SUPERGROK_TARGET, managed: false, state: "unavailable" };
  }
}
export async function removeLocalN8nSuperGrok({ confirmed }, dependencies = {}) {
  if (confirmed !== true) throw new Error("Confirm removal of the managed local n8n SuperGrok bridge.");
  const { fileSystem = defaultFileSystem, env = process.env, homeDirectory = homedir(), runProcess = runLocalProcess, platform = process.platform, lockDownPath = lockDownLocalPath, getProcessIdentity, lifecycleLockNow = Date.now } = dependencies;
  assertNoDockerOverrides(env);
  const root = await resolveLocalN8nSuperGrokInstallRoot({ env, homeDirectory, fileSystem, platform });
  const release = await acquireLocalN8nSuperGrokChangeLock({ fileSystem, getProcessIdentity, installRoot: root, lockDownPath, now: lifecycleLockNow, platform });
  return settleLocalIntegrationLifecycleOperation({
    completionLabel: "Local n8n SuperGrok removal",
    releaseLock: release,
    operation: async () => {
      const managed = await inspectManaged(fileSystem, root);
      if (!managed.marker) throw new Error("The managed local n8n SuperGrok bridge is not installed.");
      const marker = managed.marker;
      const ownedFiles = await attestStoredManagedFiles(fileSystem, root, marker);
      await recoverOwnedProject(marker, runProcess, root);
      await removeFreshGeneratedRoot(fileSystem, root, ownedFiles.files, ownedFiles.rootIdentity);
      return { removed: true, target: LOCAL_N8N_SUPERGROK_TARGET };
    },
  });
}