Skip to content

Instantly share code, notes, and snippets.

@MichaelCurrin
Last active October 19, 2023 06:07
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save MichaelCurrin/efd277d3cc164c5af89f9ca0a72b5d11 to your computer and use it in GitHub Desktop.
Save MichaelCurrin/efd277d3cc164c5af89f9ca0a72b5d11 to your computer and use it in GitHub Desktop.
Self-extracting shell scripts

Self-extracting shell scripts

Guide to using MakeSelf tool to create self-extracting shell scripts for easy distribution

How to package your shell scripts project as a release which can be easily downloaded, extracted, and run.

This is intended to work with one or more executable scripts that use a shell shebang. This could work with other shebangs I guss, but you're probably better off using the packaging approach for that language.

This guide is written for macOS / Linux.

About MakeSelf

The tool we use here is MakeSelf.

See the makeself.io docs for more info.

You can run MakeSelf locally or as part of a Github Action triggered on a release to package your project directory as a single downloadable file - usually with .run extension. This can be included on a release and then downloaded and unpacked by users.

MakeSelf compared with alternatives

The MakeSelf approach is similar to using a Github download/archive/zip file but does not require use of unzip or tar.

The .run file can be run as script with project-specific help in the output. The .run file can be read easily directly as a script in a text editor - a portion of the script will be compressed binary data which is not human-readable but which the script itself can extract.

When your bundled release is unpacked, as setup script can be run automatically. This can be useful for installing dependencies oror installing the project into a project directory e.g. ~/.my-project or ~/.local/my-project.

The MakeSelf flow is suited to shell projects which do not have a packaging pattern to flow like that of Node packages, Ruby gems or Python packages.

The MakeSelf approach is cleaner than providing a Github archive download link (for master or a tag), since the Github approach always includes the entire repo - including tests and docs.

NB. For a small project, the standard Github flow might be fine and you don't have to use MakeSelf and any of the shell command and Github Actions overhead needed for creating a release. Just created a tag and view the tag/release on Github. Also remember to include tar / unzip and make instructions in your docs so users can setup your project by hand.

Purpose of this guide

I wrote this guide to make it accessible for myself to follow the MakeSelf approach.

This guide covers how to:

  • Install Makeself
  • Run Makeself
  • Bundle a release for your own project
  • Download and run your release

Install MakeSelf

1. Download

Install from APT

If you use a Debian-based system, you can install from APT packages. This should be useful locally and for adding as a step in a Github Action workflow.

$ sudo apt install makeself

See the Makeself Debian Wiki page.

Install from Github release

Go to the releases page.

Look at the latest release at the top. For this tutorial, we use 2.4.2.

Click on the first item on the assets section.

Save it to your Downloads directory.

2. Install MakeSelf

$ cd Downloads/
$ ls
makeself-2.4.2.run

The downloaded file should be there. A portion of the script is binary content - this will be unzipped into multiple files.

Check options on the run file. These will similar to what you actually create as your own later on.

$ ./makeself-2.4.2.run --help

Run it.

$ ./makeself-2.4.2.run

Check the contents of the makeself-2.4.2/ directory created. The type annotation of * below means executable.

$ ls -F makeself-2.4.2/
COPYING  makeself.1  makeself-header.sh*  makeself.lsm  makeself.sh*  README.md  test/  VERSION

Note the makeself.sh script which is the entry point we need. And makeself-header.sh which is a dependency.

Persist

Rather than keeping MakeSelf in your Downloads directory, move it somewhere more permanent. Ideally keep all the license files and docs with it.

Move the unpacked directory. Rename it so it is easy to upgrade later.

$ mv makeself-2.4.2/ ~/.local/makeself

The section assumes $HOME/.local/bin is in your PATH.

$ mkdir -p ~/.local/bin

Symlink the main script and its header file in a bin directory. Note the header file must be in the same directory as the main script otherwise it can't be found when we run it later.

$ ln -s ~/.local/makeself/makeself.sh ~/.local/bin
$ ln -s ~/.local/makeself/makeself-header.sh ~/.local/bin

Now you can run the script from anywhere.

$ cd ~
$ makeself.sh -h
...

MakeSelf CLI

Check the help at any time.

$ ./makeself.sh -h
...

Usage

Usage: ./makeself.sh [args] archive_dir file_name label startup_script [script_args]

Options

Note the order is important - the args (options) all use -- prefix and are optional. Since MakeSelf cannot differentiate between arguments given as options or as script args except by their position around the main args.

Option Description
--notemp The generated archive will not extract the files to a temporary directory, but in a new directory created in the current directory. This is better to distribute software packages that may extract and compile by themselves (i.e. launch the compilation through the embedded script).
--license FILE Include a license.

See the docs or CLI help for all available options.

Positional arguments

These are copied from the docs:

Argument Description
archive_dir is the name of the directory that contains the files to be archived
file_name is the name of the archive to be created
label is an arbitrary text string describing the package. It will be displayed while extracting the files.
startup_script is the command to be executed from within the directory of extracted files. Thus, if you wish to execute a program contain in this directory, you must prefix your command with ./. For example, ./program will be fine. The script_args are additional arguments for this command.

My notes:

  • The archive_dir value could be your repo (or . within your repo) or it could be a subdirectory like app if your want to exclude tests and docs at the top-level. Based on the flow in the MakeSelf repo's make-release.sh script, you could create a temporary directory, copy distributable files to it and then pass that directories path to MakeSelf to bundle that.
  • A multi-word label value should be in quotes as per the examples.
  • The startup_script
    • It could be echo followed by the arguments as what you want to echo. Example from the docs: echo "Makeself has extracted itself
    • It could be the install steps. e.g. ./configure && make && make install

Example

An example copied from the docs:

# CMD         archive_dir      file_name label                         startup_script
./makeself.sh /home/joe/mysoft mysoft.sh "Joe's Nice Software Package" ./setup 

Use MakeSelf for your own project

Create a release

How to use MakeSelf to create a bundled release file for your your project

Ensure you have the makeself.sh downloaded and that you can run it, as per the instructions in the previous sections.

Navigate to your target repo. Here I used my auto-tag repo.

$ cd ~/repos/auto-tag

Prepare:

$ mkdir /tmp/autotag-dist
$ cp autotag LICENSE /tmp/autotag-dist/

In this case, autotag is an executable shell script that just has extension in the name. You could replace with your own value like my_script.sh.

Bundle and output a file in the release directory of your project.

$ makeself.sh 
  --license LICENSE 
  --no-temp \
  /tmp/autotag-dist \
  release/autotag.run \
  'Increment git tags the smart/lazy way' \
  echo 'Installed autotag'
Header is 668 lines long

About to compress 12 KB of data...
Adding files to archive named "autotag.run"...
./
./LICENSE
./autotag
./LICENSE
./autotag
CRC: 365064486
MD5: 94bd3701c6013ec091cf7158c2c68767

Self-extractable archive "autotag.run" successfully created.

I don't know why the content names appear duplicated above.

Note that by specifying --license file as an option, when you unpackage the file you'll see the license in the terminal and then be prompted to enter y to accept.

One could also include a VERSION file with a release number in it.

Include the .run file in a release by dragging it to the Github Page manually. Or using Github Actions - see release.yml or the example below.

.github/worksflows/release.yml

steps:
    - name: Checkout project
      uses: actions/checkout@v2

    - name: Build release
      run: make
        
    - name: Upload to release
      uses: fnkr/github-action-ghr@v1
      env:
        GHR_PATH: release/
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Unpack and run the release

Move the local run file (e.g. autotag.run) to Downloads or download one from your releases.

$ cd ~/Downloads

Check the help.

$ autotag.run -h

View the contents if you want.

$ view autotag.run

Unpack it.

$ autotag-run
Creating directory autotag-dist
Verifying archive integrity...  100%   MD5 checksums are OK. All good.
Uncompressing AutoTag - Increment git tags the smart/lazy way  100%  
Installed autotag
$ ls autotag-dist/
autotag LICENSE

Note that output directory name here (e.g. autotag-dist) will be the based as name of the directory that was originally packaged (/tmp/autotag-dist).

If there were any install commands with make those could have be executed at the point.

If there was a setup or install script (triggered by make install) that could even copy the files to a new directory outside of Downloads. Such as ~/.local/autotag.

Resources

Files in the source repo:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment