Search code examples
node.jsbashshellexpresspm2

Running an update script from parent node process (PM2, NodeJS)


I have a script called update.sh that updates the server.

Problem is; when I stop the PM2 process it ends the bash process(?) there.

I can't figure out how to spawn a process from the parent that isn't linked to the parent. :-S

update.sh

#!/bin/bash

# Check we are not already updating.
if [ -f update.lock ]; then 
    echo "Already updating. Locked."
    exit
fi

# Make the log file.
rm update.log
touch update.log

# Create update lock.
touch update.lock

echo "Cleaning up" | tee update.log
rm -rf the-last-word
rm -rf Server

echo "Retrieving files from BitBucket" | tee update.log
git clone git@bitbucket.org:dotdot/dot-project.git

# Remove all directories except Server (So we don't have game source code on the Server)
echo "Removing game source code" | tee update.log
cd the-last-word
# Move the Server directory one up.
mv Server ../
cd ../
rm -rf the-last-word

# Stop the previous server.
# == PM2 stop some server name.
echo "Stopping server & removing" | tee update.log
pm2 delete "Some App" | tee update.log

# Update the current & run.
echo "Installing node modules" | tee update.log
cd Server
npm install | tee update.log

# Start the server again
# == PM2 start some server name.
echo "Starting node server" | tee update.log
NODE_ENV=production pm2 start bin/www.js --name "Some App" | tee update.log

# Remove lock file.
cd ..
rm update.lock

Now I run this script using node (express route).

admin.js

// Update server
app.use("/admin/update", authMiddleware, function(req, res, next) {
    console.log('Updating server from script.');

    const { spawn } = require('child_process');
    const deploySh = spawn('sh', [ 'update.sh' ], {
        cwd: '/www/thelastword/',
        env: Object.assign({}, process.env, { PATH: process.env.PATH + ':/usr/local/bin' })
    });

    res.status(200).json('Updating Server');
});

Solution

  • Update - nohup

    If admin.js is part of the server process, try nohup

    const deploySh = spawn('sh', [ 'nohup ./update.sh' ], {
        cwd: '/www/thelastword/',
        env: Object.assign({}, process.env, { PATH: process.env.PATH + ':/usr/local/bin' })
    });
    

    nohup allow child process to run even parent is killed.

    Quote from https://unix.stackexchange.com/questions/137759/why-use-nohup-rather-than-exec :

    nohup runs the specificed program with the SIGHUP signal ignored. When a terminal is closed, the kernel sends SIGHUP to the controlling process in that terminal (i.e. the shell). The shell in turn sends SIGHUP to all the jobs running in the background. Running a job with nohup prevents it from being killed in this way if the terminal dies (which happens e.g. if you were logged in remotely and the connection drops, or if you close your terminal emulator).

    Reference:

    Update - simple test

    I did a simple test and bash did not exit after pm2 delete.

    dummy-server.js

    var pid = require('process').pid;
    var server = require('net').createServer().listen();
    

    test.sh

    echo "start dummy."
    pm2 start ./dummy-server.js --name 'Dummy'
    
    echo "stop dummy."
    pm2 delete 'Dummy'
    
    echo "Sleep 5sec"
    sleep 5
    
    echo "Done."
    

    Output:

    start dummy.                                                                           
    [PM2] Starting ./dummy-server.js in fork_mode (1 instance)                             
    [PM2] Done.                                                                            
    ┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────────────┬──────────┐   
    │ App name │ id │ mode │ pid  │ status │ restart │ uptime │ memory      │ watching │   
    ├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────────────┼──────────┤   
    │ Dummy    │ 0  │ fork │ 9328 │ online │ 0       │ 0s     │ 15.219 MB   │ disabled │   
    └──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────────────┴──────────┘   
     Use `pm2 show <id|name>` to get more details about an app                             
    stop dummy.                                                                            
    [PM2] Applying action deleteProcessId on app [Dummy](ids: 0)                           
    [PM2] [Dummy](0) ✓                                                                     
    ┌──────────┬────┬──────┬─────┬────────┬─────────┬────────┬────────┬──────────┐         
    │ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │         
    └──────────┴────┴──────┴─────┴────────┴─────────┴────────┴────────┴──────────┘         
     Use `pm2 show <id|name>` to get more details about an app                             
    Sleep 5sec                                                                             
    Done.
    

    update.sh is deleting/changing the server directory before stopping it. Sometimes that can create unexpected/strange behavior.

    I modified update.sh as follow:

    #!/bin/bash
    
    # Check we are not already updating.
    if [ -f update.lock ]; then 
        echo "Already updating. Locked."
        exit
    fi
    
    # Make the log file.
    rm update.log
    touch update.log
    
    # Create update lock.
    touch update.lock
    
    # --- Stop server before deleting its directory!
    
    # Stop the previous server.
    # == PM2 stop some server name.
    echo "Stopping server & removing" | tee update.log
    pm2 delete "Some App" | tee update.log
    
    echo "Cleaning up" | tee update.log
    rm -rf the-last-word
    rm -rf Server
    
    echo "Retrieving files from BitBucket" | tee update.log
    git clone git@bitbucket.org:dotdot/dot-project.git
    
    # Remove all directories except Server (So we don't have game source code on the Server)
    echo "Removing game source code" | tee update.log
    cd the-last-word
    # Move the Server directory one up.
    mv Server ../
    cd ../
    rm -rf the-last-word
    
    
    # Update the current & run.
    echo "Installing node modules" | tee update.log
    cd Server
    npm install | tee update.log
    
    # Start the server again
    # == PM2 start some server name.
    echo "Starting node server" | tee update.log
    NODE_ENV=production pm2 start bin/www.js --name "Some App" | tee update.log
    
    # Remove lock file.
    cd ..
    rm update.lock