UNPKG

@0x4447/hotpepper

Version:

🌶 A SystemD config generators for NodeJS projects

625 lines (500 loc) • 12.1 kB
#!/usr/bin/env node let fs = require('fs'); let os = require('os'); let npm = require('./package.json'); let exec = require('child_process').exec; let program = require('commander'); // _____ ______ _______ _______ _____ _ _ _____ _____ // / ____| | ____| |__ __| |__ __| |_ _| | \ | | / ____| / ____| // | (___ | |__ | | | | | | | \| | | | __ | (___ // \___ \ | __| | | | | | | | . ` | | | |_ | \___ \ // ____) | | |____ | | | | _| |_ | |\ | | |__| | ____) | // |_____/ |______| |_| |_| |_____| |_| \_| \_____| |_____/ // // // The CLI options for this app. // program .version(npm.version) .option('-s, --source [type]', 'path to the source folder'); // // React when the user needs help // program.on('--help', function() { // // Just add an empty line at the end of the help to make the text more // clear to the user // console.log(""); }); // // Pass the user input to the commander module // program.parse(process.argv); // __ __ _____ _ _ // | \/ | /\ |_ _| | \ | | // | \ / | / \ | | | \| | // | |\/| | / /\ \ | | | . ` | // | | | | / ____ \ _| |_ | |\ | // |_| |_| /_/ \_\ |_____| |_| \_| // // // Set the work location of the CLI. // let location = process.cwd() + "/" + program.source; // // 1. Create a container that will hold all the data for the chained // promises. // let container = { location: location }; // // 2. Since we need root privileges, we need to check for that // check_if_we_are_root(container) .then(function(container){ return check_if_dot_env_is_present(container); }).then(function(container){ return check_if_systemd_is_present(container); }).then(function(container){ return check_if_package_is_present(container); }).then(function(container){ return read_necessary_data(container); }).then(function(container){ return check_if_something_is_missing(container); }).then(function(container){ return get_os_settings(container); }).then(function(container){ return create_the_service_file(container); }).then(function(container){ return save_the_file(container); }).then(function(container){ return reaload_systemd_daemon(container); }).then(function(container){ return start_the_new_service(container); }).then(function(container){ return enable_autostart(container); }).then(function(container){ // // 1. Let the user know all went well // console.log("\n"); console.log("\tDone!"); console.log("\n"); // // -> Exit the app // process.exit(0); }).catch(function(error){ // // 1. Show the error message // console.log("\n"); console.log(error.message); console.log("\n"); // // -> Exit the app // process.exit(0); }); // _____ _____ ____ __ __ _____ _____ ______ _____ // | __ \ | __ \ / __ \ | \/ | |_ _| / ____| | ____| / ____| // | |__) | | |__) | | | | | | \ / | | | | (___ | |__ | (___ // | ___/ | _ / | | | | | |\/| | | | \___ \ | __| \___ \ // | | | | \ \ | |__| | | | | | _| |_ ____) | | |____ ____) | // |_| |_| \_\ \____/ |_| |_| |_____| |_____/ |______| |_____/ // // // Before we do anything we need to make sure this app is running as root. // // We need root to be able to: // // - save the file in to the systemD directory // - Restart systemD // - Start the new service // - Mark it as bootable // function check_if_we_are_root(container) { return new Promise(function(resolve, reject) { // // 1. Check if the SystemD directory exists // let username = os.userInfo().username; // // 2. Warn the user that the directory is not present // if(username != "root") { return reject(new Error("Run the command as root")); } // // -> Move to the next chain // return resolve(container); }); } // // Make sure we have the .env file in the project. You can use Cucumber to // create one for you automatically. Follow this URL: // // https://github.com/0x4447/0x4447-cli-node-cucumber // function check_if_dot_env_is_present(container) { return new Promise(function(resolve, reject) { // // 1. Check if the SystemD directory exists // let is_present = fs.existsSync(container.location + '/.env'); // // 2. Warn the user that the directory is not present // if(!is_present) { return reject(new Error(".env file is not present.")); } // // -> Move to the next chain // return resolve(container); }); } // // Make sure the systemD folder exists and is preset so we can save our // config file. // function check_if_systemd_is_present(container) { return new Promise(function(resolve, reject) { // // 1. Check if the SystemD directory exists // let is_present = fs.existsSync('/etc/systemd/system'); // // 2. Warn the user that the directory is not present // if(!is_present) { return reject(new Error("SystemD directory not found")); } // // -> Move to the next chain // return resolve(container); }); } // // Make sure package.json file exists // function check_if_package_is_present(container) { return new Promise(function(resolve, reject) { // // 1. Check if the package.json file exists // let is_present = fs.existsSync(container.location + '/package.json'); // // 2. Warn the user that the package.json is missing // if(!is_present) { return reject(new Error("package.json is missing")); } // // -> Move to the next chain // return resolve(container); }); } // // Get all the necessary data from the project folder so we can create // the correct config file. // function read_necessary_data(container) { return new Promise(function(resolve, reject) { // // Open the app.json file. // fs.readFile(container.location + '/package.json', 'utf8', function(error, data) { // // 1. Display Error if any // if(error) { return reject(new Error(error.message)); } // // 2. Convert the content of the file in to a JS Object // let parsed = JSON.parse(data); // // 3. Prepare the variables that will hold the data // let description = ""; let documentation = ""; // // 4. Get the name if it is possible // if(parsed.name) { description = parsed.name; } // // 5. Get the repo URL if possible // if(parsed.repository) { // // 1. Make sure the URL repo is present also // if(parsed.repository.url) { documentation = parsed.repository.url; } } // // 6. Add the data to the container // container.service_data = { name: description, description, documentation } // // -> Move to the next chain // return resolve(container); }); }); } // // Generate the right error if something is missing // function check_if_something_is_missing(container) { return new Promise(function(resolve, reject) { // // 1. Crate the array that will hold all the errors // let errors = []; if(!container.service_data.name) { errors.push(new Error("Name is missing")); } if(!container.service_data.description) { errors.push(new Error("Description is missing")); } if(!container.service_data.documentation) { errors.push(new Error("Repo URL is missing")); } // // 2. Check if we got some errors // if(errors.length > 0) { // // 1. Combine all the errors in to one string // let error_message = errors.join("\n"); // // -> Stop everything and let the user know what is missing // return reject(new Error(error_message)); } // // -> Move to the next chain // return resolve(container); }); } // // Get OS specific settings // function get_os_settings(container) { return new Promise(function(resolve, reject) { // // 1. Get the path to the project // container.service_data.cwd = process.cwd(); // // 2. Get the user name of the loged in user // container.service_data.user = os.userInfo().username; // // -> Move to the next chain // return resolve(container); }); } // // After we have all the data we can create the config file for the service. // function create_the_service_file(container) { return new Promise(function(resolve, reject) { // // 1. An array where I'm going to store the whole file before saving it // let file = []; // // 2. Add data to the array, which in the end will be used to create // the .service file // file.push("[Unit]"); file.push("Description=" + container.service_data.description); file.push("Documentation=" + container.service_data.documentation); file.push("After=network.target"); file.push(""); file.push("[Service]"); file.push("EnvironmentFile=" + container.service_data.cwd + "/.env"); file.push("Type=simple"); file.push("User=" + container.service_data.user); file.push("Group=" + container.service_data.user); file.push("WorkingDirectory=" + container.service_data.cwd); file.push("ExecStart=/usr/bin/node workers/server"); file.push("StandardOutput=journal"); file.push("StandardError=journal"); file.push("SyslogIdentifier=" + container.service_data.name); file.push("Restart=on-failure"); file.push("RestartSec=3"); file.push("KillMode=process"); file.push("ExecReload=/bin/kill -HUP $MAINPID"); file.push(""); file.push("[Install]"); file.push("WantedBy=multi-user.target"); // // Join each element of the array in to one big file where each element // is in its own line // let service_file = file.join("\n"); // // 4. Save the file in to memory // container.service_file = service_file; // // -> Move to the next chain // return resolve(container); }); } // // Save the config file in the right place // function save_the_file(container) { return new Promise(function(resolve, reject) { // // 1. Create the path for the config file // let file = "/etc/systemd/system/" + container.service_data.name + ".service"; // // 2. Save the service file // fs.writeFile(file, container.service_file, function(error) { // // 1. Display Error if any // if(error) { return reject(new Error(error.message)); } // // -> Move to the next chain // return resolve(container); }); }); } // // Tell SystemD to reload and get any new service file // function reaload_systemd_daemon(container) { return new Promise(function(resolve, reject) { // // 1. Create the command to tell SystemD to reload the config files. // let cmd = 'systemctl daemon-reload'; // // 2. Execute the command // exec(cmd, function(error, stdout, stderr) { // // 1. Make sure we show any error // if(error) { return reject(new Error(error)) } // // -> Move to the next chain // return resolve(container); }); }); } // // Start the newly created site // function start_the_new_service(container) { return new Promise(function(resolve, reject) { // // 1. Create the command that will start the new services that // we made. // let cmd = 'systemctl start ' + container.service_data.name; // // 2. Execute the command. // exec(cmd, function(error, stdout, stderr) { // // 1. Make sure we show any error // if(error) { return reject(new Error(error)) } // // -> Move to the next chain // return resolve(container); }); }); } // // Tell SystemD to start our server every time the system boots // function enable_autostart(container) { return new Promise(function(resolve, reject) { // // 1. Create the command that will enable autostart. // let cmd = 'systemctl enable ' + container.service_data.name; // // 2. Execute the command. // exec(cmd, function(error, stdout, stderr) { // // 1. Make sure we show any error // if(error) { return reject(new Error(error)) } // // -> Move to the next chain // return resolve(container); }); }); }