underpost
Version:
1,722 lines • 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;
}
case 'iptables': {
shellExec(`sudo systemctl enable nftables`);
shellExec(`sudo systemctl restart nftables`);
shellExec(`sudo tee /etc/nftables.conf <<EOF
table inet filter {
chain input {
type filter hook input priority 0;
policy drop;
tcp dport 22 accept
}
}
EOF`);
shellExec(`sudo nft -f /etc/nftables.conf`);
// sudo systemctl stop nftables
// sudo systemctl disable nftables
break;
}
case 'rpi4': {
// Rpi4 Run Bootloader:
// 1) create boot.conf
// 2) Run lite RPiOs from rpi-imager
// with boot.conf files in root disk path
// 3) cd /boot/firmware && sudo rpi-eeprom-config --apply boot.conf
// 4) sudo reboot
// 5) check: 'vcgencmd bootloader_version'
// 6) check: 'vcgencmd bootloader_config'
// 7) shutdown and restart without sd card
// sudo apt update
// sudo apt install git
break;
}
case 'blue': {
// lsusb | grep blue -i
// rfkill list
// sudo service bluetooth start
// bluetoothctl show
// sudo rfkill unblock bluetooth
// dmesg | grep -i bluetooth
// journalctl -u bluetooth -f
// sudo dnf update bluez bluez-libs bluez-utils
// sudo rmmod btusb
// sudo modprobe btusb
break;
}
case 'fastapi-models': {
shellExec(`chmod +x ../full-stack-fastapi-template/backend/initial_data.sh`);
shellExec(`../full-stack-fastapi-template/backend/initial_data.sh`);
shellExec(`../full-stack-fastapi-template/backend/initial_data.sh`);
break;
}
case 'fastapi': {
// node bin/deploy fastapi reset
// node bin/deploy fastapi reset build-back build-front secret run-back run-front
// https://github.com/NonsoEchendu/full-stack-fastapi-project
// https://github.com/fastapi/full-stack-fastapi-template
const path = `../full-stack-fastapi-template`;
const VITE_API_URL = `http://localhost:8000`;
if (process.argv.includes('reset')) shellExec(`sudo rm -rf ${path}`);
if (!fs.existsSync(path))
shellExec(`cd .. && git clone https://github.com/fastapi/full-stack-fastapi-template.git`);
shellExec(`cd ${path} && git checkout . && git clean -f -d`);
const password = fs.readFileSync(`/home/dd/engine/engine-private/postgresql-password`, 'utf8');
fs.writeFileSync(
`${path}/.env`,
fs
.readFileSync(`${path}/.env`, 'utf8')
.replace(`FIRST_SUPERUSER=admin@example.com`, `FIRST_SUPERUSER=development@underpost.net`)
.replace(`FIRST_SUPERUSER_PASSWORD=changethis`, `FIRST_SUPERUSER_PASSWORD=${password}`)
.replace(`SECRET_KEY=changethis`, `SECRET_KEY=${password}`)
.replace(`POSTGRES_DB=app`, `POSTGRES_DB=postgresdb`)
.replace(`POSTGRES_USER=postgres`, `POSTGRES_USER=admin`)
.replace(`POSTGRES_PASSWORD=changethis`, `POSTGRES_PASSWORD=${password}`),
'utf8',
);
fs.writeFileSync(
`${path}/backend/app/core/db.py`,
fs
.readFileSync(`${path}/backend/app/core/db.py`, 'utf8')
.replace(` # from sqlmodel import SQLModel`, ` from sqlmodel import SQLModel`)
.replace(` # SQLModel.metadata.create_all(engine)`, ` SQLModel.metadata.create_all(engine)`),
'utf8',
);
fs.copySync(`./manifests/deployment/fastapi/initial_data.sh`, `${path}/backend/initial_data.sh`);
fs.writeFileSync(
`${path}/frontend/Dockerfile`,
fs
.readFileSync(`${path}/frontend/Dockerfile`, 'utf8')
.replace('ARG VITE_API_URL=${VITE_API_URL}', `ARG VITE_API_URL='${VITE_API_URL}'`),
'utf8',
);
fs.writeFileSync(
`${path}/frontend/.env`,
fs
.readFileSync(`${path}/frontend/.env`, 'utf8')
.replace(`VITE_API_URL=http://localhost:8000`, `VITE_API_URL=${VITE_API_URL}`)
.replace(`MAILCATCHER_HOST=http://localhost:1080`, `MAILCATCHER_HOST=http://localhost:1081`),
'utf8',
);
if (process.argv.includes('models')) {
shellExec(`node bin/deploy fastapi-models`);
break;
}
if (process.argv.includes('build-back')) {
const imageName = `fastapi-backend:latest`;
shellExec(`sudo podman pull docker.io/library/python:3.10`);
shellExec(`sudo podman pull ghcr.io/astral-sh/uv:0.5.11`);
shellExec(`sudo rm -rf ${path}/${imageName.replace(':', '_')}.tar`);
const args = [
`node bin dockerfile-image-build --path ${path}/backend/`,
`--image-name=${imageName} --image-path=${path}`,
`--podman-save --${process.argv.includes('kubeadm') ? 'kubeadm' : 'kind'}-load --reset`,
];
shellExec(args.join(' '));
}
if (process.argv.includes('build-front')) {
const imageName = `fastapi-frontend:latest`;
shellExec(`sudo podman pull docker.io/library/node:20`);
shellExec(`sudo podman pull docker.io/library/nginx:1`);
shellExec(`sudo rm -rf ${path}/${imageName.replace(':', '_')}.tar`);
const args = [
`node bin dockerfile-image-build --path ${path}/frontend/`,
`--image-name=${imageName} --image-path=${path}`,
`--podman-save --${process.argv.includes('kubeadm') ? 'kubeadm' : 'kind'}-load --reset`,
];
shellExec(args.join(' '));
}
if (process.argv.includes('secret')) {
{
const secretSelector = `fastapi-postgres-credentials`;
shellExec(`sudo kubectl delete secret ${secretSelector}`);
shellExec(
`sudo kubectl create secret generic ${secretSelector}` +
` --from-literal=POSTGRES_DB=postgresdb` +
` --from-literal=POSTGRES_USER=admin` +
` --from-file=POSTGRES_PASSWORD=/home/dd/engine/engine-private/postgresql-password`,
);
}
{
const secretSelector = `fastapi-backend-config-secret`;
shellExec(`sudo kubectl delete secret ${secretSelector}`);
shellExec(
`sudo kubectl create secret generic ${secretSelector}` +
` --from-file=SECRET_KEY=/home/dd/engine/engine-private/postgresql-password` +
` --from-literal=FIRST_SUPERUSER=development@underpost.net` +
` --from-file=FIRST_SUPERUSER_PASSWORD=/home/dd/engine/engine-private/postgresql-password`,
);
}
}
if (process.argv.includes('run-back')) {
shellExec(`sudo kubectl apply -f ./manifests/deployment/fastapi/backend-deployment.yml`);
shellExec(`sudo kubectl apply -f ./manifests/deployment/fastapi/backend-service.yml`);
}
if (process.argv.includes('run-front')) {
shellExec(`sudo kubectl apply -f ./manifests/deployment/fastapi/frontend-deployment.yml`);
shellExec(`sudo kubectl apply -f ./manifests/deployment/fastapi/frontend-service.yml`);
}
break;
}
case 'conda': {
// set -e
// ENV_NAME="${1:-cuda_env}"
// eval "$(conda shell.bash hook)"
// conda activate "${ENV_NAME}"
shellExec(
`export PATH="/root/miniconda3/bin:$PATH" && conda init && conda config --set auto_activate_base false`,
);
shellExec(`conda env list`);
break;
}
case 'kafka': {
// https://medium.com/@martin.hodges/deploying-kafka-on-a-kind-kubernetes-cluster-for-development-and-testing-purposes-ed7adefe03cb
const imageName = `doughgle/kafka-kraft`;
shellExec(`docker pull ${imageName}`);
if (!process.argv.includes('kubeadm'))
shellExec(
`${process.argv.includes('kubeadm') ? `ctr -n k8s.io images import` : `kind load docker-image`} ${imageName}`,
);
shellExec(`kubectl create namespace kafka`);
shellExec(`kubectl apply -f ./manifests/deployment/kafka/deployment.yaml`);
// kubectl logs kafka-0 -n kafka | grep STARTED
// kubectl logs kafka-1 -n kafka | grep STARTED
// kubectl logs kafka-2 -n kafka | grep STARTED
// kafka-topics.sh --create --topic my-topic --bootstrap-server kafka-svc:9092
// kafka-topics.sh --list --topic my-topic --bootstrap-server kafka-svc:9092
// kafka-topics.sh --delete --topic my-topic --bootstrap-server kafka-svc:9092
// kafka-console-producer.sh --bootstrap-server kafka-svc:9092 --topic my-topic
// kafka-console-consumer.sh --bootstrap-server kafka-svc:9092 --topic my-topic
break;
}
case 'nvidia-gpu-operator': {
// https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
shellExec(`curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo | \
sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo`);
const NVIDIA_CONTAINER_TOOLKIT_VERSION = '1.17.8-1';
shellExec(`sudo dnf install -y \
nvidia-container-toolkit-${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
nvidia-container-toolkit-base-${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
libnvidia-container-tools-${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
libnvidia-container1-${NVIDIA_CONTAINER_TOOLKIT_VERSION}`);
// https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/getting-started.html
shellExec(`kubectl create ns gpu-operator`);
shellExec(`kubectl label --overwrite ns gpu-operator pod-security.kubernetes.io/enforce=privileged`);
shellExec(`helm repo add nvidia https://helm.ngc.nvidia.com/nvidia \
&& helm repo update`);
// shellExec(`helm install --wait --generate-name \
// -n gpu-operator --create-namespace \
// nvidia/gpu-operator \
// --version=v25.3.1 \
// --set toolkit.version=v1.16.1-ubi8`);
shellExec(`helm install --wait --generate-name \
-n gpu-operator --create-namespace \
nvidia/gpu-operator \
--version=v25.3.1 \
--set driver.enabled=false \
--set driver.repository=nvcr.io/nvidia \
--set cdi.enabled=true \
--set cdi.default=true \
--set toolkit.env[0].name=CONTAINERD_CONFIG \
--set toolkit.env[0].value=/etc/containerd/config.toml \
--set toolkit.env[1].name=CONTAINERD_SOCKET \
--set toolkit.env[1].value=/run/containerd/containerd.sock \
--set toolkit.env[2].name=CONTAINERD_RUNTIME_CLASS \
--set toolkit.env[2].value=nvidia \
--set-string toolkit.env[3].name=CONTAINERD_SET_AS_DEFAULT \
--set-string toolkit.env[3].value=true`);
// Check gpu drivers
shellExec(
`break;kubectl get nodes -o json | jq '.items[].metadata.labels | keys | any(startswith("feature.node.kubernetes.io"))'`,
);
break;
}
case 'kubeflow-spark-operator': {
// Use case:
// Data Processing Pipelines: Used for ETL tasks where Spark can handle large data volumes efficiently.
// Real-Time Analytics: Processing data from streaming sources (e.g., Kafka) for real-time analytics.
// Machine Learning and Data Science: Training and deploying machine learning models at scale using Spark MLlib.
shellExec(`helm repo add spark-operator https://kubeflow.github.io/spark-operator`);
shellExec(`helm install spark-operator spark-operator/spark-operator \
--namespace spark-operator \
--create-namespace \
--wait`);
const image = `spark:3.5.5`;
shellExec(`sudo docker pull ${image}`);
if (!process.argv.includes('kubeadm'))
shellExec(
`sudo ${
process.argv.includes('kubeadm') ? `ctr -n k8s.io images import` : `kind load docker-image`
} ${image}`,
);
shellExec(`kubectl apply -f ./manifests/deployment/spark/spark-pi-py.yaml`);
// Check the status of the Spark job:
// kubectl get sparkapplications.sparkoperator.k8s.io -n default
// kubectl get sparkapplication
// Check case log:
// kubectl logs -f spark-pi-python-driver
// kubectl logs -f spark-pi-python-driver | grep Pi
// kubectl describe sparkapplication spark-gpu-test
// Uninstall:
// kubectl delete sparkapplications.sparkoperator.k8s.io spark-pi-python -n default
// helm delete spark-operator -n spark-operator
// Gpu plugins:
// https://github.com/NVIDIA/spark-rapids
// RAPIDS Accelerator
break;
}
case 'sbt': {
// https://www.scala-sbt.org/1.x/docs/Installing-sbt-on-Linux.html
// sudo rm -f /etc/yum.repos.d/bintray-rpm.repo
// curl -L https://www.scala-sbt.org/sbt-rpm.repo > sbt-rpm.repo
// sudo mv sbt-rpm.repo /etc/yum.repos.d/
// sudo yum install sbt
break;
}
}
} catch (error) {
logger.error(error, error.stack);
}