element-web/playwright/plugins/homeserver/dendrite/index.ts

149 lines
5.2 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import * as path from "node:path";
import * as os from "node:os";
import * as fse from "fs-extra";
import { getFreePort } from "../../utils/port";
import { Homeserver, HomeserverConfig, HomeserverInstance, StartHomeserverOpts } from "../";
import { randB64Bytes } from "../../utils/rand";
import { Synapse } from "../synapse";
import { Docker } from "../../docker";
const dockerConfigDir = "/etc/dendrite/";
const dendriteConfigFile = "dendrite.yaml";
// Surprisingly, Dendrite implements the same register user Admin API Synapse, so we can just extend it
export class Dendrite extends Synapse implements Homeserver, HomeserverInstance {
protected image = "matrixdotorg/dendrite-monolith:main";
protected entrypoint = "/usr/bin/dendrite";
/**
* Start a dendrite instance: the template must be the name of one of the templates
* in the playwright/plugins/dendritedocker/templates directory
* @param opts
*/
public async start(opts: StartHomeserverOpts): Promise<HomeserverInstance> {
const denCfg = await cfgDirFromTemplate(this.image, opts);
console.log(`Starting dendrite with config dir ${denCfg.configDir}...`);
const dendriteId = await this.docker.run({
image: this.image,
params: [
"-v",
`${denCfg.configDir}:` + dockerConfigDir,
"-p",
`${denCfg.port}:8008/tcp`,
"--entrypoint",
this.entrypoint,
],
containerName: `react-sdk-playwright-dendrite`,
cmd: ["--config", dockerConfigDir + dendriteConfigFile, "--really-enable-open-registration", "true", "run"],
});
console.log(`Started dendrite with id ${dendriteId} on port ${denCfg.port}.`);
// Await Dendrite healthcheck
await this.docker.exec([
"curl",
"--connect-timeout",
"30",
"--retry",
"30",
"--retry-delay",
"1",
"--retry-all-errors",
"--silent",
"http://localhost:8008/_matrix/client/versions",
]);
const dockerUrl = `http://${await this.docker.getContainerIp()}:8008`;
this.config = {
...denCfg,
serverId: dendriteId,
dockerUrl,
};
return this;
}
public async stop(): Promise<string[]> {
if (!this.config) throw new Error("Missing existing dendrite instance, did you call stop() before start()?");
const dendriteLogsPath = path.join("playwright", "dendritelogs", this.config.serverId);
await fse.ensureDir(dendriteLogsPath);
await this.docker.persistLogsToFile({
stdoutFile: path.join(dendriteLogsPath, "stdout.log"),
stderrFile: path.join(dendriteLogsPath, "stderr.log"),
});
await this.docker.stop();
await fse.remove(this.config.configDir);
console.log(`Stopped dendrite id ${this.config.serverId}.`);
return [path.join(dendriteLogsPath, "stdout.log"), path.join(dendriteLogsPath, "stderr.log")];
}
}
export class Pinecone extends Dendrite {
protected image = "matrixdotorg/dendrite-demo-pinecone:main";
protected entrypoint = "/usr/bin/dendrite-demo-pinecone";
}
async function cfgDirFromTemplate(
dendriteImage: string,
opts: StartHomeserverOpts,
): Promise<Omit<HomeserverConfig, "dockerUrl">> {
const template = "default"; // XXX: for now we only have one template
const templateDir = path.join(__dirname, "templates", template);
const stats = await fse.stat(templateDir);
if (!stats?.isDirectory) {
throw new Error(`No such template: ${template}`);
}
const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), "react-sdk-dendritedocker-"));
// copy the contents of the template dir, omitting homeserver.yaml as we'll template that
console.log(`Copy ${templateDir} -> ${tempDir}`);
await fse.copy(templateDir, tempDir, { filter: (f) => path.basename(f) !== dendriteConfigFile });
const registrationSecret = randB64Bytes(16);
const port = await getFreePort();
const baseUrl = `http://localhost:${port}`;
// now copy homeserver.yaml, applying substitutions
console.log(`Gen ${path.join(templateDir, dendriteConfigFile)}`);
let hsYaml = await fse.readFile(path.join(templateDir, dendriteConfigFile), "utf8");
hsYaml = hsYaml.replace(/{{REGISTRATION_SECRET}}/g, registrationSecret);
await fse.writeFile(path.join(tempDir, dendriteConfigFile), hsYaml);
const docker = new Docker();
await docker.run({
image: dendriteImage,
params: ["--entrypoint=", "-v", `${tempDir}:/mnt`],
containerName: `react-sdk-playwright-dendrite-keygen`,
cmd: ["/usr/bin/generate-keys", "-private-key", "/mnt/matrix_key.pem"],
});
return {
port,
baseUrl,
configDir: tempDir,
registrationSecret,
};
}
export function isDendrite(): boolean {
return process.env["PLAYWRIGHT_HOMESERVER"] === "dendrite" || process.env["PLAYWRIGHT_HOMESERVER"] === "pinecone";
}