/*****************************************************************************\ | ( )_ _ | | _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ ___ _ | | ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/' v `\ /'_`\ | | | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| (˅) |( (_) ) | | | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' | | | | ( )_) | | | (_) \___/' | | | | General Bots Copyright (c) Pragmatismo.io. All rights reserved. | | Licensed under the AGPL-3.0. | | | | 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, | | 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. | | | | "General Bots" is a registered trademark of Pragmatismo.io. | | 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. | | | \*****************************************************************************/ /** * @fileoverview General Bots server core. */ 'use strict'; import express from 'express'; import bodyParser from 'body-parser'; import https from 'https'; import http from 'http'; import mkdirp from 'mkdirp'; import Path from 'path'; import * as Fs from 'fs'; import { GBLog, 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'; import auth from 'basic-auth'; import { ChatGPTAPIBrowser } from 'chatgpt'; import child_process from 'child_process'; import * as winston from 'winston-logs-display'; import { RootData } from './RootData.js'; import { GBSSR } from '../packages/core.gbapp/services/GBSSR.js'; import { Mutex } from 'async-mutex'; import { GBVMService } from '../packages/basic.gblib/services/GBVMService.js'; /** * 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(); if (process.env.TEST_SHELL) { GBLog.info(`Running TEST_SHELL: ${process.env.TEST_SHELL}...`); try { child_process.execSync(process.env.TEST_SHELL); } catch (error) { GBLog.error(`Running TEST_SHELL ERROR: ${error}...`); } } const server = express(); GBServer.globals.server = server; GBServer.globals.httpsServer = null; GBServer.globals.webSessions = {}; GBServer.globals.processes = {}; GBServer.globals.files = {}; GBServer.globals.appPackages = []; GBServer.globals.sysPackages = []; GBServer.globals.minInstances = []; GBServer.globals.wwwroot = null; GBServer.globals.entryPointDialog = null; GBServer.globals.debuggers = []; GBServer.globals.indexSemaphore = new Mutex(); server.use(bodyParser.json()); server.use(bodyParser.urlencoded({ extended: true })); // Setups global error handlers. process.on('unhandledRejection', (err, p) => { GBLog.error(`UNHANDLED_REJECTION(promises): ${err.toString()} ${err['stack'] ? '\n' + err['stack'] : ''}`); if (err['response']?.obj?.httpStatusCode === 404) { GBLog.warn(`Check reverse proxy: ${process.env.BOT_URL} as it seems to be invalid.`); } }); process.on('uncaughtException', (err, p) => { GBLog.error(`UNCAUGHT_EXCEPTION: ${err.toString()} ${err['stack'] ? '\n' + err['stack'] : ''}`); }); process.on('SIGTERM', () => { GBLog.info('SIGTERM signal received.'); }); // Creates working directory. process.env.PWD = process.cwd(); const workDir = Path.join(process.env.PWD, 'work'); if (!Fs.existsSync(workDir)) { mkdirp.sync(workDir); } const mainCallback = () => { (async () => { try { GBLog.info(`Now accepting connections on ${port}...`); process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; // Reads basic configuration, initialize minimal services. const core: IGBCoreService = new GBCoreService(); const importer: GBImporter = new GBImporter(core); const deployer: GBDeployer = new GBDeployer(core, importer); const azureDeployer: AzureDeployerService = await AzureDeployerService.createInstance(deployer); const adminService: GBAdminService = new GBAdminService(core); if (process.env.NODE_ENV === 'development') { const proxy = GBConfigService.get('BOT_URL'); if (proxy !== undefined) { GBServer.globals.publicAddress = proxy; } else { // Ensure that local development proxy is setup. GBLog.info(`Establishing a development local proxy (proxy) on BOT_URL...`); GBServer.globals.publicAddress = await core.ensureProxy(port); } } else { const serverAddress = process.env.BOT_URL; GBLog.info(`Defining server address at ${serverAddress}...`); GBServer.globals.publicAddress = serverAddress; } // Creates a boot instance or load it from storage. try { await core.initStorage(); } catch (error) { GBLog.verbose(`Error initializing storage: ${error}`); GBServer.globals.bootInstance = await core.createBootInstance( core, azureDeployer, GBServer.globals.publicAddress ); } core.ensureAdminIsSecured(); // Deploys system and user packages. GBLog.info(`Deploying packages...`); GBServer.globals.sysPackages = await core.loadSysPackages(core); GBLog.info(`Connecting to Bot Storage...`); await core.checkStorage(azureDeployer); await deployer.deployPackages(core, server, GBServer.globals.appPackages); await core.syncDatabaseStructure(); // Deployment of local applications for the first time. if (GBConfigService.get('DISABLE_WEB') !== 'true') { deployer.setupDefaultGBUI(); } GBLog.info(`Publishing instances...`); 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 ); await deployer.deployBotFull(instance, GBServer.globals.publicAddress); instances.push(instance); // Runs the search even with empty content to create structure. await azureDeployer['runSearch'](instance); } GBServer.globals.bootInstance = instances[0]; // Builds minimal service infrastructure. const conversationalService: GBConversationalService = new GBConversationalService(core); 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(); } } // let s = new GBVMService(); // await s.translateBASIC('work/gptA.vbs', GBServer.globals.minBoot ); // await s.translateBASIC('work/gptB.vbs', GBServer.globals.minBoot ); // await s.translateBASIC('work/gptC.vbs', GBServer.globals.minBoot ); // process.exit(9); if (process.env.ENABLE_WEBLOG) { // If global log enabled, reorders transports adding web logging. const loggers = GBLog.getLogger(); winston.default(server, loggers[1]); } server.get('*', async (req, res, next) => { if (req.originalUrl.startsWith('/logs')) { if (process.env.ENABLE_WEBLOG) { const admins = { admin: { password: process.env.ADMIN_PASS } }; // ... some not authenticated middlewares. const user = auth(req); if (!user || !admins[user.name] || admins[user.name].password !== user.pass) { res.set('WWW-Authenticate', 'Basic realm="example"'); return res.status(401).send(); } } else { await GBSSR.ssrFilter(req, res, next); } } 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(); } } catch (err) { GBLog.error(`STOP: ${err.message ? err.message : err} ${err.stack ? err.stack : ''}`); process.exit(1); } })(); }; // Setups unsecure http redirect. 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); if (process.env.CERTIFICATE_PFX) { const options1 = { passphrase: process.env.CERTIFICATE_PASSPHRASE, pfx: Fs.readFileSync(process.env.CERTIFICATE_PFX) }; 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], pfx: Fs.readFileSync(process.env[certPfxEnv]) }; httpsServer.addContext(process.env[certDomainEnv], options); } else { break; } } } else { server.listen(port, mainCallback); } } }