Search

Ubuntu - Install Firefox and Thunderbird extensions from command line

Contents[Hide]

dropcap-mozilla-addon

If you are in charge of a park of Linux workstations, you may need to automate some installation procedures thru scripting.

Under Linux, most of the software installation procedures can be scripted very easily.

But when we are dealing with Firefox or Thunderbird, installation of extensions thru command line is not that easy. It is possible, but quite poorly documented.

This article explains how to install or remove Firefox and Thunderbird extensions through command line. Installations are possible in user or global mode. It also provides a script in charge of doing all the needed job.

This add-on installation script has been tested on Ubuntu 14.04 Amd64 LTS with Firefox 42 and Thunderbird 38.

It should work for any modern Linux distro.

1. Principle

Firefox and Thunderbird allow 2 installation modes for their extensions :

  • Global mode
    These extensions will be available for any user logged on the computer.
    They can only be installed or removed by root user.
  • User mode
    These extensions will only be available for current user.
    They can then be uninstalled by the user.

Every Firefox or Thunderbird extension is provided as a xpi file and is registered by an identifier string, called UID.

A xpi file is in fact a zip archive where the UID is found in a manifest file named install.rdf :

install.rdf
...
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:em="http://www.mozilla.org/2004/em-rdf#">
  <Description about="urn:mozilla:install-manifest">
    <em:id>{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}</em:id>
...

So, to install an extension for either Firefox or Thunderbird, you need to :

  1. Download the xpi archive
  2. Retrieve the UID string from the archive
  3. Select installation path according to installation mode (user or global)
  4. Copy the XPI archive to the installation path
  5. Extract it in a sub-folder named with the extension UID

After that, the extension will be automatically available either for current user or to any user logged on the workstation.

In case of a global extension, you can't remove it from the add-ons page.

ubuntu-mozilla-global-extension

2. Determine Extensions Folders

As every Firefox or Tunderbird extension provides an install.rdf file, you can easily find the extension directories on your system by searching these files.

Terminal
# locate install.rdf
/home/youraccount/.mozilla/firefox/f6by7e8n.default/extensions/fr-dicollecte @dictionaries.addons.mozilla.org/install.rdf
/home/youraccount/.mozilla/firefox/f6by7e8n.default/extensions/printPages2Pdf @reinhold.ripper/install.rdf
/home/youraccount/.thunderbird/8fuvikbr.default/extensions/fr-dicollecte @dictionaries.addons.mozilla.org/install.rdf
/home/youraccount/.thunderbird/8fuvikbr.default/extensions/{e2fda1a4-762b-4020-b5ad-a41df1933103}/install.rdf
/usr/lib/firefox-addons/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf
/usr/lib/thunderbird-addons/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf

We clearly see here that under Ubuntu 14.04 LTS, global extensions are located under :

Software Extension type Installation path
Firefox Global /usr/lib/firefox-addons/extensions
Firefox User /home/youraccount/.mozilla/firefox/f6by7e8n.default/extensions
Thunderbird Global /usr/lib/thunderbird-addons/extensions
Thunderbird User /home/youraccount/.thunderbird/8fuvikbr.default/extensions

 

In user mode, the xxxxxxxx.default directory is a random sub-directory. This random sub-directory name is available from a profiles.ini file located in the same folder.

/home/youraccount/.mozilla/firefox/profiles.ini
[General]
StartWithLastProfile=1

[Profile0]
Name=default
IsRelative=1
Path=f6by7e8n.default

3. Script to install/remove extensions

The add-ons installation script will follow these steps :

  1. determine whether we are dealing with a Thunderbird or a Firefox extension
  2. download the Add-on
  3. determine the extension UID
  4. copy the archive content to the target directory (per-user or global)

You just need to download and install the script under /usr/local/sbin :

Terminal
# sudo wget -O /usr/local/sbin/mozilla-extension-manager https://raw.githubusercontent.com/NicolasBernaerts/ubuntu-scripts/master/mozilla/mozilla-extension-manager
# sudo chmod +x /usr/local/sbin/mozilla-extension-manager

If your global extensions folders are differents, you will also need to adapt them in the script.

/usr/local/sbin/mozilla-extension-manager
#!/bin/bash
# ------------------------------------------------------------------------
#  Command line script to handle Firefox and Thunderbird extensions
#  Manage installation, upgrade and removal of system wide and user extensions
#  
#  Depends on unzip and wget
#
#  Manual available at http://bernaerts.dyndns.org/linux/74-ubuntu/271-ubuntu-firefox-thunderbird-addon-commandline
#
#  26/03/2013, V1.0 - Creation by N. Bernaerts
#  08/11/2015, V2.0 - Complete rewrite
#                     Add --install, --remove, --system and --user parameters
#  28/12/2016, V2.1 - Add Fedora compatibility thanks to Cedric Brandenbourger
#  07/02/2017  V2.2 - Remove zenity, as running zenity under root with wayland/mir is not working
#  17/05/2017  V2.3 - Rewrite, add path parameters, read UID and name in different ways
#  25/05/2017  V2.4 - Add --update and --list parameters
#  05/09/2017  V2.5 - Change handling of sudo commands for more compatibility
# ------------------------------------------------------------------------

# --------------
#   Functions
# --------------

function GetUID ()
{
    local LOC_XPI="$1"
    local LOC_UID=""

    # get extension UID from install.rdf in original format (<em:id>extension-uid</em:id>)
    LOC_UID=$(unzip -qq -p "${LOC_XPI}" install.rdf 2>/dev/null | grep "<em:id>" | head -n 1 | cut -d'>' -f2 | cut -d'<' -f1)

    # if extension UID not found, get it from install.rdf in new format (<RDF:Description ... em:id="{extension-uid}" ...) 
    [ "${LOC_UID}" = "" ] && LOC_UID=$(unzip -qq -p "${LOC_XPI}" install.rdf 2>/dev/null | grep "em:id=" | head -n 1 | sed "s/^.*em:id=\"\([^\"]*\).*$/\1/")

    # if extension UID not found, get it from key "id" in manifest.json ("id": "extension-uid") 
    [ "${LOC_UID}" = "" ] && LOC_UID=$(unzip -qq -p "${LOC_XPI}" manifest.json 2>/dev/null | grep "\"id\"" | head -n 1 | cut -d'"' -f4)

    # if extension UID not found, get it from key "name" in manifest.json ("name": "extension name") 
    [ "${LOC_UID}" = "" ] && LOC_UID=$(unzip -qq -p "${LOC_XPI}" manifest.json 2>/dev/null | grep "\"name\"" | head -n 1 | cut -d'"' -f4 | tr ' ' '-')}

    # display extension UID
    echo "${LOC_UID}"
}

function GetName ()
{
    local LOC_XPI="$1"
    local LOC_NAME=""

    # get extension name from install.rdf in original format (<em:id>extension-uid</em:id>)
    LOC_NAME=$(unzip -qq -p "${LOC_XPI}" install.rdf 2>/dev/null | grep "<em:name>" | head -n 1 | cut -d'>' -f2 | cut -d'<' -f1)

    # if extension name not found, get it from install.rdf in new format (<RDF:Description ... em:name="extension-name" ...) 
    [ "${LOC_NAME}" = "" ] && LOC_NAME=$(unzip -qq -p "${LOC_XPI}" install.rdf 2>/dev/null | grep "em:name=" | head -n 1 | sed "s/^.*em:name//" | cut -d'"' -f2)

    # if extension name not found, get it from key "name" in manifest.json ("name": "extension name") 
    [ "${LOC_NAME}" = "" ] && LOC_NAME=$(unzip -qq -p "${LOC_XPI}" manifest.json 2>/dev/null | grep "\"name\"" | head -n 1 | cut -d'"' -f4)

    # display extension name
    echo "${LOC_NAME}"
}

function GetVersion ()
{
    local LOC_XPI="$1"
    local LOC_VERSION=""

    # get extension version from install.rdf in original format (<em:version>version-number</em:version>)
    LOC_VERSION=$(unzip -qq -p "${LOC_XPI}" install.rdf 2>/dev/null | grep "<em:version>" | head -n 1 | cut -d'>' -f2 | cut -d'<' -f1)

    # if extension version not found, get it from install.rdf in new format (<RDF:Description ... em:version="version-number" ...) 
    [ "${LOC_VERSION}" = "" ] && LOC_VERSION=$(unzip -qq -p "${LOC_XPI}" install.rdf 2>/dev/null | grep "em:version=" | head -n 1 | sed "s/^.*em:version//" | cut -d'"' -f2)

    # if extension name not found, get it from key "name" in manifest.json ("name": "extension name") 
    [ "${LOC_VERSION}" = "" ] && LOC_VERSION=$(unzip -qq -p "${LOC_XPI}" manifest.json 2>/dev/null | grep "\"version\"" | head -n 1 | cut -d'"' -f4)

    # display extension name
    echo "${LOC_VERSION}"
}


# -------------------------------------------------------
#   Check tools availability
# -------------------------------------------------------

command -v unzip >/dev/null 2>&1 || { echo "Please install unzip"; exit 1; }
command -v wget >/dev/null 2>&1 || { echo "Please install wget"; exit 1; }

# -------------------
#   Default values
# -------------------

EXT_ACTION=""
EXT_FAMILY=""
EXT_TYPE=""
EXT_URL=""
EXT_PATH=""

# ---------------
#   Parameters
# ---------------

# if no argument, display help
if [ $# -eq 0 ] 
then
    echo "Tool to install or remove mozilla firefox or thunderbird extensions"
    echo "Extensions can be installed in user mode or system mode (needs sudo)"
    echo "Parameters are :"
    echo "  --update          Install or update extension to latest version"
    echo "  --install         Install new extension only"
    echo "  --remove          Remove installed extension"
    echo "  --list            List installed extensions"
    echo "  --firefox         Firefox extension"
    echo "  --thunderbird     Thunderbird extension"
    echo "  --user            Install/remove in user space (under $HOME)"
    echo "  --system          Install/remove in system space (under /usr)"
    echo "  --path <path>     Force extension installation path"
    echo "  <url>             URL of .xpi extension file (download button from mozilla extension site)"
    exit 1
fi

# loop to retrieve arguments
while test $# -gt 0
do
    case "$1" in
    "--update")       EXT_ACTION="update"; shift; ;;
    "--install")      EXT_ACTION="install"; shift; ;;
    "--remove")       EXT_ACTION="remove"; shift; ;;
    "--list")         EXT_ACTION="list"; shift; ;;
    "--firefox")      EXT_FAMILY="firefox"; shift; ;;
    "--thunderbird")  EXT_FAMILY="thunderbird"; shift; ;;
    "--user")         EXT_TYPE="user"; shift; ;;
    "--system")       EXT_TYPE="system"; shift; ;;
    "--path")         shift; EXT_PATH="$1"; shift; ;;
    *)                EXT_URL="$1"; shift; ;;
    esac
done

# check compulsory parameters
[ "${EXT_ACTION}" = "" ] && { echo "[error] You must specify the action mode as --update, --install or --remove"; exit 1; }
[ "${EXT_TYPE}" = "" ] && { echo "[error] You must specify extension type as --user or --system"; exit 1; }

# check extension URL
[ "${EXT_URL}" = "" -a "${EXT_ACTION}" != "list" ] && { echo "[error] You must specify the extension URL"; exit 1; }

# -------------------------
#   Get extension family
# -------------------------

# if extension family is not set
if [ "${EXT_FAMILY}" = "" -a "${EXT_URL}" != "" ]
then
    # determine if we are dealing with a firefox extension
    IS_FIREFOX=$(echo "${EXT_URL}" | grep "/firefox")
    [ "${IS_FIREFOX}" != "" ] && EXT_FAMILY="firefox"  

    # determine if we are dealing with a thunderbird extension
    IS_THUNDERBIRD=$(echo "${EXT_URL}" | grep "/thunderbird")
    [ "${IS_THUNDERBIRD}" != "" ] && EXT_FAMILY="thunderbird"
fi

# check extension family is defined
[ "${EXT_FAMILY}" = "" ] && { echo "[error] Could not determine extension. Please set as --firefox or --thunderbird"; exit 1; }

# ------------------------------------
#   Set installation root and mode
# ------------------------------------

# if installation in user mode
if [ "${EXT_TYPE}" = "user" ]
then
    # set user space installation path
    [ "${EXT_FAMILY}" = "firefox" ] && PATH_USER="$HOME/.mozilla/firefox" || PATH_USER="$HOME/.thunderbird"

    # get profile path
    PROFILE_PATH=$(grep "Path=" "${PATH_USER}/profiles.ini" | head -n 1 | cut -d'=' -f2)
 
    # set user profile path
    [ "${PROFILE_PATH}" != "" ] && EXT_PATH="${PATH_USER}/${PROFILE_PATH}/extensions"

    # if no profile, error message
    [ "${EXT_PATH}" = "" ] && echo "[error] User profile doesn't exist"

# else, if system installation path has not been set, analyse distribution
elif [ "${EXT_PATH}" = "" ]
then
    # detect architecture
    ARCHITECTURE=$(arch)

    # if system is debian based (Debian, Ubuntu, Fedora, ...)
    if [ -f /etc/debian_version ]
    then
        [ "${EXT_FAMILY}" = "firefox" ] && EXT_PATH="/usr/lib/firefox-addons/extensions" || EXT_PATH="/usr/lib/thunderbird-addons/extensions"

    # else, if system is Fedora 64
    elif [ "${ARCHITECTURE}" = "x86_64" ]
    then
        [ "${EXT_FAMILY}" = "firefox" ] && EXT_PATH="/usr/lib64/firefox/extensions" || EXT_PATH="/usr/lib64/thunderbird/extensions"

    # else set for Fedora 32 
    else
        [ "${EXT_FAMILY}" = "firefox" ] && EXT_PATH="/usr/lib/firefox/extensions" || EXT_PATH="/usr/lib/thunderbird/extensions"
    fi
fi

# set user profile path or exit if it doesn't exist
[ "${EXT_PATH}" = "" ] && { echo "[error] Extension installation path could not be determined"; exit 1; }

# -----------------------------------------
#   Download .xpi file and get main data
# -----------------------------------------

# set temporary file
ADDON_XPI=$(mktemp "addon-XXXXXXXX.xpi") && rm "${ADDON_XPI}"

# if list of extensions
if [ "${EXT_URL}" != "" ]
then
    # download extension if not local (file:///)
    if [[ "${EXT_URL}" =~ ^file:///.* ]]
    then
        cp "${EXT_URL#file://}" "${ADDON_XPI}"
    else
        wget --quiet -O "${ADDON_XPI}" "${EXT_URL}"
    fi

    # extract extension UID
    EXT_UID=$(GetUID "${ADDON_XPI}")

    # extract extension name
    EXT_NAME=$(GetName "${ADDON_XPI}")

    # extract extension name
    EXT_VERSION=$(GetVersion "${ADDON_XPI}")
fi

# -----------------------------------
#   Installation, Update or Removal
# -----------------------------------

# if list of extensions
if [ "${EXT_ACTION}" = "list" ]
then
    # list installed XPI files
    ARR_XPI=( $(ls ${EXT_PATH}/*.xpi) )

    # loop thru installed XPI
    for FILE_XPI in "${ARR_XPI[@]}"
    do
        # extract XPI extension UID
        EXT_UID=$(GetUID "${FILE_XPI}")

        # extract XPI extension name
        EXT_NAME=$(GetName "${FILE_XPI}")

        # extract XPI extension name
        EXT_VERSION=$(GetVersion "${FILE_XPI}")

        # display information
        echo "[present] ${EXT_FAMILY} ${EXT_TYPE} extension ${EXT_NAME}, uid=${EXT_UID}, version=${EXT_VERSION}"
    done

# else, if extension UID could not be determined, error
elif [ "${EXT_UID}" = "" ]
then
    # error message
    echo "[error] Could not retrieve extension file from server"

# else, if action is removal and extension is not installed
elif [ "${EXT_ACTION}" = "remove" -a ! -d "${EXT_PATH}/${EXT_UID}" ]
then
    # error message
    echo "[warning] ${EXT_FAMILY} ${EXT_TYPE} extension ${EXT_NAME}, uid=${EXT_UID} is not installed"

# else, if action is removal
elif [ "${EXT_ACTION}" = "remove" ]
then
    # remove system extension directory
    [ "${EXT_TYPE}" = "user" ] && rm "${EXT_PATH}/../${EXT_UID}.xpi" || sudo rm "${EXT_PATH}/../${EXT_UID}.xpi"
    [ "${EXT_TYPE}" = "user" ] && rm -R "${EXT_PATH}/${EXT_UID}" || sudo rm -R "${EXT_PATH}/${EXT_UID}"

    # end message
    echo "[success] ${EXT_ACTION} ${EXT_FAMILY} ${EXT_TYPE} extension ${EXT_NAME}, uid=${EXT_UID}, version=${EXT_VERSION}"

# else, if action is installation only and extension already installed
elif [ "${EXT_ACTION}" = "install" -a -d "${EXT_PATH}/${EXT_UID}" ]
then
    # display installation status
    echo "[warning] ${EXT_FAMILY} ${EXT_TYPE} extension ${EXT_NAME}, uid=${EXT_UID} is already installed"

# else, action is update or installation
else
    # if extension already installed,
    if [ -d "${EXT_PATH}/${EXT_UID}" ]
    then
        # update extension
        EXT_ACTION="update"
        
        # remove previous extension version
        [ "${EXT_TYPE}" = "user" ] && rm "${EXT_PATH}/../${EXT_UID}.xpi" || sudo rm "${EXT_PATH}/../${EXT_UID}.xpi"
        [ "${EXT_TYPE}" = "user" ] && rm -R "${EXT_PATH}/${EXT_UID}" || sudo rm -R "${EXT_PATH}/${EXT_UID}"
    else
        # install new extension
        EXT_ACTION="install"
    fi

    # copy .xpi to system extension path
    [ "${EXT_TYPE}" = "user" ] && cp -f "${ADDON_XPI}" "${EXT_PATH}/../${EXT_UID}.xpi" \
                               || sudo cp -f "${ADDON_XPI}" "${EXT_PATH}/../${EXT_UID}.xpi"

    # extract extension to system extension path
    [ "${EXT_TYPE}" = "user" ] && unzip -qq "${ADDON_XPI}" -d "${EXT_PATH}/${EXT_UID}" \
                               || sudo unzip -qq "${ADDON_XPI}" -d "${EXT_PATH}/${EXT_UID}"

    # end message
    echo "[success] ${EXT_ACTION} ${EXT_FAMILY} ${EXT_TYPE} extension ${EXT_NAME}, uid=${EXT_UID}, version=${EXT_VERSION}"
fi

# -------------
#   Cleanup
# -------------

# remove downloaded file
rm -f "${ADDON_XPI}"

4. Install your first Add-On

Now that the script is installed, we can test it.

Go to any add-on page of https://addons.mozilla.org/ and copy the https://... link provided by the Add to Firefox or Download Now button.

ubuntu-firefox-add-button   ubuntu-thunderbird-add-button

Then from a console, just type :

Terminal
# mozilla-extension-manager --install https://addons.mozilla.org/firefox/downloads/latest/665872/addon-665872-latest.xpi
...
user extension 6asa42dfa4784fsf368g @youtubeconverter.me installed

Please note that if you install an extension as a global one, some commands will be executed as root (sudo).

Terminal
# mozilla-extension-manager --install --global https://addons.mozilla.org/firefox/downloads/latest/665872/addon-665872-latest.xpi
[sudo] password for youraccount:
...

global extension 6asa42dfa4784fsf368g @youtubeconverter.me installed

You can launch Firefox or Thunderbird and check in the Add-ons menu.

Note that under Firefox, if you add a user extension thru command line, you'll get a validation message on the first launch following installation :

firefox extension confirmation

5. Remove an extension

To remove an extension installed by the script, just add --remove to the command line.

Please note that an extension installed as per-user or global should be removed with the same option.

Terminal
# mozilla-extension-manager --remove --global https://addons.mozilla.org/firefox/downloads/latest/665872/addon-665872-latest.xpi
[sudo] password for nicolas:
...

global extension 6asa42dfa4784fsf368g @youtubeconverter.me removed

 

Hope it helps.

Signature Technoblog

This article is published "as is", without any warranty that it will work for your specific need.
If you think this article needs some complement, or simply if you think it saved you lots of time & trouble,
just let me know at This email address is being protected from spambots. You need JavaScript enabled to view it.. Cheers !

icon linux icon debian icon apache icon mysql icon php icon piwik icon googleplus