Setting Up An Update Server

The goal of this document is to provide instructions for installing a locally-served Firefox update for testing purposes. Note that these are not instructions for how to create or run a production update server. This method of serving updates is intended to trick Firefox into doing something that it normally wouldn’t do: download and install the same update over and over again. This is useful for testing but is obviously not the correct behavior for a production update server.

Obtaining an update MAR

Updates are served as MAR files. There are two common ways to update using a MAR: consume a prebuilt one, or consume one you’ve built yourself.

Using a prebuilt MAR

Prebuilt Nightly MARs can be found here on archive.mozilla.org. Be sure that you use the one that matches your machine’s configuration. For example, if you want the Nightly MAR from 2019-09-17 for a 64 bit Windows machine, you probably want the MAR located at https://archive.mozilla.org/pub/firefox/nightly/2019/09/2019-09-17-09-36-29-mozilla-central/firefox-71.0a1.en-US.win64.complete.mar.

Prebuilt MARs for release and beta can be found here. Beta builds are those with a b in the version string. After locating the desired version, the MARs will be in the update directory. You want to use the MAR labelled complete, not a partial MAR. Here is an example of an appropriate MAR file to use: https://archive.mozilla.org/pub/firefox/releases/69.0b9/update/win64/en-US/firefox-69.0b9.complete.mar.

Serving the prebuilt MAR from a URL

There’s a mach command for that! Try:

$ ./mach update serve -v <URL to MAR>

That will start an HTTP server that will HTTP redirect (302 Found) to the given URL. See Installing the update MAR for how to configure enterprise policies to use this server.

Building a MAR

Building a MAR locally is more complicated. Part of the problem is that MARs are signed by Mozilla and so you cannot really build an “official” MAR yourself. This is a security measure designed to prevent anyone from serving malicious updates. If you want to use a locally-built MAR, the copy of Firefox being updated will need to be built to allow un-signed MARs. See Building Firefox for more information on building Firefox locally. In order to use a locally built MAR, you will need to put this line in the mozconfig file in root of the build directory (create it if it does not exist):

ac_add_options --enable-unverified-updates

Firefox should otherwise be built normally. After building, you may want to copy the installation of Firefox elsewhere. If you update the installation without moving it, attempts at further incremental builds will not work properly, and a clobber will be needed when building next. To move the installation, first call ./mach package, then copy <obj dir>/dist/firefox elsewhere. The copied directory will be your install directory.

If you are running Windows and want the Mozilla Maintenance Service to be used, there are a few additional steps to be taken here. First, the maintenance service needs to be “installed”. Most likely, a different maintenance service is already installed, probably at C:\Program Files (x86)\Mozilla Maintenance Service\maintenanceservice.exe. Backup that file to another location and replace it with <obj dir>/dist/bin/maintenanceservice.exe. Don’t forget to restore the backup when you are done. Next, you will need to change the permissions on the Firefox install directory that you created. Both that directory and its parent directory should have permissions preventing the current user from writing to it.

Now that you have a build of Firefox capable of using a locally-built MAR, it’s time to build the MAR. First, build Firefox the way you want it to be after updating. If you want it to be the same before and after updating, this step is unnecessary and you can use the same build that you used to create the installation. Then run these commands, substituting <obj dir>, <MAR output path>, <version> and <channel> appropriately:

$ ./mach package
$ touch "<obj dir>/dist/firefox/precomplete"
$ MAR="<obj dir>/dist/host/bin/mar.exe" MOZ_PRODUCT_VERSION=<version> MAR_CHANNEL_ID=<channel> ./tools/update-packaging/make_full_update.sh <MAR output path> "<obj dir>/dist/firefox"

For macOS you should use these commands:

$ ./mach package
$ touch "<obj dir>/dist/firefox/Firefox.app/Contents/Resources/precomplete"
$ MAR="<obj dir>/dist/host/bin/mar.exe" MOZ_PRODUCT_VERSION=<version> MAR_CHANNEL_ID=<channel> ./tools/update-packaging/make_full_update.sh <MAR output path> "<obj dir>/dist/firefox/Firefox.app"

For a local build, <channel> can be default, and <version> can be the value from browser/config/version.txt (or something arbitrarily large like 2000.0a1).

Note: It can be a bit tricky to get the make_full_update.sh script to accept paths with spaces.

Serving the update MAR file

Try:

$ ./mach update serve -v <MAR output path>

Installing the update MAR

You may want to start by deleting any pending updates to ensure that no previously found updates interfere with installing the desired update. You can use this command with Firefox’s browser console to determine the update directory:

ChromeUtils.importESModule("resource://gre/modules/FileUtils.sys.mjs").FileUtils.getDir("UpdRootD", []).path

Once you have determined the update directory, close Firefox, browse to the directory and remove the subdirectory called updates.

Next, you need to change the update URL to point to the local XML file. This can be done most reliably with an enterprise policy. The policy file location depends on the operating system you are using.
Windows/Linux: <install dir>/distribution/policies.json
macOS: <install dir>/Contents/Resources/distribution/policies.json
Create the distribution directory, if necessary, and put this in policies.json:
{
  "policies": {
    "AppUpdateURL": "http://127.0.0.1:8000/update.xml"
  }
}

Now you are ready to update! Launch Firefox out of its installation directory and navigate to the Update section about:preferences. You should see it downloading the update to the update directory. Since the transfer is entirely local this should finish quickly, and a “Restart to Update” button should appear. Click it to restart and apply the update.