evaluation of logical expression chain in /bin/sh - sh

I have trouble understanding how this posix shell code works.
my_dir=/tmp/foo_dir
[ -d $my_dir ] || mkdir -p $my_dir || echo "Error creating '$my_dir'!"
Consider the case where the directory does not exist.
-d returns zero if directory doesn't exist, so then the "mkdir" is evaluated.
mkdir returns zero upon success, that should lead to "echo" being evaluated as well. Yet this code works (echo is only printed if directory creation fails by changing my_dir to /ttmp/foo_dir")

In a posix shell, 0 means success and other values means failure. When we use a command in a logical expression, we are testing it's exit status. We can use $? to check the last exit status.
Experimenting:
$ true
$ echo $?
0
$ false
$ echo $?
1
$ [ -d tmpdir ]
$ echo $?
1
$ mkdir tmpdir
$ echo $?
0
$ mkdir tmpdir
mkdir: tmpdir: File exists
$ echo $?
1
$ mkdir -p tmpdir
$ echo $?
0
$ [ -d tmpdir ]
$ echo $?
0
As you can see, -d returns 0 (success) when the directory is present and 1 otherwise. Similarly, mkdir returns 0 when it successfully creates a directory and 1 if it doesn't, but mkdir -p does not consider the file already existing as an error.
The thing is that || is a short-circuiting operator. Only what's needed gets executed. So, for example, in true || echo Hello, echo Hello never gets executed because the left side of || is always true. && is also a short-circuiting operator and false && echo Hello would never execute echo Hello because the left side of the operator is always false.
Keeping all that in mind, it's easy to see how it works:
If the directory exists, -d returns 0 (success) and the rest of the operands doesn't need to be evaluated (due to the short-circuiting behaviour)
If the directory doesn't exist, -d return 1 (failure) and mkdir -p is executed:
If the directory is successfully created (remember that mkdir -p does not return an error if the directory already exists), mkdir -p returns 0 (success) and echo msg doesn't get executed (due to short-circuiting behaviour)
If the directory isn't successfully created, mkdir -p returns 1 (failure) and echo msg is evaluated. Its exit status is the "value" of the whole expression.

Related

Jenkins build passes when ssh deploy fails

I'm using a here tag in a Jenkins build step to send my deploy commands over ssh, and unfortunately the build is passing even when the commands inside the here tag don't finish successfully:
ssh user#host <<EOF
cd /path/to/app
git pull
bower install
npm install
grunt build
cp -r /path/to/app/dist/* /path/to/dist/
forever restartall
exit
EOF
Is there a better way to approach this problem?
You are not catching any error codes inside your "here document".
Last command is exit and without an exit code, it will default to 0 which is success.
Since the last command of your ssh is a success, the whole command is treated as success, and build is a success.
Easiest way to fix that: chain all commands with && like so:
cd /path/to/app && git pull && bower install && npm install && grunt build && cp -r /path/to/app/dist/* /path/to/dist/ && forever restartall && exit
Best way to fix that: write a proper shell script, with error handling, and execute that. If you are too lazy to error handle every line, you can start the script with set -e which will fail the shell script on any individual error
Edit:
#!/bin/bash
appPath="/path/to/app"
distPath"/path/to/dist"
echo "My great deployment script"
echo "Deploying ${appPath} to ${distPath}
if [[ ! -w "${appPath}" ]]; then
echo "${appPath} is not writable, quitting"
exit 1
else
cd ${appPath} && git pull || { echo "Failed on 'git pull'"; exit 2; }
bower install || { echo "Failed on 'bower install'"; exit 3; }
npm install || { echo "Failed on 'npm install'"; exit 4; }
grunt build || { echo "Failed on 'grunt build'"; exit 5; }
if [[ -w "${distPath}" ]]; then
cp -r ${appPath}/dist/* ${distPath}/ || { echo "Failed on 'copy'"; exit 1 }
forever restartall || { echo "Failed on 'forever restartall'"; exit 6 }
echo "Deployment successful"
exit 0
fi
fi
You then execute it with: ssh user#host 'bash -s' < myfile.sh (if the file is local)
Or if you place the file remotely, then just: ssh user#host '/path/to/remote/myfile.sh'

Capistrano doesn't obey "within release_path"

I have this task:
namespace :custom do
desc "create a symlink to db config already on the server"
task :symlink_db_config do
on roles(:web) do
within release_path do
execute "pwd"
end
within release_path do
execute "ln -nfs /home/blog/config/database.yml ./database.yml"
end
end
end
end
For some infuriating reason, the pwd command is preceded by a cd to the release path, but the ln command is not. Why is that?
Here's the output, showing the above:
** Invoke custom:symlink_db_config (first_time)
** Execute custom:symlink_db_config
DEBUG[352cc4bb] Running /usr/bin/env if test ! -d /home/blog/staging/releases/20141010050707; then echo "Directory does not exist '/home/blog/staging/releases/20141010050707'" 1>&2; false; fi on 172.245.32.193
DEBUG[352cc4bb] Command: if test ! -d /home/blog/staging/releases/20141010050707; then echo "Directory does not exist '/home/blog/staging/releases/20141010050707'" 1>&2; false; fi
DEBUG[352cc4bb] Finished in 0.199 seconds with exit status 0 (successful).
// Here's the `pwd`; note the proper `cd` that occurs first:
INFO[67a83a04] Running /usr/bin/env pwd on 172.245.32.193
DEBUG[67a83a04] Command: cd /home/blog/staging/releases/20141010050707 && /usr/bin/env pwd
DEBUG[67a83a04] /home/blog/staging/releases/20141010050707
INFO[67a83a04] Finished in 0.268 seconds with exit status 0 (successful).
DEBUG[f46f64b3] Running /usr/bin/env if test ! -d /home/blog/staging/releases/20141010050707; then echo "Directory does not exist '/home/blog/staging/releases/20141010050707'" 1>&2; false; fi on 172.245.32.193
DEBUG[f46f64b3] Command: if test ! -d /home/blog/staging/releases/20141010050707; then echo "Directory does not exist '/home/blog/staging/releases/20141010050707'" 1>&2; false; fi
DEBUG[f46f64b3] Finished in 0.243 seconds with exit status 0 (successful).
//And now here's the `ln`... but where is the `cd`? I said `within release_path`, didn't I?
INFO[afdbd89c] Running /usr/bin/env ln -nfs /home/blog/config/database.yml ./database.yml on 172.245.32.193
DEBUG[afdbd89c] Command: ln -nfs /home/blog/config/database.yml ./database.yml
INFO[afdbd89c] Finished in 0.219 seconds with exit status 0 (successful).
So my ln fails because it's not in the right directory. Why didn't capistrano cd into the release directory like I told it to?
(Capistrano 3.2.1, by the way)
There's useful information in another answer here but briefly it seems the problem arises when there are spaces in the command you want to run.
I followed bricker's suggestion, e.g.
within release_path do
execute *%w[ ln -nfs /home/blog/config/database.yml ./database.yml ]
end

rpmbuild "Optional dependency" without require

I would like to build a RPM Package for a website.
in the %post section of my spec file I would like to check if the apache webserver is installed. If so, the config should be copied to /etc/httpd/conf.d/
If no Apache is installed, the config should just be stored in the projectfolder and the admin has to configure the webserver on his own.
So I need a construct like
if [Apache installed]
cp [configfile] /etc/httpd/conf.d/
EDIT
I found out, I maybe could use rpm -q httpd for it. But how can I use it in the %post section?
(rpm -q --quiet httpd && cp yourfile /etc/httpd/conf.d/) || true
This will copy your config file over.
Of course, an easier way might just be
( [ -d /etc/httpd/conf.d ] && cp yourfile /etc/httpd/conf.d/) || true
Which says if the directory exists, drop it there, regardless of why the directory is there.
The extra "|| true " at the end causes the result of the line to always be OK so rpm won't claim a scriptlet failed if it didn't exist.
Here's how I tested it:
[user#Niflheim ~]# rpm -q --quiet httpds && echo Yep
[user#Niflheim ~]# echo $?
1
[user#Niflheim ~]# # Need to move to subshell...
[user#Niflheim ~]# (rpm -q --quiet httpds && echo Yep) || true
[user#Niflheim ~]# echo $?
0
[user#Niflheim ~]# (rpm -q --quiet httpd && echo Yep) || true
Yep
[user#Niflheim ~]# echo $?
0
[user#Niflheim ~]#
There are probably some macros you should be using instead of hardcoding /etc/, but I'm on my way out the door.

Where should I add the --rest option for MongoDB?

I need to use mongodb with the --rest option. But mongodb is started automatically on boot, so I guess I need to modify a file or something.
Where can I add this --rest option?
I have this file at /etc/init/mongodb.conf, not sure what to edit:
# Ubuntu upstart file at /etc/init/mongodb.conf
limit nofile 20000 20000
kill timeout 300 # wait 300s between SIGTERM and SIGKILL.
pre-start script
mkdir -p /var/lib/mongodb/
mkdir -p /var/log/mongodb/
end script
start on runlevel [2345]
stop on runlevel [06]
script
ENABLE_MONGODB="yes"
if [ -f /etc/default/mongodb ]; then . /etc/default/mongodb; fi
if [ "x$ENABLE_MONGODB" = "xyes" ]; then exec start-stop-daemon --start --quiet --chuid mongodb --exec /usr/bin/mongod -- --config /etc/mongodb.conf; fi
end script
And this file at /etc/init.d/mongodb:
#!/bin/sh -e
# upstart-job
#
# Symlink target for initscripts that have been converted to Upstart.
set -e
INITSCRIPT="$(basename "$0")"
JOB="${INITSCRIPT%.sh}"
if [ "$JOB" = "upstart-job" ]; then
if [ -z "$1" ]; then
echo "Usage: upstart-job JOB COMMAND" 1>&2
exit 1
fi
JOB="$1"
INITSCRIPT="$1"
shift
else
if [ -z "$1" ]; then
echo "Usage: $0 COMMAND" 1>&2
exit 1
fi
fi
COMMAND="$1"
shift
if [ -z "$DPKG_MAINTSCRIPT_PACKAGE" ]; then
ECHO=echo
else
ECHO=:
fi
$ECHO "Rather than invoking init scripts through /etc/init.d, use the service(8)"
$ECHO "utility, e.g. service $INITSCRIPT $COMMAND"
# Only check if jobs are disabled if the currently _running_ version of
# Upstart (which may be older than the latest _installed_ version)
# supports such a query.
#
# This check is necessary to handle the scenario when upgrading from a
# release without the 'show-config' command (introduced in
# Upstart for Ubuntu version 0.9.7) since without this check, all
# installed packages with associated Upstart jobs would be considered
# disabled.
#
# Once Upstart can maintain state on re-exec, this change can be
# dropped (since the currently running version of Upstart will always
# match the latest installed version).
UPSTART_VERSION_RUNNING=$(initctl version|awk '{print $3}'|tr -d ')')
if dpkg --compare-versions "$UPSTART_VERSION_RUNNING" ge 0.9.7
then
initctl show-config -e "$JOB"|grep -q '^ start on' || DISABLED=1
fi
case $COMMAND in
status)
$ECHO
$ECHO "Since the script you are attempting to invoke has been converted to an"
$ECHO "Upstart job, you may also use the $COMMAND(8) utility, e.g. $COMMAND $JOB"
$COMMAND "$JOB"
;;
start|stop)
$ECHO
$ECHO "Since the script you are attempting to invoke has been converted to an"
$ECHO "Upstart job, you may also use the $COMMAND(8) utility, e.g. $COMMAND $JOB"
if status "$JOB" 2>/dev/null | grep -q ' start/'; then
RUNNING=1
fi
if [ -z "$RUNNING" ] && [ "$COMMAND" = "stop" ]; then
exit 0
elif [ -n "$RUNNING" ] && [ "$COMMAND" = "start" ]; then
exit 0
elif [ -n "$DISABLED" ] && [ "$COMMAND" = "start" ]; then
exit 0
fi
$COMMAND "$JOB"
;;
restart)
$ECHO
$ECHO "Since the script you are attempting to invoke has been converted to an"
$ECHO "Upstart job, you may also use the stop(8) and then start(8) utilities,"
$ECHO "e.g. stop $JOB ; start $JOB. The restart(8) utility is also available."
if status "$JOB" 2>/dev/null | grep -q ' start/'; then
RUNNING=1
fi
if [ -n "$RUNNING" ] ; then
stop "$JOB"
fi
# If the job is disabled and is not currently running, the job is
# not restarted. However, if the job is disabled but has been forced into the
# running state, we *do* stop and restart it since this is expected behaviour
# for the admin who forced the start.
if [ -n "$DISABLED" ] && [ -z "$RUNNING" ]; then
exit 0
fi
start "$JOB"
;;
reload|force-reload)
$ECHO
$ECHO "Since the script you are attempting to invoke has been converted to an"
$ECHO "Upstart job, you may also use the reload(8) utility, e.g. reload $JOB"
reload "$JOB"
;;
*)
$ECHO
$ECHO "The script you are attempting to invoke has been converted to an Upstart" 1>&2
$ECHO "job, but $COMMAND is not supported for Upstart jobs." 1>&2
exit 1
esac
It's probably cleaner to enable the REST interface via /etc/mongodb.conf by adding a line of:
rest = true
That setting is documented here.
MongoDB version 2.6 has switched to a YAML config file. The following two entries are required to prevent the following startup warning:
mongodb WARNING: --rest is specified without --httpinterface
net:
http:
enabled: true
RESTInterfaceEnabled: true
When u start the server using command mongod , add --rest option with command mongod like this mongod --rest.
refer mongod - MongoDB Manual 2.6.
After run command complete , u can use the following the simple Restful API:
http://127.0.0.1:28017/databaseName/collectionName/
Here is simple RestFul API Doc.
Just start the server using mongod --rest
Note: By default, the rest API's are inaccessible due to security issues. The web interface is accessible at localhost:<port>, where the number is 1000 more than the mongod port. For example, your mongodb server is running at 27017 (by default) then you can access mongodb at
http://127.0.0.1:28017/<db-name>/<collection-name>/

Capturing and mailing bash script errors

I have a script that I run nightly in cron to backup some postgres databases for several hosts on my network. I have a way of getting alerted that the script fails by leveraging the exit status, but it doesn't tell me WHY it failed.
Based off the following code, how can I capture any errors that occur when the script is run, and email them to me so I can have a better understanding of what happened.
FILEDATE=`date '+%Y-%m-%d'`
BASEDIR=/u1/$1/db_dumps
PGDUMP=/path/to/pg_dump
HOST=$1
DB=$2
if [ $DB == all ]
then
for ALLDUMPS in db1 db2 db3
do
ssh root#$HOST "env PGUSER=pguser PGPASSWORD=pgpassword $PGDUMP -Fc $ALLDUMPS" | pbzip2 > $BASEDIR/$FILEDATE-$HOST-$ALLDUMPS.dump.bz2
if [ $? -ne 0 ]
then mutt -s "dbdumper could not create a backup of $ALLDUMP from $HOST" me#myemail.com < /dev/null
fi
done
else
ssh root#$HOST "env PGUSER=pguser PGPASSWORD=pgpassword $PGDUMP -Fc $DB" | pbzip2 > $BASEDIR/$FILEDATE-$HOST-$DB.dump.bz2
if [ $? -ne 0 ]
then mutt -s "dbdumper failed to create a backup of $DB from $HOST" me#myemail.com < /dev/null
fi
fi
Capture stderr from the ssh command, and email that to yourself.
stderr=$( ssh root#$HOST "env PGUSER=user PGPASSWORD=pw $PGDUMP -Fc $ALLDUMPS" |
pbzip2 2>&1 > "$BASEDIR/$FILEDATE-$HOST-$ALLDUMPS.dump.bz2" )
if [ $? -ne 0 ]; then
subj="dbdumper could not create a backup of $ALLDUMP from $HOST"
mutt -s "$subj" me#myemail.com <<< "$stderr"
fi