/** * @copyright Copyright (c) Open-Xchange GmbH, Germany <info@open-xchange.com> * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with OX App Suite. If not, see <https://www.gnu.org/licenses/agpl-3.0.txt>. * * Any use of the work other than as authorized under this license or copyright law is prohibited. */ // Add env vars from files // Note: actual env vars supersede .env file and .env file supersedes .env.defaults file import { config } from 'dotenv-defaults' import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' import { readFileSync } from 'node:fs' import { randomUUID } from 'node:crypto' import logger from './logger.js' import fastify from 'fastify' import autoLoad from '@fastify/autoload' import { getLatestVersion, registerLatestVersionListener, versionInfo } from './version.js' import { configMap } from './config_map.js' import { warmCache } from './files.js' import { createClient, isReady, switchClient } from './redis.js' import lightship from './lightship.js' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) const subClient = createClient('sub client') const client = createClient('common client') switchClient(client) client.on('connect', async () => { logger.debug('[Redis] Common client connected') // forget local version and re-fetch from redis next time versionInfo.version = null await waitForVersionAvailable() }) // Load env vars from .env and .env.defaults files // Note: actual env vars supersede .env file and .env file supersedes .env.defaults file config() async function waitForVersionAvailable () { let version = await getLatestVersion() while (!version) { version = await getLatestVersion() logger.info(`[Health] Check latest version on startup. Found ${version}`) await new Promise(resolve => setTimeout(resolve, 1000)) } } lightship.queueBlockingTask(isReady(client)) lightship.queueBlockingTask(configMap.load()) lightship.queueBlockingTask(waitForVersionAvailable()) // Create a Fastify server const app = fastify({ requestIdLogLabel: 'requestId', disableRequestLogging: true, logger, connectionTimeout: 30000, // genreqId is used to generate a request id if one is not provided by the proxy genReqId: () => randomUUID() // This is the name of the header used to pass a request id from a proxy to the service // requestIdHeader: 'x-request-id', }) // Register plugins // Note: plugins are loaded in alphabetical order app.register(autoLoad, { dir: join(__dirname, 'plugins') }) // Register routes // Note: routes are loaded in alphabetical order const autoLoadOptions = { dir: join(__dirname, 'routes'), autoHooks: true } if (process.env.APP_ROOT !== '/') autoLoadOptions.options = { prefix: String(process.env.APP_ROOT).replace(/\/$/, '') } app.register(autoLoad, autoLoadOptions) lightship.whenFirstReady().then(async () => { // don't block getLatestVersion() .then(version => warmCache({ version })) .catch(err => logger.error(err)) registerLatestVersionListener(subClient) }) // This hook is used to signal lightship that the service is ready to receive requests app.addHook('onReady', () => { lightship.signalReady() }) const NOT_FOUND_HTML = readFileSync('./src/404.html') app.setNotFoundHandler((req, reply) => { reply.code(404) reply.headers({ 'content-type': 'text/html; charset=utf-8' }) reply.send(NOT_FOUND_HTML) }) try { // Binds and listens for connections on the specified host and port await app.listen({ host: '::', port: Number(process.env.PORT) }) } catch (err) { logger.error(err) await lightship.shutdown() } lightship.registerShutdownHandler(async () => { logger.info('[Service] Shutting down...') await Promise.all([ client.quit(), subClient.quit() ]) await app.close() })