@underpostnet/underpost
Version:
1,395 lines (1,197 loc) • 62.7 kB
JavaScript
import fs from 'fs-extra';
import axios from 'axios';
import dotenv from 'dotenv';
import { pbcopy, shellCd, shellExec } from '../src/server/process.js';
import { loggerFactory } from '../src/server/logger.js';
import {
Config,
addApiConf,
addClientConf,
buildApiSrc,
buildClientSrc,
cloneConf,
loadConf,
loadReplicas,
addWsConf,
buildWsSrc,
cloneSrcComponents,
getDeployGroupId,
deployRun,
getDataDeploy,
buildReplicaId,
Cmd,
restoreMacroDb,
fixDependencies,
setUpProxyMaintenanceServer,
writeEnv,
getUnderpostRootPath,
buildCliDoc,
} from '../src/server/conf.js';
import { buildClient } from '../src/server/client-build.js';
import { range, s4, setPad, timer, uniqueArray } from '../src/client/components/core/CommonJs.js';
import { MongooseDB } from '../src/db/mongo/MongooseDB.js';
import { Lampp } from '../src/runtime/lampp/Lampp.js';
import { DefaultConf } from '../conf.js';
import { JSONweb } from '../src/server/client-formatted.js';
import { Xampp } from '../src/runtime/xampp/Xampp.js';
import { ejs } from '../src/server/json-schema.js';
import { getLocalIPv4Address, ip } from '../src/server/dns.js';
import { Downloader } from '../src/server/downloader.js';
import colors from 'colors';
import { program } from '../src/cli/index.js';
colors.enable();
const logger = loggerFactory(import.meta);
logger.info('argv', process.argv);
const [exe, dir, operator] = process.argv;
try {
switch (operator) {
case 'save':
{
const deployId = process.argv[3];
const folder = `./engine-private/conf/${deployId}`;
if (fs.existsSync(folder)) fs.removeSync(folder);
await Config.build({ folder });
fs.writeFileSync(`${folder}/.env.production`, fs.readFileSync('./.env.production', 'utf8'), 'utf8');
fs.writeFileSync(`${folder}/.env.development`, fs.readFileSync('./.env.development', 'utf8'), 'utf8');
fs.writeFileSync(`${folder}/.env.test`, fs.readFileSync('./.env.test', 'utf8'), 'utf8');
fs.writeFileSync(`${folder}/package.json`, fs.readFileSync('./package.json', 'utf8'), 'utf8');
}
break;
case 'add-nodejs-app-client-conf':
{
const toOptions = {
deployId: process.argv[3],
clientId: process.argv[4],
host: process.argv[5],
path: process.argv[6],
};
const fromOptions = { deployId: process.argv[7], clientId: process.argv[8] };
addClientConf({ toOptions, fromOptions });
}
break;
case 'build-nodejs-conf-app':
{
const toOptions = { deployId: process.argv[3], clientId: process.argv[4] };
const fromOptions = { deployId: process.argv[5], clientId: process.argv[6] };
cloneConf({ toOptions, fromOptions });
}
break;
case 'clone-nodejs-src-client-components':
{
const fromOptions = { componentsFolder: process.argv[3] };
const toOptions = { componentsFolder: process.argv[4] };
cloneSrcComponents({ toOptions, fromOptions });
}
break;
case 'build-nodejs-src-app':
{
const toOptions = { deployId: process.argv[3], clientId: process.argv[4] };
const fromOptions = { deployId: process.argv[5], clientId: process.argv[6] };
buildClientSrc({ toOptions, fromOptions });
}
break;
case 'build-nodejs-conf-api':
{
const toOptions = { apiId: process.argv[3], deployId: process.argv[4], clientId: process.argv[5] };
const fromOptions = { apiId: process.argv[6], deployId: process.argv[7], clientId: process.argv[8] };
addApiConf({ toOptions, fromOptions });
}
break;
case 'build-nodejs-src-api':
{
const toOptions = { apiId: process.argv[3], deployId: process.argv[4], clientId: process.argv[5] };
const fromOptions = { apiId: process.argv[6], deployId: process.argv[7], clientId: process.argv[8] };
buildApiSrc({ toOptions, fromOptions });
}
break;
case 'build-nodejs-conf-ws':
{
const toOptions = {
wsId: process.argv[3],
deployId: process.argv[4],
host: process.argv[5],
paths: process.argv[6],
};
const fromOptions = {
wsId: process.argv[7],
deployId: process.argv[8],
host: process.argv[9],
paths: process.argv[10],
};
addWsConf({ toOptions, fromOptions });
}
break;
case 'build-nodejs-src-ws':
{
const toOptions = {
wsId: process.argv[3],
deployId: process.argv[4],
host: process.argv[5],
paths: process.argv[6],
};
const fromOptions = {
wsId: process.argv[7],
deployId: process.argv[8],
host: process.argv[9],
paths: process.argv[10],
};
buildWsSrc({ toOptions, fromOptions });
}
break;
case 'conf': {
loadConf(process.argv[3], process.argv[4]);
break;
}
case 'new-nodejs-app':
{
const deployId = process.argv[3];
const clientId = process.argv[4];
shellExec(`node bin/deploy build-nodejs-conf-app ${deployId} ${clientId}`);
shellExec(`node bin/deploy build-nodejs-src-app ${deployId} ${clientId}`);
shellExec(`node bin/deploy build-full-client ${deployId}`);
shellExec(`npm run dev ${deployId}`);
}
break;
case 'test-new-api':
{
const port = process.argv[3];
const apiId = process.argv[4];
let url = `http://localhost:${port}/api/${apiId}`;
{
logger.info(`POST REQUEST`, url);
const result = await axios.post(url, {});
url += '/' + result.data.data._id;
logger.info(`POST RESULT ${url}`, result.data);
}
{
logger.info(`GET REQUEST`, url);
const result = await axios.get(url);
logger.info(`GET RESULT ${url}`, result.data);
}
{
logger.info(`DELETE REQUEST`, url);
const result = await axios.delete(url);
logger.info(`DELETE RESULT ${url}`, result.data);
}
}
break;
case 'new-nodejs-api':
{
const apiId = process.argv[3];
const deployId = process.argv[4];
const clientId = process.argv[5];
shellExec(`node bin/deploy build-nodejs-conf-api ${apiId} ${deployId} ${clientId}`);
shellExec(`node bin/deploy build-nodejs-src-api ${apiId} ${deployId} ${clientId}`);
// shellExec(`npm run dev ${deployId}`);
}
break;
case 'new-nodejs-ws':
{
const wsId = process.argv[3];
const deployId = process.argv[4];
const host = process.argv[5];
const paths = process.argv[6];
shellExec(`node bin/deploy build-nodejs-conf-ws ${wsId} ${deployId} ${host} ${paths}`);
shellExec(`node bin/deploy build-nodejs-src-ws ${wsId} ${deployId} ${host} ${paths}`);
shellExec(`npm run dev ${deployId}`);
}
break;
case 'build-full-client':
{
dotenv.config({ override: true });
if (!process.argv[3]) process.argv[3] = 'default';
const { deployId, folder } = loadConf(process.argv[3]);
let argHost = process.argv[4] ? process.argv[4].split(',') : [];
let argPath = process.argv[5] ? process.argv[5].split(',') : [];
let deployIdSingleReplicas = [];
const serverConf = deployId
? JSON.parse(fs.readFileSync(`./conf/conf.server.json`, 'utf8'))
: Config.default.server;
for (const host of Object.keys(serverConf)) {
for (const path of Object.keys(serverConf[host])) {
if (argHost.length && argPath.length && (!argHost.includes(host) || !argPath.includes(path))) {
delete serverConf[host][path];
} else {
serverConf[host][path].liteBuild = process.argv.includes('l') ? true : false;
serverConf[host][path].minifyBuild = process.env.NODE_ENV === 'production' ? true : false;
if (process.env.NODE_ENV === 'development' && process.argv.includes('static')) {
serverConf[host][path].apiBaseProxyPath = '/';
serverConf[host][path].apiBaseHost = `localhost:${parseInt(process.env.PORT) + 1}`;
}
if (serverConf[host][path].singleReplica && serverConf[host][path].replicas) {
deployIdSingleReplicas = deployIdSingleReplicas.concat(
serverConf[host][path].replicas.map((replica) => buildReplicaId({ deployId, replica })),
);
// shellExec(Cmd.replica(deployId, host, path));
}
}
}
}
fs.writeFileSync(`./conf/conf.server.json`, JSON.stringify(serverConf, null, 4), 'utf-8');
await buildClient();
for (const replicaDeployId of deployIdSingleReplicas) {
shellExec(Cmd.conf(replicaDeployId, process.env.NODE_ENV));
shellExec(Cmd.build(replicaDeployId));
}
}
break;
case 'xampp': {
const directory = 'c:/xampp/htdocs';
const host = 'localhost';
const port = 80;
Xampp.removeRouter();
Xampp.appendRouter(` Listen ${port}
<VirtualHost *:${port}>
DocumentRoot "${directory}"
ServerName ${host}:${port}
<Directory "${directory}">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
`);
if (Xampp.enabled() && Xampp.router) Xampp.initService({ daemon: true });
break;
}
case 'adminer': {
const directory = '/home/dd/engine/public/adminer';
// const host = '127.0.0.1';
const host = 'localhost';
const port = 80;
if (!process.argv.includes('server')) {
if (fs.existsSync(directory)) fs.removeSync(directory);
fs.mkdirSync(directory, { recursive: true });
shellExec(`cd ${directory} && wget https://www.adminer.org/latest.php -O adminer.php`);
}
Lampp.removeRouter();
Lampp.appendRouter(` Listen ${port}
<VirtualHost *:${port}>
DocumentRoot "${directory}"
ServerName ${host}:${port}
<Directory "${directory}">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
`);
if (Lampp.enabled() && Lampp.router) Lampp.initService({ daemon: true });
shellExec(`open /opt/lampp/apache2/conf/httpd.conf`);
break;
}
case 'pma':
{
const directory = '/home/dd/engine/public/phpmyadmin';
// const host = '127.0.0.1';
const host = 'localhost';
const port = 80;
// data config path: /etc/phpmyadmin
// The config.inc.php file is not required, and only needed for custom configurations
// phpmyadmin will first refer to ./libraries/config.default.php to retrieve the default values.
// If for some reason you need to modify the default values, and the ./config.inc.php
// file doesn't exist, you will need to create one as per the Installation documentation.
// You will also need to configure pmadb for some of phpmyadmin's special features such as bookmarks.
// CREATE USER 'pma'@'localhost' IDENTIFIED VIA mysql_native_password USING 'pmapass';
// GRANT SELECT, INSERT, UPDATE, DELETE ON `<pma_db>`.* TO 'pma'@'localhost';
if (!process.argv.includes('server')) {
// if (fs.existsSync(directory)) fs.removeSync(directory);
shellExec(`sudo apt install phpmyadmin php-mbstring php-zip php-gd php-json php-curl`);
shellExec(`sudo phpenmod mbstring`);
shellExec(
`cd /usr/share/phpmyadmin && git init && git add . && git commit -m "Base phpMyAdmin implementation"`,
);
}
// if (!fs.existsSync(directory)) fs.mkdirSync(directory, { recursive: true });
// if (!fs.existsSync('./public/phpmyadmin/phpmyadmin'))
// fs.copySync('/usr/share/phpmyadmin', './public/phpmyadmin/phpmyadmin');
Lampp.removeRouter();
Lampp.appendRouter(` Listen ${port} `);
if (Lampp.enabled() && Lampp.router) Lampp.initService({ daemon: true });
// shellExec(`open /opt/lampp/apache2/conf/httpd.conf`);
// Create a link in /var/www like this:
// sudo ln -s /usr/share/phpmyadmin /var/www/
// Note: since 14.04 you may want to use /var/www/html/ instead of /var/www/
// If that's not working for you, you need to include PHPMyAdmin inside apache configuration.
// Open apache.conf using your favorite editor, mine is vim :)
// sudo vim /etc/apache2/apache2.conf
// Then add the following line:
// Include /etc/phpmyadmin/apache.conf
// For Ubuntu 15.04 and 16.04
// sudo ln -s /etc/phpmyadmin/apache.conf /etc/apache2/conf-available/phpmyadmin.conf
// sudo a2enconf phpmyadmin.conf
// sudo service apache2 reload
break;
Lampp.appendRouter(` Listen ${port}
<VirtualHost *:${port}>
DocumentRoot "${directory}"
ServerName ${host}:${port}
<Directory "${directory}">
Options Indexes FollowSymLinks MultiViews
AllowOverride All
Require all granted
</Directory>
</VirtualHost>`);
// phpMyAdmin default Apache configuration:
Lampp.appendRouter(`
Listen ${port}
Alias /phpmyadmin /usr/share/phpmyadmin
<Directory /usr/share/phpmyadmin>
Options Indexes FollowSymLinks
DirectoryIndex index.php
<IfModule mod_php5.c>
AddType application/x-httpd-php .php
php_flag magic_quotes_gpc Off
php_flag track_vars On
php_flag register_globals Off
php_value include_path .
</IfModule>
</Directory>
# Authorize for setup
<Directory /usr/share/phpmyadmin/setup>
<IfModule mod_authn_file.c>
AuthType Basic
AuthName "phpMyAdmin Setup"
AuthUserFile /etc/phpmyadmin/htpasswd.setup
</IfModule>
Require valid-user
</Directory>
# Disallow web access to directories that don't need it
<Directory /usr/share/phpmyadmin/libraries>
Order Deny,Allow
Deny from All
</Directory>
<Directory /usr/share/phpmyadmin/setup/lib>
Order Deny,Allow
Deny from All
</Directory>
`);
}
break;
case 'update-dependencies':
const files = await fs.readdir(`./engine-private/conf`, { recursive: true });
const originPackage = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
for (const relativePath of files) {
const filePah = `./engine-private/conf/${relativePath.replaceAll(`\\`, '/')}`;
if (filePah.split('/').pop() === 'package.json') {
const deployPackage = JSON.parse(fs.readFileSync(filePah), 'utf8');
deployPackage.dependencies = originPackage.dependencies;
deployPackage.devDependencies = originPackage.devDependencies;
fs.writeFileSync(filePah, JSON.stringify(deployPackage, null, 4), 'utf8');
}
}
break;
case 'run-macro':
{
if (fs.existsSync(`./tmp/await-deploy`)) fs.remove(`./tmp/await-deploy`);
const dataDeploy = getDataDeploy({
deployGroupId: process.argv[3],
buildSingleReplica: true,
deployIdConcat: ['dd-proxy', 'dd-cron'],
});
if (!process.argv[4]) await setUpProxyMaintenanceServer({ deployGroupId: process.argv[3] });
await deployRun(process.argv[4] ? dataDeploy.filter((d) => d.deployId.match(process.argv[4])) : dataDeploy);
}
break;
case 'build-macro':
{
const dataDeploy = getDataDeploy({ deployGroupId: process.argv[3], buildSingleReplica: true });
for (const deploy of dataDeploy) {
if (!process.argv[4] || (process.argv[4] && process.argv[4] === deploy.deployId)) {
shellExec(Cmd.conf(deploy.deployId));
shellExec(Cmd.build(deploy.deployId));
}
}
}
break;
case 'macro': {
shellExec(`git checkout .`);
shellExec(`node bin/deploy build-macro ${process.argv.slice(3).join(' ')}`);
shellExec(`git checkout .`);
shellExec(`node bin/deploy run-macro ${process.argv.slice(3).join(' ')}`);
break;
}
case 'keep-server': {
await setUpProxyMaintenanceServer({ deployGroupId: process.argv[3] });
break;
}
case 'prometheus':
case 'prom':
{
const rangePort = [1, 20];
const promConfigPath = `./engine-private/prometheus/prometheus-service-config.yml`;
const rawConfig = fs
.readFileSync(promConfigPath, 'utf8')
.replaceAll(
`['']`,
JSON.stringify(range(...rangePort).map((i) => `host.docker.internal:30${setPad(i, '0', 2)}`)).replaceAll(
`"`,
`'`,
),
);
console.log(rawConfig);
fs.writeFileSync(promConfigPath, rawConfig, 'utf8');
shellExec(`docker-compose -f engine-private/prometheus/prometheus-service.yml up -d`);
}
break;
case 'sync-env-port':
const dataDeploy = getDataDeploy({ deployGroupId: process.argv[3], disableSyncEnvPort: true });
const dataEnv = [
{ env: 'production', port: 3000 },
{ env: 'development', port: 4000 },
{ env: 'test', port: 5000 },
];
let port = 0;
const singleReplicaHosts = [];
for (const deployIdObj of dataDeploy) {
const { deployId, replicaHost } = deployIdObj;
if (replicaHost && !singleReplicaHosts.includes(replicaHost)) singleReplicaHosts.push(replicaHost);
const proxyInstance = deployId.match('proxy') || deployId.match('cron');
const baseConfPath = fs.existsSync(`./engine-private/replica/${deployId}`)
? `./engine-private/replica`
: `./engine-private/conf`;
for (const envInstanceObj of dataEnv) {
const envPath = `${baseConfPath}/${deployId}/.env.${envInstanceObj.env}`;
const envObj = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
envObj.PORT = proxyInstance
? envInstanceObj.port
: envInstanceObj.port + port - singleReplicaHosts.length - (replicaHost ? 1 : 0);
writeEnv(envPath, envObj);
}
const serverConf = loadReplicas(
JSON.parse(fs.readFileSync(`${baseConfPath}/${deployId}/conf.server.json`, 'utf8')),
);
if (!proxyInstance) for (const host of Object.keys(serverConf)) port += Object.keys(serverConf[host]).length;
}
break;
case 'uml':
{
shellExec(`node bin/deploy fix-uml ${process.argv.slice(3).join(' ')}`);
shellExec(`node bin/deploy build-uml ${process.argv.slice(3).join(' ')}`);
}
break;
case 'fix-uml': {
// required: java jdk-11.0.1
// comment:
// '--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax="ALL-UNNAMED"'
// in plantuml.js src
// const deployId = process.argv[3];
// const clientId = process.argv[4];
// const folder = `./src/client/public/${clientId ? clientId : 'default'}/docs/plantuml`;
// const privateConfFolder = `./engine-private/conf/${deployId}`;
// const confData = !deployId
// ? Config.default
// : {
// client: JSON.parse(fs.readFileSync(`${privateConfFolder}/conf.client.json`, 'utf8')),
// ssr: JSON.parse(fs.readFileSync(`${privateConfFolder}/conf.ssr.json`, 'utf8')),
// server: JSON.parse(fs.readFileSync(`${privateConfFolder}/conf.server.json`, 'utf8')),
// cron: JSON.parse(fs.readFileSync(`${privateConfFolder}/conf.cron.json`, 'utf8')),
// };
fs.writeFileSync(
`./node_modules/plantuml/lib/plantuml.js`,
fs
.readFileSync(`./node_modules/plantuml/lib/plantuml.js`, 'utf8')
.replace(`'--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax="ALL-UNNAMED"'`, `//`),
);
}
case 'build-uml':
{
const plantuml = await import('plantuml');
const folder = process.argv[3] ? process.argv[3] : `./src/client/public/default/plantuml`;
const confData = Config.default;
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
for (const typeConf of Object.keys(confData)) {
logger.info(`generate ${typeConf} instance`);
try {
const svg = await plantuml(`
@startjson
${JSON.stringify(confData[typeConf])}
@endjson
`);
fs.writeFileSync(`${folder}/${typeConf}-conf.svg`, svg);
} catch (error) {
logger.error(error, error.stack);
}
logger.info(`generate ${typeConf} schema`);
try {
const svg = await plantuml(`
@startjson
${JSON.stringify(ejs(confData[typeConf]))}
@endjson
`);
fs.writeFileSync(`${folder}/${typeConf}-schema.svg`, svg);
} catch (error) {
logger.error(error, error.stack);
}
}
}
break;
case 'build-single-replica': {
const deployId = process.argv[3];
const host = process.argv[4];
const path = process.argv[5];
const serverConf = loadReplicas(
JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
);
if (serverConf[host][path].replicas) {
{
let replicaIndex = -1;
for (const replica of serverConf[host][path].replicas) {
replicaIndex++;
const replicaDeployId = `${deployId}-${serverConf[host][path].replicas[replicaIndex].slice(1)}`;
// fs.mkdirSync(`./engine-private/replica/${deployId}${replicaIndex}`, { recursive: true });
await fs.copy(`./engine-private/conf/${deployId}`, `./engine-private/replica/${replicaDeployId}`);
fs.writeFileSync(
`./engine-private/replica/${replicaDeployId}/package.json`,
fs
.readFileSync(`./engine-private/replica/${replicaDeployId}/package.json`, 'utf8')
.replaceAll(`${deployId}`, `${replicaDeployId}`),
'utf8',
);
}
}
{
let replicaIndex = -1;
for (const replica of serverConf[host][path].replicas) {
replicaIndex++;
const replicaDeployId = `${deployId}-${serverConf[host][path].replicas[replicaIndex].slice(1)}`;
let replicaServerConf = JSON.parse(
fs.readFileSync(`./engine-private/replica/${replicaDeployId}/conf.server.json`, 'utf8'),
);
const singleReplicaConf = replicaServerConf[host][path];
singleReplicaConf.replicas = undefined;
singleReplicaConf.singleReplica = undefined;
replicaServerConf = {};
replicaServerConf[host] = {};
replicaServerConf[host][replica] = singleReplicaConf;
fs.writeFileSync(
`./engine-private/replica/${replicaDeployId}/conf.server.json`,
JSON.stringify(replicaServerConf, null, 4),
'utf8',
);
}
}
}
break;
}
case 'build-macro-replica':
getDataDeploy({ deployGroupId: process.argv[3], buildSingleReplica: true });
break;
case 'rename-package': {
const name = process.argv[3];
const originPackage = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
originPackage.name = name;
fs.writeFileSync(`./package.json`, JSON.stringify(originPackage, null, 4), 'utf8');
const originPackageLockJson = JSON.parse(fs.readFileSync(`./package-lock.json`, 'utf8'));
originPackageLockJson.name = name;
originPackageLockJson.packages[''].name = name;
fs.writeFileSync(`./package-lock.json`, JSON.stringify(originPackageLockJson, null, 4), 'utf8');
break;
}
case 'set-repo': {
const originPackage = JSON.parse(fs.readFileSync(`./package.json`, 'utf8'));
originPackage.repository = {
type: 'git',
url: `git+https://github.com/${process.argv[3]}.git`,
};
fs.writeFileSync(`./package.json`, JSON.stringify(originPackage, null, 4), 'utf8');
break;
}
case 'version-build': {
const originPackageJson = JSON.parse(fs.readFileSync(`package.json`, 'utf8'));
const newVersion = process.argv[3] ?? originPackageJson.version;
const { version } = originPackageJson;
originPackageJson.version = newVersion;
fs.writeFileSync(`package.json`, JSON.stringify(originPackageJson, null, 4), 'utf8');
const originPackageLockJson = JSON.parse(fs.readFileSync(`package-lock.json`, 'utf8'));
originPackageLockJson.version = newVersion;
originPackageLockJson.packages[''].version = newVersion;
fs.writeFileSync(`package-lock.json`, JSON.stringify(originPackageLockJson, null, 4), 'utf8');
if (fs.existsSync(`./engine-private/conf`)) {
const files = await fs.readdir(`./engine-private/conf`, { recursive: true });
for (const relativePath of files) {
const filePah = `./engine-private/conf/${relativePath.replaceAll(`\\`, '/')}`;
if (filePah.split('/').pop() === 'package.json') {
const originPackage = JSON.parse(fs.readFileSync(filePah, 'utf8'));
originPackage.version = newVersion;
fs.writeFileSync(filePah, JSON.stringify(originPackage, null, 4), 'utf8');
}
if (filePah.split('/').pop() === 'deployment.yaml') {
fs.writeFileSync(
filePah,
fs
.readFileSync(filePah, 'utf8')
.replaceAll(`v${version}`, `v${newVersion}`)
.replaceAll(`engine.version: ${version}`, `engine.version: ${newVersion}`),
'utf8',
);
}
}
}
fs.writeFileSync(
`./docker-compose.yml`,
fs
.readFileSync(`./docker-compose.yml`, 'utf8')
.replaceAll(`engine.version: '${version}'`, `engine.version: '${newVersion}'`),
'utf8',
);
fs.writeFileSync(
`./manifests/deployment/dd-template-development/deployment.yaml`,
fs
.readFileSync(`./manifests/deployment/dd-template-development/deployment.yaml`, 'utf8')
.replaceAll(`underpost:v${version}`, `underpost:v${newVersion}`),
'utf8',
);
if (fs.existsSync(`./.github/workflows/docker-image.yml`))
fs.writeFileSync(
`./.github/workflows/docker-image.yml`,
fs
.readFileSync(`./.github/workflows/docker-image.yml`, 'utf8')
.replaceAll(`underpost-engine:v${version}`, `underpost-engine:v${newVersion}`),
'utf8',
);
fs.writeFileSync(
`./src/index.js`,
fs.readFileSync(`./src/index.js`, 'utf8').replaceAll(`${version}`, `${newVersion}`),
'utf8',
);
shellExec(`node bin/deploy cli-docs ${version} ${newVersion}`);
shellExec(`node bin/deploy update-dependencies`);
shellExec(`auto-changelog`);
shellExec(`node bin/build dd`);
shellExec(`node bin deploy --kubeadm --build-manifest --sync --info-router --replicas 1 dd`);
shellExec(`node bin deploy --kubeadm --build-manifest --sync --info-router --replicas 1 dd production`);
break;
}
case 'version-deploy': {
shellExec(
`underpost secret underpost --create-from-file /home/dd/engine/engine-private/conf/dd-cron/.env.production`,
);
shellExec(`node bin/build dd conf`);
shellExec(`git add . && cd ./engine-private && git add .`);
shellExec(`node bin cmt . ci package-pwa-microservices-template`);
shellExec(`node bin cmt ./engine-private ci package-pwa-microservices-template`);
shellExec(`node bin push . underpostnet/engine`);
shellExec(`cd ./engine-private && node ../bin push . underpostnet/engine-private`);
break;
}
case 'update-authors': {
// #### Ordered by first contribution.
fs.writeFileSync(
'./AUTHORS.md',
`# Authors
${shellExec(`git log | grep Author: | sort -u`, { stdout: true }).split(`\n`).join(`\n\n\n`)}
#### Generated by [underpost.net](https://underpost.net)`,
'utf8',
);
break;
}
case 'restore-macro-db':
{
const deployGroupId = process.argv[3];
const deployId = process.argv[4];
await restoreMacroDb(deployGroupId, deployId);
}
break;
case 'mongo': {
await MongooseDB.server();
break;
}
case 'lampp': {
await Lampp.install();
break;
}
case 'heb': {
// https://besu.hyperledger.org/
// https://github.com/hyperledger/besu/archive/refs/tags/24.9.1.tar.gz
switch (process.platform) {
case 'linux':
{
shellCd(`..`);
// Download the Linux binary
shellExec(`wget https://github.com/hyperledger/besu/releases/download/24.9.1/besu-24.9.1.tar.gz`);
// Unzip the file:
shellExec(`tar -xvzf besu-24.9.1.tar.gz`);
shellCd(`besu-24.9.1`);
shellExec(`bin/besu --help`);
// Set env path
// export PATH=$PATH:/home/dd/besu-24.9.1/bin
// Open src
// shellExec(`sudo code /home/dd/besu-24.9.1 --user-data-dir="/root/.vscode-root" --no-sandbox`);
}
break;
default:
break;
}
break;
}
case 'fix-deps': {
await fixDependencies();
break;
}
case 'update-default-conf': {
const defaultServer = DefaultConf.server['default.net']['/'];
let confName = process.argv[3];
if (confName === 'ghpkg') {
confName = undefined;
const host = 'underpostnet.github.io';
const path = '/pwa-microservices-template-ghpkg';
DefaultConf.server = {
[host]: { [path]: defaultServer },
};
DefaultConf.server[host][path].apiBaseProxyPath = '/';
DefaultConf.server[host][path].apiBaseHost = 'www.nexodev.org';
} else if (confName === 'template') {
const host = 'default.net';
const path = '/';
DefaultConf.server[host][path].valkey = {
port: 6379,
host: 'valkey-service.default.svc.cluster.local',
};
// mongodb-0.mongodb-service
DefaultConf.server[host][path].db.host = 'mongodb://mongodb-service:27017';
confName = '';
} else if (confName) {
DefaultConf.client = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.client.json`, 'utf8'));
DefaultConf.server = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.server.json`, 'utf8'));
DefaultConf.ssr = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.ssr.json`, 'utf8'));
// DefaultConf.cron = JSON.parse(fs.readFileSync(`./engine-private/conf/${confName}/conf.cron.json`, 'utf8'));
for (const host of Object.keys(DefaultConf.server)) {
for (const path of Object.keys(DefaultConf.server[host])) {
DefaultConf.server[host][path].db = defaultServer.db;
DefaultConf.server[host][path].mailer = defaultServer.mailer;
delete DefaultConf.server[host][path]._wp_client;
delete DefaultConf.server[host][path]._wp_git;
delete DefaultConf.server[host][path]._wp_directory;
delete DefaultConf.server[host][path].wp;
delete DefaultConf.server[host][path].git;
delete DefaultConf.server[host][path].directory;
}
}
}
const sepRender = '/**/';
const confRawPaths = fs.readFileSync('./conf.js', 'utf8').split(sepRender);
confRawPaths[1] = `${JSON.stringify(DefaultConf)};`;
const targetConfPath = `./conf${confName ? `.${confName}` : ''}.js`;
fs.writeFileSync(targetConfPath, confRawPaths.join(sepRender), 'utf8');
shellExec(`prettier --write ${targetConfPath}`);
break;
}
case 'ssh': {
const host = process.argv[3] ?? `root@${await ip.public.ipv4()}`;
const domain = host.split('@')[1];
const user = 'root'; // host.split('@')[0];
const password = process.argv[4] ?? '';
const port = 22;
const setUpSSH = () => {
// Required port forwarding mapping
// ssh TCP 2222 22 <local-server-ip>
// ssh UDP 2222 22 <local-server-ip>
// Remote connect via public key
// ssh -i <key-path> <user>@<host>:2222
shellExec(`cat ./engine-private/deploy/id_rsa.pub > ~/.ssh/authorized_keys`);
// local trust on first use validator
// check ~/.ssh/known_hosts
// shellExec(`sudo sed -i -e "s@#PasswordAuthentication yes@PasswordAuthentication no@g" /etc/ssh/sshd_config`);
// shellExec(`sudo sed -i -e "s@#UsePAM no@UsePAM yes@g" /etc/ssh/sshd_config`);
// Include /etc/ssh/sshd_config.d/*.conf
// sudo tee /etc/ssh/sshd_config.d/99-custom.conf
shellExec(`sudo tee /etc/ssh/sshd_config <<EOF
PasswordAuthentication no
ChallengeResponseAuthentication yes
UsePAM yes
PubkeyAuthentication Yes
RSAAuthentication Yes
PermitRootLogin Yes
X11Forwarding yes
X11DisplayOffset 10
LoginGraceTime 120
StrictModes yes
SyslogFacility AUTH
LogLevel INFO
#HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
#HostKey /etc/ssh/ssh_host_rsa_key
AuthorizedKeysFile ~/.ssh/authorized_keys
Subsystem sftp /usr/libexec/openssh/sftp-server
ListenAddress 0.0.0.0
ListenAddress ::
ListenAddress ${domain}
ListenAddress ${domain}:22
EOF`);
shellExec(`sudo chmod 700 ~/.ssh/`);
shellExec(`sudo chmod 600 ~/.ssh/authorized_keys`);
shellExec(`sudo chmod 644 ~/.ssh/known_hosts`);
shellExec(`sudo chmod 600 ~/.ssh/id_rsa`);
shellExec(`sudo chmod 600 /etc/ssh/ssh_host_ed25519_key`);
shellExec(`chown -R ${user}:${user} ~/.ssh`);
shellExec(`ufw allow ${port}/tcp`);
shellExec(`ufw allow ${port}/udp`);
shellExec(`ufw allow ssh`);
shellExec(`ufw allow from 192.168.0.0/16 to any port 22`);
// active ssh-agent
shellExec('eval `ssh-agent -s`' + ` && ssh-add ~/.ssh/id_rsa` + ` && ssh-add -l`);
// remove all
// shellExec(`ssh-add -D`);
// remove single
// shellExec(`ssh-add -d ~/.ssh/id_rsa`);
// shellExec(`echo "@${host.split(`@`)[1]} * $(cat ~/.ssh/id_rsa.pub)" > ~/.ssh/known_hosts`);
shellExec('eval `ssh-agent -s`' + `&& ssh-keyscan -H -t ed25519 ${host.split(`@`)[1]} > ~/.ssh/known_hosts`);
// shellExec(`sudo echo "" > ~/.ssh/known_hosts`);
// ssh-copy-id -i ~/.ssh/id_rsa.pub -p <port_number> <username>@<host>
shellExec(`ssh-copy-id -i ~/.ssh/id_rsa.pub -p ${port} ${host}`);
// debug:
// shellExec(`ssh -vvv ${host}`);
shellExec(`sudo cp ./engine-private/deploy/id_rsa ~/.ssh/id_rsa`);
shellExec(`sudo cp ./engine-private/deploy/id_rsa.pub ~/.ssh/id_rsa.pub`);
shellExec(`sudo echo "" > /etc/ssh/ssh_host_ecdsa_key`);
shellExec(`sudo cp ./engine-private/deploy/id_rsa /etc/ssh/ssh_host_ed25519_key`);
shellExec(`sudo echo "" > /etc/ssh/ssh_host_rsa_key`);
shellExec(`sudo echo "" > /etc/ssh/ssh_host_ecdsa_key.pub`);
shellExec(`sudo cp ./engine-private/deploy/id_rsa.pub /etc/ssh/ssh_host_ed25519_key.pub`);
shellExec(`sudo echo "" > /etc/ssh/ssh_host_rsa_key.pub`);
shellExec(`sudo systemctl enable sshd`);
shellExec(`sudo systemctl restart sshd`);
const status = shellExec(`sudo systemctl status sshd`, { silent: true, stdout: true });
console.log(
status.match('running') ? status.replaceAll(`running`, `running`.green) : `ssh service not running`.red,
);
};
if (process.argv.includes('import')) {
setUpSSH();
break;
}
shellExec(`sudo rm -rf ./id_rsa`);
shellExec(`sudo rm -rf ./id_rsa.pub`);
if (process.argv.includes('legacy'))
shellExec(`ssh-keygen -t rsa -b 4096 -f id_rsa -N "${password}" -q -C "${host}"`);
else shellExec(`ssh-keygen -t ed25519 -f id_rsa -N "${password}" -q -C "${host}"`);
shellExec(`sudo cp ./id_rsa ~/.ssh/id_rsa`);
shellExec(`sudo cp ./id_rsa.pub ~/.ssh/id_rsa.pub`);
shellExec(`sudo cp ./id_rsa ./engine-private/deploy/id_rsa`);
shellExec(`sudo cp ./id_rsa.pub ./engine-private/deploy/id_rsa.pub`);
shellExec(`sudo rm -rf ./id_rsa`);
shellExec(`sudo rm -rf ./id_rsa.pub`);
setUpSSH();
break;
}
case 'maas-db': {
// DROP, ALTER, CREATE, WITH ENCRYPTED
// sudo -u <user> -h <host> psql <db-name>
shellExec(`DB_PG_MAAS_NAME=${process.env.DB_PG_MAAS_NAME}`);
shellExec(`DB_PG_MAAS_PASS=${process.env.DB_PG_MAAS_PASS}`);
shellExec(`DB_PG_MAAS_USER=${process.env.DB_PG_MAAS_USER}`);
shellExec(`DB_PG_MAAS_HOST=${process.env.DB_PG_MAAS_HOST}`);
shellExec(
`sudo -i -u postgres psql -c "CREATE USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
);
shellExec(
`sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ENCRYPTED PASSWORD '$DB_PG_MAAS_PASS'"`,
);
const actions = ['LOGIN', 'SUPERUSER', 'INHERIT', 'CREATEDB', 'CREATEROLE', 'REPLICATION'];
shellExec(`sudo -i -u postgres psql -c "ALTER USER \"$DB_PG_MAAS_USER\" WITH ${actions.join(' ')}"`);
shellExec(`sudo -i -u postgres psql -c "\\du"`);
shellExec(`sudo -i -u postgres createdb -O "$DB_PG_MAAS_USER" "$DB_PG_MAAS_NAME"`);
shellExec(`sudo -i -u postgres psql -c "\\l"`);
break;
}
case 'valkey': {
if (!process.argv.includes('server')) {
if (process.argv.includes('rocky')) {
// shellExec(`yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm`);
// shellExec(`sudo percona-release enable valkey experimental`);
shellExec(`sudo dnf install valkey`);
shellExec(`chown -R valkey:valkey /etc/valkey`);
shellExec(`chown -R valkey:valkey /var/lib/valkey`);
shellExec(`chown -R valkey:valkey /var/log/valkey`);
shellExec(`sudo systemctl enable valkey.service`);
shellExec(`sudo systemctl start valkey`);
shellExec(`valkey-cli ping`);
} else {
shellExec(`cd /home/dd && git clone https://github.com/valkey-io/valkey.git`);
shellExec(`cd /home/dd/valkey && make`);
shellExec(`apt install valkey-tools`); // valkey-cli
}
}
if (process.argv.includes('rocky')) {
shellExec(`sudo systemctl stop valkey`);
shellExec(`sudo systemctl start valkey`);
} else shellExec(`cd /home/dd/valkey && ./src/valkey-server`);
break;
}
case 'valkey-service': {
shellExec(`pm2 start bin/deploy.js --node-args=\"--max-old-space-size=8192\" --name valkey -- valkey server`);
break;
}
case 'update-instances': {
shellExec(`node bin deploy dd production --sync --build-manifest --info-router --dashboard-update`);
shellExec(`node bin cron --dashboard-update --init`);
const deployId = 'dd-core';
const host = 'www.nexodev.org';
const path = '/';
{
const outputPath = './engine-private/instances';
if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
const collection = 'instances';
if (process.argv.includes('export'))
shellExec(
`node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
);
if (process.argv.includes('import'))
shellExec(
`node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
);
}
{
const outputPath = './engine-private/crons';
if (fs.existsSync(outputPath)) fs.mkdirSync(outputPath, { recursive: true });
const collection = 'crons';
if (process.argv.includes('export'))
shellExec(
`node bin db --export --collections ${collection} --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
);
if (process.argv.includes('import'))
shellExec(
`node bin db --import --drop --preserveUUID --out-path ${outputPath} --hosts ${host} --paths '${path}' ${deployId}`,
);
}
break;
}
case 'cli-docs': {
buildCliDoc(program, process.argv[3], process.argv[4]);
break;
}
case 'monitor': {
shellExec(
`node bin monitor ${process.argv[6] === 'sync' ? '--sync ' : ''}--type ${process.argv[3]} ${process.argv[4]} ${
process.argv[5]
}`,
{
async: true,
},
);
break;
}
case 'postgresql': {
if (process.argv.includes('install')) {
shellExec(`sudo dnf install -y postgresql-server postgresql`);
shellExec(`sudo postgresql-setup --initdb`);
shellExec(`chown postgres /var/lib/pgsql/data`);
shellExec(`sudo systemctl enable postgresql.service`);
shellExec(`sudo systemctl start postgresql.service`);
} else {
shellExec(`sudo systemctl enable postgresql.service`);
shellExec(`sudo systemctl restart postgresql.service`);
}
shellExec(`sudo systemctl status postgresql.service`);
// sudo systemctl stop postgresql
// sudo systemctl disable postgresql
// psql login
// psql -U <user> -h 127.0.0.1 -W <db-name>
// gedit /var/lib/pgsql/data/pg_hba.conf
// host <db-name> <db-user> <db-host> md5
// local all postgres trust
// # "local" is for Unix domain socket connections only
// local all all md5
// # IPv4 local connections:
// host all all 127.0.0.1/32 md5
// # IPv6 local connections:
// host all all ::1/128 md5
// gedit /var/lib/pgsql/data/postgresql.conf
// listen_addresses = '*'
break;
}
case 'postgresql-17': {
if (process.argv.includes('install')) {
shellExec(`sudo dnf module reset postgresql -y`);
shellExec(`sudo dnf -qy module disable postgresql`);
shellExec(
`sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm`,
);
shellExec(`sudo dnf -qy module disable postgresql`);
shellExec(`sudo dnf install -y postgresql17 postgresql17-server postgresql17-contrib`);
shellExec(`sudo /usr/pgsql-17/bin/postgresql-17-setup initdb`);
}
if (process.argv.includes('uninstall')) {
shellExec(`sudo systemctl stop postgresql-17`);
shellExec(`sudo systemctl disable postgresql-17`);
// Remove PostgreSQL 17 packages and repo
shellExec(`sudo dnf remove -y postgresql17 postgresql17-server postgresql17-contrib`);
shellExec(`sudo rpm -e pgdg-redhat-repo-$(rpm -q pgdg-redhat-repo --qf '%{VERSION}-%{RELEASE}') || true`);
shellExec(`sudo rm -f /etc/yum.repos.d/pgdg-redhat-*.repo`);
// Clean up data, logs, config, and the postgres user
shellExec(`sudo rm -rf /var/lib/pgsql/17 /var/log/pgsql`);
shellExec(`sudo rm -rf /etc/postgresql`);
} else {
shellExec(`sudo systemctl enable postgresql-17`);
shellExec(`sudo systemctl start postgresql-17`);
}
break;
}
case 'postgresql-14': {
if (process.argv.includes('install')) {
shellExec(`sudo dnf module reset postgresql -y`);
shellExec(`sudo dnf -qy module disable postgresql`);
shellExec(`sudo systemctl stop postgresql-14`);
shellExec(`sudo systemctl disable postgresql-14`);
shellExec(`sudo dnf remove -y postgresql14 postgresql14-server postgresql14-contrib`);
shellExec(`sudo rm -rf /var/lib/pgsql`);
shellExec(`sudo dnf install postgresql14 postgresql14-server postgresql14-contrib -y`);
}
if (process.argv.includes('uninstall')) {
shellExec(`sudo systemctl stop postgresql-14`);
shellExec(`sudo systemctl disable postgresql-14`);
shellExec(`sudo dnf remove -y postgresql14 postgresql14-server postgresql14-contrib`);
shellExec(`sudo rm -rf /var/lib/pgsql /var/log/pgsql /etc/postgresql`);
} else {
shellExec(`sudo /usr/pgsql-14/bin/postgresql-14-setup initdb`);
shellExec(`sudo systemctl start postgresql-14`);
shellExec(`sudo systemctl enable postgresql-14`);
shellExec(`sudo systemctl status postgresql-14`);
// sudo dnf install postgresql14-contrib
}
break;
}
case 'pg-stop': {
shellExec(`sudo systemctl stop postgresql-14`);
shellExec(`sudo systemctl disable postgresql-14`);
break;
}
case 'pg-start': {
shellExec(`sudo systemctl enable postgresql-14`);
shellExec(`sudo systemctl restart postgresql-14`);
break;
}
case 'pg-list-db': {
shellExec(`sudo -i -u postgres psql -c "\\l"`);
break;
}
case 'pg-list-table': {
shellExec(`sudo -i -u postgres psql -c "\\dt *.*"`);
// schema_name.*
break;
}
case 'pg-drop-db': {
shellExec(`sudo -i -u postgres psql -c "DROP DATABASE ${process.argv[3]} WITH (FORCE)"`);
shellExec(`sudo -i -u postgres psql -c "DROP USER ${process.argv[4]}"`);
break;
}
case 'maas-stop': {
shellExec(`sudo snap stop maas`);
break;
}
case 'nfs': {
// Daemon RPC NFSv3. ports:
// 2049 (TCP/UDP) – nfsd standard port.
// 111 (TCP/UDP) – rpcbind/portmapper.
// 20048 (TCP/UDP) – rpc.mountd.
// 32765 (TCP/UDP) – rpc.statd.
// 32766 (TCP/UDP) – lockd (NLM).
// Configure export and permissions:
// /etc/exports
// Configure ports:
// /etc/nfs.conf
fs.writeFileSync(
`/etc/nfs.conf`,
`
[mountd]
port = 20048
[statd]
port = 32765
outgoing-port = 32765
[nfsd]
rdma=y
rdma-port=20049
[lockd]
port = 32766
udp-port = 32766
`,
'utf8',
);
// Client users have read-only access to resources and are identified as anonymous on the server.
// /share ip-client(ro,all_squash)
// Client users can modify resources and keep their UID on the server. Only root is identified as anonymous.
// /share ip-client(rw)
// Users on client workstation 1 can modify resources, while those on client workstation 2 have read-only access.
// UIDs are kept on the server, and only root is identified as anonymous.
// /share ip-client1(rw) ip-client2(ro)
// Client1 users can modify resources. Their UID is changed to 1001 and their GID to 100 on the server.
// /share ip-client(rw,all_squash,anonuid=1001,anongid=100)
// sudo dnf install nfs-utils
// sudo systemctl enable --now rpcbind // RPC map service
// sudo systemctl enable --now nfs-server // nfs domains nfsd
// Update exports:
// shellExec(`sudo exportfs -a -r`);
// shellExec(`sudo exportfs -v`);
// Active nfs
shellExec(`sudo exportfs -s`);
shellExec(`sudo exportfs -rav`);
// Rocky enable virt_use_nfs
// sudo setsebool -P virt_use_nfs 1
// Disable share:
// sudo exportfs -u <client-ip>:${process.env.NFS_EXPORT_PATH}/rpi4mb
// Nfs client:
// mount -t nfs <server-ip>:/server-mnt /mnt
// umount /mnt
shellExec(`sudo systemctl restart nfs-server`);
break;
}
case 'mount': {
const mounts = shellExec(`mount`).split(`\n`);
console.table(
mounts
.filter((l) => l.trim())
.map(
(o) => (
(o = o.split(' ')),
{
path: o[2],
type: o[4],
permissions: o[5],
}
),
),
);
break;
}
case 'create-ports': {
const cmd = [];
const commissioningDeviceIp = getLocalIPv4Address();
for (const port of ['5240']) {
const name = 'maas';
cmd.push(`${name}:${port}-${port}:${commissioningDeviceIp}`);
}
pbcopy(`node engine-private/r create-port ${cmd}`);
break;
}
case 'maas-ports': {
// Configure firewall:
// systemctl stop firewalld
// systemctl mask firewalld
// ufw disable
// ufw enable
// sudo snap install ufw
// const ports = ['80', '443', '22', '3000-3100'];
const ports = [
'43',
'53',
'60',
'66',
'67',
'69',
'4011',
'111',
'2049',
'20048',
'20049',
'32765',
'32766',
'5248',
'5240',
];
for (const port of ports) {
shellExec(`ufw allow ${port}/tcp`);
shellExec(`ufw allow ${port}/udp`);
}
shellExec(`sudo systemctl mask firewalld`);
break;