8 minute read
Today, I'm going to run down a bash script for installing Firefox Developer Edition on Ubuntu 18.04+. I found several blog posts already out there on how to do this, but none of them actually worked correctly when tested. This post is for those that want a single bash script to:
For the anxious, here's the entire script up front. I'll take you through it step by step afterward:
#!/bin/bash
# Function to make messaging easier
read -r error success warning notify nocolor <<<$(echo "\033[1;31m" "\033[1;32m" "\033[1;33m" "\033[1;34m" "\033[0m")
message() {
msg="$2" && echo -e "${!1}$1:${nocolor} $2" 1>&2
}
# Check for root privileges
if [[ "$EUID" != 0 ]]; then
message notify "Please assume root privileges"
exit 1
fi
# Backup helper function
check-create-backup() {
if [ ! -d "/opt/$1" ]; then
message notify "/opt/$1 is not installed."
mkdir --verbose /opt/$1
else
message notify "Previous version of $1 exists. Deleting last backup and saving current version as backup."
rm --force --recursive /opt/$1-previous
mv --verbose /opt/$1 /opt/$1-previous
message success "Old version backed up to /opt/$1-previous"
fi
}
# Check for ~/Downloads directory
if [ -d ~/Downloads ]; then
message notify "~/Downloads exists, proceeding to save file there."
else
message notify "No Downloads folder exists yet, so creating one."
mkdir --verbose ~/Downloads
fi
# Calculate current Firefox version and download URL
firefox_json=https://product-details.mozilla.org/1.0/devedition.json
firefox_version=$(curl --silent $firefox_json | jq . | egrep "version" | sort --reverse --version-sort | head -1 | awk -F[\"] '{print $4}')
firefox_file=firefox-$firefox_version.tar.bz2
firefox_url=https://download-installer.cdn.mozilla.net/pub/devedition/releases/$firefox_version/linux-x86_64/en-US/$firefox_file
message notify "Downloading Firefox Developer Edition $firefox_version from $firefox_url"
curl -o ~/Downloads/$firefox_file $firefox_url
exit_status=$?
if [ $exit_status -ne 0 ]; then
exit $exit_status
else
check-create-backup firefox
message notify "Extracting downloaded $firefox_file..."
tar --extract --bzip2 --file ~/Downloads/$firefox_file --directory /opt
message notify "Creating symlink for Firefox"
ln --force --symbolic --verbose /opt/firefox/firefox /usr/local/bin/firefox
cp --verbose ./firefox-developer.desktop /usr/share/applications/
chmod +x /usr/share/applications/firefox-developer.desktop
message notify "Cleaning up downloaded files..."
rm --verbose ~/Downloads/$firefox_file
fi
message success "Firefox Developer Edition $firefox_version has been installed."
message notify "To add to your favorites, search, right click, and select 'Add to Favorites'"
If you're curious about the first half of this script, I invite you to read my last post on bash helper functions. While it may seem a bit overwrought, helper functions make it easier to reason around bash's esoteric syntax and they make code more readable. I'm sharing these functions here, so that everyone can see them in action. They make the rest of the script easier to understand and write.
Two of the hardest things about this script are:
Many of the existing examples I saw on the internet from other blogs thought they were downloading the latest version, but actually had scripts that would break in certain cases of semantic versioning. This is due to how programs like sort return numbers appended to text. Here's the magic:
$(curl --silent $firefox_json \
| jq . \
| egrep "version" \
| sort --reverse --version-sort \
| head -1 \
| awk -F[\"] '{print $4}')
Let's break it down:
The | is a pipe operator, which stitches our command together by taking the output of one command and piping it into the next one. With all this considered together, we're actually doing the following:
After all of that work, we know the latest version of Firefox Developer Edition!
Now that we know the proper version we want, we take our research on how Mozilla distributes FireFox Developer Edition, and we make a few assumptions to construct a valid URL for the actual program.
firefox_file=firefox-$firefox_version.tar.bz2
firefox_url=https://download-installer.cdn.mozilla.net/pub/devedition/releases/$firefox_version/linux-x86_64/en-US/$firefox_file
In this case, we know Mozilla distributes this as a tarball using bzip2 compression, and that they make it available on their CDN at a predictable path for our operating system (linux), processor architecture (x86_64), and language (en-US). Future versions of this script could detect the local environment and adjust the download URL accordingly.
I posted about this yesterday, but it's extremely useful to build helper functions in bash, which you can reuse on many different projects. In this case, I knew Firefox was one piece of software I wanted to automate installing, but also that there'd be many more programs later. I took the time to write a helper function that can check on an existing pathway and then back up the files in that pathway before wiping them out:
check-create-backup() {
if [ ! -d "/opt/$1" ]; then
message notify "/opt/$1 does not exist. Creating directory at /opt/$1"
mkdir --verbose /opt/$1
else
message notify "Previous version of $1 exists. Deleting last backup and saving current version as backup."
rm --force --recursive /opt/$1-previous
mv --verbose /opt/$1 /opt/$1-previous
message success "Old version backed up to /opt/$1-previous"
fi
}
I wanted to point out two things about this simple script:
This is an even simpler example, but it's important to always check for the existence of a directory, before you try to place a file in that directory. In this case, anyone using Ubuntu should have a ~/Downloads folder already, but for users who are not, this is a useful guard clause:
if [ -d ~/Downloads ]; then
message notify "~/Downloads exists, proceeding to save file there."
else
message notify "No ~/Downloads folder exists yet, so creating one."
mkdir --verbose ~/Downloads
fi
The --verbose flag means the full result of a command is output, making it easier for the person running the script to see what is happening.
A symbolic link is a special file that points to another file or directory. If you come from a Windows background, then this is basically just a shortcut. In the Linux world, a symbolic link or symlink is an alias for another file. Why do we need one?
For this script we are creating a symbolic link in a directory that exists as an executable path. In other words, we're extracting our program files, then making a shortcut that will allow us to run our program while keeping our program files where we want them.
message notify "Extracting downloaded $firefox_file..."
tar --extract --bzip2 --file ~/Downloads/$firefox_file --directory /opt
message notify "Creating symlink for Firefox"
ln --force --symbolic --verbose /opt/firefox/firefox /usr/local/bin/firefox
In this case, we're putting our files in the /opt directory, which stands for 'options' because it has traditionally been used as a place to install add-on software that did not come with the operating system. This is exactly that.
We're symbolic linking or creating a shortcut to the /usr/local/bin directory because this directory is for normal user programs which were built locally and are not a part of the operating system's packages. We're installing this ourselves, without help of the package manager.
We want to be able to add our new Firefox program as a favorite, so it appears on our dock. In order to do that, we need to copy a configuration file that tells Ubuntu what to call our program, where to run it from, how to run it, and what icon it should have, as well as more:
# Save in file named firefox-developer.desktop
[Desktop Entry]
Name=Firefox Developer
GenericName=Firefox Developer Edition
Exec=/usr/local/bin/firefox
Terminal=false
Icon=/opt/firefox/browser/chrome/icons/default/default128.png
Type=Application
Categories=Application;Network;X-Developer;
Comment=Firefox Developer Edition Web Browser
Once you've saved that file in the same directory as your script, you can see how this part works:
cp --verbose ./firefox-developer.desktop /usr/share/applications/
chmod +x /usr/share/applications/firefox-developer.desktop
These two commands copy the file into its proper place and then make it executable.
We've done a lot with this script. The final part removes the downloaded zip files to save disk space.
message notify "Cleaning up downloaded files..."
rm --verbose ~/Downloads/$firefox_file
Even though it's only 70 or so lines of code, I'm really excited to share this with the world, because I think it will help address an issue in many different blog posts around getting the actual, real latest version. In order to write this script, I had to learn a lot of different bash commands, new syntax, new programs, and what feels like the most valuable skill of all: how to parse a JSON API to get just the data I need.
Keep on bashing on!