robotnix - Build Android (AOSP) using Nix

Robotnix is a build system for Android (AOSP) images on top of the Nix package manager. Instead of having to follow complicated instructions to install several build tools and fetch source code from multiple sources, robotnix encapsulates all this complexity in a simple Nix expression.

The documentation included here should help inform you how to create your configuration file, build your Android image, and install the image onto your phone.

If you find parts of this manual confusing, please create an issue, or (even better) create a pull request on Github. Robotnix users and developers can also be contacted on #robotnix:nixos.org on Matrix.

Configuration

Similarly to NixOS, robotnix configurations are "Nix expressions" specified using the "Nix language." Most end-user uses of robotnix should not require learning the Nix language, besides the very basics of syntax.

Inline Configuration

Robotnix can be built using configuration specified on the command line using --arg configuration ... as an argument to nix-build. For example, if the current directory contains a checked out copy of robotnix, the following will produce a vanilla image for the crosshatch device (Pixel 3 XL):

$ nix-build --arg configuration '{ device="crosshatch"; flavor="vanilla"; }' -A img

By default, nix-build uses the default.nix in the current directory as the Nix entry point. If robotnix is checked out in another directory, such as $HOME/src/robotnix, the above command could instead be

$ nix-build $HOME/src/robotnix --arg configuration '{ device="crosshatch"; flavor="vanilla"; }' -A img

Configuration Files

A configuration file should be created for anything more complicated than the very simple configurations that could be conveniently specified on the command line. The following is an example robotnix configuration that could be saved to (for example) crosshatch.nix.

{
  # Most uses of robotnix should at least set the device and flavor options.
  device = "crosshatch";
  flavor = "vanilla";

  # variant = "user"; # Other options are userdebug, or eng. Builds used in production should use "user"

  # Signing should be enabled for builds used in production.
  signing.enable = true;
  # When signing is enabled, keyStorePath should refer to a path containing keys created by `genereteKeysScript`
  # This is used to automatically obtain key fingerprints / metadata from the generated public keys.
  # Alternatively, it may be possible to manually set the required options like `signing.avb.fingerprint` or `apps.prebuilt.<name>.fingerprint` to avoid including this path.
  signing.keyStorePath = "/var/secrets/android-keys";

  # Additional modules can be enabled and included in the build. See individual module documentation
  apps.fdroid.enable = true;
  microg.enable = true;
}

The --arg configuration ... option for nix-build can also refer to a .nix file containing the robotnix configuration. If the above configuration was saved to crosshatch.nix in the local directory, an image could be built using the following command:

$ nix-build --arg configuration ./crosshatch.nix -A img

See my own configuration under example.nix for inspiration. Robotnix module options are described in this documentation here. Reference documentation of the available options is here.

Flakes (experimental)

Nix flakes are an upcoming feature of Nix that provides an alternative configuration structure for use with the new nix command. It can provide the benefit of explicitly pinning your robotnix configuration against a particular revision of the robotnix repository. The feature remains experimental for the time-being, and is not currently the recommended way to use robotnix for new users.

Robotnix provides an example nix flake template, which can be used to prepopulate the current directory with the command nix flake init -t github:danielfullmer/robotnix.

Example usage:

$ mkdir flake-test
$ cd flake-test
$ nix flake init -t github:danielfullmer/robotnix
$ # Edit flake.nix in current directory
$ nix build .#robotnixConfigurations.dailydriver.img

Flavors

In normal usage, the user needs to select a robotnix "flavor" in their configuration file by setting the flavor option. Flavors may change the default settings of some other modules, which might not match the default setting shown on the Options reference page. As an example, the GrapheneOS flavor enables apps.vanadium.enable by default.

Currently, the vanilla and GrapheneOS flavors are based on Android 11, while the LineageOS flavor uses either Android 10 or Android 11, depending on the device. If a robotnix flavor supports multiple Android versions (either older or experimental newer versions), this can be overridden by setting (for example) androidVersion = 10.

Vanilla

The vanilla flavor is meant to represent a (mostly) unaltered version of the AOSP code published by Google. Vanilla AOSP support may be enabled by setting flavor = "vanilla";. It does, however, include some small fixes and usability improvements. Enabling the vanilla flavor also enables the chromium webview/app by default as well.

The vanilla flavor in robotnix currently supports only Pixel phones (>= Pixel 3). Older Pixel phones (e.g. marlin / Pixel 1 XL) may be still be built by overriding androidVersion = 10;. However, these might be removed in the future as they are no longer receiving device updates from Google.

Like GrapheneOS described below, the vanilla flavor retains working support for Android Verified Boot, and allows a user to re-lock the bootloader using the user's own generated root of trust.

GrapheneOS

GrapheneOS is a privacy and security focused mobile OS with Android app compatibility developed as a non-profit open source project. GrapheneOS support may be enabled by setting flavor = "grapheneos";. Enabling the GrapheneOS flavor will also enable the Vanadium app/webview and Seedvault robotnix modules. Additionally, the upstream GrapheneOS updater is disabled, but the robotnix updater app (based on the GrapheneOS updater app) can be enabled by setting apps.updater.enable = true; and apps.updater.url = "...";.

The user should understand that enabling certain robotnix modules may have security implications as they produce a may produce a larger attack surface than is intended by the GrapheneOS project. Some modules, such as the MicroG and the F-Droid privileged extension, have been explicitly rejected by upstream GrapheneOS. If the user wishes to enable these modules, they should understand and be willing to accept the usability/security tradeoffs.

GrapheneOS releases are tagged in robotnix, but before 2021.05.16.04, the date suffix (YY.MM.DD.HH) did not match the one used in the upstream release. Only the latest GrapheneOS release is included with robotnix, even if that release is only a "beta" release upstream. If you would prefer to stick to only stable releases, wait until the release is marked "stable" upstream.

Before reporting bugs to upstream GrapheneOS, please ensure that you can reproduce your issue using the official GrapheneOS images. Alternatively, feel free to ask about your issue on the #robotnix:nixos.org channel on Matrix.

LineageOS

LineageOS is a free and open-source operating system for various devices, based on the Android mobile platform. LineageOS support may be enabled by setting flavor = "lineageos";. At the time of writing, this includes support for ~160 devices.

Robotnix includes support for both LineageOS 17.1 as well as LineageOS 18.1. By default, robotnix will select the latest supported version for the device specified in the configuration. This can be overridden by setting androidVersion to either 10 or 11, for LineageOS 17.1 and 18.1, respectively.

Since LineageOS does not produce tagged releases like vanilla AOSP or GrapheneOS, we periodically take snapshots of the upstream repositories and include metadata in robotnix which pins the source repositories to particular revisions. This metadata can be found under flavors/lineageos/*/*.json.

LineageOS support in robotnix should be considered "experimental," as it does yet have the same level of support provided for vanilla and grapheneos flavors. LineageOS source metadata may be updated irregularly in robotnix, and certain modules (such as the updater) are not guaranteed to work. Moreover, LineageOS does not appear to provide the same level of security as even the vanilla flavor, with dm-verity/AVB, userdebug as the default variant, and vendor files with unclear origin. LineageOS support is still valuable to include as it extends support to a much wider variety of devices, and provides the base that many other Android ROMs use to customize. Contributions and fixes from LineageOS users are especially welcome!

For devices with "boot-as-recovery", the typical LineageOS flashing process involves first producing a boot.img and ota, flashing boot.img with fastboot, and then sideloading the ota in recovery mode. The boot.img and ota targets can be built using nix-build ... -A bootImg or nix-build ... -A ota, respectively. Check the upstream documentation for your particular device before following the above instructions.

Anbox

Anbox is a Free and open-source container-based approach at running Android on Linux systems. Anbox support may be enabled by setting flavor = "anbox";.

At the time of writing, support is experimental. Given that Anbox is based on an older Android release (7), support for Robotnix options is not guaranteed.

F-Droid

The following configuration will enable the F-Droid app and the F-Droid privileged extension.

{
    apps.fdroid.enable = true;
}

The F-Droid privileged extension enables F-Droid to install and delete apps without needing "Unknown Sources" to be enabled (e.g. just like Google Play does). It also enables F-Droid to install updates in the background without the user having to click "install".

Adding F-Droid repositories

F-Droid can manage multiple repositories to fetch apps from. These can be set up manually in the app, but with robotnix it is also possible to preload F-Droid with some repositories at build time.

To add an F-Droid repository you need at least the URL and a public key. Obtaining the URL is in general very easy but it's not obvious where to obtain the public key.

We'll take the microG repository as an example. The repository is located here. To obtain the repository index download the index.xml file from the repository root:

$ curl -LO https://microg.org/fdroid/repo/index.xml

The content of this XML file contains metadata about the repository, including the public key:

<?xml version="1.0" encoding="utf-8"?>
<fdroid>
	<repo name="microG F-Droid repo" icon="fdroid-icon.png" url="https://microg.org/fdroid/repo" version="19" timestamp="1606314565" pubkey="308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37">
		<description>This is a repository of microG apps to be used with F-Droid. Applications in this repository are signed official binaries built by the microG Team from the corresponding source code. </description>
	</repo>
	<application id="...">
		<!-- ... list of contained applications ... -->
	</application>
</fdroid>

Simply copy the name and pubkey fields and use them as the name and public key for the corresponding Nix expression respectively.

{
  apps.fdroid.additionalRepos = {
    "microG F-Droid repo" = {
      enable = true;
      url = "https://microg.org/fdroid/repo";
      pubkey = "308202ed308201d5a003020102020426ffa009300d06092a864886f70d01010b05003027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a656374301e170d3132313030363132303533325a170d3337303933303132303533325a3027310b300906035504061302444531183016060355040a130f4e4f47415050532050726f6a65637430820122300d06092a864886f70d01010105000382010f003082010a02820101009a8d2a5336b0eaaad89ce447828c7753b157459b79e3215dc962ca48f58c2cd7650df67d2dd7bda0880c682791f32b35c504e43e77b43c3e4e541f86e35a8293a54fb46e6b16af54d3a4eda458f1a7c8bc1b7479861ca7043337180e40079d9cdccb7e051ada9b6c88c9ec635541e2ebf0842521c3024c826f6fd6db6fd117c74e859d5af4db04448965ab5469b71ce719939a06ef30580f50febf96c474a7d265bb63f86a822ff7b643de6b76e966a18553c2858416cf3309dd24278374bdd82b4404ef6f7f122cec93859351fc6e5ea947e3ceb9d67374fe970e593e5cd05c905e1d24f5a5484f4aadef766e498adf64f7cf04bddd602ae8137b6eea40722d0203010001a321301f301d0603551d0e04160414110b7aa9ebc840b20399f69a431f4dba6ac42a64300d06092a864886f70d01010b0500038201010007c32ad893349cf86952fb5a49cfdc9b13f5e3c800aece77b2e7e0e9c83e34052f140f357ec7e6f4b432dc1ed542218a14835acd2df2deea7efd3fd5e8f1c34e1fb39ec6a427c6e6f4178b609b369040ac1f8844b789f3694dc640de06e44b247afed11637173f36f5886170fafd74954049858c6096308fc93c1bc4dd5685fa7a1f982a422f2a3b36baa8c9500474cf2af91c39cbec1bc898d10194d368aa5e91f1137ec115087c31962d8f76cd120d28c249cf76f4c70f5baa08c70a7234ce4123be080cee789477401965cfe537b924ef36747e8caca62dfefdd1a6288dcb1c4fd2aaa6131a7ad254e9742022cfd597d2ca5c660ce9e41ff537e5a4041e37";
    };
  };
}

The name can really be anything, but the one that is provided here is the one that shows up before refreshing the F-Droid repo list for the first time, so if you want that to look pretty, give it a pretty name here.

The apps.fdroid.additionalRepos variable is only used to prepopulate the internal database of F-Droid repositories upon the first run of the application. Changes to this variable will not have any effect on phones that have already started the F-Droid application.

Seedvault Backup

Seedvault is a backup application for the Android Open Source Project. The following configuration will enable the Seedvault:

{
  apps.seedvault.enable = true;
}

Backing Up

Normally, the settings for the Seedvault backup application is available under "Settings -> System -> Backup". However, if you have flashed a new ROM including Seedvault over one which did not have Seedvault initially (without wiping userdata), you may need to manually set the backup transport using adb.

$ adb shell bmgr enable true
$ adb shell bmgr transport com.stevesoltys.seedvault.transport.ConfigurableBackupTransport

Restoring

The GrapheneOS and LineageOS flavors provide the option to use Seedvault upon first boot using the SetupWizard. The vanilla flavor currently does not use SetupWizard, so the restore activity must be manually started using:

adb shell am start-activity -a com.stevesoltys.seedvault.RESTORE_BACKUP

See the following issue: seedvault#85

MicroG

MicroG is a free-as-in-freedom re-implementation of Google’s proprietary Android user space apps and libraries. MicroG support may be enabled using:

{
  microg.enable = true;
}

MicroG requires a patch to the Android system to allow spoofing Google's signature for MicroG's reimplemented version of Google services. The patch included in robotnix locks down this signature spoofing functionality to only the MicroG application and the Google signatures.

Over-the-Air (OTA) Updater

The following robotnix configuration enables the OTA updater app.

{
    apps.updater.enable = true;
    apps.updater.url = "...";
}

The apps.updater.url setting needs to point to a URL hosting the OTA files described below.

Additionally, the buildDateTime option is set by default by the flavor, and is updated when those flavors have new releases. If you make new changes to your build that you want to be pushed by the OTA updater, you should set buildDateTime yourself, using date "+%s" to get the current time.

Building OTA updates

The OTA file and metadata can be generated as part of the releaseScript output. If you are signing builds inside Nix using the sandbox exception, robotnix additionally includes a convenient target which will build a directory containing OTA files ready to be sideloaded or served over the web. To generate the OTA directory, build the otaDir attribute (here for sunfish):

$ nix-build --arg configuration ./sunfish.nix -A otaDir -o ota-dir

The directory structure will look similar to this (arrows indicate symbolic links) with possibly different timestamps and hashes of course:

$ tree -l ota-dir
├── sunfish-ota_update-2021.02.06.16.zip -> /nix/store/wwr49all6x868f0mdl11369ybfwyir0f-sunfish-ota_update-2021.02.06.16.zip
├── sunfish-stable -> /nix/store/c1rp46m9spncanacglqs5mxk6znfs44s-sunfish-stable
└── sunfish-target_files-2021.02.06.16.zip -> /nix/store/8ys21rzjqhi2055d7bd4iwa15fv1m446-sunfish-signed_target_files-2021.02.06.16.zip

The file sunfish-ota_update-2021.02.06.16.zip can be sideloaded with adb as described in the next section.

Actually serving OTA updates over the air

Note: The Vanilla and GrapheneOS flavors use a different updater than the LineageOS flavor, therefore they might behave slightly different from one another. The instructions below should however work for both.

Essentially this boils down to just serving the otaDir build output on the web, e.g. with nginx. To receive OTA updates on the device, enable the updater and point it to the domain and possibly subdirectory that you will be serving OTA updates from:

# Device configuration
{
  apps = {
    updater.enable = true;
    updater.url = "https://example.com/android/";
  };
}

On a NixOS server, it is as easy as serving a directory at the required endpoint:

# NixOS server configuration
{
  services.nginx.enable = true;
  systemd.services.nginx.serviceConfig.ReadOnlyPaths = [ "/var/www" ];
  services.nginx.virtualHosts."example.com" = {
    forceSSL = true;
    enableACME = true;
    locations."/android/" = {
      root = "/var/www";
      tryFiles = "$uri $uri/ =404";
    };
  };
}

The directory simply contains symlinks to the store paths that were contained in the otaDir output that was built earlier. I choose to just copy the result symlink to /var/www/android. It is recommended to use a symlink or mv operation to expose the otaDir to the web server, since (if you are copying/uploading slowly) the OTA updater app on your phone might start updating before the copy/upload is complete.

$ cp --no-dereference ota-dir /var/www/android
$ tree -l /var/www
/var/www
└── android -> /nix/store/dbjcl9lwn6xif9c0fy8d2wwpn9zi4hw4-sunfish-otaDir
    ├── sunfish-ota_update-2021.02.06.16.zip -> /nix/store/wwr49all6x868f0mdl11369ybfwyir0f-sunfish-ota_update-2021.02.06.16.zip
    ├── sunfish-stable -> /nix/store/c1rp46m9spncanacglqs5mxk6znfs44s-sunfish-stable
    └── sunfish-target_files-2021.02.06.16.zip -> /nix/store/8ys21rzjqhi2055d7bd4iwa15fv1m446-sunfish-signed_target_files-2021.02.06.16.zip

Of course, this doesn't have to be located at /var/www and it's totally possible to integrate updating of the OTA directory into your other robotnix build automation. In this case it is as easy as updating the /var/www/android symlink with the new build output.

Here it was assumed that robotnix was built on the same machine that you will serve the OTA from. If that is not the case you can conveniently copy the closure to a remote host using nix copy as in

$ nix copy --to ssh://user@example.com ./ota-dir

Don't forget to add the store path of the ota-dir as a garbage collector root on the remote machine or it might be collected in the next sweep.

Browsers / Webview

A properly functioning Android system requires the use of a "webview". Chromium-based browsers may also provide this webview

Robotnix can also build chromium-based browsers from source. We currently package Chromium, Bromite, and Vanadium for use with robotnix.

Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all users to experience the web.

Bromite is a Chromium fork with ad blocking and enhanced privacy.

Vanadium is a privacy and security hardened variant of Chromium providing the WebView (used by other apps to render web content) and standard browser for GrapheneOS. It depends on hardening and compatibility fixes in GrapheneOS rather than reinventing the wheel inside Vanadium

The following shows the available options for chromium. The corresponding options for vanadium and bromite are similar.

{
  apps.chromium.enable = true;
  webview.chromium.enable = true;
  webview.chromium.availableByDefault = true; # At least one webview must be availableByDefault
  webview.chromium.isFallback = true; # If true, this provider will be disabled and only used if no others are available. At most one webview can be isFallback.
}

If multiple webview providers are included in a build, it is possible to select the one used on a running phone under "Settings -> System -> Developer Options -> Webview implementation".

The Vanilla and LineageOS flavors enable the standard Chromium browser and webview by default. The GrapheneOS flavor enables Vanadium browser and webview by default.

Set up remote attestation

GrapheneOS has created an Auditor app, as well as a Remote Attestation service, which "use hardware-based security features to validate the identity of a device along with authenticity and integrity of the operating system. See the About page for additional details.

Robotnix patches the Auditor app and Remote Attestation service to allow for using the user-created keys. This makes the Android build itself depend on the signing key. The current code in robotnix only works with a single custom device type. E.g. the attestation service cannot handle robotnix-customized versions of both crosshatch and sunfish. Future improvements may allow the Auditor app and attestation service to work with multiple custom robotnix devices.

Android side

  1. You can enable the Auditor app in the configuration:

    {
      signing.enable = true;
      signing.keyStorePath = "/dev/shm/android-keys";
    
      apps = {
        auditor.enable = true;
        auditor.domain = "attestation.example.com";
      };
    }
    

    You also need to have signing enabled during build time because the Auditor app needs to know its own signing key during build.

  2. That's it from the Android side. Note that the custom Auditor app will be named “Robotnix Auditor”. When you build GrapheneOS the normal Auditor app will still be there in case you'd like to.

Server side

  1. Before we begin we have to obtain the fingerprint of the custom Auditor app and the AVB fingerprint. To get the Auditor app fingerprint, we simply use OpenSSL to extract the fingerprint of the signing certificate:

    $ openssl x509 -noout -fingerprint -sha256 -in keys/auditor.x509.pem | awk -F '=' '{gsub(/:/,""); print $2}'
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    

    The AVB fingerprint is a bit more tricky. I own a Pixel 4a (sunfish) and on this device the AVB fingerprint is simply the SHA256 hash of the AVB key, but this is not the case on other devices. Check the Auditor source code for details.

    $ sha256sum keys/sunfish/avb_pkmd.bin | awk '{print toupper($1)}'
    BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
    
  2. Now you can import robotnix in your NixOS configuration with the aforementioned fingerprints.

    { config, lib, pkgs, ... }:
    {
      imports = [
        ((builtins.fetchTarball {
          name = "robotnix";
          # Replace the git revision and sha256 with ones referring to a recent commit
          url =
            "https://github.com/danielfullmer/robotnix/archive/61b91d145f0b08cf0d4d73fb1d7ba74b9899b788.zip";
          sha256 = "1dihmdw5w891jq2fm7mcx30ydjjd33ggbb60898841x5pzjx6ynv";
        }) + "/nixos")
      ];
    
      services.attestation-server = {
        enable = true;
        domain = "attestation.example.com";
        device = "sunfish";
        signatureFingerprint = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
        avbFingerprint = "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB";
        #disableAccountCreation = true; # optionally uncomment after creating your account
        #nginx.enableACME = true; # optionally uncomment if you want to use Let's Encrypt for HTTPS
      };
    }
    
  3. Register and optionally disable account creation. The start the “Robotnix Auditor” app on your phone and open the menu (three dots). Choose “Enable remote verification” and scan the QR code on your attestation server.

  4. The attestation server keeps its state in /var/lib/private/attestation. Make periodic backups!

Prebuilt Apps

Robotnix provides convenient configuration options for including additional prebuilt applications in the Android build using apps.prebuilt.* options. These apps are "prebuilt" from the perspective of the Android build step, and might even be built from source using Nix in another build step.

Perhaps the main reason to include additional prebuilt applications is to take advantage of privileged permissions only available to system applications. Secondarily, other Android applications that are built and customized from source inside Nix might be useful to include as prebuilts to the overall Android build. As each change to the robotnix configuration may require a long build process each time, try to avoid the temptation to include all of the applications you typically use in your robotnix configuration.

To include a prebuilt app in the robotnix build, consider the following example configuration:

{ pkgs, ... }:
{
  apps.prebuilt.ExampleApp = {
    apk = (pkgs.fetchurl {
      url = "https://example.com/test.apk";
      sha256 = "0000000000000000000000000000000000000000000000000000000000000000";
    });
    privileged = true;  # Enable if the application needs access to privileged permissions
    privappPermissions = [ "INSTALL_PACKAGES "];
    packageName = "com.example.test";  # Needs to be set if using privappPermissions
  };
}

The use of pkgs.fetchurl above is for example only. apps.prebuilt.<name>.apk could also refer to an existing APK file by path, or could refer to some APK file output by another Nix expression. In fact, many of the included robotnix modules (such as F-Droid and Chromium) are implemented using the apps.prebuilt module. Some of these Nix expressions for these apks are available under apks/.

Source Directories

The AOSP source code is spread across a large number of git repositories. The directories included in the robotnix build may be specified using the source.dirs.* options.

For example, the following configuration will include a new repository checked out under foo/bar by setting the source.dir.<name>.src option.

{
  source.dirs."foo/bar".src = pkgs.fetchGit {
    url = "https://example.com/repo/foobar.git";
    rev = "f506faf86b8f01f9c09aae877e00ad0a2b4bc511";
    sha256 = "0000000000000000000000000000000000000000000000000000000000000000";
  };
}

While the above uses pkgs.fetchGit, the src option could refer to any Nix derivation producing a directory.

Additionaly, robotnix provides a convenient mechanism for patching existing source directories:

{
  # source.dirs.<name>.patches can refer to a list of patches to apply
  source.dirs."frameworks/base".patches = [ ./example.patch ];

  # source.dirs.<name>.postPatch can refer to a snippet of shell script to modify the source tree
  source.dirs."frameworks/base".postPatch = ''
    sed -i 's/hello/there/' example.txt
  '';
}

Each flavor in robotnix conditionally sets the default source.dirs to include the Android source directories required for the build.

Robotnix supports two alternative approaches for fetching source files:

  • Build-time source fetching with pkgs.fetchgit. This is the default. An end user wanting to fetch sources not already included in robotnix would need to create a repo json file using scripts/mk_repo_file.py and set source.dirs = lib.importJSON ./example.json;
  • Evaluation-time source fetching with builtins.fetchGit. This is more convenient for development when changing branches, as it allows use of a shared user git cache. The end user will need to set source.manifest.{url,rev,sha256} and enable source.evalTimeFetching. However, with builtins.fetchGit, the drvs themselves depend on the source, and nix-copy-closure of even just the .drv files would require downloading the source as well. This option is not as well tested as the build-time source fetching option.

Other Modules

Resources

Android applications may have resources which are additional static content such as bitmaps, user interface strings, configuration values, and others. Some simple resources may be set for certain packages using the resources option.

For example, the settings available here may be configured in robotnix by setting (for example):

{
  resources."frameworks/base/core/res".config_displayWhiteBalanceAvailable = true;
}

The first key refers toe the relative path for the package resources, and the second key refers to the resource name. The resource type is automatically determined based on value set. Setting resources.<path>.<name>.type can be used to override the automatically determined type. Available values types are bool, integer, dimen, color, string, integer-array, and string-array. If this manual override is used, the value must be set using resources.<path>.<name>.value.

CCache

Set ccache.enable = true in configuration, and be sure to pass /var/cache/ccache as a sandbox exception when building. In NixOS, to set up the cache, also run (as root):

# mkdir -p -m0770 /var/cache/ccache
# chown root:nixbld /var/cache/ccache
# echo max_size = 100G > /var/cache/ccache/ccache.conf

This option only applies to the Android build process. (It does not apply to chromium, kernels, etc.) CCache support is deprecated in upstream AOSP, and might be removed from robotnix in the future.

Building

Build outputs

Robotnix provides a number of Nix outputs that can be built with a given configuration. The specific output is selected using the -A option of nix-build. For example, the following will build an img zip associated with the configuration in config.nix.

$ nix-build --arg configuration ./config.nix -A img

Some of the outputs provided by robotnix are the following:

  • img - Image zip, which can be flashed to a device using fastboot update.
  • factoryImg - Factory image, a zip which contains the contents of img as well as a radio / bootloader if available.
  • ota - Over-the-air zip, which can be flashed to a device in recovery mode using adb sideload.
  • releaseScript - Script which produces the img, ota, and factoryImg products outside of Nix.
  • generateKeysScript - Script to generate required device / application keys for a given configuration.

Building and Signing Releases

After creating a configuration file, you need to generate keys for your device (if you are using signed builds, with signing.enable = true;):

$ nix-build --arg configuration ./crosshatch.nix -A generateKeysScript -o generate-keys
$ ./generate-keys ./keys

This will create a keys directory containing the app and device keys needed for the build. The output of this script should be placed in the location specified by signing.keyStorePath in the robotnix configuration. If you intend to build the img/factoryImg/ota Nix outputs instead of using the releaseScript, do not apply a passphrase to your keys here. (You can still encrypt them at rest on your own through other means.) This is because we cannot prompt you for your passphrase during the Nix build, but we can outside of Nix using generateKeysScript.

Sometimes changing your configuration will require that you generate additional new keys (e.g. for additional applications). Rebuilding and rerunning the generate keys script will produce the new keys (without overwriting your existing keys).

Next, build and sign your release. There are two ways to do this. The first option is to build the final products entirely inside Nix.

$ nix-build ./default.nix --arg configuration ./crosshatch.nix -A img --option extra-sandbox-paths /keys=$(pwd)/keys

If the Nix sandbox is enabled (it normally is), this will require a sandbox exception so the secret keys are available to the build scripts. To use extra-sandbox-paths, the user must be a trusted-user in nix.conf. If the Nix sandbox is not enabled, we can instead set signing.buildTimeKeysStorePath in addition to signing.keyStorePath to a string of the absolute path to the generated keys. Additionally, the nix builder will also need read access to these keys. This can be set using chgrp -R nixbld ./keys and chmod -R g+r ./keys.

The second option involves building a "release script" with Nix, which depends on and therefore builds the unsigned image inside the Nix build sandbox. The final build steps of signing target files and creating img/ota files are then done outside the sandbox by running the release script:

$ nix-build --arg configuration ./crosshatch.nix -A releaseScript -o release
$ ./release ./keys

This has the additional benefit that the build can take place on a remote machine, and the releaseScript could be copied using nix-copy-closure to a local machine which containing the keys. You might need to manually set certain required options like signing.avb.fingerprint or apps.prebuilt.<name>.fingerprint if you build on a remote machine that does not have access to the signing.keyStorePath.

Binary Cache

Robotnix now has an optional binary cache provided by Cachix on robotnix.cachix.org. Using the robotnix binary cache will allow the user to avoid building some parts that can be shared between users. Currently, only the device kernels and browser builds are published through the binary cache. This is because these derivation outputs are most likely to be shared between users, and those outputs also can take a very long time to build. The build products previously discussed should be uploaded for at least every robotnix release tag. To use, install cachix, run cachix use robotnix, and then build robotnix like normal.

Flakes

If the robotnix configuration is specified in a flake, the robotnix outputs can be produced by running (for example):

$ nix build .#robotnixConfigurations.dailydriver.img

This is assuming the flake.nix is in the current directory, the desired configuration is named dailydriver, and will produce the img output. Other robotnix outputs are available using a similar command.

Installing for the first time with (optional) verified boot

The following instructions are specific to Pixel phones using either the Vanilla or GrapheneOS flavors. For LineageOS, please refer to upstream device-specific documentation on how to install LineageOS builds on your device.

It is assumed that you have successfully built your factory image and signed it with your own keys, either by using the factoryImg Nix output or by running releaseScript. Make sure that you know the location of the image and the AVB signing key. The instructions in this document were tested on the Google Pixel 4a (sunfish). Other Pixel phones are similar, but please refer to this upstream documentation.

  1. Before you can begin you have to boot the stock OS, go to "Settings / About phone" and tap the "Build number" field 7 times to enable the "Developer options" menu. Next go to “Settings / System / Advanced / Developer options” and enable “OEM unlocking”. This option is greyed out until you connect your device to Google at least once. This is part of Google's so called Factory Reset Protection (FRP) for anti-theft protection. You do not need to insert a SIM or log into a Google Account to make the “OEM unlocking” option available, but connecting to the internet is required.

  2. First reboot into the bootloader. You can either do that physically by turning off your phone and then holding both the POWER and the VOLUME DOWN button to turn it back on, or your can connect the phone to your computer with USB Debugging turned on and issue

    $ adb reboot bootloader
    
  3. Connect your phone to your computer and run

    $ fastboot devices
    09071JEC217048  device
    
  4. Unlock the bootloader by running

    $ fastboot flashing unlock
    

    Select the option to unlock the device and confirm. This step effectively performs a factory reset, and will remove all user data from the device.

  5. Flash your custom AVB signing key using

    $ fastboot erase avb_custom_key
    $ fastboot flash avb_custom_key ./avb_pkmd.bin
    $ fastboot reboot bootloader
    
  6. Unzip the factory image built by robotnix. Double check that you're flashing to the correct device. To flash the image run

    $ ./flash-all.sh
    

    The factory image produced by robotnix includes the bootloader and radio firmware in addition to the android image. If you are certain the bootloader and radio are already up to date, you can instead build the standard img robotnix output, and flash the image with

    $ fastboot -w --skip-reboot update sunfish-img-2020.11.06.04.zip
    

    This will erase the userdata partition (-w) and prevent the automatic reboot after flashing (--skip-reboot).

    After flashing with the flash-all.sh script or with fastboot update, return to the bootloader with

    $ fastboot reboot bootloader
    
  7. At this point you want to relock the bootloader to enable enforcement of the verified boot chain.

    $ fastboot flashing lock
    

    This step has to be confirmed on the device.

  8. After rebooting you will be greeted with a yellow exclamation mark and a message like

    Your device is loading a different operating system.

    Visit this link on another device: g.co/ABH

    ID: BA135E0F

    This is expected because Android Verified Boot is designed to warn the user when not booting the stock OS, see https://source.android.com/security/verifiedboot/boot-flow. In fact, the ID on the last line are the first eight characters of the fingerprint of your AVB key.

  9. Finally you can disable OEM unlocking and afterwards even the developer options again if you do not actively use them. Note that if you later lose the ability to re-enable OEM unlocking, for example by pushing a bad update that you cannot rollback, and you cannot push another working update because you also lost your signing keys, you might not even be able to recover your device by flashing a stock image, effectively bricking the device.

If you are unable to enroll a custom AVB key on your device, you could theoretically skip steps 4, 6 and 8. This is highly discouraged as it leaves your device in the vulnerable UNLOCKED state instead of being LOCKED with a custom root of trust..

Updating by sideloading OTA files

Preferably, you can update your Vanilla/GrapheneOS flavor device using true "over-the-air" mechanism provided by the apps.updater module with a server hosting the OTA files, as shown here. If this is not available, it is still possible to update by sideloading the OTA file.

It is recommended to update using the OTA file instead of using fastboot update with a new img. OTA files can also contain updates to the modem / bootloader that are not included in the img output. fastboot update also cannot be used with a re-locked bootloader without wiping userdata.

To install OTA updates you have to put the device in sideload-mode.

  1. First reboot into the bootloader. You can either do that physically by turning off your phone and then holding both the POWER and the VOLUME DOWN button to turn it back on, or your can connect the phone to your computer with USB Debugging turned on and issue

    $ adb reboot recovery
    

    If you used the physical method, at the bootloader prompt use the VOLUME keys to select “Recovery Mode” and confirm with the POWER button.

  2. Now the recovery mode should have started and you should see a dead robot with a read exclamation mark on top. If you see “No command” on the screen, press and hold POWER. While holding POWER, press VOLUME UP and release both.

  3. At the recovery menu use the VOLUME keys to select “Apply update from ADB” and use POWER to confirm.

  4. Connect your phone to your computer and run

    $ adb devices
    List of devices attached
    09071JEC217048  sideload
    

    The output should show that the device is in sideload mode.

  5. Now you can proceed to sideload the new update.

    $ adb sideload sunfish-ota_update-2021.02.06.16.zip
    

    The sideload might terminate at 94% with “adb: failed to read command: Success”. This is not an error even though it is not obvious, see also here.

  6. Once finished and the device doesn't automatically reboot just select reboot from the menu and confirm.

Development

Robotnix modules use the same Nix-based module system used in NixOS. To understand the NixOS module system, please read this.

Robotnix does not primarily aim to significantly decrease the complexity of Android development, but rather (once a developer has a working build) makes it easier to share that work with others.

As such, robotnix does not replace the existing Android build system, but provides a convenient Nix-based wrapper around the build system. (See blueprint2nix and soongnix for an experimental attempt at reimplementing part of the Android build system natively in Nix.)

Feel free to ask robotnix development questions in #robotnix:nixos.org on Matrix.

Git mirrors

Robotnix can be configured to use local git mirrors of Android source code. The AOSP documentation includes instructions to create a local mirror of the Android source code. Maintaining a local mirror can save bandwidth in the long-run when repeatedly updating a flavor over time which contains incremental updates.

This functionality is enabled by setting the ROBOTNIX_GIT_MIRRORS environment variable. The value of ROBOTNIX_GIT_MIRRORS contains a number of mappings, each separated by a | character. Each mapping is of the format <remote_url>=<local_url>. For example:

ROBOTNIX_GIT_MIRRORS=https://android.googlesource.com=/mnt/cache/mirror|https://github.com/LineageOS=/mnt/cache/lineageos/LineageOS

Both the robotnix update scripts as well as robotnix's overridden fetchgit derivation use ROBOTNIX_GIT_MIRRORS. This environment variable is passed to fetchgit via impureEnvVars (search for impureEnvVars in the Nix manual). If the Nix daemon is being used, it needs to have this ROBOTNIX_GIT_MIRRORS in its environment, not just in the user's environment when running nix-build or nix build. The following NixOS configuration can be used to easily set this environment variable for the Nix daemon:

let
  mirrors = {
    "https://android.googlesource.com" = "/mnt/cache/mirror";
    "https://github.com/LineageOS" = "/mnt/cache/lineageos/LineageOS";
  };
in
{
  systemd.services.nix-daemon.serviceConfig.Environment = [
    ("ROBOTNIX_GIT_MIRRORS=" + lib.concatStringsSep "|" (lib.mapAttrsToList (local: remote: "${local}=${remote}") mirrors))
  ];

  # Also add local mirrors to nix sandbox exceptions
  nix.sandboxPaths = lib.attrValues mirrors;
}

Helper scripts

Robotnix can produce a few helper scripts that can make Android development easier in some circumstances.

Running nix-build --arg configuration <cfg> -A <output> for the outputs below will produce the corresponding helper script, using the provided robotnix configuration.

  • config.build.debugEnterEnv produces a script which enters an FHS environment with the required dependencies, as well as the Android source files bind-mounted under the current directory. Useful in conjunction with cd $(mktemp -d) to enter a temporary directory. Files are bind-mounted readonly, so files cannot be edited ad-hoc using this script.

The following outputs can be useful with an existing Android source checkout made using repo.

  • config.build.env produces a robotnix-build script under bin/ which enters an FHS environment that contains all required dependencies to build Android.
  • config.build.debugUnpackScript produces a script which will copy the robotnix-specific source directories into ./robotnix/.
  • config.build.debugPatchScript produces a script which will patch all Android source directories under the current directory in a similar way they would be patched during a normal robotnix build.

External modules

Robotnix is welcome to contributions of well-written modules that can be maintained in an ongoing fashion. Modules can provide support for new flavors, additional devices with an existing flavor, included system/privileged applications, and others.

If the proposed module is not suitable for inclusion as an upstream robotnix module, it can still be developed and maintained externally and easily included by a user. This can be done in a similar way as is done with NixOS modules. For instance, if the module is provided by default.nix in the owner/repo repository on GitHub:

{
    imports = [ (builtins.fetchTarball {
        url = "https://github.com/owner/repo/archive/9b034054166e1f01b3bdb6a1948daa3bdafe039a.tar.gz";
        sha256 = "0000000000000000000000000000000000000000000000000000000000000000";
    }) ];
}

The above imports statement will include the provided module in the robotnix build, pinned by the provided revision and sha256. Any options or configuration set by the specified module will be included in the build.

Developing a new flavor

To create a new flavor, the developer should create a robotnix module that conditions on config.flavor. The flavor configuration defaults should be set conditionally using (for example) mkIf (config.flavor = "...") { ... }. Those configuration defaults should include:

  • Setting source.dirs using a repo JSON file produced by mk_repo_file.py.
  • Setting the default androidVersion.
  • Setting the default buildDateTime based on (for example) the time that the flavor was last updated.
  • Providing a warning if the user has not selected a valid device for this flavor.

Additionally, flavors should provide update scripts that can (at least) automatically produce an updated repo JSON file. It is recommended to take a look at the Nix expressions implementing the current flavors under flavors/.

Emulator

Robotnix can also build a script which will start the Android emulator using an attached robotnix-built system image. This can be accomplished with the emulator Nix output. To build and run an emulator with an attached vanilla system image, use (for example):

$ nix-build ./default.nix --arg configuration '{device="x86_64"; flavor="vanilla";}' -A emulator
$ ./result

This currently only works well when using the generic x86_64 device.

Testing / CI / Reproducibility

All devices (Pixel 3-5(a) (XL)) have very basic checks to ensure that the android build process will at least start properly. See release.nix for the set of configurations with this minimal build testing. This check is run using nix-build ./release.nix -A check. As each build takes approximately 4 hours--I only build marlin and crosshatch builds for myself. At some point, I would love to set up a build farm and publish build products on s3 or cachix. This would allow an end-user to simply sign releases using their own keys without building the entire AOSP themselves.

As of 2020-05-17, target_files, signed_target_files, img, and ota files have all been verified to be bit-for-bit reproducible for crosshatch and marlin using the vanilla flavor. Automated periodic testing of this is still desired.

One option being investigated is to have multiple independent remote builders produce unsigned target files for a number of device and flavor combinations. An end-user could then verify that the builders produced the same unsigned target files, and finish the process by signing the target files and producing their own img and ota files. This eliminates the requirement for an end-user to spend hours building android.

There are, however, a few places where user-specific public keys are included in the build for key pinning. This unfortunately decreases the possibility of sharing build products between users. The F-Droid privileged extension and Trichrome (disabled for now) are two components which have this issue. Fixes for this are still under investigation.

Additional Notes

Robotnix bind mounts the source directories from /nix/store. These files/directories have their "user write" (u-w) permission removed. Sometimes, Android Makefiles which copy files from the source directories may assume the files have the write permission enabled, which can then break later steps. To work around these issues, it is usually sufficient to add a chmod command or add --no-preserve=owner,mode to the cp command in the Makefile.

Robotnix Configuration Options

Some robotnix flavors or modules may change the option defaults shown below. Refer to the flavor or module source for details

androidVersion

Used to select which Android version to use

Default: 12

Type: signed integer

Declared by: modules/base.nix

apps.auditor.domain

Domain running the AttestationServer (over HTTPS) for remote verification

Example: "attestation.example.com"

Type: string

Declared by: modules/apps/auditor.nix

apps.auditor.enable

Whether to enable Auditor.

Default: false

Example: true

Type: boolean

Declared by: modules/apps/auditor.nix

apps.bromite.enable

Whether to enable bromite browser.

Default: false

Example: true

Type: boolean

Declared by: modules/apps/chromium.nix

apps.chromium.enable

Whether to enable chromium browser.

Default: false

Example: true

Type: boolean

Declared by: modules/apps/chromium.nix

apps.fdroid.additionalRepos

Additional F-Droid repositories to include in the default build. Note that changes to this setting will only take effect on a freshly installed device--or if the F-Droid storage is cleared.

Default: { }

Type: attribute set of submodules

Declared by: modules/apps/fdroid.nix

apps.fdroid.additionalRepos.<name>.description

Longer textual description of this repository

Default: "Empty description"

Type: string

Declared by: modules/apps/fdroid.nix

apps.fdroid.additionalRepos.<name>.enable

Whether to enable this repository by default in F-Droid.

Default: false

Type: boolean

Declared by: modules/apps/fdroid.nix

apps.fdroid.additionalRepos.<name>.name

Display name to use for this repository

Default: "‹name›"

Type: string

Declared by: modules/apps/fdroid.nix

apps.fdroid.additionalRepos.<name>.pubkey

Public key associated with this repository. Can be found in /index.xml under the repo URL.

Type: string

Declared by: modules/apps/fdroid.nix

apps.fdroid.additionalRepos.<name>.pushRequests

Allow this repository to specify apps which should be automatically installed/uninstalled

Default: "ignore"

Type: one of "ignore", "prompt", "always"

Declared by: modules/apps/fdroid.nix

apps.fdroid.additionalRepos.<name>.url

URL for F-Droid repository

Type: string

Declared by: modules/apps/fdroid.nix

apps.fdroid.enable

Whether to enable F-Droid.

Default: false

Example: true

Type: boolean

Declared by: modules/apps/fdroid.nix

apps.prebuilt

Prebuilt APKs to include in the robotnix build

Default: { }

Type: attribute set of submodules

Declared by: modules/apps/prebuilt.nix

apps.prebuilt.<name>.allowInPowerSave

Whether to allow this application to operate in "power save" mode. Disables battery optimization for this app.

Default: false

Type: boolean

Declared by: modules/apps/prebuilt.nix

apps.prebuilt.<name>.apk

APK file to include in build

Type: path

Declared by: modules/apps/prebuilt.nix

apps.prebuilt.<name>.certificate

Name of certificate to sign APK with. Defaults to the name of the prebuilt app. If it is a device-specific certificate, the cert/key should be under ${keyStorePath}/${device}/${certificate}.{x509.pem,pk8}. Otherwise, it should be ${keyStorePath}/${certificate}.{x509.pem,pk8}. Finally, the special string "PRESIGNED" will just use the APK as-is.

Default: "‹name›"

Type: string

Declared by: modules/apps/prebuilt.nix

apps.prebuilt.<name>.defaultPermissions

Permissions to be enabled by default without user prompting.

Default: [ ]

Example: ["INSTALL_PACKAGES"]

Type: list of strings

Declared by: modules/apps/prebuilt.nix

apps.prebuilt.<name>.name

Name of application. (No spaces)

Default: "‹name›"

Type: string

Declared by: modules/apps/prebuilt.nix

apps.prebuilt.<name>.packageName

APK's Java-style package name (applicationId). This setting only necessary to be set if also using privappPermissions.

Example: "com.android.test"

Type: string matching the pattern [a-zA-Z0-9_.]*

Declared by: modules/apps/prebuilt.nix

apps.prebuilt.<name>.partition

Partition on which to place this app

Type: one of "vendor", "system", "product"

Declared by: modules/apps/prebuilt.nix

apps.prebuilt.<name>.privappPermissions

Privileged permissions to apply to this application. Refer to this link and note permissions which say "not for use by third-party applications".

Default: [ ]

Example: ["INSTALL_PACKAGES"]

Type: list of strings

Declared by: modules/apps/prebuilt.nix

apps.prebuilt.<name>.privileged

Whether this APK should be included as a privileged application.

Default: false

Type: boolean

Declared by: modules/apps/prebuilt.nix

apps.seedvault.enable

Whether to enable Seedvault (backup).

Default: false

Example: true

Type: boolean

Declared by: modules/apps/seedvault.nix

apps.updater.enable

Whether to enable OTA Updater.

Default: false

Example: true

Type: boolean

Declared by: modules/apps/updater.nix

apps.updater.flavor

Which updater package to use, and which kind of metadata to generate for it.

Default: "grapheneos"

Type: one of "grapheneos", "lineageos"

Declared by: modules/apps/updater.nix

apps.updater.url

URL for OTA updates

Type: string

Declared by: modules/apps/updater.nix

apps.vanadium.enable

Whether to enable vanadium browser.

Default: false

Example: true

Type: boolean

Declared by: modules/apps/chromium.nix

apv.buildID

Build ID associated with the upstream img/ota (used to select images)

Type: string

Declared by: modules/apv

apv.enable

Whether to enable android-prepare-vendor.

Default: false

Example: true

Type: boolean

Declared by: modules/apv

apv.img

A factory image .zip from upstream whose vendor contents should be extracted and included in the build

Default: null

Type: path

Declared by: modules/apv

apv.ota

An OTA from upstream whose vendor contents should be extracted and included in the build. (Android >=10 builds require this in addition to apv.img)

Default: null

Type: path

Declared by: modules/apv

arch

Architecture of phone, usually set automatically by device

Default: "arm64"

Type: one of "arm64", "arm", "x86_64", "x86"

Declared by: modules/base.nix

buildDateTime

Unix time (seconds since the epoch) that this build is taking place. Needs to be monotonically increasing for each build if you use the over-the-air (OTA) update mechanism. e.g. output of date +%s

Default: "*maximum of source.dirs.<name>.dateTime*"

Example: 1565645583

Type: signed integer

Declared by: modules/base.nix

buildNumber

Set this to something meaningful to identify the build. Defaults to YYYYMMDDHH based on buildDateTime. Should be unique for each build for disambiguation.

Example: "201908121"

Type: string

Declared by: modules/base.nix

buildType

one of "release", "debug"

Default: "release"

Type: one of "release", "debug"

Declared by: modules/base.nix

ccache.enable

Whether to enable ccache.

Default: false

Example: true

Type: boolean

Declared by: modules/base.nix

channel

Default channel to use for updates (can be modified in app)

Default: "stable"

Type: one of "stable", "beta"

Declared by: modules/release.nix

device

Code name of device build target

Default: null

Example: "marlin"

Type: null or string

Declared by: modules/base.nix

deviceDisplayName

Display name of device build target

Default: null

Example: "Pixel XL"

Type: null or string

Declared by: modules/base.nix

etc

Set of files to be included under /etc

Default: { }

Type: attribute set of submodules

Declared by: modules/etc.nix

etc.<name>.partition

Partition on which to place this etc file

Type: one of "vendor", "system", "product"

Declared by: modules/etc.nix

etc.<name>.source

Path of the source file

Type: path

Declared by: modules/etc.nix

etc.<name>.target

Name of symlink (relative to /etc). Defaults to the attribute name.

Type: string

Declared by: modules/etc.nix

etc.<name>.text

Text of the file

Default: null

Type: null or string

Declared by: modules/etc.nix

flavor

Set to one of robotnix's supported flavors. Current options are vanilla, grapheneos, and lineageos.

Default: null

Example: "vanilla"

Type: null or string

Declared by: modules/base.nix

hosts

Custom hosts file

Default: null

Type: null or path

Declared by: modules/hosts.nix

incremental

Whether to include an incremental build in otaDir output

Default: false

Type: boolean

Declared by: modules/release.nix

kernel.buildDateTime

Unix time to use for kernel build timestamp

Default: "config.buildDateTime"

Type: signed integer

Declared by: modules/kernel.nix

kernel.clangVersion

Version of prebuilt clang to use for kernel. See https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+/master/README.md"

Type: string

Declared by: modules/kernel.nix

kernel.enable

Whether to enable building custom kernel.

Default: false

Example: true

Type: boolean

Declared by: modules/kernel.nix

kernel.patches

List of patches to apply to kernel source

Default: [ ]

Type: list of paths

Declared by: modules/kernel.nix

kernel.postPatch

Commands to run after patching kernel source

Default: ""

Type: strings concatenated with "\n"

Declared by: modules/kernel.nix

kernel.relpath

Relative path in source tree to place kernel build artifacts

Type: string

Declared by: modules/kernel.nix

kernel.src

Path to kernel source

Type: path

Declared by: modules/kernel.nix

microg.enable

Whether to enable MicroG.

Default: false

Example: true

Type: boolean

Declared by: modules/microg.nix

nixpkgs.overlays

Nixpkgs overlays to override the default packages used while building robotnix.

Default: [ ]

Type: list of unspecifieds

Declared by:

pixel.activeEdge.enable

Whether to enable Active Edge gestures using the open-source implementation from LineageOS.

Default: false

Example: true

Type: boolean

Declared by: modules/pixel/active-edge.nix

pixel.useUpstreamDriverBinaries

Use device vendor binaries from https://developers.google.com/android/drivers

Default: false

Type: boolean

Declared by: modules/pixel/driver-binaries.nix

product.additionalProductPackages

PRODUCT_PACKAGES to add under product partition.

Default: [ ]

Type: list of strings

Declared by: modules/base.nix

productName

Product name for choosecombo/lunch

Default: "${productNamePrefix}${device}"

Example: "aosp_crosshatch"

Type: string

Declared by: modules/base.nix

productNamePrefix

Prefix for product name used with choosecombo/lunch

Default: "aosp_"

Type: string

Declared by: modules/base.nix

removedProductPackages

PRODUCT_PACKAGES to remove from build

Default: [ ]

Type: list of strings

Declared by: modules/base.nix

resources

Additional package resources to include. The first key refers to the relative path for the package, and the second key refers to the resource name

Default: { }

Example: { "frameworks/base/core/res".config_enableAutoPowerModes = true; }

Type: attribute set of attribute set of boolean or signed integer or string or list of string or signed integers or submoduless

Declared by: modules/resources.nix

retrofit

Generate a retrofit OTA for upgrading a device without dynamic partitions. See also https://source.android.com/devices/tech/ota/dynamic_partitions/ab_legacy#generating-update-packages

Default: false

Type: boolean

Declared by: modules/release.nix

signing.apex.enable

Whether to enable signing APEX packages.

Default: false

Example: true

Type: boolean

Declared by: modules/signing.nix

signing.apex.packageNames

APEX packages which need to be signed

Default: [ ]

Type: list of strings

Declared by: modules/signing.nix

signing.avb.enable

Whether to enable AVB signing.

Default: false

Example: true

Type: boolean

Declared by: modules/signing.nix

signing.avb.fingerprint

SHA256 hash of avb_pkmd.bin. Should be set automatically based on file under keyStorePath if signing.enable = true

Type: string matching the pattern [0-9A-F]{64}

Declared by: modules/signing.nix

signing.avb.mode

Mode of AVB signing to use.

Default: "vbmeta_chained"

Type: one of "verity_only", "vbmeta_simple", "vbmeta_chained", "vbmeta_chained_v2"

Declared by: modules/signing.nix

signing.avb.verityCert

Verity certificate for AVB. e.g. in x509 DER format.x509.pem. Only needed if signing.avb.mode = "verity_only"

Type: path

Declared by: modules/signing.nix

signing.buildTimeKeyStorePath

Path to generated keys for signing to use at build-time, as opposed to keyStorePath, which is used at evaluation-time.

Type: string or path

Declared by: modules/signing.nix

signing.enable

Whether to sign build using user-provided keys. Otherwise, build will be signed using insecure test-keys.

Default: false

Type: boolean

Declared by: modules/signing.nix

signing.keyStorePath

String containing absolute path to generated keys for signing. This must be a string and not a "nix path" to ensure that your secret keys are not imported into the public /nix/store.

Example: "/var/secrets/android-keys"

Type: string

Declared by: modules/signing.nix

source.dirs

Directories to include in Android build process. Normally set by the output of mk_repo_file.py. However, additional source directories can be added to the build here using this option as well.

Default: { }

Type: attribute set of submodules

Declared by: modules/source.nix

source.dirs.<name>.enable

Whether to include this directory in the android build source tree.

Default: true

Type: boolean

Declared by: modules/source.nix

source.dirs.<name>.patches

Patches to apply to source directory.

Default: [ ]

Type: list of paths

Declared by: modules/source.nix

source.dirs.<name>.postPatch

Additional commands to run after patching source directory.

Default: ""

Type: strings concatenated with "\n"

Declared by: modules/source.nix

source.dirs.<name>.relpath

Relative path under android source tree to place this directory. Defaults to attribute name.

Default: "‹name›"

Type: string

Declared by: modules/source.nix

source.dirs.<name>.src

Source to use for this android source directory.

Default: <derivation empty>

Type: path

Declared by: modules/source.nix

source.evalTimeFetching

Set config.source.dirs automatically using IFD with information from source.manifest. Also enables use of builtins.fetchGit instead of pkgs.fetchgit if not all sha256 hashes are available. (Can be useful for development, but not recommended normally)

Default: false

Type: unspecified

Declared by: modules/source.nix

source.excludeGroups

Project groups to exclude from source tree

Default: [ "darwin" "mips" ]

Type: list of strings

Declared by: modules/source.nix

source.includeGroups

Project groups to include in source tree (overrides excludeGroups)

Default: [ ]

Type: list of strings

Declared by: modules/source.nix

source.manifest.rev

Revision/tag to use from repo manifest repository.

Type: string

Declared by: modules/source.nix

source.manifest.sha256

Nix sha256 hash of repo manifest repository.

Type: string

Declared by: modules/source.nix

source.manifest.url

URL to repo manifest repository. Not necessary to set if using source.dirs directly.

Type: string

Declared by: modules/source.nix

system.additionalProductPackages

PRODUCT_PACKAGES to add under system partition.

Default: [ ]

Type: list of strings

Declared by: modules/base.nix

useReproducibilityFixes

Apply additional fixes for reproducibility

Default: true

Type: boolean

Declared by: modules/base.nix

variant

user has limited access and is suited for production. userdebug is like user but with root access and debug capability. eng is the development configuration with additional debugging tools.

Default: "user"

Type: one of "user", "userdebug", "eng"

Declared by: modules/base.nix

webview

Webview providers to include in Android build. Pre-specified options are chromium, bromite, and vanadium.

Example: { bromite.enable = true; }

Type: attribute set of submodules

Declared by: modules/webview.nix

webview.<name>.apk

APK file containing webview package.

Type: path

Declared by: modules/webview.nix

webview.<name>.availableByDefault

If true, this provider can be automatically selected by the framework, if it's the first valid choice. If false, this provider will only be used if the user selects it themselves from the developer settings menu.

Default: false

Type: boolean

Declared by: modules/webview.nix

webview.<name>.description

The name shown to the user in the developer settings menu.

Default: "Android System WebView"

Type: string

Declared by: modules/webview.nix

webview.<name>.enable

Whether to enable ‹name› webview.

Default: false

Example: true

Type: boolean

Declared by: modules/webview.nix

webview.<name>.isFallback

If true, this provider will be automatically disabled by the framework, preventing it from being used or updated by app stores, unless there is no other valid provider available. Only one provider can be a fallback.

Default: false

Type: boolean

Declared by: modules/webview.nix

webview.<name>.packageName

The Android package name of the APK.

Default: "com.android.webview"

Type: string

Declared by: modules/webview.nix