diff --git a/src/config-manager.ts b/src/config-manager.ts index e651a69..3499f05 100644 --- a/src/config-manager.ts +++ b/src/config-manager.ts @@ -13,7 +13,8 @@ export const disabledFeatures = { email: false, captcha: false, s3: false, - datastore: false + datastore: false, + serverProvisioning: false }; const hexadecimalStringRegex = /^[0-9a-f]+$/i; @@ -41,6 +42,9 @@ export const config: Config = { connection_string: process.env.PN_ACT_CONFIG_MONGO_CONNECTION_STRING || '', options: mongooseConnectOptions }, + provisioning: { + server_config: process.env.PN_ACT_PROVISIONING_SERVER_CONFIG || '' + }, redis: { client: { url: process.env.PN_ACT_CONFIG_REDIS_URL || '' @@ -282,6 +286,11 @@ if (!config.datastore.signature_secret) { } } +if (!config.provisioning.server_config) { + LOG_WARN('A server provisioning config file as not been set. Disabling feature, no server data will be provisioned. To enable feature set the PN_ACT_PROVISIONING_SERVER_CONFIG environment variable'); + disabledFeatures.serverProvisioning = true; +} + if (!configValid) { LOG_ERROR('Config is invalid. Exiting'); process.exit(0); diff --git a/src/provisioning.ts b/src/provisioning.ts new file mode 100644 index 0000000..6e06c94 --- /dev/null +++ b/src/provisioning.ts @@ -0,0 +1,78 @@ +import fs from 'node:fs/promises'; +import { z } from 'zod'; +import mongoose from 'mongoose'; +import { config, disabledFeatures } from './config-manager'; +import { LOG_INFO, LOG_WARN } from './logger'; +import { Server } from './models/server'; + +// Provisioning has a couple edgecases: +// - It will only update existing entries, will not add new one +// - Only the fields in the below schema will be updated + +const serverProvisioningSchema = z.object({ + servers: z.array(z.object({ + id: z.string(), + name: z.string(), + ip: z.string(), + port: z.coerce.number() + })) +}); + +async function readServerProvisioning(configPath: string): Promise> { + const fileContents = await fs.readFile(configPath, 'utf-8'); + const parsedConfig = JSON.parse(fileContents); + return serverProvisioningSchema.parse(parsedConfig); +} + +export async function handleServerProvisioning(): Promise { + const serverData = await readServerProvisioning(config.provisioning.server_config).catch((err) => { + LOG_WARN('Failed to parse server provisioning config:'); + console.error(err); + }); + if (!serverData) { + return; + } + + LOG_INFO('Starting server provisioning'); + for (const server of serverData.servers) { + const id = new mongoose.Types.ObjectId(server.id); + const result = await Server.findOneAndUpdate({ + id + }, { + $set: { + id, + service_name: server.name, + ip: server.ip, + port: server.port + } + }); + if (!result) { + LOG_WARN(`Could not find existing server DB entry for ID ${server.id} - skipping provisioning`); + } + } +} + +export function startProvisioner(): void { + if (disabledFeatures.serverProvisioning) { + return; + } + + const runProvisioning = (): void => { + handleServerProvisioning().catch((err) => { + LOG_WARN('Failed to provision servers:'); + console.error(err); + }); + }; + + // Run once at boot + runProvisioning(); + + (async (): Promise => { + const watcher = fs.watch(config.provisioning.server_config); + // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Dont need this var + for await (const _ of watcher) { + LOG_INFO('Detected a change in the server provisioning config'); + runProvisioning(); + } + })(); +} diff --git a/src/server.ts b/src/server.ts index 4bc2ec1..cd48ace 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,6 +16,7 @@ import api from '@/services/api'; import localcdn from '@/services/local-cdn'; import assets from '@/services/assets'; import { config, disabledFeatures } from '@/config-manager'; +import { startProvisioner } from './provisioning'; process.title = 'Pretendo - Account'; process.on('uncaughtException', (err, origin) => { @@ -113,6 +114,8 @@ async function main(): Promise { await startGRPCServer(); LOG_SUCCESS(`gRPC server started on port ${config.grpc.port}`); + startProvisioner(); + app.listen(config.http.port, () => { LOG_SUCCESS(`HTTP server started on port ${config.http.port}`); }); diff --git a/src/types/common/config.ts b/src/types/common/config.ts index 66a77c7..0eab22f 100644 --- a/src/types/common/config.ts +++ b/src/types/common/config.ts @@ -13,6 +13,9 @@ export interface Config { connection_string: string; options: mongoose.ConnectOptions; }; + provisioning: { + server_config: string; + }; redis: { client: { url: string;