UNPKG

@cocreate/nginx

Version:

Automates NGINX server management tasks including installation, configuration, startup, and dynamic reloading in response to application changes. Designed to streamline web server setup and management.

297 lines (244 loc) 10.3 kB
const util = require('node:util'); const exec = util.promisify(require('node:child_process').exec); let fs = require('fs'); const os = require('os'); const conf = "/etc/nginx/nginx.conf" const available = "/etc/nginx/sites-available/" const enabled = "/etc/nginx/sites-enabled/" class CoCreateNginx { constructor(cluster) { if (cluster.worker.id === 1) this.init() } async init() { try { const platform = os.platform(); if (platform === 'linux') { // For Debian/Ubuntu try { await exec('nginx -v'); } catch (error) { console.log('Nginx not found, installing...'); // Add Nginx repository // await exec('echo "deb http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list'); // await exec('echo "deb-src http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee -a /etc/apt/sources.list.d/nginx.list'); // await exec('curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add -'); // await exec('sudo apt-get update'); // await exec('sudo apt-get install -y nginx'); // await exec('sudo apt-get install -y ufw'); // await exec("sudo ufw allow 'Nginx Full'"); await exec('sudo apt-get update && sudo apt-get install -y nginx'); await exec('sudo apt-get install -y nginx-full'); await exec("sudo ufw allow 'Nginx Full'"); } // await exec('sudo systemctl start nginx') // await exec('sudo systemctl enable nginx'); // await exec('[ -d /etc/nginx/sites-available ] || sudo mkdir /etc/nginx/sites-available'); // await exec('[ -d /etc/nginx/sites-enabled ] || sudo mkdir /etc/nginx/sites-enabled'); let stream = `user www-data; worker_processes auto; pid /run/nginx.pid; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; # multi_accept on; } http { include /etc/nginx/mime.types; default_type application/octet-stream; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256'; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; ssl_stapling on; ssl_stapling_verify on; # resolver [YOUR_DNS_RESOLVER_IP] valid=300s; # Replace with your DNS resolver IP resolver_timeout 5s; add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; # add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'"; server_tokens off; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; gzip on; gzip_disable "msie6"; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } stream { map $ssl_preread_server_name $upstream { default nodejs_ssl; } server { listen 443; proxy_pass $upstream; ssl_preread on; } upstream nginx_ssl { server 127.0.0.1:12345; # Nginx handles SSL } upstream nodejs_ssl { server 127.0.0.1:8443; # Node.js SSL } } ` await exec(`sudo chmod 777 ${conf}`); fs.writeFileSync(conf, stream) await exec(`sudo chmod 777 ${available}`); await exec(`sudo chmod 777 ${enabled}`); let main = `server { listen 80; listen [::]:80; location /.well-known/acme-challenge/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; } location / { return 301 https://$host$request_uri; } } ` if (fs.existsSync(`${enabled}main.txt`)) fs.writeFileSync(`${available}main.txt`, main) else { fs.writeFileSync(`${available}main.txt`, main) await exec(`sudo ln -s ${available}main.txt ${enabled}`); } if (fs.existsSync(`${enabled}default`)) fs.unlinkSync(`${enabled}default`) if (fs.existsSync(`${available}default`)) fs.unlinkSync(`${available}default`) let test = await exec(`sudo nginx -t`); if (test.stderr.includes('test is successful')) { await exec(`sudo systemctl reload nginx`); console.log('Nginx is running!'); } else { console.log('Nginx config test failed') } } else if (platform === 'darwin') { // TODO: For macOS await exec('brew install nginx'); } else if (platform === 'win32') { // TODO: For Windows, assuming Chocolatey is installed await exec('choco install nginx'); } else { console.log('Unsupported OS'); } } catch (error) { console.error('Nginx failed to install: ', error); } } async createServer(hosts) { const response = {} if (!Array.isArray(hosts)) hosts = [hosts] for (let host of hosts) { const hostParts = host.split('.') const domain = hostParts[0]; const tld = hostParts[1]; const stream = fs.readFileSync(conf, 'utf8'); const modifiedStream = stream.replace('default nodejs_ssl;', `${host} nginx_ssl;\n\t\tdefault nodejs_ssl;`); fs.writeFileSync(conf, modifiedStream); const server = ` server { listen 12345 ssl http2; server_name ~^(?<sub>.+)\.${domain}\.${tld} ${host}; location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k; } ssl_certificate /etc/certificates/${host}/fullchain.pem; # Adjust to your certificate path ssl_certificate_key /etc/certificates/${host}/private-key.pem; # Adjust to your key path } `; fs.writeFileSync(`${available}${host}`, server) if (!fs.existsSync(`${enabled}${host}`)) await exec(`sudo ln -s ${available}${host} ${enabled}`); let test = await exec(`sudo nginx -t`); if (test.stderr.includes('test is successful')) { await exec(`sudo systemctl reload nginx`); console.log(`Nginx reloaded successfully for ${host}!`) response[host] = true } else { console.log(host, `Nginx config test failed for ${host}`) response[host] = false } } return response } async deleteServer(hosts) { const response = {} if (!Array.isArray(hosts)) hosts = [hosts] for (let host of hosts) { // TODO: delete from nginx.conf stream.map if (fs.existsSync(`${enabled}${host}`)) fs.unlinkSync(`${enabled}${host}`) if (fs.existsSync(`${available}${host}`)) fs.unlinkSync(`${available}${host}`) response[host] = true } return response } async hasServer(hosts) { if (!Array.isArray(hosts)) hosts = [hosts] for (let host of hosts) { const { stdout, stderr } = await exec(`grep -Ri 'server_name.*${host}' /etc/nginx/sites-enabled`) if (stderr) { console.error(`exec error: ${err}`); return; } if (stdout) { console.log(`Host found in the following configuration file(s):\n${stdout}`); } else { console.log('Host not found in Nginx configurations.'); } if (stderr) console.error(`stderr: ${stderr}`); } } updateSudoers() { const newRules = [ 'appuser ALL=(ALL) NOPASSWD: /bin/ln -s /etc/nginx/sites-available/* /etc/nginx/sites-enabled/*', 'appuser ALL=(ALL) NOPASSWD: /usr/sbin/nginx -t', 'appuser ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx', // Include any specific command or script you've prepared for safely writing to sites-available // 'appuser ALL=(ALL) NOPASSWD: /path/to/your/specific_command_or_script' ]; // Convert array of rules into a single string, each rule separated by a newline const rulesStr = newRules.join("\\n"); const cmd = `echo '${rulesStr}' | sudo EDITOR='tee -a' visudo`; exec(cmd, (error, stdout, stderr) => { if (error) { console.error(`Error: ${error}`); return; } if (stderr) { console.error(`Stderr: ${stderr}`); return; } console.log(`Sudoers updated: ${stdout}`); }); } } module.exports = CoCreateNginx