mirror of
https://github.com/the-draupnir-project/Draupnir.git
synced 2026-05-26 10:24:05 +00:00
Add config schema to appservice config.
Make appservice datapath example consistent with docker image. Make the appservice config schema check the admin room properly. We now parse the room id/alias/or permalink. Make sure to parse the config from cli.ts
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
import { DataStore } from ".//datastore";
|
||||
import { PgDataStore } from "./postgres/PgDataStore";
|
||||
import { Api } from "./Api";
|
||||
import { IConfig } from "./config/config";
|
||||
import { AppserviceConfig } from "./config/config";
|
||||
import { AccessControl } from "./AccessControl";
|
||||
import { AppserviceCommandHandler } from "./bot/AppserviceCommandHandler";
|
||||
import { getStoragePath, SOFTWARE_VERSION } from "../config";
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
ClientCapabilityFactory,
|
||||
RoomStateManagerFactory,
|
||||
joinedRoomsSafe,
|
||||
resolveRoomReferenceSafe,
|
||||
} from "matrix-protection-suite-for-matrix-bot-sdk";
|
||||
import {
|
||||
ClientsInRoomMap,
|
||||
@@ -43,11 +42,11 @@ import {
|
||||
} from "matrix-protection-suite";
|
||||
import { AppServiceDraupnirManager } from "./AppServiceDraupnirManager";
|
||||
import {
|
||||
isStringRoomAlias,
|
||||
isStringRoomID,
|
||||
MatrixRoomReference,
|
||||
StringRoomID,
|
||||
StringUserID,
|
||||
isStringRoomAlias,
|
||||
isStringRoomID,
|
||||
} from "@the-draupnir-project/matrix-basic-types";
|
||||
import { SqliteRoomStateBackingStore } from "../backingstore/better-sqlite3/SqliteRoomStateBackingStore";
|
||||
|
||||
@@ -65,7 +64,7 @@ export class MjolnirAppService {
|
||||
* use `makeMjolnirAppService`.
|
||||
*/
|
||||
private constructor(
|
||||
public readonly config: IConfig,
|
||||
public readonly config: AppserviceConfig,
|
||||
public readonly bridge: Bridge,
|
||||
public readonly draupnirManager: AppServiceDraupnirManager,
|
||||
public readonly accessControl: AccessControl,
|
||||
@@ -97,7 +96,7 @@ export class MjolnirAppService {
|
||||
* @returns A new `MjolnirAppService`.
|
||||
*/
|
||||
public static async makeMjolnirAppService(
|
||||
config: IConfig,
|
||||
config: AppserviceConfig,
|
||||
dataStore: DataStore,
|
||||
eventDecoder: EventDecoder,
|
||||
registrationFilePath: string,
|
||||
@@ -122,24 +121,6 @@ export class MjolnirAppService {
|
||||
disableStores: true,
|
||||
});
|
||||
await bridge.initialise();
|
||||
const adminRoom = (() => {
|
||||
if (isStringRoomID(config.adminRoom)) {
|
||||
return MatrixRoomReference.fromRoomID(config.adminRoom);
|
||||
} else if (isStringRoomAlias(config.adminRoom)) {
|
||||
return MatrixRoomReference.fromRoomIDOrAlias(config.adminRoom);
|
||||
} else {
|
||||
const parseResult = MatrixRoomReference.fromPermalink(config.adminRoom);
|
||||
if (isError(parseResult)) {
|
||||
throw new TypeError(
|
||||
`${config.adminRoom} needs to be a room id, alias or permalink`
|
||||
);
|
||||
}
|
||||
return parseResult.ok;
|
||||
}
|
||||
})();
|
||||
const accessControlRoom = (
|
||||
await resolveRoomReferenceSafe(bridge.getBot().getClient(), adminRoom)
|
||||
).expect("Unable to resolve the admin room");
|
||||
const clientsInRoomMap = new StandardClientsInRoomMap();
|
||||
const clientProvider = async (clientUserID: StringUserID) =>
|
||||
bridge.getIntent(clientUserID).matrixClient;
|
||||
@@ -162,6 +143,24 @@ export class MjolnirAppService {
|
||||
const botRoomJoiner = clientCapabilityFactory
|
||||
.makeClientPlatform(botUserID, bridge.getBot().getClient())
|
||||
.toRoomJoiner();
|
||||
const adminRoom = (() => {
|
||||
if (isStringRoomID(config.adminRoom)) {
|
||||
return MatrixRoomReference.fromRoomID(config.adminRoom);
|
||||
} else if (isStringRoomAlias(config.adminRoom)) {
|
||||
return MatrixRoomReference.fromRoomIDOrAlias(config.adminRoom);
|
||||
} else {
|
||||
const parseResult = MatrixRoomReference.fromPermalink(config.adminRoom);
|
||||
if (isError(parseResult)) {
|
||||
throw new TypeError(
|
||||
`${config.adminRoom} needs to be a room id, alias or permalink`
|
||||
);
|
||||
}
|
||||
return parseResult.ok;
|
||||
}
|
||||
})();
|
||||
const accessControlRoom = (
|
||||
await botRoomJoiner.resolveRoom(adminRoom)
|
||||
).expect("Unable to resolve the admin room");
|
||||
const appserviceBotPolicyRoomManager =
|
||||
await roomStateManagerFactory.getPolicyRoomManager(botUserID);
|
||||
const accessControl = (
|
||||
@@ -224,7 +223,7 @@ export class MjolnirAppService {
|
||||
*/
|
||||
public static async run(
|
||||
port: number,
|
||||
config: IConfig,
|
||||
config: AppserviceConfig,
|
||||
registrationFilePath: string
|
||||
): Promise<MjolnirAppService> {
|
||||
Logger.configure(config.logging ?? { console: "debug" });
|
||||
@@ -232,7 +231,7 @@ export class MjolnirAppService {
|
||||
await dataStore.init();
|
||||
const eventDecoder = DefaultEventDecoder;
|
||||
const storagePath = getStoragePath(config.dataPath);
|
||||
const backingStore = config.roomStateBackingStore.enabled
|
||||
const backingStore = config.roomStateBackingStore?.enabled
|
||||
? SqliteRoomStateBackingStore.create(storagePath, eventDecoder)
|
||||
: undefined;
|
||||
const service = await MjolnirAppService.makeMjolnirAppService(
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
|
||||
import { Cli } from "matrix-appservice-bridge";
|
||||
import { MjolnirAppService } from "./AppService";
|
||||
import { IConfig } from "./config/config";
|
||||
import { Task } from "matrix-protection-suite";
|
||||
import { AppserviceConfig } from "./config/config";
|
||||
import { Value } from "@sinclair/typebox/value";
|
||||
|
||||
/**
|
||||
* This file provides the entrypoint for the appservice mode for draupnir.
|
||||
@@ -27,7 +28,7 @@ const cli = new Cli({
|
||||
},
|
||||
generateRegistration: MjolnirAppService.generateRegistration,
|
||||
run: function (port: number) {
|
||||
const config: IConfig | null = cli.getConfig() as unknown as IConfig | null;
|
||||
const config = cli.getConfig();
|
||||
if (config === null) {
|
||||
throw new Error("Couldn't load config");
|
||||
}
|
||||
@@ -35,7 +36,9 @@ const cli = new Cli({
|
||||
(async () => {
|
||||
await MjolnirAppService.run(
|
||||
port,
|
||||
config,
|
||||
// we use the matrix-appservice-bridge library to handle cli arguments for loading the config
|
||||
// but we have to still validate it ourselves.
|
||||
Value.Decode(AppserviceConfig, config),
|
||||
cli.getRegistrationFilePath()
|
||||
);
|
||||
})()
|
||||
|
||||
@@ -18,7 +18,7 @@ webAPI:
|
||||
port: 9001
|
||||
|
||||
# The directory the bot should store various bits of information in
|
||||
dataPath: "./test/harness/mjolnir-data/"
|
||||
dataPath: "/data/storage"
|
||||
|
||||
roomStateBackingStore:
|
||||
enabled: false
|
||||
|
||||
+101
-36
@@ -8,44 +8,109 @@
|
||||
// https://github.com/matrix-org/mjolnir
|
||||
// </text>
|
||||
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import * as fs from "fs";
|
||||
import { load } from "js-yaml";
|
||||
import { LoggingOpts } from "matrix-appservice-bridge";
|
||||
import { Value } from "@sinclair/typebox/value";
|
||||
import { EDStatic } from "matrix-protection-suite";
|
||||
|
||||
export interface IConfig {
|
||||
/** Details for the homeserver the appservice will be serving */
|
||||
homeserver: {
|
||||
/** The domain of the homeserver that is found at the end of mxids */
|
||||
domain: string;
|
||||
/** The url to use to acccess the client server api e.g. "https://matrix-client.matrix.org" */
|
||||
url: string;
|
||||
};
|
||||
/** Details for the database backend */
|
||||
db: {
|
||||
/** Postgres connection string */
|
||||
connectionString: string;
|
||||
};
|
||||
/** Config for the web api used to access the appservice via the widget */
|
||||
webAPI: {
|
||||
port: number;
|
||||
};
|
||||
/** The admin room for the appservice bot. Not called managementRoom like draupnir on purpose, so they're not mixed in code somehow. */
|
||||
adminRoom: string;
|
||||
/** configuration for matrix-appservice-bridge's Logger */
|
||||
logging?: LoggingOpts;
|
||||
|
||||
dataPath: string;
|
||||
|
||||
// Store room state using sqlite to improve startup time when Synapse responds
|
||||
// slowly to requests for `/state`.
|
||||
roomStateBackingStore: {
|
||||
enabled?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export function read(configPath: string): IConfig {
|
||||
export function read(configPath: string): AppserviceConfig {
|
||||
const content = fs.readFileSync(configPath, "utf8");
|
||||
const parsed = load(content);
|
||||
const config = parsed as object as IConfig;
|
||||
return config;
|
||||
const jsonParsed = load(content);
|
||||
const decodedConfig = Value.Decode(AppserviceConfig, jsonParsed);
|
||||
return decodedConfig;
|
||||
}
|
||||
|
||||
export const LoggingOptsSchema = Type.Object({
|
||||
console: Type.Optional(
|
||||
Type.Union(
|
||||
[
|
||||
Type.Literal("debug"),
|
||||
Type.Literal("info"),
|
||||
Type.Literal("warn"),
|
||||
Type.Literal("error"),
|
||||
Type.Literal("trace"),
|
||||
Type.Literal("off"),
|
||||
],
|
||||
{ description: "The log level used by the console output." }
|
||||
)
|
||||
),
|
||||
json: Type.Optional(
|
||||
Type.Boolean({
|
||||
description:
|
||||
"Should the logs be outputted in JSON format, for consumption by a collector.",
|
||||
})
|
||||
),
|
||||
colorize: Type.Optional(
|
||||
Type.Boolean({
|
||||
description:
|
||||
"Should the logs color-code the level strings in the output.",
|
||||
})
|
||||
),
|
||||
timestampFormat: Type.Optional(
|
||||
Type.String({
|
||||
description: "Timestamp format used in the log output.",
|
||||
default: "HH:mm:ss:SSS",
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export type AppserviceConfig = EDStatic<typeof AppserviceConfig>;
|
||||
export const AppserviceConfig = Type.Object({
|
||||
homeserver: Type.Object(
|
||||
{
|
||||
domain: Type.String({
|
||||
description:
|
||||
"The domain of the homeserver that is found at the end of mxids",
|
||||
}),
|
||||
url: Type.String({
|
||||
description:
|
||||
"The url to use to access the client server api e.g. 'https://matrix-client.matrix.org'",
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: "Details for the homeserver the appservice will be serving ",
|
||||
}
|
||||
),
|
||||
db: Type.Object(
|
||||
{
|
||||
connectionString: Type.String({
|
||||
description: "Postgres connection string",
|
||||
}),
|
||||
},
|
||||
{ description: "Details for the database backend" }
|
||||
),
|
||||
webAPI: Type.Object(
|
||||
{
|
||||
port: Type.Number({
|
||||
description:
|
||||
"Port number for the web API used to access the appservice via the widget",
|
||||
}),
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Config for the web api used to access the appservice via the widget",
|
||||
}
|
||||
),
|
||||
adminRoom: Type.String({
|
||||
description:
|
||||
"The admin room for the appservice bot. Not called managementRoom like draupnir on purpose, so they're not mixed in code somehow.",
|
||||
}),
|
||||
roomStateBackingStore: Type.Optional(
|
||||
Type.Object(
|
||||
{
|
||||
enabled: Type.Boolean(),
|
||||
},
|
||||
{
|
||||
description:
|
||||
"Store room state using sqlite to improve startup time when Synapse responds slowly to requests for `/state`.",
|
||||
}
|
||||
)
|
||||
),
|
||||
dataPath: Type.String({
|
||||
description:
|
||||
"A directory where the appservice can storestore persistent data.",
|
||||
default: "/data/storage",
|
||||
}),
|
||||
logging: Type.Optional(LoggingOptsSchema),
|
||||
});
|
||||
|
||||
@@ -13,13 +13,14 @@ import { MjolnirAppService } from "../../../src/appservice/AppService";
|
||||
import { ensureAliasedRoomExists } from "../../integration/mjolnirSetupUtils";
|
||||
import {
|
||||
read as configRead,
|
||||
IConfig,
|
||||
AppserviceConfig,
|
||||
} from "../../../src/appservice/config/config";
|
||||
import { newTestUser } from "../../integration/clientHelper";
|
||||
import { CreateEvent, MatrixClient } from "matrix-bot-sdk";
|
||||
import { POLICY_ROOM_TYPE_VARIANTS } from "matrix-protection-suite";
|
||||
import { isStringRoomAlias } from "@the-draupnir-project/matrix-basic-types";
|
||||
|
||||
export function readTestConfig(): IConfig {
|
||||
export function readTestConfig(): AppserviceConfig {
|
||||
return configRead(
|
||||
path.join(__dirname, "../../../src/appservice/config/config.harness.yaml")
|
||||
);
|
||||
@@ -30,6 +31,14 @@ export async function setupHarness(): Promise<MjolnirAppService> {
|
||||
const utilityUser = await newTestUser(config.homeserver.url, {
|
||||
name: { contains: "utility" },
|
||||
});
|
||||
if (
|
||||
typeof config.adminRoom !== "string" ||
|
||||
!isStringRoomAlias(config.adminRoom)
|
||||
) {
|
||||
throw new TypeError(
|
||||
"This test expects the harness config to have a room alias."
|
||||
);
|
||||
}
|
||||
await ensureAliasedRoomExists(utilityUser, config.adminRoom);
|
||||
return await MjolnirAppService.run(
|
||||
9000,
|
||||
|
||||
Reference in New Issue
Block a user