Shipping Electron Apps From CI

Developers want deployment of their application to be handled by their CI/CD service, often in the cloud where hardware is managed for you. Shipping Electron apps from a continuous build was already complicated, and this summer’s changes to Microsoft’s signing rules make it even moreso. Starting from May 2023 new certificates may no longer be stored in files. You have to use hardware security modules instead. Although this can improve security by avoiding the need to revoke stolen keys, it can seriously complicate your setup. Hardware headaches can be eliminated by using a cloud signing service, so let’s take a look at different ways to do it.

Intro

Deploying apps from CI/CD requires solving at least two hard problems:

  1. Managing a multi-step build on several different operating systems.
  2. Code signing.

It’s especially awkward if you’re using a Windows extended validation certificate, or if you bought your certificates after the summer of 2023. In those cases Microsoft mandates that signing keys are held in a hardware security module (HSM). This is traditionally a USB device but when using cloud-managed CI like GitHub Actions, Azure DevOps, Circle CI or TeamCity Cloud you don’t have the ability to plug in devices.

Fortunately certificate authorities have launched a new generation of cloud-hosted HSMs. Services like SSL.com eSigner or DigiCert KeyLocker will compute signatures for you, and you can access them using just some textual credentials. But how to use these services with tools like Electron Forge or Electron Builder isn’t so obvious.

To do it with Electron Builder you will need to follow these instructions to replace the core signing logic of the tool, as well as of course install extra software on a Windows CI build agent. For Forge, there unfortunately doesn’t seem to be any direct support yet.

Whilst it’s always possible to make these tools do what you need given sufficient effort, you’d still have the hassle of properly configuring and maintaining Windows/Mac build agents with the necessary programs. It would be good if there was a way to build and deploy an app from a cheap Linux build agent, complete with cloud signing and handling of Apple notarization as well.

With Hydraulic Conveyor you can fix both problems at once. Conveyor is a tool similar to Forge which works for non-Electron apps too. It has several features that can simplify your deployment:

  1. It has extensive support for Windows cloud signing services/HSMs, as well as Apple notarization.
  2. It can do the entire packaging and deployment process for every target OS in one step, from any OS. In other words, you can do it from your developer laptop or cheap Linux builders. You don’t need Mac or Windows machines anymore.
  3. It can do forced updates on every launch, to ensure users stay up to date.
  4. Your app uses platform native software update solutions instead of Squirrel, using the Windows Deployment Engine on Windows for background updates and the Sparkle Framework on macOS. Whereas the Squirrel engine Electron apps use by default is largely unmaintained, these update engines receive regular fixes and improvements.

All this is possible because Conveyor does everything itself without relying on any native tools, so you don’t need to wrangle Windows-specific EXEs from your certificate authority or master a vast array of plugins.

You can also migrate projects from Forge/Squirrel. We’re working on a tutorial for how to do this and will post it on this blog when we’re done, but if you’d like a sneak preview just get in touch via email or using the form below. Conveyor is a local tool you can download without signups, is free for open source projects and a cheap monthly subscription for commercial apps.

Let’s see how to use it to deploy a Windows Electron app, and we’ll sign using a cloud signing service.

Step 1. Write your config file

Packaging with Conveyor is easy, but it’s not the same as with Electron Forge. You can read this tutorial about packaging a real app like GitHub Desktop, or if you want something simpler and more interactive take the tutorial. The user guide may also be helpful.

If you run conveyor generate electron hello-world to scaffold a simple app, the resulting config will look like this:

include required("/stdlib/electron/electron.conf")

// Import name, version, electron version etc from your package.json file.
package-json {
  include required("package-lock.json")
}

// Config file documentation: https://conveyor.hydraulic.dev/latest/configs
app {
  display-name = "Hello World"
  rdns-name = hello-world
  site.base-url = "localhost:3000"

  // Check for and apply updates synchronously on every app launch instead of in the background.
  // Consider removing this line for your own app!
  updates = aggressive

  // Use the Electron logo as an icon.
  //
  // Conveyor can generate a simple icon for you based on your project display name.
  // To try it out just delete this line. For more info see https://conveyor.hydraulic.dev/latest/configs/#icons
  icons = icons/icon.svg

  // For iteration speed. Remove for release.
  compression-level = low
}

By the end of this process you should have a conveyor.conf file tailored to your app that takes your Electron app files and produces self-updating packages for each OS you wish to support. But unfortunately they’ll be self-signed, and so users will get security errors and warnings from their OS. Let’s fix that.

Step 2. Configure a cloud signing service

Pick a cloud signing service like eSigner or KeyLocker. Now sign up for an account and generate credentials. Taking eSigner as an example, you’ll need a username, password the “TOTP secret” and the signing key alias.

You can now add a few lines of config to make Conveyor use your account:

app {
  windows {
    signing-key {
      ssl-esigner {
        username = your-username
        password = ${env.ESIGNER_PASSWORD}
        totp-secret = ${env.ESIGNER_TOTP_SECRET}
      }
    }
    signing-key-alias = your-key-alias
  }
}

This will read your password and TOTP secret from environment variables. Most CI systems let you pass secrets to deployment steps in this manner. Alternatively, you could split these out to a separate file that’s only present when building from CI using the convenient HOCON syntax for JSON, which supports file inclusion.

Now when you run conveyor make site (to build) or conveyor make copied-site (to also deploy), everything will be properly signed. On Windows that means signing:

  • The ~500kb installer EXE. This program is interaction-free and downloads, installs then starts your app. Thanks to features of the Windows Deployment Engine files that the user already has (i.e. from another Electron app) will be reused instead of downloaded, which can save significant time. This also acts as the basis of the delta update mechanism, and rerunning the installer will simply update the user to the latest version then start it.
  • The MSIX package file that will be handed to Windows for management.
  • All the EXE and DLL files within the package. This kind of “deep signing” is often missed by other tools and the lack of it can cause problems with virus scanners.

The signatures will be timestamped, ensuring your app still works even if your code signing certificate expires, and Conveyor watches out for common traps and errors. For example, it will warn you if your certificate expiry date is coming up.

Step 3. Profit

That’s it, actually! There is no step 3.

We still have some useful things to tell you though.

You will probably want to set up Apple signing and notarization, which is a very similar process. Conveyor will download Electron for both Intel and ARM macOS, create a bundle with your app, convert icons, sign it and notarize it, all in parallel. You just have to give it the keys you get from Apple’s developer website. Like with Windows signing, Conveyor has a ton of little usability and quality-of-life polishes to help you through this process. A simple example we fixed the other day is that when Apple change their developer agreement and need you to re-agree to it, the error message their servers return isn’t clear on what action to take - Conveyor replaces it with a more helpful message.

If you use GitHub Actions then we have a deployment action and tutorial for using it.

You might be interested in this article by one of our users on packaging Electron apps.

In a large organization you may already have code signing keys stored in a USB device or larger (non USB) HSM. Conveyor can use any device for which you have a PKCS#11 driver, so can be easily integrated into corporate code signing environments.

Finally, Conveyor doesn’t just build packages and updates. It also creates a simple download website for you, and can upload all the generated files to remote web servers, S3 buckets, GitHub or the Microsoft Store. In other words it takes you straight from your app HTML/JS files through to a working deployment.

.