1984 – the techblog

(Hopefully) useful various sysadmin and other stuff.

Self-extracting shell scripts

Recently at work, I had to fig­ure out on how to trans­fer some data to sys­tems used by scripts that were exe­cuted directly on the hosts. It wasn’t that much pay­load, and I really didn’t want to be both­ered to set up a sep­a­rate filesys­tem place where to hosts rsync the data from, make sure that ssh works, etc. So, why not attach the data to the scripts itself? Here’s what I came up with.

SUBJECT="/path/to/your/payload"

if [[ "$1" = "rebuild" || "$1" = "clean" ]]
then
	echo "rebuilding myself..."
	PAYLOADSTART=`awk '/^__PAYLOAD_FOLLOWS__/ {print NR + 1; exit 0; }' $0`
	# full replace, must use cp for that (piping breaks control flow)
	head -n "$(( $PAYLOADSTART-1 ))" $0 > tmp
	cp tmp $0
	rm tmp
	echo "cleaned."
	[ "$1" != "clean" ] || exit 0
	echo "archiving payload... ($SUBJECT)"
	OLDPWD=`pwd`
	cd "dirname $SUBJECT"
	TMPDIR=`mktemp -d /tmp/pkgInstaller.XXXXXX`
	tar -czf $TMPDIR/archive-o-matic.tar.gz "$SUBJECT"
	cd "$OLDPWD"
	cat "$TMPDIR/archive-o-matic.tar.gz" >> $0
	echo "payload attached."
	rm -rf "$TMPDIR"
	echo "done here."
	exit 0
fi

# Self extraction
export TMPDIR=`mktemp -d /tmp/pkgInstaller.XXXXXX`

# determine start of payload
PAYLOADSTART=`awk '/^__PAYLOAD_FOLLOWS__/ {print NR + 1; exit 0; }' $0`

# extract payload
tail -n+$PAYLOADSTART $0 | tar xzv -C $TMPDIR

# copy to target
cp -R "$TMPDIR/$SUBJECT" "/your/target/path"

# cleanup
rm -rf $TMPDIR

exit 0

__PAYLOAD_FOLLOWS__

What this basi­cally does, is to append, read or delete data after the line __PAYLOAD_FOLLOWS__ (in the same file!). To pre­vent the shell to choke on this data (it shouldn’t be exe­cuted as well), the exit 0 state­ment lets the script exe­cu­tion be halted before reach­ing this por­tion of the file.

Fun­nily enough, Bash doesn’t mind when you append to an cur­rently exe­cut­ing script. In ear­lier version’s, I’ve used exec to replace the cur­rent (script-executing) process in order to be able mod­ify the files held open by it. Turn’s out that doesn’t seem to be nec­es­sary. Note that while rebuild­ing, the script replaces itself mid-execution com­pletely. I’m not sure wether this works only because a copy of the orig­i­nal script is held in mem­ory (and didn’t have the time to check that out either). But if it’s really replac­ing itself, that would mean self-modifying shell­code was cre­ated here accidentally :)

Oh, and don’t try to open the file in your favourite edi­tor after the pay­load has been attached – most often, it’ll have a hard time fig­ur­ing out what’s going on, eat up your CPU and mem­ory while doing so and some­times get so con­fused that a crash fol­lows suit. Does not com­pute, eh?

Hat tip to the linux jour­nal arti­cle, from which I got some ideas.

Leave a Reply