kozukata-toa/src/lib/setup/config.ts

142 lines
3.7 KiB
TypeScript

import dotenv from "dotenv";
import JSON5 from "json5";
import { z } from "zod";
import { fromZodError } from "zod-validation-error";
import fs from "fs";
dotenv.config();
const logger = console;
const confLocation = process.env.TOA_CONFIG ?? "./config.json5";
let configFile;
try {
configFile = fs.readFileSync(confLocation, "utf-8");
} catch (err) {
logger.error("Error while trying to open conf.json5. Is one present?", { err });
process.exit(1);
}
const config: unknown = JSON5.parse(configFile);
const zod16bitNumber = z.number().gt(0).lte(65535);
const zodLogLevel = z.enum(["crit", "debug", "error", "info", "verbose", "warn"]);
const zodHexString = z.string().regex(/^[0-9a-z]+$/iu, "value is not a hex string");
const zodHexString16 = zodHexString.length(16);
const configSchema = z.object({
NAME: z.string().default("Kozukata Toa"),
DATABASE_PATH: z.string().default("data/db.sqlite3"),
LISTEN_ADDRESS: z.string().ip().default("0.0.0.0"),
LOGGER_CONFIG: z.object({
LOG_LEVEL: zodLogLevel.default("info"),
CONSOLE: z.boolean().default(true),
FOLDER: z.string().default("logs"),
}),
ALLNET_CONFIG: z.object({
ENABLED: z.boolean().default(true),
PORT: zod16bitNumber.default(80),
ALLOW_UNREGISTERED_SERIALS: z.boolean().default(true),
UPDATE_CFG_FOLDER: z.string().optional(),
}),
AIMEDB_CONFIG: z.object({
ENABLED: z.boolean().default(true),
PORT: zod16bitNumber.default(22345),
KEY: z.string().length(16).default("Copyright(C)SEGA"),
AIME_MOBILE_CARD_KEY: zodHexString16.default("5CD3E81B9024F67A"),
RESERVED_CARD_PREFIX: z.string().length(5).default("01053"),
RESERVED_CARD_KEY: zodHexString16.default("E8179645DB3FC02A"),
}),
TITLES_CONFIG: z.object({
ENABLED: z.boolean().default(true),
PORT: zod16bitNumber.default(8080),
HOSTNAME: z.string().default("localhost"),
}),
CHUNITHM_CONFIG: z.object({
ENABLE: z.boolean().default(true),
MODS: z
.object({
TEAM_NAME: z.string().optional(),
USE_LOGIN_BONUS: z.boolean().default(false),
FORCE_UNLOCK_ALL: z.boolean().default(false),
})
.default({ USE_LOGIN_BONUS: false, FORCE_UNLOCK_ALL: false }),
VERSIONS: z
.record(
z.object({
rom: z.string(),
data: z.string(),
})
)
.default({
"200": {
rom: "2.00.00",
data: "2.00.00",
},
"205": {
rom: "2.05.00",
data: "2.05.00",
},
"210": {
rom: "2.10.00",
data: "2.10.00",
},
}),
CRYPTO: z
.object({
ENCRYPTED_ONLY: z.boolean().default(false),
KEYS: z.record(z.array(zodHexString).min(2).max(3)).optional(),
})
.refine((arg) => {
if (arg.ENCRYPTED_ONLY) {
return !!arg.KEYS && Object.keys(arg.KEYS).length >= 1;
}
return true;
}, "Must provide keys for at least one version if running in encrypted only mode.")
.default({
ENCRYPTED_ONLY: false,
KEYS: {
"210": [
"75695c3d265f434c3953454c5830522b4b3c4d7b42482a312e5627216b2b4060",
"31277c37707044377141595058345a6b",
"04780206ca5f36f4",
],
},
}),
}),
});
const parseResult = configSchema.safeParse(config);
if (!parseResult.success) {
const humanFriendlyMessage = fromZodError(parseResult.error);
throw new Error(`Invalid config.json5 file: ${humanFriendlyMessage}`);
}
export const Config = parseResult.data;
// Environment Variable Validation
const nodeEnv = process.env.NODE_ENV ?? "";
if (!nodeEnv) {
logger.error(`No NODE_ENV specified in environment. Terminating.`);
process.exit(1);
}
if (!["dev", "production", "staging", "test"].includes(nodeEnv)) {
logger.error(
`Invalid NODE_ENV set in environment. Expected dev, production, test or staging. Got ${nodeEnv}.`
);
process.exit(1);
}
export const Environment = {
nodeEnv: nodeEnv as "dev" | "production" | "staging" | "test",
};