mirror of
https://github.com/thelounge/thelounge
synced 2024-11-22 12:03:11 +00:00
74aff7ee5a
Once this is getting hooked up, it'll periodically delete old messages. The StoragePolicy can be chosen by the user, currently there's two versions, delete everything based on age is the obvious. The other is for the data hoarders among us. It'll only delete message types which can be considered low value... Types with a time aspect like away / back... joins / parts etc. It tries to do that in a sensible way, so that we don't block all other db writers that are ongoing. The "periodically" interval is by design not exposed to the user.
295 lines
6.4 KiB
TypeScript
295 lines
6.4 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
import path from "path";
|
|
import fs, {Stats} from "fs";
|
|
import os from "os";
|
|
import _ from "lodash";
|
|
import colors from "chalk";
|
|
|
|
import log from "./log";
|
|
import Helper from "./helper";
|
|
import Utils from "./command-line/utils";
|
|
import Network from "./models/network";
|
|
|
|
// TODO: Type this
|
|
export type WebIRC = {
|
|
[key: string]: any;
|
|
};
|
|
|
|
type Https = {
|
|
enable: boolean;
|
|
key: string;
|
|
certificate: string;
|
|
ca: string;
|
|
};
|
|
|
|
type FileUpload = {
|
|
enable: boolean;
|
|
maxFileSize: number;
|
|
baseUrl?: string;
|
|
};
|
|
|
|
export type Defaults = Pick<
|
|
Network,
|
|
| "name"
|
|
| "host"
|
|
| "port"
|
|
| "password"
|
|
| "tls"
|
|
| "rejectUnauthorized"
|
|
| "nick"
|
|
| "username"
|
|
| "realname"
|
|
| "leaveMessage"
|
|
| "sasl"
|
|
| "saslAccount"
|
|
| "saslPassword"
|
|
> & {
|
|
join?: string;
|
|
};
|
|
|
|
type Identd = {
|
|
enable: boolean;
|
|
port: number;
|
|
};
|
|
|
|
type SearchDN = {
|
|
rootDN: string;
|
|
rootPassword: string;
|
|
filter: string;
|
|
base: string;
|
|
scope: string;
|
|
};
|
|
|
|
type Ldap = {
|
|
enable: boolean;
|
|
url: string;
|
|
tlsOptions: any;
|
|
primaryKey: string;
|
|
searchDN: SearchDN;
|
|
baseDN?: string;
|
|
};
|
|
|
|
type TlsOptions = any;
|
|
|
|
type Debug = {
|
|
ircFramework: boolean;
|
|
raw: boolean;
|
|
};
|
|
|
|
type StoragePolicy = {
|
|
enabled: boolean;
|
|
maxAgeDays: number;
|
|
deletionPolicy: "statusOnly" | "everything";
|
|
};
|
|
|
|
export type ConfigType = {
|
|
public: boolean;
|
|
host: string | undefined;
|
|
port: number;
|
|
bind: string | undefined;
|
|
reverseProxy: boolean;
|
|
maxHistory: number;
|
|
https: Https;
|
|
theme: string;
|
|
prefetch: boolean;
|
|
disableMediaPreview: boolean;
|
|
prefetchStorage: boolean;
|
|
prefetchMaxImageSize: number;
|
|
prefetchMaxSearchSize: number;
|
|
prefetchTimeout: number;
|
|
fileUpload: FileUpload;
|
|
transports: string[];
|
|
leaveMessage: string;
|
|
defaults: Defaults;
|
|
lockNetwork: boolean;
|
|
messageStorage: string[];
|
|
storagePolicy: StoragePolicy;
|
|
useHexIp: boolean;
|
|
webirc?: WebIRC;
|
|
identd: Identd;
|
|
oidentd?: string;
|
|
ldap: Ldap;
|
|
debug: Debug;
|
|
themeColor: string;
|
|
};
|
|
|
|
class Config {
|
|
values = require(path.resolve(
|
|
path.join(__dirname, "..", "defaults", "config.js")
|
|
)) as ConfigType;
|
|
#homePath = "";
|
|
|
|
getHomePath() {
|
|
return this.#homePath;
|
|
}
|
|
|
|
getConfigPath() {
|
|
return path.join(this.#homePath, "config.js");
|
|
}
|
|
|
|
getUserLogsPath() {
|
|
return path.join(this.#homePath, "logs");
|
|
}
|
|
|
|
getStoragePath() {
|
|
return path.join(this.#homePath, "storage");
|
|
}
|
|
|
|
getFileUploadPath() {
|
|
return path.join(this.#homePath, "uploads");
|
|
}
|
|
|
|
getUsersPath() {
|
|
return path.join(this.#homePath, "users");
|
|
}
|
|
|
|
getUserConfigPath(name: string) {
|
|
return path.join(this.getUsersPath(), `${name}.json`);
|
|
}
|
|
|
|
getClientCertificatesPath() {
|
|
return path.join(this.#homePath, "certificates");
|
|
}
|
|
|
|
getPackagesPath() {
|
|
return path.join(this.#homePath, "packages");
|
|
}
|
|
|
|
getPackageModulePath(packageName: string) {
|
|
return path.join(this.getPackagesPath(), "node_modules", packageName);
|
|
}
|
|
|
|
getDefaultNick() {
|
|
if (!this.values.defaults.nick) {
|
|
return "thelounge";
|
|
}
|
|
|
|
return this.values.defaults.nick.replace(/%/g, () =>
|
|
Math.floor(Math.random() * 10).toString()
|
|
);
|
|
}
|
|
|
|
merge(newConfig: ConfigType) {
|
|
this._merge_config_objects(this.values, newConfig);
|
|
}
|
|
|
|
_merge_config_objects(oldConfig: ConfigType, newConfig: ConfigType) {
|
|
// semi exposed function so that we can test it
|
|
// it mutates the oldConfig, but returns it as a convenience for testing
|
|
|
|
for (const key in newConfig) {
|
|
if (!Object.prototype.hasOwnProperty.call(oldConfig, key)) {
|
|
log.warn(`Unknown key "${colors.bold(key)}", please verify your config.`);
|
|
}
|
|
}
|
|
|
|
return _.mergeWith(oldConfig, newConfig, (objValue, srcValue, key) => {
|
|
// Do not override config variables if the type is incorrect (e.g. object changed into a string)
|
|
if (
|
|
typeof objValue !== "undefined" &&
|
|
objValue !== null &&
|
|
typeof objValue !== typeof srcValue
|
|
) {
|
|
log.warn(`Incorrect type for "${colors.bold(key)}", please verify your config.`);
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
return objValue;
|
|
}
|
|
|
|
// For arrays, simply override the value with user provided one.
|
|
if (_.isArray(objValue)) {
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
return srcValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
setHome(newPath: string) {
|
|
this.#homePath = Helper.expandHome(newPath);
|
|
|
|
// Reload config from new home location
|
|
const configPath = this.getConfigPath();
|
|
|
|
if (fs.existsSync(configPath)) {
|
|
const userConfig = require(configPath);
|
|
|
|
if (_.isEmpty(userConfig)) {
|
|
log.warn(
|
|
`The file located at ${colors.green(
|
|
configPath
|
|
)} does not appear to expose anything.`
|
|
);
|
|
log.warn(
|
|
`Make sure it is non-empty and the configuration is exported using ${colors.bold(
|
|
"module.exports = { ... }"
|
|
)}.`
|
|
);
|
|
log.warn("Using default configuration...");
|
|
}
|
|
|
|
this.merge(userConfig);
|
|
}
|
|
|
|
if (this.values.fileUpload.baseUrl) {
|
|
try {
|
|
new URL("test/file.png", this.values.fileUpload.baseUrl);
|
|
} catch (e: any) {
|
|
this.values.fileUpload.baseUrl = undefined;
|
|
|
|
log.warn(
|
|
`The ${colors.bold("fileUpload.baseUrl")} you specified is invalid: ${String(
|
|
e
|
|
)}`
|
|
);
|
|
}
|
|
}
|
|
|
|
const manifestPath = Utils.getFileFromRelativeToRoot("public", "thelounge.webmanifest");
|
|
|
|
// Check if manifest exists, if not, the app most likely was not built
|
|
if (!fs.existsSync(manifestPath)) {
|
|
log.error(
|
|
`The client application was not built. Run ${colors.bold(
|
|
"NODE_ENV=production yarn build"
|
|
)} to resolve this.`
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
// Load theme color from the web manifest
|
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
this.values.themeColor = manifest.theme_color;
|
|
|
|
// log dir probably shouldn't be world accessible.
|
|
// Create it with the desired permission bits if it doesn't exist yet.
|
|
let logsStat: Stats | undefined = undefined;
|
|
|
|
const userLogsPath = this.getUserLogsPath();
|
|
|
|
try {
|
|
logsStat = fs.statSync(userLogsPath);
|
|
} catch {
|
|
// ignored on purpose, node v14.17.0 will give us {throwIfNoEntry: false}
|
|
}
|
|
|
|
if (!logsStat) {
|
|
try {
|
|
fs.mkdirSync(userLogsPath, {recursive: true, mode: 0o750});
|
|
} catch (e: any) {
|
|
log.error("Unable to create logs directory", e);
|
|
}
|
|
} else if (logsStat && logsStat.mode & 0o001) {
|
|
log.warn(
|
|
userLogsPath,
|
|
"is world readable.",
|
|
"The log files may be exposed. Please fix the permissions."
|
|
);
|
|
|
|
if (os.platform() !== "win32") {
|
|
log.warn(`run \`chmod o-x "${userLogsPath}"\` to correct it.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new Config();
|