traceExport

tcmts.cli.web.traceExport · #ibe3ikxr · term · runs on the server

uses node:crypto

Hand the server's traces to whoever holds its traces token (GET /__tcm/traces.sqlite, Authorization: Bearer <TCM_TRACES_TOKEN>): a copy of .tcm/data/traces.sqlite as it is now (gzipped, if asked for), which the studio reads as production's. Without TCM_TRACES_TOKEN set, there's nothing there (404): a server's traces stay its own.

import type { Codebase } from "#codebase/tcmts/cli/codebase";
import type { Middleware } from "#codebase/tcmts/cli/web";
import { timingSafeEqual } from "node:crypto";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
import { createGzip } from "node:zlib";

export function traceExport(cb: Codebase): Middleware {
  return (req, res, next) => {
    if ((req.url ?? "").split("?")[0] !== "/__tcm/traces.sqlite") return next();
    const answer = (status: number, text = "") => {
      res.statusCode = status;
      res.end(text);
    };
    const token = process.env.TCM_TRACES_TOKEN;
    if (!token) return answer(404, "not found");
    if (req.method !== "GET") return answer(405);
    const given = Buffer.from(/^Bearer (.+)$/.exec(req.headers.authorization ?? "")?.[1] ?? "");
    const want = Buffer.from(token);
    if (given.length !== want.length || !timingSafeEqual(given, want))
      return answer(401, "a traces token is needed");
    const file = path.join(cb.lay.tcmDir, "data", "traces.sqlite");
    if (!fs.existsSync(file)) return answer(204);
    // (a consistent copy, made apart from the tracer writing on: VACUUM INTO reads it as one transaction)
    const copy = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "tcm-traces-")), "traces.sqlite");
    const db = new DatabaseSync(file, { readOnly: true });
    try {
      db.exec(`VACUUM INTO '${copy.replaceAll("'", "''")}'`);
    } catch {
      return answer(500, "the traces couldn't be read");
    } finally {
      db.close();
    }
    const gzip = /\bgzip\b/.test(String(req.headers["accept-encoding"] ?? ""));
    res.writeHead(200, {
      "content-type": "application/vnd.sqlite3",
      "cache-control": "no-store",
      ...(gzip ? { "content-encoding": "gzip" } : {}),
    });
    const done = () => fs.rmSync(path.dirname(copy), { recursive: true, force: true });
    const stream = fs.createReadStream(copy);
    stream.on("close", done);
    (gzip ? stream.pipe(createGzip()) : stream).pipe(res);
  };
}

uses

used by