6

Hej There,

I'm trying to add some non-conventional functionality to my NodeJS application but I'm having some trouble. What I'm trying to do is the following:

I want to update my server code from the client. (An auto-update functionality if you will.)

My first attempt was to utilize the NPM API and run:

 npm.commands.install([package], function(err, data)

But of course this results in an error telling me NPM can not install while the server is running.

My second attempt was spawning NPM update using the following code:

  spawnProcess('npm', ['update'], { cwd: projectPath }, done);

The spawnProcess function is a generic spawn function:

var projectPath = path.resolve(process.cwd());
var spawnProcess = function(command, args, options, callback) {
    var spawn = require('child_process').spawn;
    var process = spawn(command, args, options);
    var err = false;

    process.stdout.on('data', function(data) {
        console.log('stdout', data.toString());
    });

    process.stderr.on('data', function(data) {
        err = true;
        console.log('stderr', data.toString());
    });

    if (typeof callback === 'function') {
        process.on('exit', function() {
            if (!err) {
                return callback();
            }
        });
    }
};

But this gives me a stderr followed by a 'CreateProcessW: can not find file' error. I don't quite know what I'm doing wrong.

If all else fails I thought it might be possible to write a shellscript killing Node, updating the application and then rebooting it. Something like:

kill -9 45728
npm update
node server

But I don't know if this is a plausible solution and how I would go about executing it from my node server. I'd rather have the spawn function working of course.

Any help is welcome. Thanks in advance!

10
  • 1
    Try data.toString(). Commented Dec 16, 2013 at 17:50
  • 1
    If you succeed with your codeat updating the modules, the old versions will still be loaded in memory: the only way to reload dependencies is to restart your application. Commented Dec 16, 2013 at 17:52
  • Great! how stupid of me I did not think of that! It gives me an CeatProcessW error. So I'm still not quite sure what the problem is. But Thanks though! Commented Dec 16, 2013 at 17:52
  • @Paul Mougel: I know so I would reload the server in the callback of the spawn process (That functionality is already in place) :) Commented Dec 16, 2013 at 17:53
  • @Vpml, What's the command you use to start your server? Commented Dec 16, 2013 at 17:54

3 Answers 3

8

So I finally fixed this issue. If someone is interested how I did it, this is how:

I built a function using the NPM api to check if the current version is up to date:

 exports.checkVersion = function(req, res) {
    npm.load([], function (err, npm) {
        npm.commands.search(["mediacenterjs"], function(err, data){
            if (err){
                console.log('NPM search error ' + err);
                return;
            } else{
                var currentInfo = checkCurrentVersion();
                for (var key in data) {
                    var obj = data[key];
                    if(obj.name === 'mediacenterjs' && obj.version > currentInfo.version){
                        var message = 'New version '+obj.version+' Available';
                        res.json(message);
                    }
                }
            }
        });
    });
}

var checkCurrentVersion = function(){
    var info = {};
    var data = fs.readFileSync('./package.json' , 'utf8');

    try{
        info = JSON.parse(data);
    }catch(e){
        console.log('JSON Parse Error', e);
    }

    return info;
};

If the version is not up to date I initiate a download (in my case the github master repo url) using node-wget:

var wget = require('wget');
var download = wget.download(src, output, options);
download.on('error', function(err) {
    console.log('Error', err);
    callback(output);
});
download.on('end', function(output) {
    console.log(output);
    callback(output);
});
download.on('progress', function(progress) {
    console.log(progress * 100);
});

The callback kicks off the unzip function bases on @John Munsch 'Self help' script but I added a check to see if there was a previous unzip attempt and if so, I delete the folder:

if(fs.existsSync(dir) === false){
    fs.mkdirSync(dir);
} else {
    rimraf(dir, function (err) { 
        if(err) {
            console.log('Error removing temp folder', err);
        } else {
            fileHandler.downloadFile(src, output, options, function(output){
                console.log('Done', output);
                unzip(req, res, output, dir);
            });
        }
    });
}
console.log("Unzipping New Version...");
var AdmZip = require("adm-zip");
var zip = new AdmZip(output);
zip.extractAllTo(dir, true);

fs.openSync('./configuration/update.js', 'w');

The openSync function kicks off my 'NodeMon' based file (https://github.com/jansmolders86/mediacenterjs/blob/master/server.js) which kills the server because it is listing for changes to that specific file. Finally it restart and starts the following functions:

function installUpdate(output, dir){
    console.log('Installing update...');
    var fsExtra = require("fs.extra");
    fsExtra.copy(dir+'/mediacenterjs-master', './', function (err) {
        if (err) {
            console.error('Error', err);
        } else {
            console.log("success!");
            cleanUp(output, dir);
        }
    });
}

function cleanUp(output, dir) {
    console.log('Cleanup...');
    var rimraf = require('rimraf');
    rimraf(dir, function (e) {
        if(e) {
            console.log('Error removing module', e .red);
        }
    });

    if(fs.existsSync(output) === true){
        fs.unlinkSync(output);
        console.log('Done, restarting server...')
        server.start();
    }
}

Thanks to everyone that helped me out!

Sign up to request clarification or add additional context in comments.

2 Comments

Great solution. Were you able to run your 'NodeMon' based server.js with 'forever' ?
Hey @FranklinDattein, I wrote my own version if NodeMon which basically does the same thing as the aforementioned. It worked perfectly with the above solution. This is my keep alive server: github.com/jansmolders86/mediacenterjs/blob/master/server.js Hope that helps
3

Untested!

Have you tried the "prestart" npm script handle? Of course this would mean that you'd use npm to run your server: npm start

In your package.json (if you have) :

{ "scripts" :
  { 
    "start" : "node server.js",
    "prestart" : "scripts/customScript.js"
  }
}

NPM start command is implicitly defaulted to "start": "node server.js". If your server script is called server.js, there is no need to include this. You can do your npm install on the customScript.js or might be able to call npm install directly though I haven't tested this.

You can also assign/read your handler using the environment variable process.env.npm_package_scripts_prestart

10 Comments

Hej there, I've added the script and it works great! Thank you very much. But I do not yet understand how I can trigger a restart using the 'npm start' instead of my current ''nodemon server' solution. Do I need to kill the server programmatically and spawn the npm start? Thanks again.
@Vprnl There is also a restart script handle (prerestart and postrestart). I'd give those a go. You could certainly just run npm restart once you have that configured. You have additionally stop (prestop, poststop) if you need. There are many places you can execute custom code or commands.
Hej cbayram, thanks for being so patient with me but I have one more question if I may: I currenyly use: if(that.update === true){ npm.load([], function (err, npm) { npm.commands.stop(); npm.commands.restart(); }); } to stop and start the server. This works great but, I still get the error that it can not install as a dependency of itself. I do not get this error with prestart though.
@Vprnl where/what is your npm update code exactly? which dependency gives that eror? Is there code/github repo you can point me to?
Thanks for helping me man. There sure is. this file kills the server and restarts using npm: github.com/jansmolders86/mediacenterjs/blob/master/server.js This file actually updates the application: github.com/jansmolders86/mediacenterjs/blob/master/update.js Thanks again! About the error: I'm trying to auto-update the application so in this case mediacenterjs is trying to execute npm install on itself which apparently is not possible even though I kill the server and restart it with NPM.
|
1

It doesn't use NPM to accomplish it but here's a working experiment I put together a while back because I wanted to be able to build a self-updating app using Node.js for installation on people's machines (think apps like SABnzbd+, Couch Potato, or the Plex Media Server).

The Github for the example code is here: https://github.com/JohnMunsch/selfhelp

I got the example to the point where it would automatically notify you when a new version was available and then download, update, and restart itself. Even if you don't end up using it as-is, you should be able to get some ideas from it.

1 Comment

Thanks for sharing. This seems very much in line with what I had in mind. I definitely can take away some great ideas from it. Thanks!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.