fast-nginx
Version:
A fast and easy-to-use CLI tool to configure your domain with Nginx using just a single command.
339 lines (307 loc) โข 11.4 kB
JavaScript
const { Command } = require("commander");
const fs = require("fs");
const { execSync, spawn } = require("child_process");
const chalk = require("chalk");
const program = new Command();
const packageJson = require("../package.json");
const {
sudoWriteFsFile,
sudoLinkFsFile,
sudoUnlinkFsFile,
} = require("../src/utils/file.helper");
const {
generateBasicNginxConfigTemplate,
} = require("../src/templates/basic.template");
const checkSystemRequirements = require("../src/validators/system.validator");
const validateDomain = require("../src/validators/domain.validator");
const validatePort = require("../src/validators/port.validator");
const validateEmail = require("../src/validators/email.validator");
const { askUser } = require("../src/utils/ask.helper");
// CLI Configuration
program
.name("fast-nginx")
.description("๐ Automate Nginx server block setup with SSL support")
.version(packageJson.version)
.requiredOption("-d, --domain <domain>", "Domain name for the server block")
.option("-p, --port <port>", "Port number for the upstream server", "3000")
.option("--ssl", "Setup SSL certificate with Let's Encrypt")
.option("--email <email>", "Email for SSL certificate (required with --ssl)")
.option("--www <www>", "Include www subdomain in SSL certificate")
.option("--force", "Overwrite existing configuration")
.option("--dry-run", "Show what would be done without executing")
.option(
"--template <template>",
"Use custom template (basic|api|spa)",
"basic",
)
.option("--no-reload", "Skip Nginx reload")
.option("--yes", "Auto-answer yes to all prompts")
.parse();
const options = program.opts();
function getTemplate(templateType, domain, port) {
const templates = {
basic: generateBasicNginxConfigTemplate(options, domain, port),
};
return templates[templateType] || templates.basic;
}
async function setupSSL(domain, email) {
console.log(
chalk.yellow("๐ Setting up SSL certificate with Let's Encrypt..."),
);
try {
try {
execSync("which certbot", { stdio: "pipe" });
console.log(chalk.green("โ
Certbot found"));
} catch (error) {
console.log(chalk.yellow("๐ฆ Installing Certbot..."));
try {
if (fs.existsSync("/etc/debian_version")) {
execSync(
"sudo apt update && sudo apt install certbot python3-certbot" +
"t-nginx -y",
{ stdio: "inherit" },
);
} else if (fs.existsSync("/etc/redhat-release")) {
execSync("sudo yum install certbot python3-certbot-nginx -y", {
stdio: "inherit",
});
} else {
throw new Error("Unsupported OS for automatic certbot installation");
}
} catch (installError) {
console.error(chalk.red("โ Failed to install certbot automatically"));
console.log(chalk.yellow("๐ก Please install certbot manually:"));
console.log(
chalk.gray(
" Ubuntu/Debian: sudo apt install certbot python3-certbot-nginx",
),
);
console.log(
chalk.gray(
" CentOS/RHEL: sudo yum install certbot python3-certbot-nginx",
),
);
return false;
}
}
// Build certbot command
const domains = options.www
? `-d ${domain} -d www.${domain}`
: `-d ${domain}`;
execSync("sudo systemctl stop nginx", { stdio: "inherit" });
const certbotCmd = `sudo certbot --nginx ${domains} --email ${email} --agree-tos --non-interactive --redirect`;
console.log(chalk.gray(`Running: ${certbotCmd}`));
execSync(certbotCmd, { stdio: "inherit" });
console.log(chalk.green("โ
SSL certificate installed successfully!"));
// Test SSL configuration
console.log(chalk.yellow("๐งช Testing SSL configuration..."));
console.log(chalk.green("โ
SSL configuration test passed"));
return true;
} catch (error) {
console.error(chalk.red("โ SSL setup failed:"), error.message);
console.log(chalk.yellow("๐ก You can set up SSL manually later with:"));
console.log(chalk.gray(` sudo certbot --nginx -d ${domain}`));
return false;
}
}
async function setupNginxServerBlock() {
const { domain, port, dryRun, force, template } = options;
console.log(chalk.blue.bold("๐ fast-nginx v" + packageJson.version));
console.log(chalk.gray("Nginx Server Block Automation Tool"));
console.log(chalk.gray("=====================================\n"));
console.log(chalk.yellow("๐ Checking system requirements..."));
const systemChecks = await checkSystemRequirements(options);
execSync("sudo systemctl stop nginx", { stdio: "inherit" });
let color;
let icon;
for (const check of systemChecks) {
switch (check.status) {
case "ok":
color = "green";
icon = "โ
";
break;
case "warning":
color = "yellow";
icon = "โ ๏ธ";
break;
case "error":
color = "red";
icon = "โ";
process.exit(1);
break;
default:
color = "red";
icon = "โ";
}
console.log(chalk[color](`${icon} ${check.name}: ${check.message}`));
}
console.log(chalk.yellow("๐ Validating inputs..."));
if (!validateDomain(domain)) {
console.error(chalk.red("โ Invalid domain format:", domain));
console.log(chalk.gray(" Example: myapp.uz , api.myapp.uz"));
process.exit(1);
}
if (!validatePort(port)) {
console.error(chalk.red("โ Invalid port number:", port));
console.log(chalk.gray(" Port must be between 1 and 65535"));
process.exit(1);
}
if (options.ssl && !options.email) {
console.error(chalk.red("โ Email is required when using --ssl option"));
console.log(
chalk.gray(
"Use: fast-nginx --domain domain.com --port [your app port] --ssl --email your@email.com --yes",
),
);
process.exit(1);
}
if (options.ssl && options.email && !validateEmail(options.email)) {
console.error(chalk.red("โ Invalid email format:", options.email));
process.exit(1);
}
console.log(chalk.green("โ
Domain:", domain));
console.log(chalk.green("โ
Port:", port));
console.log(chalk.green("โ
Template:", template));
if (options.ssl) {
console.log(chalk.green("โ
SSL setup requested"));
console.log(chalk.green("โ
Email:", options.email));
if (options.www) {
console.log(chalk.green("โ
Including www subdomain"));
}
}
const sitesAvailable = `/etc/nginx/sites-available/${domain}`;
const sitesEnabled = `/etc/nginx/sites-enabled/${domain}`;
console.log(chalk.yellow("\n๐ Generating Nginx configuration..."));
const nginxConfig = getTemplate(template, domain, port);
try {
if (fs.existsSync(sitesAvailable) && !force) {
const userResponse = await askUser(
options,
` โ ๏ธ Configuration for ${domain} already exists. Overwrite?`,
);
if (!userResponse) {
console.log(chalk.yellow(`โ ๏ธ Operation cancelled by user`));
console.log(
chalk.gray(` Existing configuration at: ${sitesAvailable}`),
);
console.log(
chalk.gray(` Use --force flag to override without prompting`),
);
process.exit(0);
}
await sudoUnlinkFsFile(sitesAvailable, (err) => {
if (err) {
console.log("โ Failed to unlink:", sitesAvailable);
}
});
}
console.log(chalk.yellow("๐ Writing configuration file..."));
await sudoWriteFsFile(sitesAvailable, nginxConfig, (err) => {
if (err) {
console.error(chalk.red("โ Failed to write configuration file:"), err);
process.exit(1);
}
});
console.log(chalk.green("โ
Configuration written to:", sitesAvailable));
console.log(chalk.yellow("๐ Creating symbolic link..."));
if (fs.existsSync(sitesEnabled) && !force) {
const userResponse = await askUser(
options,
` โ ๏ธ Nginx enabled file already exists. Overwrite?`,
);
if (userResponse) {
await sudoUnlinkFsFile(sitesEnabled, (err) => {
if (err) {
console.log("โ Failed to unlink:", sitesEnabled);
}
});
}
}
await sudoUnlinkFsFile(sitesEnabled, (err) => {
if (err) {
console.log("โ Failed to unlink:", sitesEnabled);
}
});
await sudoLinkFsFile(sitesAvailable, sitesEnabled, (err) => {
if (err) {
console.error(chalk.red("โ Failed to link:"), err);
process.exit(1);
}
});
console.log(chalk.green("โ
Symbolic link created:", sitesEnabled));
// Test Nginx configuration
console.log(chalk.yellow("๐งช Testing Nginx configuration..."));
let sslSuccess = false;
if (options?.ssl) {
sslSuccess = await setupSSL(domain, options.email);
}
console.log(
chalk.green.bold("\n๐ Server block setup completed successfully!"),
);
console.log(chalk.gray("====================================="));
console.log(chalk.white(`๐ Configuration Summary:`));
console.log(chalk.white(` Domain: ${domain}`));
console.log(chalk.white(` Template: ${template}`));
console.log(chalk.white(` Upstream: http://localhost:${port}`));
console.log(chalk.white(` Config: ${sitesAvailable}`));
if (sslSuccess) {
execSync("sudo systemctl start nginx", { stdio: "inherit" });
console.log(chalk.green(` SSL: โ
HTTPS enabled`));
console.log(chalk.white(` URL: https://${domain}`));
} else if (options?.ssl) {
console.log(
chalk.yellow(` SSL: โ ๏ธ Setup attempted but may have failed`),
);
}
console.log(chalk.gray("\n๐ก Next steps:"));
console.log(chalk.gray(` 1. Ensure your app is running on port ${port}`));
if (!sslSuccess && !options.ssl) {
console.log(chalk.gray(`2. Point your domain DNS to this server`));
console.log(
chalk.gray(
` 3. Set up SSL: fast-nginx -d ${domain} --ssl --email your@email.com`,
),
);
} else if (sslSuccess) {
console.log(chalk.gray(`2. Your site is ready at https://${domain}`));
console.log(
chalk.gray(
` 3. SSL will auto-renew (check: sudo certbot renew --dry-run)`,
),
);
}
console.log(
chalk.gray(
` 4. Monitor logs: sudo tail -f /var/log/nginx/${domain}_*.log`,
),
);
} catch (error) {
console.error(chalk.red("โ Error during setup:"), error.message);
if (error.code === "EACCES") {
console.log(
chalk.yellow(
"๐ก Try running with sudo: sudo fast-nginx -d your-domain.com",
),
);
}
process.exit(1);
}
}
process.on("uncaughtException", (error) => {
console.error(chalk.red("โ Unexpected error:"), error.message);
console.log(
chalk.gray(
"Please report this issue at: https://github.com/farruhzoirov/fast-nginx/issues",
),
);
process.exit(1);
});
process.on("unhandledRejection", (reason, promise) => {
console.error(chalk.red("โ Unhandled promise rejection:"), reason);
process.exit(1);
});
setupNginxServerBlock().catch((error) => {
console.error(chalk.red("โ Setup failed:"), error.message);
process.exit(1);
});