Offline Firmware

The LVFS is a public webservice designed to allow OEMs and ODMs to upload firmware easily, and for it to be distributed securely to tens of millions of end users. For some people, this simply does not work for various reasons:

  • They don’t trust the LVFS team, fwupd.org, GPG, certain OEMs or the CDN we use

  • They don’t want thousands of computers on an internal network downloading all the files over and over again

  • The internal secure network has no internet connectivity

For these cases there are a few different ways to keep your hardware updated, in order of simplicity:

Deploy in immutable image

If the OS is shipped as an image, you can just install the .cab files into /usr/share/fwupd/remotes.d/vendor/firmware and then enable vendor-directory.conf with fwupdmgr enable-remote vendor-directory.

Then once you have disabled the public LVFS using fwupdmgr disable-remote lvfs, running fwupdmgr will use only the cabinet archives you deploy in your immutable image. Of course, you’re deploying a larger image because you might have several unused firmware files included for each image, but this is how Google Chrome OS is using fwupd.

Mirror the public firmware

Using pulp-server

You can use Pulp to mirror the entire public contents of the LVFS (but never private or embargoed firmware). Create a repo pointing to PULP_MANIFEST and then sync that on a regular basis to download the metadata and firmware. The contents will not change any more frequently than every 4 hours, so please use a polling interval of at least that.

Using a helper script

There is a helper script sync-pulp.py that can be used if pulp-server is not installed:

./contrib/sync-pulp.py https://cdn.fwupd.org/downloads /mnt/mirror

You can then use a webserver such as Nginx or Apache to export /mnt/mirror as https://my.private.server/mirror.

Then, disable the LVFS by deleting or modifying /etc/fwupd/remotes.d/lvfs.conf and then create a /etc/fwupd/remotes.d/myprivateserver.conf file:

[fwupd Remote]
Enabled=true
Type=download
MetadataURI=https://my.private.server/mirror/firmware.xml.gz
FirmwareBaseURI=https://my.private.server/mirror

To instead mirror the private embargoed files, you can use:

./contrib/sync-pulp.py https://cdn.fwupd.org/downloads /mnt/mirror \
    --username=login@name.com \
    --token=XA1A5ZV7R65FUZBZ

Warning

Do not use your login password here! Generate a token when logged in to the LVFS using the User Profile settings.

To restrict the downloaded firmware to a specific tag (perhaps a vendor Best Known Configuration) use the following command:

./contrib/sync-pulp.py https://cdn.fwupd.org/downloads /mnt/mirror \
    --filter-tag=hughski-2020q1

Note, this command can be run safely multiple times with different --filter-tag values on the same destination directory; the superset of files will be downloaded.

Approved firmware

By exporting the entire LVFS (including the metadata, and metadata signature) you can still delay the deployment of firmware. Using the approved firmware list the client can filter out firmware that has not been tested by your organization without creating and signing a custom remote.

The allow-list of firmware can be set using:

fwupdmgr set-approved-firmware checksum1,checksum2,checksum3

Some versions of fwupd (>= 1.7.1) also support loading the list of checksums using a filename, e.g.

fwupdmgr set-approved-firmware filename

…where checksum is the SHA1 or SHA256 checksum of the .cab archive.

Note

The org.freedesktop.fwupd.set-approved-firmware PolicyKit action may require root permission to use.

Export a shared directory

Again, use PULP_MANIFEST to create a big directory holding all the firmware (currently ~50GB, but growing), and keep it synced.

Create a NFS or Samba share and export it to clients. Map the folder on each client, and then create a myprivateshare.conf file in /etc/fwupd/remotes.d:

[fwupd Remote]
Enabled=false
Title=Vendor
Keyring=none
MetadataURI=file:///mnt/myprivateshare/fwupd/remotes.d/firmware.xml.gz
FirmwareBaseURI=file:///mnt/myprivateshare/fwupd/remotes.d

Downloading manually

Download the .cab files that match your hardware and then install them on the target hardware via Ansible or Puppet using fwupdmgr install foo.cab. You can also use fwupdagent get-devices to get the existing firmware versions of all hardware in a format you can parse from scripts.

Building a custom remote

The local.py script allows you to create the metadata for a directory of .cab files.

Note

If you want to use signed metadata then please use jcat-tool firmware.xml.gz.jcat firmware.xml.gz CERTIFICATE PRIVATE_KEY. You will need to create a custom certificate and you’ll also need to distribute the the PKCS#7 certificate on all clients that are going to use the remote.

Create your own LVFS

The LVFS is a free software Python 3 Flask application and an instance can be set up internally if required. You have to configure much more this way, including generating your own GPG and PKCS#7 keys, uploading your own firmware and setting up users and groups on the server.

Doing all this has a few advantages, namely:

  • You can upload each firmware file and QA it, only pushing it to stable when ready

  • You don’t ship firmware which you didn’t upload

  • You can control the staged deployment, e.g. only allowing the same update to be deployed to 1000 servers per day

  • You can see failure reports from clients, to verify if the deployment is going well

  • You can see nice graphs about how many updates are being deployed across your organization

However, running a secure LVFS instance is a lot of work as PostgreSQL has to be used as a database, Redis has to also be set up as a queue manager, and Celery is used to manage the worker queues.

Although minor versions of the LVFS can be upgraded easily, you should review all the commits to lvfs-website to ensure that any manual migration is also performed.