mirror of
https://github.com/the-draupnir-project/Draupnir.git
synced 2026-06-03 22:31:17 +00:00
Report validation errors.
This has failed miserably because for some reason transform errors are reported differently to schema validation errors. They aren't treated like a TypeBox `ValueError`. `Config.util.extendDeep` also for some obscure reason introduces `null`s when objects such as `sentry` are undefined, so the schema had to be changed for that.
This commit is contained in:
+36
-113
@@ -30,6 +30,11 @@ import { load } from "js-yaml";
|
||||
import { MatrixClient, LogService } from "matrix-bot-sdk";
|
||||
import Config from "config";
|
||||
import path from "path";
|
||||
import { ConfigSchema } from "./ConfigSchema";
|
||||
import { ActionResult, DecodeException, Logger, StringRoomID, Value, isError } from "matrix-protection-suite";
|
||||
import { ValueError } from "@sinclair/typebox/errors"
|
||||
|
||||
const log = new Logger('config');
|
||||
|
||||
/**
|
||||
* The configuration, as read from production.yaml
|
||||
@@ -39,106 +44,7 @@ import path from "path";
|
||||
// The object is magically generated by external lib `config`
|
||||
// from the file specified by `NODE_ENV`, e.g. production.yaml
|
||||
// or harness.yaml.
|
||||
export interface IConfig {
|
||||
homeserverUrl: string;
|
||||
rawHomeserverUrl: string;
|
||||
accessToken: string;
|
||||
pantalaimon: {
|
||||
use: boolean;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
dataPath: string;
|
||||
/**
|
||||
* If true, Mjolnir will only accept invites from users present in managementRoom.
|
||||
* Otherwise a space must be provided to `acceptInvitesFromSpace`.
|
||||
*/
|
||||
autojoinOnlyIfManager: boolean;
|
||||
/** Mjolnir will accept invites from members of this space if `autojoinOnlyIfManager` is false. */
|
||||
acceptInvitesFromSpace: string;
|
||||
recordIgnoredInvites: boolean;
|
||||
managementRoom: string;
|
||||
verboseLogging: boolean;
|
||||
logLevel: "DEBUG" | "INFO" | "WARN" | "ERROR";
|
||||
logMutedModules: string[],
|
||||
syncOnStartup: boolean;
|
||||
verifyPermissionsOnStartup: boolean;
|
||||
disableServerACL: boolean;
|
||||
noop: boolean;
|
||||
protectedRooms: string[]; // matrix.to urls
|
||||
fasterMembershipChecks: boolean;
|
||||
automaticallyRedactForReasons: string[]; // case-insensitive globs
|
||||
protectAllJoinedRooms: boolean;
|
||||
/**
|
||||
* Backgrounded tasks: number of milliseconds to wait between the completion
|
||||
* of one background task and the start of the next one.
|
||||
*/
|
||||
backgroundDelayMS: number;
|
||||
pollReports: boolean;
|
||||
/**
|
||||
* Whether or not new reports, received either by webapi or polling,
|
||||
* should be printed to our managementRoom.
|
||||
*/
|
||||
displayReports: boolean;
|
||||
admin?: {
|
||||
enableMakeRoomAdminCommand?: boolean;
|
||||
}
|
||||
commands: {
|
||||
allowNoPrefix: boolean;
|
||||
additionalPrefixes: string[];
|
||||
confirmWildcardBan: boolean;
|
||||
features: string[];
|
||||
ban: {
|
||||
defaultReasons: string[]
|
||||
}
|
||||
};
|
||||
protections: {
|
||||
wordlist: {
|
||||
words: string[];
|
||||
minutesBeforeTrusting: number;
|
||||
};
|
||||
};
|
||||
health: {
|
||||
healthz: {
|
||||
enabled: boolean;
|
||||
port: number;
|
||||
address: string;
|
||||
endpoint: string;
|
||||
healthyStatus: number;
|
||||
unhealthyStatus: number;
|
||||
};
|
||||
// If specified, attempt to upload any crash statistics to sentry.
|
||||
sentry?: {
|
||||
dsn: string;
|
||||
|
||||
// Frequency of performance monitoring.
|
||||
//
|
||||
// A number in [0.0, 1.0], where 0.0 means "don't bother with tracing"
|
||||
// and 1.0 means "trace performance at every opportunity".
|
||||
tracesSampleRate: number;
|
||||
};
|
||||
};
|
||||
web: {
|
||||
enabled: boolean;
|
||||
port: number;
|
||||
address: string;
|
||||
abuseReporting: {
|
||||
enabled: boolean;
|
||||
}
|
||||
};
|
||||
// Store room state using sqlite to improve startup time when Synapse responds
|
||||
// slowly to requests for `/state`.
|
||||
roomStateBackingStore: {
|
||||
enabled?: boolean;
|
||||
};
|
||||
// Experimental usage of the matrix-bot-sdk rust crypto.
|
||||
// This can not be used with Pantalaimon.
|
||||
experimentalRustCrypto: boolean;
|
||||
|
||||
/**
|
||||
* Config options only set at runtime. Try to avoid using the objects
|
||||
* here as much as possible.
|
||||
*/
|
||||
export interface IConfig extends ConfigSchema {
|
||||
RUNTIME: {
|
||||
client?: MatrixClient;
|
||||
};
|
||||
@@ -154,19 +60,14 @@ const defaultConfig: IConfig = {
|
||||
password: "",
|
||||
},
|
||||
dataPath: "/data/storage",
|
||||
acceptInvitesFromSpace: '!noop:example.org',
|
||||
managementRoom: '!noop:example.org' as StringRoomID,
|
||||
acceptInvitesFromSpace: '!noop:example.org' as StringRoomID,
|
||||
autojoinOnlyIfManager: true,
|
||||
recordIgnoredInvites: false,
|
||||
managementRoom: "!noop:example.org",
|
||||
verboseLogging: false,
|
||||
logLevel: "INFO",
|
||||
logMutedModules: ['MatrixHttpClient', 'MatrixClientLite'],
|
||||
syncOnStartup: true,
|
||||
verifyPermissionsOnStartup: true,
|
||||
noop: false,
|
||||
disableServerACL: false,
|
||||
protectedRooms: [],
|
||||
fasterMembershipChecks: false,
|
||||
automaticallyRedactForReasons: ["spam", "advertising"],
|
||||
protectAllJoinedRooms: false,
|
||||
backgroundDelayMS: 500,
|
||||
@@ -176,9 +77,6 @@ const defaultConfig: IConfig = {
|
||||
allowNoPrefix: false,
|
||||
additionalPrefixes: ["draupnir"],
|
||||
confirmWildcardBan: true,
|
||||
features: [
|
||||
"synapse admin",
|
||||
],
|
||||
ban: {
|
||||
defaultReasons: [
|
||||
"spam",
|
||||
@@ -203,6 +101,7 @@ const defaultConfig: IConfig = {
|
||||
healthyStatus: 200,
|
||||
unhealthyStatus: 418,
|
||||
},
|
||||
sentry: undefined,
|
||||
},
|
||||
web: {
|
||||
enabled: false,
|
||||
@@ -229,19 +128,43 @@ export function getDefaultConfig(): IConfig {
|
||||
/**
|
||||
* @returns The users's raw config, deep copied over the `defaultConfig`.
|
||||
*/
|
||||
function readConfigSource(): IConfig {
|
||||
function readConfigSource(): Record<string, unknown> {
|
||||
const explicitConfigPath = getCommandLineOption(process.argv, "--draupnir-config");
|
||||
if (explicitConfigPath !== undefined) {
|
||||
const content = fs.readFileSync(explicitConfigPath, "utf8");
|
||||
const parsed = load(content);
|
||||
return Config.util.extendDeep({}, defaultConfig, parsed);
|
||||
} else {
|
||||
return Config.util.extendDeep({}, defaultConfig, Config.util.toObject()) as IConfig;
|
||||
return Config.util.extendDeep({}, defaultConfig, Config.util.toObject());
|
||||
}
|
||||
}
|
||||
|
||||
function readConfigSourceAndValidate(): ActionResult<IConfig, DecodeException> {
|
||||
const source = readConfigSource();
|
||||
const decodedSource = Value.Decode(ConfigSchema, source, {suppressLogOnError: true});
|
||||
return decodedSource as ActionResult<IConfig, DecodeException>;
|
||||
}
|
||||
|
||||
function renderValidationError(error: ValueError): void {
|
||||
log.error(
|
||||
`Config value at path ${error.path} failed to validate with value:`,
|
||||
error.value,
|
||||
`and the following message:`,
|
||||
error.message
|
||||
);
|
||||
log.info(`Here is the description for ${error.path}:`, error.schema.description);
|
||||
}
|
||||
|
||||
export function read(): IConfig {
|
||||
const config = readConfigSource();
|
||||
const configResult = readConfigSourceAndValidate();
|
||||
if (isError(configResult)) {
|
||||
configResult.error.errors.forEach(renderValidationError)
|
||||
if (configResult.error.errors.length === 0) {
|
||||
log.error(`Config error:`, configResult.error.exception);
|
||||
}
|
||||
throw new TypeError(`Config file failed validation`);
|
||||
}
|
||||
const config = configResult.ok;
|
||||
const explicitAccessTokenPath = getCommandLineOption(process.argv, "--access-token-path");
|
||||
const explicitPantalaimonPasswordPath = getCommandLineOption(process.argv, "--pantalaimon-password-path");
|
||||
if (explicitAccessTokenPath !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user