149 lines
5.2 KiB
TypeScript
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";
|
|
}
|