Scripting Background Scripts on Kinsta

One of my favorite SSH commands with Kinsta is screen. I use it quite frequently to handle long running scripts like site migrations and image optimizations. I’ve often wondered if it would be possible to put screen inside of a shell script instead of manually running it from the command line. Today that’s just what I’ll be covering.

Initiating screen from a shell script.

Let’s start with a proof of concept. Create two files within Kinsta’s private directory called run.sh and script.sh with the following contents. Inside script.sh can include any long running script you want to insure doesn’t stop even if disconnected. Even something as crude as a slow counter like this.

echo "start"
sleep 10
echo "1"
sleep 10
echo "2"
sleep 10
echo "3"
sleep 10
echo "done"

Inside run.sh put the following.

cd ~/private
timestamp=$( date +'%Y-%m-%d_%H-%M-%S')
screen -dmS run_script_${timestamp} sh script.sh

Running run.sh will start screen in the background executing script.sh. At this point you can disconnect from SSH, reconnect and use top to see that screen is still running script.sh in the background.

Enabling screen‘s log file to track output.

With screen you can enable logging with the argument -L. Unfortunately the version of screen installed with Kinsta is a bit old and doesn’t include the argument -Logfile which defines a name of the log file. As a workaround you can create a custom .screenrc file with configuration to define the log file name like this.

logfile my_custom_log_file.log

The custom .screenrc is read in with the argument -c .screenrc.

Wrapping it all up into a single bundled script with working output.

To make this usable everything needs to be bundled up into a single script. This script can be stored locally and run on any Kinsta site like this.

ssh sitename@sitename.kinsta.cloud "bash -s" < background.sh

Try it out! Create the following background.sh file locally and run it on Kinsta site as shown above. If successful you’ll see the script slowly start counting to 3, and then complete. Within the private folder you’ll find a log file containing the output of the script. You can even start the script and disconnect from SSH and the log output will still complete just fine due to the marvelous screen command.

# Generate captaincore_run_${timestamp}.sh.
read -r -d '' run_content << 'heredoc'
#####################################
#######  BEGIN CUSTOM SCRIPT  #######

    echo "start"
    sleep 10
    echo "1"
    sleep 10
    echo "2"
    sleep 10
    echo "3"
    sleep 10
    echo "done"

#######   END CUSTOM SCRIPT   #######
#####################################
heredoc

# Find private folder and switch to it
if [ ! -d "../private" ]; then
    echo "Can't find private folder '../private'. Script terminated."
    return 1
fi
if [ -d "../private" ]; then
    cd ../private
    private=$(pwd)
    cd ~/public
fi

# Prepare script and log file names
timestamp=$( date +'%Y-%m-%d_%H-%M-%S')
run_wrapper_file=captaincore_run_wrapper_${timestamp}.sh
run_file=captaincore_run_${timestamp}.sh
log_file=captaincore_run_${timestamp}.log

# Initiate log file
touch ${private}/${log_file}

# Generate captaincore_run_wrapper_${timestamp}.sh
read -r -d '' run_wrapper_content << heredoc
# Start captaincore_run_timestamp.sh with output directed to log file
( ${private}/${run_file} ) 2>&1 > ${private}/$log_file

# When finished, terminate tail on log file
for process_id in \$( ps auxf | grep "tail -f ${private}/$log_file" | awk '{print \$2}' ); do
    kill -9 \$process_id &> /dev/null
done
heredoc

# Generate custom config for `screen`
echo "logfile $log_file" > ${private}/.screenrc
echo "$run_wrapper_content" > ${private}/${run_wrapper_file}
echo "$run_content" > ${private}/${run_file}
chmod +x ${private}/${run_wrapper_file}
chmod +x ${private}/${run_file}

# Verify we aren't in screen then run command
if [ -z "$STY" ]; then 
    screen -c ${private}/.screenrc -dmS ${run_file//.sh/} sh ${private}/${run_wrapper_file}
fi

# Start tailing log file for output of background process. Silence when the process is terminated by passing to `cat`.
tail -f ${private}/${log_file} | cat

# Cleanup generated scripts
rm ${private}/${run_file}
rm ${private}/${run_wrapper_file}

# Optional cleanup log file
# rm ${private}/${log_file}

Any long running script can be wrapped using this method.

The example of counting isn’t very useful. However this wrapper template can be used for any long running script. To use simply swap out the contents of run_content with any arbitrary shell script like my over the air example of running a site migration.