Zero Touch Provisioning Support

This commit is contained in:
Catalan Lover committed 2026-04-16 20:03:40 +02:00
1 parent caa9938147
commit 336f413f8d
16 files changed
+450 -78

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
---
"draupnir": minor
---
Add managed management/admin room support to bot mode / appservice mode.
\- Add managed management / admin room support
- Add new config options for zero touch provisioning of managed rooms:
\- managedManagementRoom and initialManager for bot mode
\- managedAdminRoom and initialManager for appservice mode
\- If managed modes are enabled while unmanaged rooms are defined the bot will
crash.
\- Fix TrustedReporters being broken by managed management rooms due to explicit
reliance on a management room in the config.
\- Add integration testing for Zero Touch Provisioning workflows.
+34 -19
View File
@@ -48,7 +48,10 @@ import { ResultError } from "@gnuxie/typescript-result";
import { SafeModeCause, SafeModeReason } from "./safemode/SafeModeCause";
import { SafeModeBootOption } from "./safemode/BootOption";
import { TopLevelStores } from "./backingstore/DraupnirStores";
import { MANAGEMENT_ROOM_ACCOUNT_DATA_EVENT_TYPE } from "./managedRoomAccountData";
import { makeManagementRoom } from "./appservice/AppServiceDraupnirManager";
import { patchMatrixClient } from "./utils";
import { resolveManagedRoom } from "./managedRoomBootstrap";
const log = new Logger("DraupnirBotMode");
@@ -116,17 +119,6 @@ export class DraupnirBotModeToggle implements BotModeTogle {
if (!isStringUserID(clientUserID)) {
throw new TypeError(`${clientUserID} is not a valid mxid`);
}
if (
!isStringRoomAlias(config.managementRoom) &&
!isStringRoomID(config.managementRoom)
) {
throw new TypeError(
`${config.managementRoom} is not a valid room id or alias`
);
}
const configManagementRoomReference = MatrixRoomReference.fromRoomIDOrAlias(
config.managementRoom
);
const clientsInRoomMap = new StandardClientsInRoomMap();
const clientCapabilityFactory = new ClientCapabilityFactory(
clientsInRoomMap,
@@ -142,14 +134,37 @@ export class DraupnirBotModeToggle implements BotModeTogle {
clientUserID,
client
);
const managementRoom = (
await clientPlatform
.toRoomResolver()
.resolveRoom(configManagementRoomReference)
).expect("Unable to resolve Draupnir's management room");
(await clientPlatform.toRoomJoiner().joinRoom(managementRoom)).expect(
"Unable to join Draupnir's management room"
);
const managementRoom = await resolveManagedRoom({
managedRoomEnabled: config.managedManagementRoom,
configuredRoom: config.managementRoom,
initialManager: config.initialManager,
clientUserID,
client,
clientPlatform,
accountDataEventType: MANAGEMENT_ROOM_ACCOUNT_DATA_EVENT_TYPE,
roomKindDescription: "management room",
parseConfiguredRoom(configuredRoom: string): MatrixRoomReference {
if (
!isStringRoomAlias(configuredRoom) &&
!isStringRoomID(configuredRoom)
) {
throw new TypeError(
`${configuredRoom} is not a valid room id or alias`
);
}
return MatrixRoomReference.fromRoomIDOrAlias(configuredRoom);
},
async createManagedRoom(initialManager, resolvedClientUserID) {
return (
await makeManagementRoom(
clientPlatform.toRoomCreator(),
clientPlatform.toClientCapabilitiesNegotiation(),
initialManager,
resolvedClientUserID
)
).expect("Unable to create managed management room");
},
});
const clientProvider = async (userID: StringUserID) => {
if (userID !== clientUserID) {
throw new TypeError(`Bot mode shouldn't be requesting any other mxids`);
+43 -16
View File
@@ -55,6 +55,9 @@ import { SqliteRoomStateBackingStore } from "../backingstore/better-sqlite3/Sqli
import { TopLevelStores } from "../backingstore/DraupnirStores";
import { patchMatrixClient } from "../utils";
import { Result } from "@gnuxie/typescript-result";
import { ADMIN_ROOM_ACCOUNT_DATA_EVENT_TYPE } from "../managedRoomAccountData";
import { makeManagementRoom } from "./AppServiceDraupnirManager";
import { resolveManagedRoom } from "../managedRoomBootstrap";
const log = new Logger("AppService");
/**
@@ -198,27 +201,51 @@ export class MjolnirAppService {
joinedRoomsSafe(bridge.getBot().getClient())
)
).expect("Unable to initialize client rooms for the appservice bot user");
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);
const botClient = bridge.getBot().getClient();
const botClientPlatform = clientCapabilityFactory.makeClientPlatform(
botUserID,
botClient
);
const botRoomJoiner = botClientPlatform.toRoomJoiner();
const accessControlRoom = await resolveManagedRoom({
managedRoomEnabled: config.managedAdminRoom,
configuredRoom: config.adminRoom,
initialManager: config.initialManager,
clientUserID: botUserID,
client: botClient,
clientPlatform: botClientPlatform,
accountDataEventType: ADMIN_ROOM_ACCOUNT_DATA_EVENT_TYPE,
roomKindDescription: "admin room",
parseConfiguredRoom(configuredRoom: string): MatrixRoomReference {
if (isStringRoomID(configuredRoom)) {
return MatrixRoomReference.fromRoomID(configuredRoom);
}
if (isStringRoomAlias(configuredRoom)) {
return MatrixRoomReference.fromRoomIDOrAlias(configuredRoom);
}
const parseResult = MatrixRoomReference.fromPermalink(configuredRoom);
if (isError(parseResult)) {
throw new TypeError(
`${config.adminRoom} needs to be a room id, alias or permalink`
`${configuredRoom} 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");
},
async createManagedRoom(initialManager, resolvedClientUserID) {
return (
await makeManagementRoom(
botClientPlatform.toRoomCreator(),
botClientPlatform.toClientCapabilitiesNegotiation(),
initialManager,
resolvedClientUserID,
{
invitees: [initialManager],
roomName: `${initialManager}'s Draupnir Appservice Admin room`,
}
)
).expect("Unable to create managed admin room");
},
});
const appserviceBotPolicyRoomManager =
await roomStateManagerFactory.getPolicyRoomManager(botUserID);
const accessControl = (
@@ -52,7 +52,7 @@ import {
MatrixRoomID,
} from "@the-draupnir-project/matrix-basic-types";
import { TopLevelStores } from "../backingstore/DraupnirStores";
import { Result } from "@gnuxie/typescript-result";
import { Result, ResultError } from "@gnuxie/typescript-result";
const log = new Logger("AppServiceDraupnirManager");
@@ -468,7 +468,11 @@ export async function makeManagementRoom(
roomCreator: RoomCreator,
clientCapabilitiesNegotiation: ClientCapabilitiesNegotiation,
requestingUserID: StringUserID,
draupnirUserID: StringUserID
draupnirUserID: StringUserID,
options?: {
invitees?: StringUserID[];
roomName?: string;
}
): Promise<Result<MatrixRoomID>> {
const capabilities =
await clientCapabilitiesNegotiation.getClientCapabilities();
@@ -477,15 +481,20 @@ export async function makeManagementRoom(
"Failed to fetch room versions from client capabilities"
);
}
const isRoomVersionWithPriviligedCreators =
RoomVersionMirror.isVersionWithPrivilegedCreators(
capabilities.ok.capabilities["m.room_versions"].default
const defaultRoomVersion =
capabilities.ok.capabilities["m.room_versions"].default;
if (typeof defaultRoomVersion !== "string") {
return ResultError.Result(
"Client capabilities did not contain a valid default room version"
);
}
const isRoomVersionWithPrivilegedCreators =
RoomVersionMirror.isVersionWithPrivilegedCreators(defaultRoomVersion);
return await roomCreator.createRoom({
preset: "private_chat",
invite: [requestingUserID],
name: `${requestingUserID}'s Draupnir`,
power_level_content_override: isRoomVersionWithPriviligedCreators
invite: options?.invitees ?? [requestingUserID],
name: options?.roomName ?? `${requestingUserID}'s Draupnir`,
power_level_content_override: isRoomVersionWithPrivilegedCreators
? {
users: {
[requestingUserID]: 150,
@@ -13,6 +13,12 @@ db:
# See docs/access_control.md
adminRoom: "#draupnir-admin:localhost:9999"
# Enable automatic admin-room recovery/creation on first boot.
managedAdminRoom: false
# The initial manager to invite if the admin room has to be created.
# initialManager: "@admin:example.org"
# The directory the bot should store various bits of information in
dataPath: "/data/storage"
@@ -8,6 +8,10 @@ db:
adminRoom: "#draupnir-admin:localhost:9999"
managedAdminRoom: false
# initialManager: "@admin:example.org"
# The directory the bot should store various bits of information in
dataPath: "./test/harness/mjolnir-data/"
+10 -3
View File
@@ -14,6 +14,7 @@ import * as fs from "fs";
import { load } from "js-yaml";
import { Value } from "@sinclair/typebox/value";
import { EDStatic } from "matrix-protection-suite";
import { StringUserIDSchema } from "matrix-protection-suite";
export function read(configPath: string): AppserviceConfig {
const content = fs.readFileSync(configPath, "utf8");
@@ -81,10 +82,16 @@ export const AppserviceConfig = Type.Object({
},
{ description: "Details for the database backend" }
),
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.",
managedAdminRoom: Type.Boolean({
default: false,
}),
initialManager: Type.Optional(StringUserIDSchema),
adminRoom: Type.Optional(
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(
{
+7 -1
View File
@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
// Copyright 2022 Gnuxie <Gnuxie@protonmail.com>
// Copyright 2019, 2021 The Matrix.org Foundation C.I.C.
//
@@ -18,6 +19,7 @@ import Config from "config";
import path from "path";
import { SafeModeBootOption } from "./safemode/BootOption";
import { Logger, setGlobalLoggerProvider } from "matrix-protection-suite";
import { StringUserID } from "@the-draupnir-project/matrix-basic-types";
LogService.setLogger(new RichConsoleLogger());
setGlobalLoggerProvider(new RichConsoleLogger());
@@ -84,7 +86,9 @@ export interface IConfig {
/** Draupnir will accept invites from members of this space if `autojoinOnlyIfManager` is false. */
acceptInvitesFromSpace: string | undefined;
recordIgnoredInvites: boolean;
managementRoom: string;
managedManagementRoom: boolean;
initialManager: StringUserID | undefined;
managementRoom: string | undefined;
logLevel: "DEBUG" | "INFO" | "WARN" | "ERROR";
logMutedModules: string[];
verifyPermissionsOnStartup: boolean;
@@ -205,6 +209,8 @@ const defaultConfig: IConfig = {
acceptInvitesFromSpace: "!noop:example.org",
autojoinOnlyIfManager: true,
recordIgnoredInvites: false,
managedManagementRoom: false,
initialManager: undefined,
managementRoom: "!noop:example.org",
logLevel: "INFO",
logMutedModules: ["MatrixHttpClient", "MatrixClientLite"],
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
//
// SPDX-License-Identifier: AFL-3.0
import { Type } from "@sinclair/typebox";
import { EDStatic, StringRoomIDSchema } from "matrix-protection-suite";
import {
BotSDKMatrixAccountData,
MatrixSendClient,
} from "matrix-protection-suite-for-matrix-bot-sdk";
export const MANAGEMENT_ROOM_ACCOUNT_DATA_EVENT_TYPE =
"space.draupnir.management.room";
export const ADMIN_ROOM_ACCOUNT_DATA_EVENT_TYPE = "space.draupnir.admin.room";
export const ManagedRoomAccountDataRecordSchema = Type.Object({
room_id: StringRoomIDSchema,
});
export type ManagedRoomAccountDataRecord = EDStatic<
typeof ManagedRoomAccountDataRecordSchema
>;
export function makeManagedRoomAccountDataStore(
client: MatrixSendClient,
eventType: string
): BotSDKMatrixAccountData<ManagedRoomAccountDataRecord> {
return new BotSDKMatrixAccountData(
eventType,
ManagedRoomAccountDataRecordSchema,
client
);
}
+101
View File
@@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
//
// SPDX-License-Identifier: AFL-3.0
import { ClientPlatform } from "matrix-protection-suite";
import { MatrixSendClient } from "matrix-protection-suite-for-matrix-bot-sdk";
import {
isStringUserID,
MatrixRoomID,
MatrixRoomReference,
StringUserID,
} from "@the-draupnir-project/matrix-basic-types";
import { makeManagedRoomAccountDataStore } from "./managedRoomAccountData";
export type ManagedRoomBootstrapOptions = {
managedRoomEnabled: boolean;
configuredRoom: string | undefined;
initialManager: StringUserID | undefined;
clientUserID: StringUserID;
client: MatrixSendClient;
clientPlatform: ClientPlatform;
accountDataEventType: string;
roomKindDescription: string;
parseConfiguredRoom(configuredRoom: string): MatrixRoomReference;
createManagedRoom(
initialManager: StringUserID,
clientUserID: StringUserID
): Promise<MatrixRoomID>;
};
export async function resolveManagedRoom(
options: ManagedRoomBootstrapOptions
): Promise<MatrixRoomID> {
if (options.managedRoomEnabled && options.configuredRoom !== undefined) {
throw new TypeError(
`managed ${options.roomKindDescription} mode cannot be used with an explicit ${options.roomKindDescription}; remove the configured room or disable managed mode`
);
}
const roomAccountData = makeManagedRoomAccountDataStore(
options.client,
options.accountDataEventType
);
const roomResolver = options.clientPlatform.toRoomResolver();
const roomJoiner = options.clientPlatform.toRoomJoiner();
if (options.managedRoomEnabled) {
const storedRoom = (await roomAccountData.requestAccountData()).expect(
`Unable to read managed ${options.roomKindDescription} account data`
);
if (storedRoom !== undefined) {
const roomReference = MatrixRoomReference.fromRoomID(storedRoom.room_id);
const room = (await roomResolver.resolveRoom(roomReference)).expect(
`Unable to resolve managed Draupnir ${options.roomKindDescription}`
);
(await roomJoiner.joinRoom(room)).expect(
`Unable to join managed Draupnir ${options.roomKindDescription}`
);
return room;
}
if (options.initialManager === undefined) {
throw new TypeError(
`managed ${options.roomKindDescription} mode is enabled but initialManager is not configured`
);
}
if (!isStringUserID(options.initialManager)) {
throw new TypeError(
`${options.initialManager} is not a valid initial manager mxid`
);
}
const createdRoom = await options.createManagedRoom(
options.initialManager,
options.clientUserID
);
(
await roomAccountData.storeAccountData({
room_id: createdRoom.toRoomIDOrAlias(),
})
).expect(`Unable to persist managed ${options.roomKindDescription}`);
return createdRoom;
}
if (options.configuredRoom === undefined) {
throw new TypeError(
`${options.roomKindDescription} is required when managed ${options.roomKindDescription} mode is disabled`
);
}
const configuredRoomReference = options.parseConfiguredRoom(
options.configuredRoom
);
const configuredRoom = (
await roomResolver.resolveRoom(configuredRoomReference)
).expect(`Unable to resolve Draupnir's ${options.roomKindDescription}`);
(await roomJoiner.joinRoom(configuredRoom)).expect(
`Unable to join Draupnir's ${options.roomKindDescription}`
);
return configuredRoom;
}
@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
// Copyright 2022 Gnuxie <Gnuxie@protonmail.com>
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
@@ -194,7 +195,7 @@ export class TrustedReporters
if (met.length > 0) {
await this.draupnir.client.sendMessage(
this.draupnir.config.managementRoom,
this.draupnir.managementRoomID,
{
msgtype: "m.notice",
body:
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
//
// SPDX-License-Identifier: AFL-3.0
/// <reference types="mocha" />
import { StringUserID } from "@the-draupnir-project/matrix-basic-types";
import { readTestConfig, setupHarnessWithConfig } from "../utils/harness";
import { newTestUser } from "../../integration/clientHelper";
import { MjolnirAppService } from "../../../src/appservice/AppService";
describe("Managed room bootstrap startup integration", function (this: Mocha.Suite) {
it("appservice mode reaches started stage with managed admin room enabled", async function (this: Mocha.Context) {
this.timeout(120000);
const config = readTestConfig();
const initialManager = await newTestUser(config.homeserver.url, {
name: { contains: "managed-admin" },
});
config.managedAdminRoom = true;
config.adminRoom = undefined;
config.initialManager = (await initialManager.getUserId()) as StringUserID;
let appservice: MjolnirAppService | undefined;
try {
appservice = await setupHarnessWithConfig(config, {
ensureAdminRoomAlias: false,
});
if (appservice.accessControlRoomID.length === 0) {
throw new TypeError(
"Managed appservice bootstrap did not produce an admin room"
);
}
} finally {
initialManager.stop();
if (appservice) {
await appservice.close();
}
}
});
});
+30 -24
View File
@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
// Copyright 2022 Gnuxie <Gnuxie@protonmail.com>
// Copyright 2022 The Matrix.org Foundation C.I.C.
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
@@ -33,33 +34,38 @@ export async function setupHarness(): Promise<MjolnirAppService> {
}
export async function setupHarnessWithConfig(
config: AppserviceConfig
config: AppserviceConfig,
options: {
ensureAdminRoomAlias?: boolean;
} = {}
): Promise<MjolnirAppService> {
const utilityUser = await newTestUser(config.homeserver.url, {
name: { contains: "utility" },
isAdmin: true,
});
if (
typeof config.adminRoom !== "string" ||
!isStringRoomAlias(config.adminRoom)
) {
throw new TypeError(
"This test expects the harness config to have a room alias."
if (options.ensureAdminRoomAlias ?? true) {
const utilityUser = await newTestUser(config.homeserver.url, {
name: { contains: "utility" },
isAdmin: true,
});
if (
typeof config.adminRoom !== "string" ||
!isStringRoomAlias(config.adminRoom)
) {
throw new TypeError(
"This test expects the harness config to have a room alias."
);
}
await utilityUser
.deleteRoomAlias(config.adminRoom)
// Ignore if alias doesn't exist yet.
.catch((_: unknown) => undefined);
const adminRoomID = await ensureAliasedRoomExists(
utilityUser,
config.adminRoom
);
await utilityUser.setUserPowerLevel(
"@draupnir-moderation:localhost:9999",
adminRoomID,
100
);
}
await utilityUser
.deleteRoomAlias(config.adminRoom)
// Ignore if alias doesn't exist yet.
.catch((_: unknown) => undefined);
const adminRoomID = await ensureAliasedRoomExists(
utilityUser,
config.adminRoom
);
await utilityUser.setUserPowerLevel(
"@draupnir-moderation:localhost:9999",
adminRoomID,
100
);
return await MjolnirAppService.run(
9000,
config,
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
//
// SPDX-License-Identifier: AFL-3.0 AND Apache-2.0
import { configRead, getStoragePath, IConfig } from "../../src/config";
import { DraupnirBotModeToggle } from "../../src/DraupnirBotMode";
import { makeTopLevelStores } from "../../src/backingstore/DraupnirStores";
import { draupnirClient, makeBotModeToggle } from "./mjolnirSetupUtils";
import { DefaultEventDecoder } from "matrix-protection-suite";
import { StringUserID } from "@the-draupnir-project/matrix-basic-types";
function makeManagedBotModeConfig(): IConfig {
const config = configRead();
const username = `managed_bootstrap_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
const initialManager = `managed_bootstrap_manager_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
const homeserverDomain = new URL(config.homeserverUrl).host;
config.pantalaimon.username = username;
config.pantalaimon.password = username;
config.managementRoom = undefined;
config.managedManagementRoom = true;
config.initialManager =
`@${initialManager}:${homeserverDomain}` as StringUserID;
return config;
}
describe("Managed room bootstrap startup integration", function (this: Mocha.Suite) {
it("bot mode reaches started stage with managed management room enabled", async function (this: Mocha.Context) {
this.timeout(120000);
const config = makeManagedBotModeConfig();
const storagePath = getStoragePath(config.dataPath);
const stores = makeTopLevelStores(storagePath, DefaultEventDecoder, {
isRoomStateBackingStoreEnabled:
config.roomStateBackingStore.enabled ?? false,
});
let toggle: DraupnirBotModeToggle | undefined;
try {
toggle = await makeBotModeToggle(config, {
stores,
allowSafeMode: false,
deleteManagementRoomAliasOnStart: false,
ensureManagementRoomAlias: false,
});
await draupnirClient()?.start();
await toggle.encryptionInitialized();
} finally {
await toggle?.[Symbol.asyncDispose]();
draupnirClient()?.stop();
stores.dispose();
}
});
});
@@ -1,3 +1,4 @@
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
// Copyright 2022 - 2024 Gnuxie <Gnuxie@protonmail.com>
// Copyright 2021 - 2022 The Matrix.org Foundation C.I.C.
//
@@ -144,11 +145,13 @@ export async function makeBotModeToggle(
eraseAccountData,
allowSafeMode,
deleteManagementRoomAliasOnStart: deleteAlias,
ensureManagementRoomAlias,
}: {
stores: TopLevelStores;
eraseAccountData?: boolean;
allowSafeMode?: boolean;
deleteManagementRoomAliasOnStart?: boolean;
ensureManagementRoomAlias?: boolean;
} = { stores: { dispose() {} } }
): Promise<DraupnirBotModeToggle> {
await configureMjolnir(config);
@@ -175,13 +178,48 @@ export async function makeBotModeToggle(
config.homeserverUrl,
await client.getUserId()
);
// defaults to true
if (deleteAlias === undefined || deleteAlias) {
await client
.deleteRoomAlias(config.managementRoom)
.catch((_: unknown) => undefined);
if (config.managedManagementRoom && config.initialManager !== undefined) {
const managerMatch = /^@([^:]+):/.exec(config.initialManager);
if (managerMatch === null || managerMatch[1] === undefined) {
throw new TypeError(
`${config.initialManager} is not a valid initial manager mxid`
);
}
const managerLocalpart = managerMatch[1];
try {
await registerUser(
config.homeserverUrl,
managerLocalpart,
managerLocalpart,
managerLocalpart,
true
);
} catch (e) {
if (
typeof e !== "object" ||
e === null ||
!("body" in e) ||
typeof e.body !== "object" ||
e.body === null ||
!("errcode" in e.body) ||
e.body.errcode !== "M_USER_IN_USE"
) {
throw e;
}
}
}
// defaults to true
if (ensureManagementRoomAlias ?? true) {
if (config.managementRoom === undefined) {
throw new TypeError("managementRoom is required when ensuring alias");
}
if (deleteAlias === undefined || deleteAlias) {
await client
.deleteRoomAlias(config.managementRoom)
.catch((_: unknown) => undefined);
}
await ensureAliasedRoomExists(client, config.managementRoom);
}
await ensureAliasedRoomExists(client, config.managementRoom);
const toggle = await DraupnirBotModeToggle.create(
client,
new SafeMatrixEmitterWrapper(client, DefaultEventDecoder),
+6
View File
@@ -52,6 +52,12 @@ acceptInvitesFromSpace: "!example:example.org"
# Whether Draupnir should report ignored invites to the management room (if autojoinOnlyIfManager is true).
recordIgnoredInvites: false
# Enable automatic management-room recovery/creation on first boot.
managedManagementRoom: false
# The initial manager to invite if the management room has to be created.
# initialManager: "@admin:example.org"
# The room ID (or room alias) of the management room, anyone in this room can issue commands to Draupnir.
#
# Draupnir has no more granular access controls other than this, be sure you trust everyone in this room - secure it!