Create Self Extracting and Running Archive

Ever wanted to create your own installer.sh file? Well it’s actually pretty easy.

Backstory

Recently my sister was in the hospital for an extended stay, and we tried to get the ChromeCast to work so she could watch some movies rather than go insane (even though the insane ward was just down the street!). However, Google in it’s infinite wisdom forgot to allow the ChromeCast to be able to accept the hospital’s wifi terms page. So I built a router/bridge to connect to the hospital which allowed it to work.

Yet I still wanted to be able to connect to it remotely and do little fixes if needed. I should have done so when I initially set it up. But now it’s in the hospital with her, and I can only stop by to visit for a bit. (And the visit better be to visit her, otherwise she’s going to feel neglected with me messing around on the computer and not spending time with her!) So I needed something that would be able to install openvpn, the config files, set it all up, and do so in under 60 seconds.

So, why not make an extractor that could set it all up in a single command!

Theory Behind the Magic

Self extracting archives usually just have a bash script at the top of them that does all the work, and then the archive attached to the bottom of it. It’s pretty simple. The file is opened by bash, and the commands are run that will identify the bottom of the file as the archive and extract it. The script doesn’t actually get to the archived part, as it exits before it reaches it. Then once extracted the script will run the installer script to do the actual install.

How to do it.

First, place all your needed files in a folder called ‘payload’. Your installer should be in it, and named ‘installer’. Then copy the two scripts below, place them in the folder above payload, and call them ‘build.sh’ and ‘decompress’ respectively.

The decompress file is the file that will be attached to the top of the archive. You’ll see it below. Some things to note:

  • I commented out mktemp command as it is not found on all linux distributions. Instead just make a tmp folder
  • the ARCHIVE=awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' $0 command identifies where the archive is in the file.
  • tail -n+$ARCHIVE $0 | tar xzv -C $TMPDIR takes the above info and gives it to tar to extract the archive.
#!/bin/bash
# save as ./decompress

echo ""
echo "Self Extracting Installer"
echo ""

#export TMPDIR=`mktemp -d /tmp/selfextract.XXXXXX`
export TMPDIR=/tmp/extractpayload$RANDOM
mkdir -p $TMPDIR
chmod 700 $TMPDIR

ARCHIVE=`awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' $0`

tail -n+$ARCHIVE $0 | tar xzv -C $TMPDIR

CDIR=`pwd`
cd $TMPDIR
chmod 755 ./installer
./installer

cd $CDIR
rm -rf $TMPDIR

echo "All done"

exit 0

__ARCHIVE_BELOW__

The build.sh script is pretty obvious. It creates the archive folder, and then attaches the ‘decompress’ file to the top of it, making it all one file that can be executed.

#!/bin/bash
# save as ./build.sh

cd payload
tar cf ../payload.tar ./*
cd ..

if [ -e "payload.tar" ]; then
    gzip payload.tar

    if [ -e "payload.tar.gz" ]; then
        cat decompress payload.tar.gz > selfextractor.sh
    else
        echo "payload.tar.gz does not exist"
        exit 1
    fi
else
    echo "payload.tar does not exist"
    exit 1
fi

rm -f payload.tar.gz
echo "selfextractor.sh created"
exit 0

Make sure you have a new line after the ARCHIVE_BELOW line above. Otherwise you will get:

$ bash ./selfextractor.sh

Self Extracting Installer

gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now
./selfextractor.sh: line 17: ./installer: No such file or directory

And what should your put in the ‘installer’ script? Well, whatever is needed to make it work. In my case I put this (oh and this pertains to debian, but the above scripts will work about anywhere):

#!/bin/bash
# saved as './payload/installer'

CDIR="$(pwd)"

echo "Installing openvpn"
apt-get -y install openvpn

echo "copying over files"
mkdir -p /etc/openvpn/
cp -RP --remove-destination $CDIR/openvpn/* /etc/openvpn/
# perms
chmod 700 /etc/openvpn/ovpn
chmod 400 /etc/openvpn/ovpn/*.key
chmod 400 /etc/openvpn/ovpn/ca.crt

echo "Enabling service and starting it"
systemctl enable openvpn@ovpn
systemctl start  openvpn@ovpn

# wait and check it
sleep 10
if [ -n "$(ip addr | grep tun)" ] ; then
  echo "Successfully installed"
else
  echo "Failed to bring device up"
fi

I had put my openvpn files in a folder called ‘openvpn’ which was then to be copied when it ran, along with installing openvpn and enabling it.

Build it

Now run the build.sh script. This will build the actual file selfextractor.sh, which you can rename to whatever you woul like to.

[user@home]% bash ./build.sh
selfextractor.sh created

Test It Out

Now that we have the script we should just run other onto our production machine and run it right? Wrong!!! That’s a great way to get into trouble.

Instead, what you should do it open up your VirtualBox/KVM/DigitalOcean and try it out on a simular setup to what your production machine has. In my case I just installed Debian onto DigitalOcean, as it is related to Raspberry Pi’s Rasbian OS, and tested it out there.

And did I find errors? You bet I did! Was I able to fix them? Yes, from the comfort of home, with everything fresh on my mind.

But imagine if I was over at the hospital trying to run it, and then it messed up the Raspberry Pi in the hospital, and all that hard work was gone…

Or if in your case your boss was watching over your shoulder when the pi hit the fan…..

You get the idea.

Run it

But now that it works and you verified you got all the parts going as they should, it’s time to run the script on the needed machine, or any other machine where it’s needed:

[root@another-machine]# bash ./selfextractor.sh

Self Extracting Installer

./installer    <-- (this shows the files that are being extracted)
./openvpn/     
./openvpn/ovpn.conf
./openvpn/ovpn/
./openvpn/ovpn/client.key
./openvpn/ovpn/ta.key
./openvpn/ovpn/ca.crt
./openvpn/ovpn/client.crt
---------------------------------------
- NOTE: Below is my installer output: -
---------------------------------------
Installing openvpn
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following package was automatically installed and is no longer required:
  liblockfile1
Use 'apt-get autoremove' to remove it.
The following extra packages will be installed:
  easy-rsa liblzo2-2 libpkcs11-helper1 opensc opensc-pkcs11
The following NEW packages will be installed:
  easy-rsa liblzo2-2 libpkcs11-helper1 opensc opensc-pkcs11 openvpn
0 upgraded, 6 newly installed, 0 to remove and 0 not upgraded.
Need to get 0 B/1,501 kB of archives.
After this operation, 4,660 kB of additional disk space will be used.
Preconfiguring packages ...
Selecting previously unselected package liblzo2-2:amd64.
(Reading database ... 33391 files and directories currently installed.)
Preparing to unpack .../liblzo2-2_2.08-1.2_amd64.deb ...
Unpacking liblzo2-2:amd64 (2.08-1.2) ...
Selecting previously unselected package libpkcs11-helper1:amd64.
Preparing to unpack .../libpkcs11-helper1_1.11-2_amd64.deb ...
Unpacking libpkcs11-helper1:amd64 (1.11-2) ...
Selecting previously unselected package opensc-pkcs11:amd64.
Preparing to unpack .../opensc-pkcs11_0.14.0-2_amd64.deb ...
Unpacking opensc-pkcs11:amd64 (0.14.0-2) ...
Selecting previously unselected package openvpn.
Preparing to unpack .../openvpn_2.3.4-5+deb8u1_amd64.deb ...
Unpacking openvpn (2.3.4-5+deb8u1) ...
Selecting previously unselected package easy-rsa.
Preparing to unpack .../easy-rsa_2.2.2-1_all.deb ...
Unpacking easy-rsa (2.2.2-1) ...
Selecting previously unselected package opensc.
Preparing to unpack .../opensc_0.14.0-2_amd64.deb ...
Unpacking opensc (0.14.0-2) ...
Processing triggers for man-db (2.7.0.2-5) ...
Processing triggers for systemd (215-17+deb8u3) ...
Setting up liblzo2-2:amd64 (2.08-1.2) ...
Setting up libpkcs11-helper1:amd64 (1.11-2) ...
Setting up opensc-pkcs11:amd64 (0.14.0-2) ...
Setting up openvpn (2.3.4-5+deb8u1) ...
[ ok ] Restarting virtual private network daemon.:.
Setting up easy-rsa (2.2.2-1) ...
Setting up opensc (0.14.0-2) ...
Processing triggers for libc-bin (2.19-18+deb8u2) ...
Processing triggers for systemd (215-17+deb8u3) ...
copying over files
Enabling service and starting it
Successfully installed
All done

Here’s a debug look at the layout of what it does:

bash -x ./selfextractor.sh
+ echo ''
+ echo 'Self Extracting Installer'
Self Extracting Installer
+ echo ''
+ export TMPDIR=/tmp/extractpayload7206
+ TMPDIR=/tmp/extractpayload7206
+ mkdir -p /tmp/extractpayload7206
+ chmod 700 /tmp/extractpayload7206
++ awk '/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }' ./selfextractor.sh
+ ARCHIVE=27
+ tar xzv -C /tmp/extractpayload7206
+ tail -n+27 ./selfextractor.sh
./installer
./openvpn/
./openvpn/ovpn.conf
./openvpn/ovpn/
./openvpn/ovpn/client.key
./openvpn/ovpn/ta.key
./openvpn/ovpn/ca.crt
./openvpn/ovpn/client.crt
++ pwd
+ CDIR=/root
+ cd /tmp/extractpayload7206
+ ./installer
[ installer runs here]
+ cd /root
+ echo 'All done'
All done
+ exit 0

Other Resources.

I got the ideas for it here but then added my own ideas too.

There is also a program built to do the above, called makeself.sh. I just like having my own thing.