botserver/src/app.ts

372 lines
14 KiB
TypeScript
Raw Normal View History

2018-04-21 02:59:30 -03:00
/*****************************************************************************\
2024-01-09 17:40:48 -03:00
| ® |
| |
| |
| |
| |
2018-04-21 02:59:30 -03:00
| |
| General Bots Copyright (c) pragmatismo.com.br. All rights reserved. |
2018-04-21 02:59:30 -03:00
| Licensed under the AGPL-3.0. |
2018-11-11 19:09:18 -02:00
| |
2018-04-21 02:59:30 -03:00
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
2018-09-11 19:40:53 -03:00
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
2018-04-21 02:59:30 -03:00
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
2024-01-10 14:52:01 -03:00
| "General Bots" is a registered trademark of pragmatismo.com.br. |
2018-04-21 02:59:30 -03:00
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
2018-11-11 19:09:18 -02:00
/**
* @fileoverview General Bots server core.
*/
'use strict';
import express from 'express';
import bodyParser from 'body-parser';
import https from 'https';
2023-07-09 08:46:45 -03:00
import http from 'http';
import mkdirp from 'mkdirp';
import Path from 'path';
2024-01-16 23:32:04 -03:00
import swaggerUI from 'swagger-ui-dist';
import path from 'path';
import fs from 'fs';
import { GBLog, GBMinInstance, IGBCoreService, IGBInstance, IGBPackage } from 'botlib';
import { GBAdminService } from '../packages/admin.gbapp/services/GBAdminService.js';
import { AzureDeployerService } from '../packages/azuredeployer.gbapp/services/AzureDeployerService.js';
import { GBConfigService } from '../packages/core.gbapp/services/GBConfigService.js';
import { GBConversationalService } from '../packages/core.gbapp/services/GBConversationalService.js';
import { GBCoreService } from '../packages/core.gbapp/services/GBCoreService.js';
import { GBDeployer } from '../packages/core.gbapp/services/GBDeployer.js';
import { GBImporter } from '../packages/core.gbapp/services/GBImporterService.js';
import { GBMinService } from '../packages/core.gbapp/services/GBMinService.js';
2022-11-19 23:34:58 -03:00
import auth from 'basic-auth';
import { ChatGPTAPIBrowser } from 'chatgpt';
2022-11-19 23:34:58 -03:00
import child_process from 'child_process';
import * as winston from 'winston-logs-display';
2022-12-14 08:23:39 -03:00
import { RootData } from './RootData.js';
import { GBSSR } from '../packages/core.gbapp/services/GBSSR.js';
2023-02-26 06:05:57 -03:00
import { Mutex } from 'async-mutex';
2024-04-17 10:50:33 -03:00
import {httpProxy} from 'http-proxy';
2022-01-23 19:35:20 -03:00
2018-04-21 02:59:30 -03:00
/**
* General Bots open-core entry point.
*/
export class GBServer {
public static globals: RootData;
/**
* Program entry-point.
*/
public static run() {
GBLog.info(`The Bot Server is in STARTING mode...`);
GBServer.globals = new RootData();
GBConfigService.init();
const port = GBConfigService.getServerPort();
2022-11-19 23:34:58 -03:00
if (process.env.TEST_SHELL) {
GBLog.info(`Running TEST_SHELL: ${process.env.TEST_SHELL}...`);
2022-11-19 23:34:58 -03:00
try {
child_process.execSync(process.env.TEST_SHELL);
} catch (error) {
GBLog.error(`Running TEST_SHELL ERROR: ${error}...`);
}
}
const server = express();
2024-01-16 23:32:04 -03:00
this.initEndpointsDocs(server);
GBServer.globals.server = server;
GBServer.globals.httpsServer = null;
2023-02-15 22:12:24 -03:00
GBServer.globals.webSessions = {};
GBServer.globals.processes = {};
GBServer.globals.files = {};
GBServer.globals.appPackages = [];
GBServer.globals.sysPackages = [];
GBServer.globals.minInstances = [];
GBServer.globals.minBoot = new GBMinInstance();
GBServer.globals.wwwroot = null;
GBServer.globals.entryPointDialog = null;
2022-11-12 21:33:45 -03:00
GBServer.globals.debuggers = [];
2023-02-26 06:05:57 -03:00
GBServer.globals.indexSemaphore = new Mutex();
server.use(bodyParser.json());
server.use(bodyParser.json({ limit: '1mb' }));
server.use(bodyParser.urlencoded({ limit: '1mb', extended: true }));
process.on('SIGTERM', () => {
GBLog.info('SIGTERM signal received.');
});
process.on('uncaughtException', (err, p) => {
if (err !== null) {
err = err['e'] ? err['e'] : err;
2024-02-18 11:04:07 -03:00
const msg = `${err['code'] ? err['code'] : ''} ${err?.['response']?.['data'] ? err?.['response']?.['data']: ''} ${err.message ? err.message : ''} ${err['description'] ? err['description'] : ''}`
GBLog.error(`UNCAUGHT_EXCEPTION: ${err.toString()} ${err['stack'] ? '\n' + err['stack'] : ''} ${msg}`);
} else {
GBLog.error('UNCAUGHT_EXCEPTION: Unknown error (err is null)');
}
});
// Creates working directory.
2022-12-12 16:09:49 -03:00
process.env.PWD = process.cwd();
const workDir = Path.join(process.env.PWD, 'work');
2024-01-16 23:32:04 -03:00
if (!fs.existsSync(workDir)) {
mkdirp.sync(workDir);
}
2022-01-23 19:35:20 -03:00
const mainCallback = () => {
2018-09-09 16:40:04 -03:00
(async () => {
2018-09-09 16:40:04 -03:00
try {
GBLog.info(`Now accepting connections on ${port}...`);
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
2018-04-21 02:59:30 -03:00
2018-09-09 16:40:04 -03:00
// Reads basic configuration, initialize minimal services.
2018-09-09 14:39:37 -03:00
const core: IGBCoreService = new GBCoreService();
2018-11-27 22:56:11 -02:00
const importer: GBImporter = new GBImporter(core);
const deployer: GBDeployer = new GBDeployer(core, importer);
2023-07-14 18:45:17 -03:00
let azureDeployer: AzureDeployerService;
// Ensure that local proxy is setup.
if (process.env.NODE_ENV === 'development') {
const proxy = GBConfigService.get('BOT_URL');
if (proxy !== undefined) {
GBServer.globals.publicAddress = proxy;
} else {
GBServer.globals.publicAddress = await core.ensureProxy(port);
process.env.BOT_URL = GBServer.globals.publicAddress;
GBLog.info(`Auto-proxy address at: ${process.env.BOT_URL}...`);
}
} else {
const serverAddress = process.env.BOT_URL;
GBLog.info(`.env address at ${serverAddress}...`);
GBServer.globals.publicAddress = serverAddress;
}
2023-07-14 18:45:17 -03:00
// Creates a boot instance or load it from storage.
2024-02-18 11:04:07 -03:00
let runOnce = false;
2023-07-14 18:45:17 -03:00
if (GBConfigService.get('STORAGE_SERVER')) {
azureDeployer = await AzureDeployerService.createInstance(deployer);
await core.initStorage();
2023-07-14 18:45:17 -03:00
} else {
2023-12-29 19:14:48 -03:00
runOnce = true;
2023-07-14 18:45:17 -03:00
[GBServer.globals.bootInstance, azureDeployer] = await core['createBootInstanceEx'](
2022-11-19 23:34:58 -03:00
core,
2023-07-14 18:45:17 -03:00
null,
GBServer.globals.publicAddress,
deployer,
GBConfigService.get('FREE_TIER')
2022-11-19 23:34:58 -03:00
);
}
2019-02-25 09:44:39 -03:00
core.ensureAdminIsSecured();
// Deploys system and user packages.
2023-08-13 17:08:04 -03:00
GBLog.info(`Deploying System packages...`);
GBServer.globals.sysPackages = await core.loadSysPackages(core);
2022-01-03 13:11:21 -03:00
GBLog.info(`Connecting to Bot Storage...`);
await core.checkStorage(azureDeployer);
await deployer.deployPackages(core, server, GBServer.globals.appPackages);
await core.syncDatabaseStructure();
2024-02-18 11:04:07 -03:00
if (runOnce) {
2023-12-29 19:14:48 -03:00
await core.saveInstance(GBServer.globals.bootInstance);
}
2020-12-31 15:36:19 -03:00
2022-01-05 15:27:20 -03:00
// Deployment of local applications for the first time.
if (GBConfigService.get('DISABLE_WEB') !== 'true') {
deployer.setupDefaultGBUI();
}
GBLog.info(`Publishing instances...`);
2020-12-31 15:36:19 -03:00
const instances: IGBInstance[] = await core.loadAllInstances(
core,
azureDeployer,
GBServer.globals.publicAddress
);
if (instances.length === 0) {
const instance = await importer.importIfNotExistsBotPackage(
GBConfigService.get('BOT_ID'),
'boot.gbot',
'packages/boot.gbot',
GBServer.globals.bootInstance
);
instances.push(instance);
GBServer.globals.minBoot.instance = instances[0];
GBServer.globals.bootInstance = instances[0];
await deployer.deployBotFull(instance, GBServer.globals.publicAddress);
// Runs the search even with empty content to create structure.
await azureDeployer['runSearch'](instance);
}
GBServer.globals.bootInstance = instances[0];
2018-11-27 22:56:11 -02:00
// Builds minimal service infrastructure.
const conversationalService: GBConversationalService = new GBConversationalService(core);
2023-07-14 18:45:17 -03:00
const adminService: GBAdminService = new GBAdminService(core);
2018-11-27 22:56:11 -02:00
const minService: GBMinService = new GBMinService(core, conversationalService, adminService, deployer);
GBServer.globals.minService = minService;
await minService.buildMin(instances);
if (process.env.OPENAI_EMAIL) {
if (!GBServer.globals.chatGPT) {
GBServer.globals.chatGPT = new ChatGPTAPIBrowser({
email: process.env.OPENAI_EMAIL,
password: process.env.OPENAI_PASSWORD,
markdown: false
});
await GBServer.globals.chatGPT.init();
}
}
2022-08-06 19:38:13 -03:00
if (process.env.ENABLE_WEBLOG) {
2023-03-27 10:07:28 -03:00
// If global log enabled, reorders transports adding web logging.
const loggers = GBLog.getLogger();
winston.default(server, loggers[1]);
}
2022-08-06 19:38:13 -03:00
2023-03-27 10:07:28 -03:00
server.get('*', async (req, res, next) => {
2024-04-17 10:50:33 -03:00
const host = req.headers.host;
// Roteamento com base no domínio.
2024-04-17 10:54:59 -03:00
2024-04-17 10:50:33 -03:00
if (host === process.env.API_HOST) {
2024-04-17 10:54:59 -03:00
GBLog.info(`Redirecting to API...`);
2024-04-17 10:50:33 -03:00
return httpProxy.web(req, res, { target: 'http://localhost:1111' }); // Express server
}
2023-03-27 10:07:28 -03:00
if (req.originalUrl.startsWith('/logs')) {
if (process.env.ENABLE_WEBLOG === "true") {
2023-03-27 10:07:28 -03:00
const admins = {
admin: { password: process.env.ADMIN_PASS }
};
2022-08-06 19:38:13 -03:00
2023-03-27 10:07:28 -03:00
// ... some not authenticated middlewares.
2022-11-19 23:34:58 -03:00
const user = auth(req);
2022-08-07 11:05:20 -03:00
if (!user || !admins[user.name] || admins[user.name].password !== user.pass) {
res.set('WWW-Authenticate', 'Basic realm="example"');
return res.status(401).send();
}
} else {
2023-03-27 10:07:28 -03:00
await GBSSR.ssrFilter(req, res, next);
2022-08-06 19:38:13 -03:00
}
2023-03-27 10:07:28 -03:00
} else {
await GBSSR.ssrFilter(req, res, next);
}
});
GBLog.info(`The Bot Server is in RUNNING mode...`);
// Opens Navigator.
if (process.env.DEV_OPEN_BROWSER) {
core.openBrowserInDevelopment();
}
2018-09-09 16:40:04 -03:00
} catch (err) {
GBLog.error(`STOP: ${err.message ? err.message : err} ${err.stack ? err.stack : ''}`);
process.exit(1);
2018-09-09 16:40:04 -03:00
}
})();
2022-01-23 19:35:20 -03:00
};
2022-12-16 10:36:09 -03:00
2023-07-09 12:59:12 -03:00
// Setups unsecure http redirect.
2023-07-14 18:45:17 -03:00
if (process.env.NODE_ENV === 'production') {
const server1 = http.createServer((req, res) => {
const host = req.headers.host.startsWith('www.') ?
req.headers.host.substring(4) : req.headers.host;
res.writeHead(301, {
Location: "https://" + host + req.url
}).end();
});
server1.listen(80);
}
2023-07-09 12:59:12 -03:00
2022-01-27 17:48:38 -03:00
if (process.env.CERTIFICATE_PFX) {
2024-04-17 10:50:33 -03:00
// var options = {
// changeOrigin: true,
// target: {
// https: true
// }
// }
// httpProxy.createServer(443, 'www.google.com', options).listen(8001);
2022-12-14 08:23:39 -03:00
const options1 = {
passphrase: process.env.CERTIFICATE_PASSPHRASE,
2024-01-16 23:32:04 -03:00
pfx: fs.readFileSync(process.env.CERTIFICATE_PFX)
2022-01-23 19:35:20 -03:00
};
2023-07-09 08:46:45 -03:00
2022-12-14 08:23:39 -03:00
const httpsServer = https.createServer(options1, server).listen(port, mainCallback);
GBServer.globals.httpsServer = httpsServer;
for (let i = 2; ; i++) {
const certPfxEnv = `CERTIFICATE${i}_PFX`;
const certPassphraseEnv = `CERTIFICATE${i}_PASSPHRASE`;
const certDomainEnv = `CERTIFICATE${i}_DOMAIN`;
if (process.env[certPfxEnv] && process.env[certPassphraseEnv] && process.env[certDomainEnv]) {
const options = {
passphrase: process.env[certPassphraseEnv],
2024-01-16 23:32:04 -03:00
pfx: fs.readFileSync(process.env[certPfxEnv])
};
httpsServer.addContext(process.env[certDomainEnv], options);
} else {
break;
}
}
}
else {
2022-01-23 19:35:20 -03:00
server.listen(port, mainCallback);
}
2018-04-21 02:59:30 -03:00
}
2024-01-16 23:32:04 -03:00
public static initEndpointsDocs(app: express.Application) {
const ENDPOINT = '/docs';
const SWAGGER_FILE_NAME = 'swagger.yaml';
const swaggerUiAssetPath = swaggerUI.getAbsoluteFSPath();
// A workaround for swagger-ui-dist not being able to set custom swagger URL
const indexContent = fs
2024-02-18 11:04:07 -03:00
.readFileSync(path.join(swaggerUiAssetPath, 'swagger-initializer.js'))
.toString()
.replace('https://petstore.swagger.io/v2/swagger.json', `/${SWAGGER_FILE_NAME}`);
2024-01-16 23:32:04 -03:00
app.get(`${ENDPOINT}/swagger-initializer.js`, (req, res) => res.send(indexContent));
// Serve the swagger-ui assets
app.use(ENDPOINT, express.static(swaggerUiAssetPath));
// Serve the swagger file
app.get(`/${SWAGGER_FILE_NAME}`, (req, res) => {
2024-02-18 11:04:07 -03:00
res.sendFile(path.join(process.env.PWD, SWAGGER_FILE_NAME));
2024-01-16 23:32:04 -03:00
});
2024-02-18 11:04:07 -03:00
}
2018-04-21 02:59:30 -03:00
}