/* 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 { 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 { 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> { 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"; }