netget
Version:
Rette Adepto/ Recibido Directamente.
711 lines (648 loc) • 25.2 kB
JavaScript
//netget/src/modules/NetGetX/Domains/domainsOptions.js
import inquirer from 'inquirer';
import chalk from 'chalk';
import NetGetX_CLI from '../NetGetX.cli.js';
import { loadOrCreateXConfig, saveXConfig } from '../config/xConfig.js';
import { scanAndLogCertificates } from './SSL/SSLCertificates.js';
import { registerDomain, deleteDomain, updateDomainTarget, updateDomainType } from '../../../sqlite/utils_sqlite3.js';
import domainsMenu from './domains.cli.js';
import sqlite3 from 'sqlite3';
/**
* Retrieves and displays the subdomains table for a given domain.
* @param {string} domain - The parent domain to list subdomains for.
*/
function retrieveSubdomainsTable(domain) {
return new Promise((resolve, reject) => {
const db = new sqlite3.Database('/opt/.get/domains.db', sqlite3.OPEN_READONLY);
db.all(
// Exclude rows where domain === subdomain (shouldn't happen, but just in case)
'SELECT domain, target, type, subdomain FROM domains WHERE subdomain = ? ORDER BY domain',
[domain],
(err, rows) => {
db.close();
if (err) {
console.log(chalk.red('Error reading subdomains:'), err.message);
return reject(err);
}
if (rows.length === 0) {
// console.log(chalk.yellow('No subdomains configured for this domain.'));
return resolve([]);
} else {
console.log(chalk.blue('\nSubdomains for domain:'), chalk.green(domain));
const subDomainsTable = rows.map(row => ({
DomainAndSubdomain: row.domain,
Target: row.target,
Type: row.type
}));
return resolve(subDomainsTable);
}
}
);
});
}
/**
* Logs the domain information to the console.
* @memberof module:NetGetX.Domains
* @param {Object} domainConfig - The domain configuration object.
* @param {string} domain - The domain name.
*/
async function logDomainInfo(domain) {
// console.table([{
// Domain: domainConfig.domain,
// Target: domainConfig.target,
// Type: domainConfig.type,
// Owner: domainConfig.owner,
// Email: domainConfig.email
// }]);
try {
const subDomainsTable = await retrieveSubdomainsTable(domain);
if (subDomainsTable.length > 0) {
console.table(subDomainsTable);
} else {
console.log(chalk.yellow('No subdomains configured for this domain.'));
}
} catch (err) {
console.error(chalk.red('Error retrieving subdomains:'), err.message);
}
}
/**
* Displays the domains table by reading from the SQLite3 database.
*/
const domainsTable = () => {
const db = new sqlite3.Database('/opt/.get/domains.db', sqlite3.OPEN_READONLY, (err) => {
if (err) {
console.log(chalk.red('Error opening database:'), err.message);
return;
}
});
db.all('SELECT domain, target, type FROM domains ORDER BY domain', [], (err, rows) => {
if (err) {
console.log(chalk.red('Error reading domains:'), err.message);
db.close();
return;
}
if (rows.length === 0) {
console.log(chalk.yellow('No domains configured.'));
} else {
console.log(chalk.blue('\nDomains Information:'));
console.table(rows.map(row => ({
Domain: row.domain,
Target: row.target,
Type: row.type
})));
}
db.close();
});
};
const validateDomain = (domain) => {
const domainRegex = /^(?!:\/\/)([a-zA-Z0-9-_]{1,63}\.)+[a-zA-Z]{2,6}$/;
return domainRegex.test(domain) ? true : 'Enter a valid domain (e.g., example.com or sub.example.com)';
};
/**
* Adds a new domain to the database.
* @memberof module:NetGetX.Domains
* @returns {Promise<void>}
*/
const addNewDomain = async () => {
while (true) {
const description_message =
'Add a new domain to your NetGetX configuration. You can choose to serve static content or forward traffic to a specific port on your server.\n' +
chalk.blue('Available Service Types:\n' +
'- Serve Static Content: Host static files (like HTML, CSS, JS, images) from a folder on your server. Great for simple websites or landing pages.\n' +
'- Forward Port: Forward all incoming traffic to a specific port on your server. Useful for connecting your domain to a backend service, app, or container running on a different port.\n\n') +
chalk.white('Select the type of service for this domain:');
const serviceTypeAnswer = await inquirer.prompt([
{
type: 'list',
name: 'serviceType',
message: description_message,
validate: input => input ? true : 'Service type is required.',
choices: [
{ name: 'Serve Static Content', value: 'static' },
{ name: 'Forward Port', value: 'server' },
{ name: 'Back', value: 'back' }
]
}
]);
if (serviceTypeAnswer.serviceType === 'back') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
const type = serviceTypeAnswer.serviceType;
let port = '';
if (serviceTypeAnswer.serviceType === 'server') {
const forwardPortAnswer = await inquirer.prompt([
{
type: 'input',
name: 'server',
message: 'Enter the forward port for this domain (type /b to go back):',
validate: input => {
if (input === '/b') return true;
const portNum = Number(input);
if (
!Number.isInteger(portNum) ||
portNum < 1 ||
portNum > 65535
) {
return 'Enter a valid port number (1-65535)';
}
return true;
}
}
]);
if (forwardPortAnswer.server === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
port = forwardPortAnswer.server;
} else if (serviceTypeAnswer.serviceType === 'static') {
const staticPathAnswer = await inquirer.prompt([
{
type: 'input',
name: 'staticPath',
message: 'Enter the path to the static file you want to serve (type /b to go back):',
validate: input => input ? true : 'Static file path is required.'
}
]);
if (staticPathAnswer.staticPath === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
port = staticPathAnswer.staticPath;
}
const domainAnswer = await inquirer.prompt([
{
type: 'input',
name: 'domain',
message: 'Enter the new domain (e.g., example.com or sub.example.com) (type /b to go back):',
validate: input => {
if (input === '/b') return true;
return validateDomain(input);
}
}
]);
if (domainAnswer.domain === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
const emailAnswer = await inquirer.prompt([
{
type: 'input',
name: 'email',
message: 'Enter the email associated with this domain (type /b to go back):',
validate: input => {
if (input === '/b') return true;
// Simple email regex validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(input) ? true : 'Enter a valid email address.';
}
}
]);
if (emailAnswer.email === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
const ownerAnswer = await inquirer.prompt([
{
type: 'input',
name: 'owner',
message: 'Enter the owner of this domain (type /b to go back):',
validate: input => input ? true : 'Owner is required.'
}
]);
if (ownerAnswer.owner === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
const { domain, email, owner } = { ...domainAnswer, ...emailAnswer, ...ownerAnswer };
// Verifica si el dominio ya existe en la base de datos
const db = new sqlite3.Database('/opt/.get/domains.db', sqlite3.OPEN_READONLY);
const exists = await new Promise((resolve) => {
db.get('SELECT 1 FROM domains WHERE domain = ? AND subdomain IS NULL', [domain], (err, row) => {
db.close();
resolve(!!row);
});
});
if (exists) {
console.log(chalk.red(`Domain ${domain} already exists.`));
return;
}
registerDomain(
domain,
domain, // No subdomain for the main domain
email,
'letsencrypt', // Default SSL mode
'',
'',
port,
type,
'',
owner);
console.log(chalk.green(`Domain ${domain} added successfully.`));
return; // Exit the loop after successful addition
}
};
/**
* Adds a subdomain to the specified domain.
* @memberof module:NetGetX.Domains
* @param {string} domain - The domain to add the subdomain to.
* @returns {Promise<void>}
*/
const addSubdomain = async (domain) => {
try {
const { subdomain } = await inquirer.prompt([
{
type: 'input',
name: 'subdomain',
message: 'Enter the subdomain name (type /b to go back):',
validate: input => {
if (input === '/b') return true;
if (!input) return 'Subdomain name cannot be empty.';
return true;
}
}
]);
if (subdomain === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
const serviceTypeAnswer = await inquirer.prompt([
{
type: 'list',
name: 'serviceType',
message: 'Select the type of service for this domain:',
choices: [
{ name: 'Serve Static Content', value: 'static' },
{ name: 'Forward Port', value: 'server' },
{ name: 'Back', value: 'back' }
]
}
]);
if (serviceTypeAnswer.serviceType === 'back') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
let port = '';
if (serviceTypeAnswer.serviceType === 'server') {
const forwardPortAnswer = await inquirer.prompt([
{
type: 'input',
name: 'server',
message: 'Enter the forward port for this domain (type /b to go back):',
validate: input => input ? true : 'Forward port is required.'
}
]);
if (forwardPortAnswer.server === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
port = forwardPortAnswer.server;
} else if (serviceTypeAnswer.serviceType === 'static') {
const staticPathAnswer = await inquirer.prompt([
{
type: 'input',
name: 'staticPath',
message: 'Enter the path to the static file you want to serve (type /b to go back):',
validate: input => input ? true : 'Static file path is required.'
}
]);
if (staticPathAnswer.staticPath === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
port = staticPathAnswer.staticPath;
}
const ownerAnswer = await inquirer.prompt([
{
type: 'input',
name: 'owner',
message: 'Enter the owner of this subdomain (type /b to go back):',
validate: input => input ? true : 'Owner is required.'
}
]);
if (ownerAnswer.owner === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
// Retrieve email, SSLCertificateSqlitePath, and SSLCertificateKeySqlitePath from the parent domain in the database
let parentDomainConfig;
try {
const db = new sqlite3.Database('/opt/.get/domains.db', sqlite3.OPEN_READONLY);
parentDomainConfig = await new Promise((resolve, reject) => {
db.get(
'SELECT email, sslCertificate, sslCertificateKey FROM domains WHERE domain = ?',
[domain],
(err, row) => {
db.close();
if (err) return reject(err);
resolve(row);
}
);
});
} catch (err) {
console.log(chalk.red('Error retrieving parent domain config:'), err.message);
return;
}
if (!parentDomainConfig) {
console.log(chalk.red(`Parent domain ${domain} not found in database.`));
return;
}
try {
await registerDomain(
subdomain,
domain,
parentDomainConfig.email,
'letsencrypt',
parentDomainConfig.sslCertificate,
parentDomainConfig.sslCertificateKey,
port,
serviceTypeAnswer.serviceType,
'',
ownerAnswer.owner
);
} catch (err) {
console.log(chalk.red('Error registering subdomain:'), err.message);
return;
}
} catch (err) {
console.log(chalk.red('An error occurred while adding the subdomain:'), err.message);
return;
}
console.log(chalk.green(`Subdomain ${subdomain} added to domain ${domain}.`));
return;
};
const editDomainDetails = async (domain) => {
const editOptions = await inquirer.prompt([
{
type: 'list',
name: 'editOption',
message: 'Select an option to edit:',
choices: [
{ name: 'Edit Type', value: 'editType' },
{ name: 'Edit Target', value: 'editTarget' },
{ name: 'Back to Domains Menu', value: 'back' }
]
}
]);
switch (editOptions.editOption) {
case 'editType':
const typeAnswer = await inquirer.prompt([
{
type: 'list',
name: 'serviceType',
message: 'Select the new type of service for this domain:',
choices: [
{ name: 'Serve Static Content', value: 'static' },
{ name: 'Forward Port', value: 'server' },
{ name: 'Back', value: 'back' }
]
}
]);
if (typeAnswer.serviceType === 'back') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
await updateDomainType(domain, typeAnswer.serviceType);
break;
case 'editTarget':
const targetAnswer = await inquirer.prompt([
{
type: 'input',
name: 'target',
message: 'Enter the new target for this domain (type /b to go back):',
validate: input => {
if (input === '/b') return true;
return input ? true : 'Target is required.';
}
}
]);
if (targetAnswer.target === '/b') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
await updateDomainTarget(domain, targetAnswer.target);
break;
case 'back':
return;
}
return;
};
/**
* Edit or delete a subdomain for a given domain.
* @param {string} domain - The parent domain.
* @returns {Promise<void>}
*/
const editOrDeleteSubdomain = async (domain) => {
console.clear();
try {
// Listar subdominios asociados a este dominio
const db = new sqlite3.Database('/opt/.get/domains.db', sqlite3.OPEN_READONLY);
const subdomains = await new Promise((resolve) => {
db.all('SELECT domain FROM domains WHERE subdomain = ? ORDER BY domain', [domain], (err, rows) => {
db.close();
resolve(rows.map(r => r.domain));
});
});
if (subdomains.length === 0) {
console.log(chalk.red('No subdomains available to edit or delete.'));
return;
}
const { subDomain } = await inquirer.prompt([
{
type: 'list',
name: 'subDomain',
message: 'Select a subdomain to edit or delete:',
choices: [...subdomains, { name: 'Back', value: 'back' }]
}
]);
if (subDomain === 'back') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
const { action } = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: `What do you want to do with subdomain ${subDomain}?`,
choices: [
{ name: 'Edit Subdomain', value: 'edit' },
{ name: 'Delete Subdomain', value: 'delete' },
{ name: 'Back', value: 'back' }
]
}
]);
if (action === 'back') {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
if (action === 'edit') {
await editDomainDetails(subDomain);
console.log(chalk.green(`Subdomain ${subDomain} edited successfully.`));
} else if (action === 'delete') {
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `Are you sure you want to delete the subdomain ${subDomain}?`,
default: false
}
]);
if (!confirm) {
console.log(chalk.blue('Subdomain deletion was cancelled.'));
return;
}
const dbDel = new sqlite3.Database('/opt/.get/domains.db');
dbDel.run('DELETE FROM domains WHERE domain = ? AND subdomain = ?', [subDomain, domain], (err) => {
if (err) {
console.log(chalk.red('Error deleting subdomain:'), err.message);
} else {
console.log(chalk.green(`Subdomain ${subDomain} deleted successfully.`));
}
dbDel.close();
});
}
} catch (error) {
console.error(chalk.red('An error occurred in the Edit/Delete Subdomain Menu:', error.message));
}
};
/**
* Edits or deletes a domain from the database.
* @memberof module:NetGetX.Domains
* @param {string} domain - The domain to edit or delete.
* @returns {Promise<void>}
*/
const editOrDeleteDomain = async (domain) => {
console.clear();
try {
// Leer la configuración del dominio desde la base de datos
const db = new sqlite3.Database('/opt/.get/domains.db', sqlite3.OPEN_READONLY);
const domainConfig = await new Promise((resolve, reject) => {
db.get('SELECT * FROM domains WHERE domain = ?', [domain], (err, row) => {
db.close();
if (err) return reject(err);
resolve(row);
});
});
if (!domainConfig) {
console.log(chalk.red(`Domain ${domain} configuration not found in database.`));
return;
}
const options = [
{ name: 'Edit Domain', value: 'editDomain' },
{ name: 'Delete Domain', value: 'deleteDomain' },
{ name: 'Back', value: 'back' }
];
const answer = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'Select an option:',
choices: options
}
]);
switch (answer.action) {
case 'editDomain':
console.clear();
await editDomainDetails(domain);
console.log(chalk.green(`Domain ${domain} edited successfully.`));
return;
case 'deleteDomain':
const confirmDelete = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `Are you sure you want to delete the domain ${domain}? (This will also delete all associated subdomains)`,
default: false
}
]);
if (!confirmDelete.confirm) {
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
// Elimina el dominio y sus subdominios asociados
const dbDel = new sqlite3.Database('/opt/.get/domains.db');
dbDel.run('DELETE FROM domains WHERE domain = ? OR subdomain = ?', [domain, domain], (err) => {
if (err) {
console.log(chalk.red('Error deleting domain:'), err.message);
} else {
console.log(chalk.green(`Domain ${domain} and its subdomains deleted successfully.`));
}
dbDel.close();
});
await domainsMenu();
return;
case 'back':
return;
}
// After an action, redisplay the menu
await editOrDeleteDomain(domain);
} catch (error) {
console.error(chalk.red('An error occurred in the Edit/Delete Domain Menu:', error.message));
}
};
/**
* Displays the advance settings for the domain.
* @memberof module:NetGetX.Domains
* @returns {Promise<void>}
*/
async function advanceSettings() {
try {
const answers = await inquirer.prompt({
type: 'list',
name: 'action',
message: 'Select an option:',
choices: [
{ name: 'Scan All SSL Certificates Issued', value: 'scan' },
{ name: 'View Certbot Logs', value: 'logs' },
{ name: 'Back', value: 'back' }
]
});
switch (answers.action) {
case 'scan':
await scanAndLogCertificates();
case 'logs':
console.log(chalk.yellow('Certbot logs soon to be implemented.'));
case 'back':
console.log(chalk.blue('Going back to the previous menu...'));
return;
}
await advanceSettings();
}
catch (error) {
console.error(chalk.red('An error occurred in the Advance Domain Menu:', error.message));
}
}
const linkDevelopmentAppProject = async (domain) => {
const { projectPath } = await inquirer.prompt([
{
type: 'input',
name: 'projectPath',
message: 'Enter the path where the project is being developed:',
}
]);
const xConfig = await loadOrCreateXConfig();
xConfig.domains[domain].projectPath = projectPath;
await saveXConfig(xConfig);
await updateDomain(
domain,
xConfig.domains[domain].email,
'letsencrypt',
xConfig.domains[domain].SSLCertificateSqlitePath,
xConfig.domains[domain].SSLCertificateKeySqlitePath,
xConfig.domains[domain].target,
xConfig.domains[domain].type,
projectPath
);
console.log(chalk.green(`Linked development app project at ${projectPath} with domain ${domain}.`));
};
export {
validateDomain,
addNewDomain,
addSubdomain,
editOrDeleteDomain,
editOrDeleteSubdomain,
logDomainInfo,
linkDevelopmentAppProject,
domainsTable,
advanceSettings
};