Build a self-contained shell installer

Have you ever had yourself in the situation where you want to have a one-in-all installer for stuff on *nix-systems or macOS? I got you covered.

What are we going to build?

The idea is simple, you have an installer, which also includes your files, what? Exactly! We will have an archive with a script part!

Let's create the script

We will use the bash, of course you can use any standardized shell or your favorite one. It is all dependent on your target group at the end.

#!/bin/bash
# Specify install location for your files
INSTALL_LOCATION="/opt/my-software"

# Require root, since we want to create folders in a location where we maybe dont have access to
if [ "$(whoami)" != "root" ]; then
        echo "ERROR Install script must be run as root"
        exit -1
fi

# Find __ARCHIVE__ maker, read archive content and decompress it
ARCHIVE=$(awk '/^__ARCHIVE__/ {print NR + 1; exit 0; }' "${0}")
tail -n+${ARCHIVE} "${0}" | tar xpJ -C ${INSTALL_LOCATION}

exit 0
__ARCHIVE__

You will notice something weird in this script: We have a special marker at the end: __ARCHIVE__. This is not a valid expression for a script. And that's the neat part, it won't be evaluated anywhere in your script. How does this work?

Before reaching the marker, we simply exit the script. This way the parser never reaches the line and since shells interpret line by line it's valid.

So, what happens in a nutshell: We execute the script, find the marker and start extracting the tar file appended to the script. Basically, the same file, just glued together with an archive.

You can do whatever post exact steps you want to, running some setup, executing scripts etc. Whatever you like to do.

But wait a second, there is something missing? Where is the archive? — Let's go on, adding the archive part.

Append the archive

First, we will create the regular archive file:

tar cJf installer_files.tar.xz --directory=<source-folder>

This file is just a regular archive, nothing special, so first create a copy of your installer:

cp installer.sh installer.bin

Now we append the tar file as is to the installer:

cat installer_files.tar.xz >> installer.bin

So, we append all the contents from the tar to the installer file.

And there you go – one file with everything required to run the installer.

Using it

Then you can host it on GitHub, your web server etc. and use it as simple as this:

bash <(curl -sS https://my-super-secure-host.biz/installer)

Closing words

That's already the magic behind it, works pretty well for some small script collections, helpers etc. where you just need a quick and dirty solution that you can quickly scale. Updating etc. is not pretty with this one, so in these scenarios build something proper instead.

For a working example also check out the installer for deterministic-zip