aboutsummaryrefslogtreecommitdiffstats
path: root/packages/benchmarks/src/startContainers.ts
blob: ed4e02507ffeb0feb4dc5d7d2dc6a4654821823e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import { execSync } from "child_process";
import net from "net";
import path from "path";
import { fileURLToPath } from "url";

import { logInfo, logStep, logSuccess, logWarn } from "./log";
import { sleep, waitUntil } from "./utils";

async function getRandomPort(): Promise<number> {
  const server = net.createServer();
  return new Promise<number>((resolve, reject) => {
    server.unref();
    server.on("error", reject);
    server.listen(0, () => {
      const port = (server.address() as net.AddressInfo).port;
      server.close(() => resolve(port));
    });
  });
}

async function waitForHealthy(port: number): Promise<void> {
  await waitUntil(
    async () => {
      const res = await fetch(`http://localhost:${port}/api/health`);
      return res.status === 200;
    },
    "Karakeep stack to become healthy",
    60_000,
    1_000,
  );
}

async function captureDockerLogs(composeDir: string): Promise<void> {
  const logsDir = path.join(composeDir, "setup", "docker-logs");
  try {
    execSync(`mkdir -p "${logsDir}"`, { cwd: composeDir });
  } catch {
    // ignore
  }

  const services = ["web", "meilisearch", "chrome", "nginx", "minio"];
  for (const service of services) {
    try {
      execSync(
        `/bin/sh -c 'docker compose logs ${service} > "${logsDir}/${service}.log" 2>&1'`,
        {
          cwd: composeDir,
          stdio: "ignore",
        },
      );
      logInfo(`Captured logs for ${service}`);
    } catch (error) {
      logWarn(`Failed to capture logs for ${service}: ${error}`);
    }
  }
}

export interface RunningContainers {
  port: number;
  stop: () => Promise<void>;
}

export async function startContainers(): Promise<RunningContainers> {
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
  const composeDir = path.join(__dirname, "..");
  const port = await getRandomPort();
  const skipBuild =
    process.env.BENCH_NO_BUILD === "1" || process.env.BENCH_SKIP_BUILD === "1";
  const buildArg = skipBuild ? "" : "--build";

  logStep(`Starting docker compose on port ${port}`);
  execSync(`docker compose up ${buildArg} -d`, {
    cwd: composeDir,
    stdio: "inherit",
    env: { ...process.env, KARAKEEP_PORT: String(port) },
  });

  logInfo("Waiting for services to report healthy...");
  await waitForHealthy(port);
  await sleep(5_000);
  logSuccess("Containers are ready");

  process.env.KARAKEEP_PORT = String(port);

  let stopped = false;
  const stop = async (): Promise<void> => {
    if (stopped) return;
    stopped = true;
    logStep("Collecting docker logs");
    await captureDockerLogs(composeDir);
    logStep("Stopping docker compose");
    execSync("docker compose down", { cwd: composeDir, stdio: "inherit" });
  };

  return { port, stop };
}