Skip to content

Instantly share code, notes, and snippets.

@NDiiong
Last active March 18, 2024 13:01
Show Gist options
  • Save NDiiong/b113dbbc474e0c758df19d6394953a74 to your computer and use it in GitHub Desktop.
Save NDiiong/b113dbbc474e0c758df19d6394953a74 to your computer and use it in GitHub Desktop.
// Cronicle Auto Installer
// Copyright (c) 2015 - 2023 Joseph Huckaby, MIT License.
// https://github.com/jhuckaby/Cronicle
// To install, issue this command as root:
// curl -s "https://raw.githubusercontent.com/jhuckaby/Cronicle/master/bin/install.js" | node
var path = require('path');
var fs = require('fs');
var util = require('util');
var os = require('os');
var cp = require('child_process');
var installer_version = '1.5';
var base_dir = '/opt/cronicle';
var log_dir = base_dir + '/logs';
var log_file = '';
var gh_repo_url = 'http://github.com/jhuckaby/Cronicle';
var gh_releases_url = 'https://api.github.com/repos/jhuckaby/Cronicle/releases';
var gh_head_tarball_url = 'https://github.com/jhuckaby/Cronicle/archive/master.tar.gz';
// don't allow npm to delete these (ugh)
var packages_to_check = ['couchbase', 'redis', 'ioredis', 'ioredis-timeout', 'sqlite3'];
var packages_to_rescue = {};
// Error out if Node.js version is old
if (process.version.match(/^v?(\d+)/) && (parseInt(RegExp.$1) < 16) && !process.env['CRONICLE_OLD']) {
console.error("\nERROR: You are using an incompatible version of Node.js (" + process.version + "). Please upgrade to v16 or later. Instructions: https://nodejs.org/en/download/package-manager\n\nTo ignore this error and run unsafely, set a CRONICLE_OLD environment variable. Do this at your own risk.\n");
process.exit(1);
}
var restore_packages = function() {
// restore packages that npm killed during upgrade
var cmd = "npm install";
var num_found = 0;
for (var pkg in packages_to_rescue) {
cmd += ' ' + pkg + '@' + packages_to_rescue[pkg];
num_found++;
}
if (!num_found) return; // nothing to restore
if (log_file) {
fs.appendFileSync(log_file, "\nExecuting npm command to restore lost packages: " + cmd + "\n");
cmd += ' >>' + log_file + ' 2>&1';
}
cp.execSync(cmd);
};
var print = function(msg) {
process.stdout.write(msg);
if (log_file) fs.appendFileSync(log_file, msg);
};
var warn = function(msg) {
process.stderr.write(msg);
if (log_file) fs.appendFileSync(log_file, msg);
};
var die = function(msg) {
warn( "\nERROR: " + msg.trim() + "\n\n" );
process.exit(1);
};
var logonly = function(msg) {
if (log_file) fs.appendFileSync(log_file, msg);
};
// create base and log directories
try { cp.execSync( "mkdir -p " + base_dir + " && chmod 775 " + base_dir ); }
catch (err) { die("Failed to create base directory: " + base_dir + ": " + err); }
try { cp.execSync( "mkdir -p " + log_dir + " && chmod 777 " + log_dir ); }
catch (err) { die("Failed to create log directory: " + log_dir + ": " + err); }
// start logging from this point onward
log_file = log_dir + '/install.log';
logonly( "\nStarting install run: " + (new Date()).toString() + "\n" );
print(
"\nCronicle Installer v" + installer_version + "\n" +
"Copyright (c) 2015 - 2022 PixlCore.com. MIT Licensed.\n" +
"Log File: " + log_file + "\n\n"
);
process.chdir( base_dir );
var is_preinstalled = false;
var cur_version = '';
var new_version = process.argv[2] || '';
try {
var stats = fs.statSync( base_dir + '/package.json' );
var json = require( base_dir + '/package.json' );
if (json && json.version) {
cur_version = json.version;
is_preinstalled = true;
}
}
catch (err) {;}
var is_running = false;
if (is_preinstalled) {
var pid_file = log_dir + '/cronicled.pid';
try {
var pid = fs.readFileSync(pid_file, { encoding: 'utf8' });
is_running = process.kill( pid, 0 );
}
catch (err) {;}
}
print( "Fetching release list...\n");
logonly( "Releases URL: " + gh_releases_url + "\n" );
cp.exec('curl -s ' + gh_releases_url, function (err, stdout, stderr) {
if (err) {
print( stdout.toString() );
warn( stderr.toString() );
die("Failed to fetch release list: " + gh_releases_url + ": " + err);
}
var releases = null;
try { releases = JSON.parse( stdout.toString() ); }
catch (err) {
die("Failed to parse JSON from GitHub: " + gh_releases_url + ": " + err);
}
// util.isArray is DEPRECATED??? Nooooooooode!
var isArray = Array.isArray || util.isArray;
if (!isArray(releases)) die("Unexpected response from GitHub Releases API: " + gh_releases_url + ": Not an array");
var release = null;
for (var idx = 0, len = releases.length; idx < len; idx++) {
var rel = releases[idx];
var ver = rel.tag_name.replace(/^\D+/, '');
rel.version = ver;
if (!new_version || (ver == new_version)) {
release = rel;
new_version = ver;
idx = len;
}
} // foreach release
if (!release) {
// no release found -- use HEAD rev?
if (!new_version || new_version.match(/HEAD/i)) {
release = {
version: 'HEAD',
tarball_url: gh_head_tarball_url
};
}
else {
die("Release not found: " + new_version);
}
}
// sanity check
if (is_preinstalled && (cur_version == new_version)) {
if (process.argv[2]) print( "\nVersion " + cur_version + " is already installed.\n\n" );
else print( "\nVersion " + cur_version + " is already installed, and is the latest.\n\n" );
process.exit(0);
}
// proceed with installation
if (is_preinstalled) print("Upgrading Cronicle from v"+cur_version+" to v"+new_version+"...\n");
else print("Installing Cronicle v"+new_version+"...\n");
if (is_running) {
print("\n");
try { cp.execSync( base_dir + "/bin/control.sh stop", { stdio: 'inherit' } ); }
catch (err) { die("Failed to stop Cronicle: " + err); }
print("\n");
}
// download tarball and expand into current directory
var tarball_url = release.tarball_url;
logonly( "Tarball URL: " + tarball_url + "\n" );
cp.exec('curl -L ' + tarball_url + ' | tar zxf - --strip-components 1', function (err, stdout, stderr) {
if (err) {
print( stdout.toString() );
warn( stderr.toString() );
die("Failed to download release: " + tarball_url + ": " + err);
}
else {
logonly( stdout.toString() + stderr.toString() );
}
try {
var stats = fs.statSync( base_dir + '/package.json' );
var json = require( base_dir + '/package.json' );
}
catch (err) {
die("Failed to download package: " + tarball_url + ": " + err);
}
print( is_preinstalled ? "Updating dependencies...\n" : "Installing dependencies...\n");
var npm_cmd = is_preinstalled ? "npm update --unsafe-perm" : "npm install --unsafe-perm";
logonly( "Executing command: " + npm_cmd + "\n" );
// temporarily stash add-on modules that were installed separately (thanks npm)
if (is_preinstalled) packages_to_check.forEach( function(pkg) {
if (fs.existsSync('node_modules/' + pkg)) {
packages_to_rescue[pkg] = JSON.parse( fs.readFileSync('node_modules/' + pkg + '/package.json', 'utf8') ).version;
}
});
// install dependencies via npm
cp.exec(npm_cmd, function (err, stdout, stderr) {
if (err) {
print( stdout.toString() );
warn( stderr.toString() );
if (is_preinstalled) restore_packages();
die("Failed to install dependencies: " + err);
}
else {
logonly( stdout.toString() + stderr.toString() );
}
print("Running post-install script...\n");
logonly( "Executing command: node bin/build.js dist\n" );
// finally, run postinstall script
cp.exec('node bin/build.js dist', function (err, stdout, stderr) {
if (is_preinstalled) {
// for upgrades only print output on error
if (err) {
print( stdout.toString() );
warn( stderr.toString() );
if (is_preinstalled) restore_packages();
die("Failed to run post-install: " + err);
}
else {
if (is_preinstalled) restore_packages();
print("Upgrade complete.\n\n");
if (is_running) {
try { cp.execSync( base_dir + "/bin/control.sh start", { stdio: 'inherit' } ); }
catch (err) { die("Failed to start Cronicle: " + err); }
print("\n");
}
}
} // upgrade
else {
// first time install, always print output
print( stdout.toString() );
warn( stderr.toString() );
if (err) {
die("Failed to run post-install: " + err);
}
else {
print("Installation complete.\n\n");
}
} // first install
logonly( "Completed install run: " + (new Date()).toString() + "\n" );
process.exit(0);
} ); // build.js
} ); // npm
} ); // download
} ); // releases api
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment