diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 00000000..41aa0450 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,25 @@ +name: Generate documentation + +on: + push: + branches: [ docs ] + +jobs: + docs: + name: Build documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.3.4 + with: + fetch-depth: 0 + - uses: cachix/install-nix-action@v12 + with: + install_url: https://github.com/numtide/nix-flakes-installer/releases/download/nix-2.4pre20201221_9fab14a/install + extra_nix_config: | + experimental-features = nix-command flakes + - run: nix build .#manual -o manual + - uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./manual/book + cname: docs.robotnix.org diff --git a/NEWS.md b/NEWS.md index 9afd99ab..81a037ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -8,6 +8,12 @@ changes occur which require user intervention / configuration changes. These are highlights since the last update, and are not meant to be an exhaustive listing of changes. See the git commit log for additional details. +# 2021-04-XX +## Highlights: + +## Backward incompatible changes +- Renamed `kernel.useCustom` to `kernel.enable`. + # 2021-03-02 ## Highlights: - Added support for Pixel 5 (redfin) and Pixel 4a (5g) (bramble) [PR #79](https://github.com/danielfullmer/robotnix/pull/79) diff --git a/README.md b/README.md index aee5dbcf..8c11658c 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,22 @@ SPDX-License-Identifier: MIT # robotnix - Build Android (AOSP) using Nix -Robotnix enables a user to easily and reliably build Android (AOSP) images using the Nix package manager. -AOSP projects often contain long and complicated build instructions requiring a variety of tools for fetching source code and executing the build. -This applies not only to Android itself, but also to projects which are to be included in the Android build, such as the Linux kernel, Chromium webview, MicroG, other external/prebuilt privileged apps, etc. -Robotnix orchestrates the diverse build tools across these multiple projects using Nix, inheriting its reliability and reproducibility benefits, and consequently making the build and signing process very simple for an end-user. +Robotnix enables a user to easily and reliably build Android (AOSP) images using the Nix package manager / build tool. + +## Quick Start +Here is a single command to build an `img` which can be flashed onto a Pixel 3 XL (`crosshatch`). +```console +$ nix-build "https://github.com/danielfullmer/robotnix/archive/master.tar.gz" \ + --arg configuration '{ device="crosshatch"; flavor="vanilla"; }' \ + -A img +``` +The command above will build an image signed with publicly known `test-keys`, so definitely don't use this for anything intended to be secure. +To flash the result to your device, run `fastboot update -w `. + +## Motivation +Android projects often contain long and complicated build instructions requiring a variety of tools for fetching source code and executing the build. +This applies not only to Android itself, but also to projects included in the Android build, such as the Linux kernel, Chromium webview, MicroG, other external/prebuilt privileged apps, etc. +Robotnix orchestrates the diverse build systems across these multiple projects using Nix, inheriting its reliability and reproducibility benefits, and consequently making the build and signing process very simple for an end-user. Robotnix includes a NixOS-style module system which allows users to easily customize various aspects of the their builds. Some optional modules include: @@ -20,175 +32,45 @@ Some optional modules include: - Browser / Webview: [Chromium](https://www.chromium.org/Home), [Bromite](https://www.bromite.org/), [Vanadium](https://github.com/GrapheneOS/Vanadium) - [Seamless OTA updates](https://github.com/GrapheneOS/platform_packages_apps_Updater) - [MicroG](https://microg.org/) - - Certain google apps (currently just stuff for Google Fi) + - Certain Google apps (currently just stuff for Google Fi) - Easily setting various framework configuration settings such as those found [here](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml) - Custom built kernels - Custom `/etc/hosts` file - Extracting vendor blobs from Google's images using [android-prepare-vendor](https://github.com/anestisb/android-prepare-vendor) -Future goals include: - - Support for additional flavors and devices - - Better documentation, especially for module options - - Continuous integration / testing for various devices - - Automating CTS (Compatibility Test Suite) like nixos tests. - - Automatic verification of build reproducibility - - Replacing android prebuilt toolchains with nixpkgs equivalents. - -This has currently only been tested on crosshatch (Pixel 3 XL, my daily driver), sunfish (Pixel 4a), and marlin (Pixel XL, which is now deprecated by google and no longer receiving updates). +## Documentation -## Quick Start -Here is a single command to build an `img` which can be flashed onto a Pixel 3 XL (`crosshatch`). -```console -$ nix-build "https://github.com/danielfullmer/robotnix/archive/master.tar.gz" \ - --arg configuration '{ device="crosshatch"; flavor="vanilla"; }' \ - -A img -``` -The command above will build an image signed with `test-keys`, so definitely don't use this for anything intended to be secure. -To flash the result to your device, run `fastboot update -w `. +More detailed robotnix documentation is available at [https://docs.robotnix.org](https://docs.robotnix.org), and should be consulted before use. + +Robotnix was presented at Nixcon 2020, and a recording of the talk is available [here](https://youtu.be/7sQa04olUA0?t=22314). +Slides for the talk are also available [here](https://cfp.nixcon.org/media/robotnix-nixcon2020-final.pdf). ## Requirements The AOSP project recommends at least 250GB free disk space as well as 16GB RAM. (Certain device kernels which use LTO+CFI may require even more memory) -A typical build requires approximately 45GB free disk space to check out the android source, 14GB for chromium, plus ~100GB of additional free space for intermediate build products. -Ensure your `/tmp` is not mounted using `tmpfs`, since the intermediate builds products are very large and will easily use all of your RAM (even if you have 32GB)! -A user can use the `--cores` option for `nix-build` to set the number of cores to -use, which can also be useful to decrease parallelism in case memory usage of -certain build steps is too large. +A typical build requires approximately 45GB free disk space to check out the android source, ~14GB for chromium, plus ~100GB of additional free space for intermediate build products. +By default, Nix uses `/tmp` to store these intermediate build products, so ensure your `/tmp` is not mounted using `tmpfs`, since the intermediate builds products are very large and will easily use all of your RAM (even if you have 32GB)! +A user can use the `--cores` option for `nix-build` to set the number of cores to use, which can also be useful to decrease parallelism in case memory usage of certain build steps is too large. -A full Android 10 build with chromium webview takes approximately 10 hours on my quad-core i7-3770 with 16GB of memory. +Robotnix also requires support for user namespaces (`CONFIG_USER_NS` Linux kernel option). +Currently, using the "signing inside Nix with a sandbox exception" feature also requires a Nix daemon with the sandbox support enabled. +This feature is currently not supported inside Docker for this reason. + +A full Android 10 build with Chromium webview takes approximately 10 hours on my quad-core i7-3770 with 16GB of memory. AOSP takes approximately 4 hours of that, while webview takes approximately 6 hours. I have recently upgraded to a 3970x Threadripper with 32-cores. This can build chromium+android in about an hour. -## Configuration and Build Options -A configuration file should be created for anything more complicated, including creating signed builds. -See my own configuration under `example.nix` for inspiration. -After creating a configuration file, generate keys for your device: - -```console -$ nix-build ./default.nix --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. -Next, build and sign your release. -There are two ways to do this. -The first option involves creating a "release script" which does the final build steps of signing target files and creating ota/img files outside of nix: -```console -$ nix-build ./default.nix --arg configuration ./crosshatch.nix -A releaseScript -o release -$ ./release ./keys -``` -One advantage of using a release script as above is that the build can take place on a different machine than the signing. -`nix-copy-closure` could be used to transfer this script and its dependencies to another computer to finish the release. - -The other option is to build the final products entirely inside nix instead of using `releaseScript` -```console -$ nix-build ./default.nix --arg configuration ./crosshatch.nix -A img --option extra-sandbox-paths /keys=$(pwd)/keys -``` -This, however, will require a nix 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`. -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`. - -### Installation -See `docs/installation.md` for details on flashing and updating for Pixel devices. - -### Binary Cache -Robotnix now has an optional binary cache provided by cachix. -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. - -### OTA Updater -The Over-the-Air (OTA) updater can be enabled using `apps.updater.enable = true;`. -The URL that the updater will query for updates is set using `apps.updater.url = "...";`. -This URL needs to point to a directory containing the OTA update file, as well as some metadata. -Conveniently, these files are generated as part of the `releaseScript` output. -If instead, you are signing builds inside nix with the sandbox exception, the desired output can be built using `nix-build ... -A otaDir`. - -### Testing / CI / Reproducibility - -All devices (Pixel 1-4(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](https://cachix.org). -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. - -### LineageOS Support -LineageOS support may be enabled by setting `flavor = "lineageos";`. -The typical LineageOS flashing process involves first producing a `boot.img` and `ota`, flashing `boot.img` with fastboot, and then flashing 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. - -LineageOS support should be considered "experimental," as it does yet have the same level of support I intend to provide 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 often disabled, `userdebug` as the default variant, and vendor files with unclear origin. -LineageOS support is still valuable to include as it extends preliminary 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! - -### Emulator - -To build and run an emulator with an attached vanilla system image, use (for example): -```console -$ 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. - -### Fetching android source files - -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 `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 git cache. - The end user will need to set `source.manifest.{url,rev,sha256}` and enable `source.evalTimeFetching`. - However, with `builtins.fetchGit`, the `drv`s themselves depend on the source, - and `nix-copy-closure` of even just the `.drv` files would require downloading the source as well. - -### Additional information - - -Optional CCACHE stuff. -As root: -```console -# mkdir -p -m0770 /var/cache/ccache -# chown root:nixbld /var/cache/ccache -# echo max_size = 100G > /var/cache/ccache/ccache.conf -``` -Set `ccache.enable = true` in configuration, and be sure to pass `/var/cache/ccache` as a sandbox exception when building. - -## Notable mentions -See also: [NixDroid](https://github.com/ajs124/NixDroid), [RattlesnakeOS](https://github.com/dan-v/rattlesnakeos-stack), [aosp-build](https://github.com/hashbang/aosp-build), and [CalyxOS](https://calyxos.org/) - -## Donating to Robotnix +## Community +The `#robotnix` IRC channel on Freenode is available for a place to chat about the project, ask questions, and discuss robotnix development. -If you find Robotnix helpful, please consider donating to the project. -Especially consider making a donation if you rely on regular Robotnix updates for your personal device. +## Donating to robotnix +If you find robotnix helpful, please consider donating to the project. +Especially consider making a donation if you rely on regular robotnix updates for your personal device. Donations will be used to support ongoing Android updates, developer hardware and build infrastructure, as well as future improvements. Your support is greatly appreciated. - My GitHub sponsors page is [here](https://github.com/sponsors/danielfullmer). - Paypal donations may be made using this [link](https://www.paypal.com/donate/?cmd=_donations&business=FAV4QV9CTLXF2¤cy_code=USD). - Bitcoin donations may be directed toward `3GEtqfkPkSSrRFpdcfy4T6YALgTwRyufip`. -## Community -The `#robotnix` IRC channel on Freenode is available for a place to chat about the project, ask questions, and discuss robotnix development. - -## License information +## License information This project is available as open source under the terms of MIT license. However, for accurate information, please check individual files. diff --git a/default.nix b/default.nix index b0dd0265..6027ced4 100644 --- a/default.nix +++ b/default.nix @@ -14,6 +14,7 @@ let options.nixpkgs.overlays = mkOption { default = []; type = types.listOf types.unspecified; + description = "Nixpkgs overlays to override the default packages used while building robotnix."; }; config = { diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 00000000..5dc8b56b --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,5 @@ +[book] +language = "en" +multilingual = false +src = "src" +title = "Robotnix" diff --git a/docs/default.nix b/docs/default.nix new file mode 100644 index 00000000..98b14cec --- /dev/null +++ b/docs/default.nix @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: 2020 Daniel Fullmer and robotnix contributors +# SPDX-License-Identifier: MIT + +{ pkgs ? import ../pkgs { } }: + +with pkgs.lib; +let + eval = import ../default.nix { inherit pkgs; configuration = { }; }; + + robotnixOptionsDoc = pkgs.nixosOptionsDoc { + inherit (eval) options; + }; + + optionsMd = + let + options = robotnixOptionsDoc.optionsNix; + in '' + # Robotnix Configuration Options + *Some robotnix flavors or modules may change the option defaults shown below.* + *Refer to the flavor or module source for details* + + '' + + concatStrings (map + (name: + let + option = options.${name}; + exampleText = + if option.example ? _type && (option.example._type == "literalExample") + then option.example.text + else builtins.toJSON option.example; + declarationToLink = declaration: let + trimmedDeclaration = concatStringsSep "/" (drop 4 (splitString "/" declaration)); + in + if hasPrefix "/nix/store/" declaration + then "[${trimmedDeclaration}](https://github.com/danielfullmer/robotnix/blob/master/${trimmedDeclaration})" + else declaration; + body = '' + ${option.description} + + '' + optionalString (option ? defaultText || option ? default) '' + *Default*: `${option.defaultText or (generators.toPretty {} option.default)}` + + '' + optionalString (option ? example) '' + *Example*: `${exampleText}` + + '' + '' + *Type*: ${option.type} + + *Declared by*: + ${concatMapStringsSep ", " (declaration: declarationToLink declaration) option.declarations} + ''; + in + '' + ### `${name}` + ${body} + '' + ) + (attrNames options)); +in +{ + manual = pkgs.stdenv.mkDerivation { + name = "manual"; + phases = [ "unpackPhase" "buildPhase" "installPhase" ]; + src = ./.; + nativeBuildInputs = [ pkgs.mdbook ]; + buildPhase = '' + cp ${builtins.toFile "options.md" optionsMd} src/options.md + mdbook build + ''; + installPhase = '' + mkdir $out + cp -R book $out/book + cp -R src $out/src + cp book.toml $out/book.toml + ''; + }; +} diff --git a/docs/installation.md b/docs/installation.md deleted file mode 100644 index c8e5e44b..00000000 --- a/docs/installation.md +++ /dev/null @@ -1,79 +0,0 @@ - - -# Installing for the first time and verified boot - -It is assumed that you have successfully built your image and signed it with -your own keys. 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). For other devices please refer to -https://source.android.com/setup/build/running - - 0. Before you can begin you have to boot the stock OS and go to “Settings / - System / Advanced / Developer options” and enable “OEM unlocking”. On my - device I had to insert a SIM card and connect to the network for that, so - it looks like you have to connect your device with Google at least once. - This is part of Google's so called Factory Reset Protection (FRP) for - anti-theft protection - (https://grapheneos.org/install#enabling-oem-unlocking). However, [this - comment](https://www.kuketz-blog.de/grapheneos-das-android-fuer-sicherheits-und-datenschutzfreaks/#comment-52681) - on a German IT privacy blog suggests that it is sufficient to allow access - to the captive portal such that the phone thinks it is online. - - 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 - ```console - $ adb reboot bootloader - ``` - - 2. Connect your phone to your computer and run - ```console - $ fastboot devices - 09071JEC217048 device - ``` - - 3. First flash your custom AVB signing key using - ```console - $ fastboot erase avb_custom_key - $ fastboot flash avb_custom_key avb_pkmd.bin - $ fastboot reboot bootloader - ``` - - 4. To flash you image use - ```console - $ fastboot -w --skip-reboot update sunfish-img-2020.11.06.04.zip - $ fastboot reboot bootloader - ``` - This will erase the `userdata` partition (`-w`) and prevent the automatic - reboot after flashing (`--skip-reboot`). Instead it reboots back into the - bootloader from where the user can then manually trigger the reboot using - ```console - $ fastboot reboot - ``` - - 5. At this point you want to relock the bootloader to enable the verified boot - chain. - ``` - $ fastboot flashing lock - ``` - This step has to be confirmed on the device. - - 6. After rebooting you will be greeted with an orange 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. diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 00000000..4f7b98b8 --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,19 @@ +# Summary + +- [Welcome to robotnix](welcome.md) +- [Configuration](configuration.md) + - [Flavors](modules/flavors.md) + - [F-Droid](modules/f-droid.md) + - [Seedvault Backup](modules/seedvault.md) + - [MicroG](modules/microg.md) + - [Over-the-Air (OTA) Updater](modules/ota.md) + - [Browsers / Webview](modules/browsers.md) + - [Remote Attestation](modules/attestation.md) + - [Prebuilt Apps](modules/prebuilt.md) + - [Source Directories](modules/source.md) + - [Other Modules](modules/other.md) +- [Building](building.md) +- [Installation / Updating](installation.md) +- [Development](development.md) +- [Reference]() + - [Options](options.md) diff --git a/docs/src/building.md b/docs/src/building.md new file mode 100644 index 00000000..6336a7d7 --- /dev/null +++ b/docs/src/building.md @@ -0,0 +1,69 @@ + + +# 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`. +```shell +$ 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;`): +```console +$ 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 `keyStorePath` in the robotnix configuration. + +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. +```console +$ nix-build ./default.nix --arg configuration ./crosshatch.nix -A img --option extra-sandbox-paths /keys=$(pwd)/keys +``` +This, however, will require a nix 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`. +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 creating a "release script" which does the final build steps of signing target files and creating `img`/`ota` files outside of Nix: +```console +$ 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..fingerprint` if you build on a remote machine that does not have access to `keyStorePath`. + +## Binary Cache +Robotnix now has an optional binary cache provided by [Cachix](https://cachix.org/) on [robotnix.cachix.org](https://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): +```shell +$ 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. diff --git a/docs/src/configuration.md b/docs/src/configuration.md new file mode 100644 index 00000000..3f4b5e52 --- /dev/null +++ b/docs/src/configuration.md @@ -0,0 +1,71 @@ + + +# Configuration + +Similarly to [NixOS](https://nixos.org/), robotnix configurations are "Nix expressions" specified using the "[Nix language](https://nixos.org/manual/nix/stable/#ch-expression-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): +```shell +$ 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 +```shell +$ 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`. +```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..fingerprint` to avoid including this path. + 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: +```shell +$ 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](modules.md). +Reference documentation of the available options is [here](options.md). + +## 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: +```shell +$ mkdir flake-test +$ cd flake-test +$ nix flake init -t github:danielfullmer/robotnix +$ # Edit flake.nix in current directory +$ nix build .#robotnixConfigurations.dailydriver.img +``` diff --git a/docs/src/development.md b/docs/src/development.md new file mode 100644 index 00000000..a0b46ee6 --- /dev/null +++ b/docs/src/development.md @@ -0,0 +1,91 @@ + + +# Development + +Robotnix modules use the same Nix-based module system used in NixOS. +To understand the NixOS module system, please [read this](https://nixos.org/manual/nixos/stable/index.html#sec-writing-modules). + +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](https://github.com/danielfullmer/blueprint2nix) and [soongnix](https://github.com/danielfullmer/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` on Freenode. + +## Helper scripts +Robotnix can produce a few helper scripts that can make Android development easier in some circumstances. + +Running `nix-build --arg configuration -A ` 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: +```nix +{ + 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): +```console +$ 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](https://cachix.org). +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. diff --git a/docs/src/installation.md b/docs/src/installation.md new file mode 100644 index 00000000..2263448f --- /dev/null +++ b/docs/src/installation.md @@ -0,0 +1,153 @@ + + +# 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](https://source.android.com/setup/build/running). + + 0. 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”. On my device I had to insert a SIM + card and connect to the network for that, so it looks like you have to + connect your device with Google [at least + once](https://grapheneos.org/install#enabling-oem-unlocking). This is part + of Google's so called Factory Reset Protection (FRP) for anti-theft + protection . However, [this + comment](https://www.kuketz-blog.de/grapheneos-das-android-fuer-sicherheits-und-datenschutzfreaks/#comment-52681) + on a German IT privacy blog suggests that it is sufficient to allow access + to the captive portal such that the phone thinks it is online. + + 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 + ```console + $ adb reboot bootloader + ``` + + 2. Connect your phone to your computer and run + ```console + $ fastboot devices + 09071JEC217048 device + ``` + + 3. Unlock the bootloader by running + ```console + $ 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. + + 4. *(Strongly recommended, but technically optional)* + Flash your custom AVB signing key using + ```console + $ fastboot erase avb_custom_key + $ fastboot flash avb_custom_key avb_pkmd.bin + $ fastboot reboot bootloader + ``` + + 5. Unzip the factory image built by robotnix. To flash the image run + ```console + $ ./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 + ```console + $ 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 + ```console + $ fastboot reboot bootloader + ``` + + 6. *(Strongly recommended, but technically optional)* + At this point you want to relock the bootloader to enable enforcement of + the verified boot chain. + ```console + $ fastboot flashing lock + ``` + This step has to be confirmed on the device. + + 7. After rebooting you will be greeted with an orange 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. + + +## 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](modules/ota.md). +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 + ```console + $ 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. + + 3. 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. + + 4. At the recovery menu use the VOLUME keys to select “Apply update from ADB” + and use POWER to confirm. + + 5. Connect your phone to your computer and run + ```console + $ adb devices + List of devices attached + 09071JEC217048 sideload + ``` + The output should show that the device is in sideload mode. + + 6. Now you can proceed to sideload the new update. + ```console + $ 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](https://np.reddit.com/r/LineageOS/comments/dt2et4/adb_failed_to_read_command_success/f6u352m). + + 7. Once finished and the device doesn't automatically reboot just select + reboot from the menu and confirm. diff --git a/docs/attestation.md b/docs/src/modules/attestation.md similarity index 77% rename from docs/attestation.md rename to docs/src/modules/attestation.md index 748bd2db..8edacb33 100644 --- a/docs/attestation.md +++ b/docs/src/modules/attestation.md @@ -5,6 +5,15 @@ SPDX-License-Identifier: MIT # 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](https://attestation.app/about) 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: @@ -24,7 +33,7 @@ SPDX-License-Identifier: MIT 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, don't get confused (like I did). + will still be there in case you'd like to. ## Server side @@ -44,7 +53,7 @@ SPDX-License-Identifier: MIT BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB ``` - 2. Now you can import Robotnix in your NixOS configuration with the + 2. Now you can import robotnix in your NixOS configuration with the aforementioned fingerprints. ```nix { config, lib, pkgs, ... }: diff --git a/docs/src/modules/browsers.md b/docs/src/modules/browsers.md new file mode 100644 index 00000000..fc9c650d --- /dev/null +++ b/docs/src/modules/browsers.md @@ -0,0 +1,30 @@ +# 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](https://www.chromium.org/) 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](https://www.bromite.org/) is a Chromium fork with ad blocking and enhanced privacy. + +> [Vanadium](https://github.com/GrapheneOS/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. +```nix +{ + 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. diff --git a/docs/f-droid.md b/docs/src/modules/f-droid.md similarity index 81% rename from docs/f-droid.md rename to docs/src/modules/f-droid.md index 05dab69d..4719e08e 100644 --- a/docs/f-droid.md +++ b/docs/src/modules/f-droid.md @@ -3,27 +3,34 @@ SPDX-FileCopyrightText: 2020 Daniel Fullmer and robotnix contributors SPDX-License-Identifier: MIT --> -# Adding F-Droid repositories +# F-Droid + +The following configuration will enable the [F-Droid app](https://www.f-droid.org/) and the [F-Droid privileged extension](https://gitlab.com/fdroid/privileged-extension). +```nix +{ + 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 +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 at -https://microg.org/fdroid/repo. To obtain the repository index download the -`index.jar` file from the repository root: -```console -$ curl -LO https://microg.org/fdroid/repo/index.jar -``` -Java Archives are simply ZIP files with a certain structure, so to get the -repository index out of this file, we unzip the contained `index.xml`: +We'll take the microG repository as an example. The repository is located +[here]( https://microg.org/fdroid/repo). To obtain the repository index +download the `index.xml` file from the repository root: ```console -$ unzip index.jar index.xml +$ 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 @@ -53,3 +60,6 @@ key for the corresponding Nix expression respectively. 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. diff --git a/docs/src/modules/flavors.md b/docs/src/modules/flavors.md new file mode 100644 index 00000000..b2f23b64 --- /dev/null +++ b/docs/src/modules/flavors.md @@ -0,0 +1,55 @@ +# 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](options.md) 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 is based on Android 10. +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. + +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` IRC channel on Freenode. + +## 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 ~150 devices. + +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 flashing 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. diff --git a/docs/src/modules/microg.md b/docs/src/modules/microg.md new file mode 100644 index 00000000..f60e5fb8 --- /dev/null +++ b/docs/src/modules/microg.md @@ -0,0 +1,18 @@ + + +# MicroG + + +[MicroG](https://microg.org/) is a free-as-in-freedom re-implementation of Google’s proprietary Android user space apps and libraries. +MicroG support may be enabled using: +```nix +{ + 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. diff --git a/docs/ota.md b/docs/src/modules/ota.md similarity index 55% rename from docs/ota.md rename to docs/src/modules/ota.md index 75085425..7086dd9c 100644 --- a/docs/ota.md +++ b/docs/src/modules/ota.md @@ -1,8 +1,24 @@ -# Building OTA updates +# Over-the-Air (OTA) Updater -Robotnix 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): +The following robotnix configuration enables the [OTA updater app](https://github.com/GrapheneOS/platform_packages_apps_Updater). +```nix +{ + 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): ```console $ nix-build --arg configuration ./sunfish.nix -A otaDir -o ota-dir ``` @@ -17,58 +33,18 @@ $ tree -l ota-dir The file `sunfish-ota_update-2021.02.06.16.zip` can be sideloaded with adb as described in the next section. -# Installing OTA updates with adb - -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 - ```console - $ 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. - - 3. 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. - - 4. At the recovery menu use the VOLUME keys to select “Apply update from ADB” - and use POWER to confirm. - - 5. Connect your phone to your computer and run - ```console - $ adb devices - List of devices attached - 09071JEC217048 sideload - ``` - The output should show that the device is in sideload mode. - - 6. Now you can proceed to sideload the new update. - ```console - $ 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](https://np.reddit.com/r/LineageOS/comments/dt2et4/adb_failed_to_read_command_success/f6u352m). - - 7. Once finished and the device doesn't automatically reboot just select - reboot from the menu and confirm. - -# Actually serving OTA updates over the air +## Actually serving OTA updates over the air -> *Note:* These instructions were only tested with the GrapheneOS flavor. This -> method does not work for the LineageOS flavor because it uses its own updater. +> *Note:* These instructions have only been tested with the Vanilla and +> GrapheneOS flavors. This method likely will not with the LineageOS flavor +> because it uses its own updater. 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: ```nix -# device configuration +# Device configuration { apps = { updater.enable = true; @@ -76,7 +52,7 @@ OTA updates from: }; } ``` -On the server, it is as easy as serving a directory at the required +On a NixOS server, it is as easy as serving a directory at the required endpoint: ```nix # NixOS server configuration @@ -110,7 +86,7 @@ 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 +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 ``` console diff --git a/docs/src/modules/other.md b/docs/src/modules/other.md new file mode 100644 index 00000000..cdfb8b12 --- /dev/null +++ b/docs/src/modules/other.md @@ -0,0 +1,27 @@ +# Other Modules + +## Resources +Android applications may have [resources](https://developer.android.com/guide/topics/resources/providing-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](https://android.googlesource.com/platform/frameworks/base/+/master/core/res/res/values/config.xml) may be configured in robotnix by setting (for example): +```nix +{ + 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...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...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): +```shell +# mkdir -p -m0770 /var/cache/ccache +# chown root:nixbld /var/cache/ccache +# echo max_size = 100G > /var/cache/ccache/ccache.conf +``` diff --git a/docs/src/modules/prebuilt.md b/docs/src/modules/prebuilt.md new file mode 100644 index 00000000..ca1a2faf --- /dev/null +++ b/docs/src/modules/prebuilt.md @@ -0,0 +1,29 @@ +# 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: +```nix +{ 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..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/`. diff --git a/docs/src/modules/seedvault.md b/docs/src/modules/seedvault.md new file mode 100644 index 00000000..64aabda6 --- /dev/null +++ b/docs/src/modules/seedvault.md @@ -0,0 +1,27 @@ +# Seedvault Backup + +[Seedvault](https://github.com/seedvault-app/seedvault) is a backup application for the Android Open Source Project. +The following configuration will enable the Seedvault: +```nix +{ + 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`. +```shell +$ 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: +```shell +adb shell am start-activity -a com.stevesoltys.seedvault.RESTORE_BACKUP +``` +See the following issue: [seedvault#85](https://github.com/seedvault-app/seedvault/issues/85) diff --git a/docs/src/modules/source.md b/docs/src/modules/source.md new file mode 100644 index 00000000..7e795eea --- /dev/null +++ b/docs/src/modules/source.md @@ -0,0 +1,47 @@ +# 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..src` option. +```nix +{ + 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: +```nix +{ + # source.dirs..patches can refer to a list of patches to apply + source.dirs."frameworks/base".patches = [ ./example.patch ]; + + # source.dirs..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 `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 `drv`s 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. diff --git a/docs/src/options.md b/docs/src/options.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/src/vanilla.md b/docs/src/vanilla.md new file mode 100644 index 00000000..065089d1 --- /dev/null +++ b/docs/src/vanilla.md @@ -0,0 +1 @@ +# Vanilla diff --git a/docs/src/welcome.md b/docs/src/welcome.md new file mode 100644 index 00000000..6da30619 --- /dev/null +++ b/docs/src/welcome.md @@ -0,0 +1,18 @@ + + +# 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](configuration.md), +[build your Android image](building.md), +and [install the image onto your phone](installation.md). + +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` on Freenode. diff --git a/example.nix b/example.nix index 1341a651..cdc698aa 100644 --- a/example.nix +++ b/example.nix @@ -1,6 +1,9 @@ # SPDX-FileCopyrightText: 2020 Daniel Fullmer and robotnix contributors # SPDX-License-Identifier: MIT +# This is an example configuration that I personally use for my device. +# Please read the manual instead of simply copying this file for your own use. + { config, pkgs, lib, ... }: with pkgs; @@ -52,7 +55,7 @@ in url = "https://fdroid.${myDomain}"; pubkey = "308204e7308202cfa003020102020426abb91d300d06092a864886f70d01010b050030243110300e060355040b1307462d44726f69643110300e0603550403130762656c6c6d616e301e170d3139303531383230343734345a170d3436313030333230343734345a30243110300e060355040b1307462d44726f69643110300e0603550403130762656c6c6d616e30820222300d06092a864886f70d01010105000382020f003082020a0282020100adaa800eecc1430a73ab68cdac5d343e7e9d23cd9551a7107c719d51c1ab6e78b1b4ca8cb7454439e312de0316d61b7b6eddeeb9ca2b0851857e88c4440b1990a156ee5ea80aba8cd2fd790fdbac1ea809b5f35d2de081c655c16a99bcfb70338f463a8c1399d402cab86bba00c550aeced23e65e921c8e15619e9917e167d32f4a4ee4950acf7842afd6ad16af7227508aeecfb235312705caf7344e31dd2a8500c373cbbb8108360d899f9d88cdc03f5f437f5d0d13b510c1a362b5ff4d2639b6a06a7b1d918d75ef682ab2efb94345d4a792275a16e06430a52b0a7ebc03518381c6e0a9a854c582886fa146e0a2f430c9947aea3838a2c214cd06def9c4381a34ecb11e658d2c4a94f95d3c6ba9059e72e10c92d0f6a0b6d909226e133b02de5adad53fba42fb9c5d09335c2bd3793a271e9f048a0179741dd0e33c2b4a08b1f6dfc9edc56213f38cf9705fef07b4e8fd40ecc4bddb2b16a6039f319c81138b655e0033cd823566ceb39f08d14d6c21eb23c7d24a7290cb3e3d43093c5d77ea0c013ea1e6a55911f0dc4c0d0815673842f3c24f19c4ae5cfc836b407cebd4e36eed9389cd3abba40e008bb5fdfc2a74f96fb0bf9b23f3d971491f199ea20a111e898423caf46ad3da8784b260be2785ed979b653048f6404f9a729d9153a6921a763aab035959d18352030dbf436452a443a3602913ff22d00810971f2390203010001a321301f301d0603551d0e04160414234ec5a5137a9df3e575dd74d09a00b7ffd676c3300d06092a864886f70d01010b050003820201000c37f2860cd3f1cc446ad656fff1a3444b8975ff7fcfe97f731517af9d98323dee8ed088a1ad31375d927ac52e2891ae6695ae28bd7ad7f083ab53b362046a361b83a54f326aadd0ec61f9fbefa0ddb5afdc793d8fa9970605b583463a92dad8046c8cde7b52f5aca3de5610136b83d6dd5bed6de5e96c25502a7ca451fd00453fe8a4be920da185e75a80b1df75850f440b814183ca22785c2ca1bccbceedbcfca7defceaa9ab2b61bf838e631df65d7733d39d8e28357e8452f70e8a65afaa20384f3f70c018a9274e16cc51bfba6cb6bbb5a1ea6cf1c38ebfd63123b7efd29981bf36dffa395090f1df37f23864ed666442f73b4685d619d9f734f278277c1397f6a4eb0d137c0255def8b7fe9f6676e1a0add9ed4b3e28ffa3af560419a2c5737506df5807ebfbc73c621539d5bb114e405aab7681a9d6c2d492cea902fbc4889c33383ae4fa817e442e1db1634186e6e66234ad4120e67231917cfd29afc48793bf9a95550b4836f5704502b071afc5265ccad0850fdcf6d0541ccfec84d2036ca0f8768390c641bb8b962d3312738a306732f4adc322840965ff4d1a5370c0ca2ca2cd42c524ca6023d40d5550832b7b3fb73b3479484520c8692984c85ccb9ce1e9001f9d9d07a16a0b9d7bff305d6fd30fd7a682f96423effcc32d2463c20c91b4574f6ec2968bfbea59d4258523b9b51e7b60d5d2362889a3f86fd2"; }; - Robotnix = { + robotnix = { enable = true; url = "https://${myDomain}/${config.device}/fdroid/repo"; pubkey = "308204e7308202cfa00302010202045bbd0632300d06092a864886f70d01010b050030243110300e060355040b1307462d44726f69643110300e0603550403130762656c6c6d616e301e170d3139303831363232323134325a170d3437303130313232323134325a30243110300e060355040b1307462d44726f69643110300e0603550403130762656c6c6d616e30820222300d06092a864886f70d01010105000382020f003082020a028202010098d534dc3456e2525385e0c02bb0c6297ae1a0c0f9daa7bce30109abc6fcaa7acb9dda24341ab3e818b68313129de6645c521fcb77b83c3760efabf6373c3a46353aedd1ce2130578c92f12d6a3ce698b7a1f6824bab46c4fbb5b99a0a7bc0c7ce83b3a8ec5d3da8a3f4439e9770cb4e4412ec5fa9cbb36d84925bc6fb56a494e69200b45d59a0ce52840defe995ca82d3d0c807b630a1d129439b0d6d135a58e789b69e91ba0d9ec65294494d5d63d1cb7ce0849f810a8f940698ee6bdbf29f7aa4eb76329e039e0c09edd05cd3da020c19baf9bab67ee59b0eae5ce29950e0d4ab4b9a4b358f80d37a57c0ed09498666b79b9e77cca0842c6826dbe7e2f7d3a0bfad616c609d3f948803be031100aec90abd227e84675d555290536d05302067bdeddcdf4d64a1abc17e7cfa5d5eae08c018cc3877d63d29d4cd425b162f0af7106deecea5a7e8a1d6bd5afd12c2761f4a26451e3f85646582ffb3738ba18a7d5a69a2b6dabd9a3f6689e5dc8f06e4409e00d723c277925aa9b69c123e227f6ff5164df747cfe661a250b4c123cf0dcf87a2ae9108d9ac21db7e933e8c3082208a9842d9d47474581bc31b853f8f48a54663a6a9d272a6de0068444314fbd3085a43cea4512923e6b85087d643e6de88e6e99640996854fbf867be53053558c154071929a0cc3d5182aec9555f52bee71e4eac408031f7dcabdd2811f3db010203010001a321301f301d0603551d0e041604146512cf370744fba93fac225689e8130ac0f1e01f300d06092a864886f70d01010b050003820201002c59f96e42ea0d769eafb25ed3ce8f37e99b6c5745ff35c6d11754eb1802aef5effdc8a78af9d54715ad9a1ecfbe7417c72d88a4ab5c8d392c3b7d5cd33817ce5e54e25f1a8a10b596854121ebbac9fd9d93cf88d12d4674e338ad50711db54c856314c3ef6d3cdcf979f19c8ce30c7717a374d7cc1d2d033f456de0e668a81a11465f5b80ab31e5282a38117c58c99262c732c206222e50e947b241d34324cb79ea3b98f3c6bc2f1f6f7185937bc70bd86ad33ea7f18c0df2e2620cb31f6b3bc9b59f21bcdcffbd4e3dbd5f0e7c33491e8cde2704a80bece6635bdbf7c3c3581d24a5cae729a5866ece0a0e6d094610de3d274de06aeada48dac6562a9e6299c1206816722bd3b3f261a5f90d731610c495d3e1e6cad8239bf6c54fb0eaf7e156dbd1dcea4633a8b2b5561febc9e8e962454b66bd2fe1c953fd48481990ce40557632d4b9195376fae5ab7fb8575dde793dc9bc6a32fd88cdd774c03747d258a9ed4bbf881a640aff3e5a913bb60845dbf385cc6f902e2f29ea63ca7588b445a94abde6acb1ea6a2c1b48ed549bb79964a4a05221e9683427ddb30f44124015d09cf53130634963c4e3e70ced5739b7a041b55dfa8a7f6ac4fcc0650f36a35c23c5ae398f34f6c1cb61368e69b66b0a0703594a7dab98bdf33e7d9a8cf477824afe5f8a1b8d5bd805a638d959f818f27932a3579e08a30d3bf63d21babb445d"; diff --git a/flake.nix b/flake.nix index e3b052b6..ab95e3a2 100644 --- a/flake.nix +++ b/flake.nix @@ -27,5 +27,9 @@ nixosModules.attestation-server = import ./nixos/attestation-server/module.nix; checks.x86_64-linux = {}; + + packages.x86_64-linux = { + manual = (import ./docs { pkgs = nixpkgs.legacyPackages.x86_64-linux; }).manual; + }; }; } diff --git a/flavors/grapheneos/default.nix b/flavors/grapheneos/default.nix index c1310dd1..51322e17 100644 --- a/flavors/grapheneos/default.nix +++ b/flavors/grapheneos/default.nix @@ -78,14 +78,13 @@ in mkIf (config.flavor == "grapheneos") (mkMerge [ signing.apex.enable = false; # Don't include updater by default since it would download updates signed with grapheneos's keys. - # TODO: Encourage user to set apps.updater.enable source.dirs."packages/apps/Updater".enable = false; # Leave the existing auditor in the build--just in case the user wants to # audit devices using the official upstream build } (mkIf (elem config.deviceFamily phoneDeviceFamilies) { - kernel.useCustom = mkDefault true; + kernel.enable = mkDefault true; kernel.src = mkDefault config.source.dirs."kernel/google/${config.kernel.name}".src; kernel.configName = config.device; kernel.relpath = "device/google/${config.device}-kernel"; diff --git a/flavors/vanilla/default.nix b/flavors/vanilla/default.nix index e2584552..32ea8c33 100644 --- a/flavors/vanilla/default.nix +++ b/flavors/vanilla/default.nix @@ -126,7 +126,7 @@ in mkIf (config.flavor == "vanilla") (mkMerge [ # TODO: Currently, only build kernel for marlin since it needs verity key in build. # Could also build for other devices, like is done for Android 11 - kernel.useCustom = mkDefault config.signing.enable; + kernel.enable = mkDefault config.signing.enable; }) ])) @@ -162,7 +162,7 @@ in mkIf (config.flavor == "vanilla") (mkMerge [ ]; } (mkIf (elem config.deviceFamily phoneDeviceFamilies) { - kernel.useCustom = mkDefault true; + kernel.enable = mkDefault true; kernel.configName = mkMerge [ (mkIf (elem config.deviceFamily [ "taimen" "muskie" ]) "wahoo") (mkIf (config.deviceFamily == "crosshatch") "b1c1") diff --git a/modules/apps/auditor.nix b/modules/apps/auditor.nix index cc820d3e..4497ea70 100644 --- a/modules/apps/auditor.nix +++ b/modules/apps/auditor.nix @@ -15,8 +15,8 @@ in domain = mkOption { type = types.str; - default = "attestation.app"; - description = "Domain running the AttestationServer (over https) for remote verification"; + description = "Domain running the AttestationServer (over HTTPS) for remote verification"; + example = "attestation.example.com"; }; }; }; @@ -24,7 +24,7 @@ in config = mkIf cfg.enable { assertions = [ { assertion = builtins.elem config.device supportedDevices; - message = "Device ${config.device} is currently unsupported for use with auditor app."; + message = "Device ${config.device} is currently unsupported for use with Auditor app."; } ]; apps.prebuilt.Auditor = { diff --git a/modules/apps/fdroid.nix b/modules/apps/fdroid.nix index 56e5a08d..658eb56d 100644 --- a/modules/apps/fdroid.nix +++ b/modules/apps/fdroid.nix @@ -17,41 +17,55 @@ in options.apps.fdroid = { enable = mkEnableOption "F-Droid"; - # See apps/src/main/java/org/fdroid/fdroid/data/DBHelper.java in fdroid source - # Note that changes to this setting will only take effect on a freshly - # installed device--or if the FDroid storage is cleared + # See also `apps/src/main/java/org/fdroid/fdroid/data/DBHelper.java` in F-Droid source additionalRepos = mkOption { default = {}; + description = '' + 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. + ''; type = types.attrsOf (types.submodule ({ name, ... }: { options = { - enable = mkEnableOption name; + enable = mkOption { + default = false; + type = types.bool; + description = "Whether to enable this repository by default in F-Droid."; + }; name = mkOption { default = name; type = types.str; + description = "Display name to use for this repository"; }; url = mkOption { type = types.str; + description = "URL for F-Droid repository"; }; description = mkOption { - default = "Empty description"; # fdroid parsing of additional_repos.xml requires all items to have text type = types.str; + default = "Empty description"; # fdroid parsing of additional_repos.xml requires all items to have text + description = "Longer textual description of this repository"; }; - version = mkOption { # Not sure what this one is for exactly - default = 1; + version = mkOption { type = types.int; + default = 0; + description = "Which version of fdroidserver built this repo"; + internal = true; }; - pushRequests = mkOption { # Repo metadata can specify apps to be installed/removed + pushRequests = mkOption { type = types.strMatching "(ignore|prompt|always)"; + description = "Allow this repository to specify apps which should be automatically installed/uninstalled"; default = "ignore"; }; pubkey = mkOption { # Wew these are long AF. TODO: Some way to generate these? type = types.str; + description = "Public key associated with this repository. Can be found in `/index.xml` under the repo URL."; }; }; })); diff --git a/modules/apps/prebuilt.nix b/modules/apps/prebuilt.nix index 32dd5e30..e2d7b8b7 100644 --- a/modules/apps/prebuilt.nix +++ b/modules/apps/prebuilt.nix @@ -60,44 +60,50 @@ in options = { apps.prebuilt = mkOption { default = {}; + description = "Prebuilt APKs to include in the robotnix build"; + type = let _config = config; in types.attrsOf (types.submodule ({ name, config, ... }: { options = { name = mkOption { default = name; - type = types.str; # No spaces (use strMatching?) + description = "Name of application. (No spaces)"; + type = types.str; # TODO: Use strMatching to enforce no spaces? }; apk = mkOption { type = types.path; + description = "APK file to include in build"; }; signedApk = mkOption { type = types.path; internal = true; + description = "Robotnix-signed version of APK file"; }; fingerprint = mkOption { - description = "SHA256 fingerprint from certificate used to sign apk"; + description = "SHA256 fingerprint from certificate used to sign apk. Should be set automatically based on `keyStorePath` if `signing.enable = true`"; type = types.strMatching "[0-9A-F]{64}"; # TODO: Type check fingerprints elsewhere apply = toUpper; internal = true; }; packageName = mkOption { # Only used with privapp permissions - type = types.str; - description = "example: com.android.test"; + description = "APK's Java-style package name (applicationId). This setting only necessary to be set if also using `privappPermissions`."; + type = types.strMatching "[a-zA-Z0-9_.]*"; + example = "com.android.test"; }; certificate = mkOption { default = toLower name; type = types.str; description = '' - Certificate name to sign apk with. Defaults to the name of the prebuilt app. - If it is a device-specific certificate, the cert/key will be ''${keyStorePath}/''${device}/''${certificate}.{x509.pem,pk8} - Otherwise, it will be ''${keyStorePath}/''${certificate}.{x509.pem,pk8} - Finally, the special string "PRESIGNED" will just use the apk as-is. + 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. ''; }; @@ -109,39 +115,45 @@ in privileged = mkOption { default = false; type = types.bool; + description = "Whether this APK should be included as a privileged application."; }; privappPermissions = mkOption { default = []; type = types.listOf types.str; description = '' - See https://developer.android.com/reference/android/Manifest.permission and note permissions which say + Privileged permissions to apply to this application. + Refer to this [link](https://developer.android.com/reference/android/Manifest.permission) and note permissions which say "not for use by third-party applications". ''; - example = ''[ "INSTALL_PACKAGES" ]''; + example = [ "INSTALL_PACKAGES" ]; }; defaultPermissions = mkOption { default = []; type = types.listOf types.str; - description = '' - Permissions which are to be enabled by default without user prompting - ''; - example = ''[ "INSTALL_PACKAGES" ]''; + description = "Permissions to be enabled by default without user prompting."; + example = [ "INSTALL_PACKAGES" ]; }; partition = mkOption { + description = "Partition on which to place this app"; type = types.strMatching "(vendor|system|product)"; }; allowInPowerSave = mkOption { default = false; type = types.bool; + description = '' + Whether to allow this application to operate in \"power save\" mode. + Disables battery optimization for this app. + ''; }; extraConfig = mkOption { default = ""; type = types.lines; + internal = true; }; }; diff --git a/modules/apps/updater.nix b/modules/apps/updater.nix index 9fab154e..0c787824 100644 --- a/modules/apps/updater.nix +++ b/modules/apps/updater.nix @@ -17,7 +17,7 @@ in { options = { apps.updater = { - enable = mkEnableOption "updater"; + enable = mkEnableOption "OTA Updater"; url = mkOption { type = types.str; diff --git a/modules/apv.nix b/modules/apv.nix index c13e1f5e..1e7de52e 100644 --- a/modules/apv.nix +++ b/modules/apv.nix @@ -3,7 +3,7 @@ { config, pkgs, lib, ... }: -# Robotnix module for android-prepare-vendor (apv) +# Android-prepare-vendor is currently only useful for Pixel phones with lib; let @@ -73,29 +73,30 @@ in img = mkOption { default = null; type = types.path; - description = "A factory image .zip from upstream whose vendor contents should be extracted and included in the build"; + description = "A factory image `.zip` from upstream whose vendor contents should be extracted and included in the build"; }; ota = mkOption { default = null; type = types.path; - description = "An ota from upstream whose vendor contents should be extracted and included in the build (Android 10 builds need an OTA as well)"; + description = "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`)"; }; systemBytecode = mkOption { type = types.listOf types.str; default = []; + internal = true; }; systemOther = mkOption { type = types.listOf types.str; default = []; + internal = true; }; buildID = mkOption { type = types.str; description = "Build ID associated with the upstream img/ota (used to select images)"; - internal = true; }; }; diff --git a/modules/base.nix b/modules/base.nix index 3f45ee1b..90c688a4 100644 --- a/modules/base.nix +++ b/modules/base.nix @@ -38,7 +38,10 @@ in flavor = mkOption { default = null; type = types.nullOr types.str; - description = "One of robotnix's supported flavors."; + description = '' + Set to one of robotnix's supported flavors. + Current options are `vanilla`, `grapheneos`, and `lineageos`. + ''; example = "vanilla"; }; @@ -54,7 +57,6 @@ in type = types.nullOr types.str; description = "Display name of device build target"; example = "Pixel XL"; - internal = true; }; deviceFamily = mkOption { @@ -72,12 +74,18 @@ in variant = mkOption { default = "user"; type = types.strMatching "(user|userdebug|eng)"; - description = "one of \"user\", \"userdebug\", or \"eng\""; + description = '' + `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. + ''; }; productName = mkOption { type = types.str; - description = "Product name for choosecombo/lunch (defaults to aosp_\${device})"; + description = "Product name for choosecombo/lunch"; + defaultText = "\${productNamePrefix}\${device}"; + example = "aosp_crosshatch"; }; productNamePrefix = mkOption { @@ -94,21 +102,29 @@ in buildNumber = mkOption { type = types.str; - description = "Set this to something meaningful, like the date. Needs to be unique for each build for the updater to work"; + description = '' + Set this to something meaningful to identify the build. + Defaults to `YYYY.MM.DD.HH` based on `buildDateTime`. + Needs to be unique for each build to enable the updater to work + ''; example = "2019.08.12.1"; }; buildDateTime = mkOption { default = 1; type = types.int; - description = "Seconds since the epoch that this build is taking place. Needs to be monotone increasing for the updater to work. e.g. output of \"date +%s\""; + description = '' + Seconds since the epoch that this build is taking place. + Needs to be monotonically increasing for each build to enable the updater to work. + e.g. output of `date +%s` + ''; example = 1565645583; }; androidVersion = mkOption { default = 11; type = types.int; - description = "Used to select which android version to use"; + description = "Used to select which Android version to use"; }; apiLevel = mkOption { @@ -120,19 +136,19 @@ in system.additionalProductPackages = mkOption { default = []; type = types.listOf types.str; - description = "PRODUCT_PACKAGES to add to build"; + description = "`PRODUCT_PACKAGES` to add under `system` partition."; }; product.additionalProductPackages = mkOption { default = []; type = types.listOf types.str; - description = "PRODUCT_PACKAGES to add to build"; + description = "`PRODUCT_PACKAGES` to add under `product` partition."; }; removedProductPackages = mkOption { default = []; type = types.listOf types.str; - description = "PRODUCT_PACKAGES to remove from build"; + description = "`PRODUCT_PACKAGES` to remove from build"; }; system.extraConfig = mkOption { diff --git a/modules/etc.nix b/modules/etc.nix index 3df71c38..afd1cb9c 100644 --- a/modules/etc.nix +++ b/modules/etc.nix @@ -25,21 +25,27 @@ in options = { etc = mkOption { default = {}; + description = "Set of files to be included under `/etc`"; + type = let _config = config; in types.attrsOf (types.submodule ({ name, config, ... }: { + # robotnix etc.* options correspond to the etc.* options from NixOS options = { target = mkOption { type = types.str; + description = "Name of symlink (relative to `/etc`). Defaults to the attribute name."; }; text = mkOption { default = null; type = types.nullOr types.str; + description = "Text of the file"; }; source = mkOption { type = types.path; + description = "Path of the source file"; }; moduleName = mkOption { @@ -49,6 +55,7 @@ in partition = mkOption { type = types.strMatching "(vendor|system|product)"; + description = "Partition on which to place this etc file"; }; }; diff --git a/modules/framework.nix b/modules/framework.nix index c0ad9b19..94c94f71 100644 --- a/modules/framework.nix +++ b/modules/framework.nix @@ -25,16 +25,20 @@ in options = { framework = mkOption { default = {}; + internal = true; # TODO: Expose to user after cleaning up + type = let _config = config; in types.attrsOf (types.submodule ({ name, config, ... }: { options = { target = mkOption { type = types.str; + internal = true; }; source = mkOption { type = types.path; + internal = true; }; moduleName = mkOption { @@ -44,6 +48,7 @@ in partition = mkOption { type = types.strMatching "(vendor|system|product)"; + internal = true; }; }; diff --git a/modules/google.nix b/modules/google.nix index 2887875b..7e16d2c3 100644 --- a/modules/google.nix +++ b/modules/google.nix @@ -32,9 +32,9 @@ in # TODO: Add other google stuff. Ensure that either google play services or microg is enabled if these are. options = { google = { - base.enable = mkEnableOption "Base Google OEM files"; - dialer.enable = mkEnableOption "Google Dialer"; - fi.enable = mkEnableOption "Google Fi"; + base.enable = mkEnableOption "Base Google OEM files (experimental)"; + dialer.enable = mkEnableOption "Google Dialer (experimental)"; + fi.enable = mkEnableOption "Google Fi (experimental)"; }; }; diff --git a/modules/kernel.nix b/modules/kernel.nix index 0fcda8c1..084b14ee 100644 --- a/modules/kernel.nix +++ b/modules/kernel.nix @@ -48,10 +48,7 @@ in { options = { kernel = { - useCustom = mkOption { - default = false; - type = types.bool; - }; + enable = mkEnableOption "building custom kernel"; name = mkOption { internal = true; @@ -61,20 +58,24 @@ in configName = mkOption { internal = true; type = types.str; + description = ''Name of kernel configuration to build. Make builds ''${kernel.configName}_defconfig"''; }; src = mkOption { type = types.path; + description = "Path to kernel source"; }; patches = mkOption { default = []; type = types.listOf types.path; + description = "List of patches to apply to kernel source"; }; postPatch = mkOption { default = ""; type = types.lines; + description = "Commands to run after patching kernel source"; }; relpath = mkOption { @@ -85,21 +86,26 @@ in compiler = mkOption { default = "clang"; type = types.strMatching "(gcc|clang)"; + description = "Compilter to use for building kernel"; }; clangVersion = mkOption { type = types.str; - description = "Version of prebuilt clang to use for kernel. See https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+/master/README.md"; + description = '' + Version of prebuilt clang to use for kernel. + See https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+/master/README.md" + ''; }; linker = mkOption { default = "gold"; type = types.strMatching "(gold|lld)"; + description = "Linker to use for building kernel"; }; buildProductFilenames = mkOption { type = types.listOf types.str; - description = "list of build products in kernel out/ to copy into relpath"; + description = "List of build products in kernel `out/` to copy into path specified by `kernel.relpath`."; }; }; }; @@ -188,7 +194,7 @@ in # config.build.kernel drv output in place of source.dirs.${cfg.relpath}. # This is because there are some additional things in the prebuilt kernel # output directory like kernel headers for sunfish under device/google/sunfish-kernel/sm7150 - source = mkIf cfg.useCustom { + source = mkIf cfg.enable { dirs.${cfg.relpath}.postPatch = '' cp -fv ${config.build.kernel}/* . ''; diff --git a/modules/microg.nix b/modules/microg.nix index e8a1fc96..eb0b0186 100644 --- a/modules/microg.nix +++ b/modules/microg.nix @@ -18,7 +18,7 @@ let in { options = { - microg.enable = mkEnableOption "microg"; + microg.enable = mkEnableOption "MicroG"; }; config = mkIf config.microg.enable { diff --git a/modules/release.nix b/modules/release.nix index 1300f52d..43a6f273 100644 --- a/modules/release.nix +++ b/modules/release.nix @@ -85,14 +85,16 @@ in incremental = mkOption { default = false; type = types.bool; - description = "Whether to include an incremental build in otaDir"; + description = "Whether to include an incremental build in `otaDir` output"; }; retrofit = mkOption { default = false; type = types.bool; - description = "Generate a retrofit OTA for upgrading a device without dynamic partitions"; - # https://source.android.com/devices/tech/ota/dynamic_partitions/ab_legacy#generating-update-packages + description = '' + 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 + ''; }; otaArgs = mkOption { @@ -143,8 +145,9 @@ in ); # TODO: Do this in a temporary directory. It's ugly to make build dir and ./tmp/* dir gets cleared in these scripts too. - # Maybe just remove this script? It's definitely complicated--and often untested - releaseScript = pkgs.writeScript "release.sh" ('' + releaseScript = + (if (!config.signing.enable) then warn "releaseScript should be used only if signing.enable = true; Otherwise, the build might be using incorrect keys / certificate metadata" else id) + pkgs.writeScript "release.sh" ('' #!${pkgs.runtimeShell} set -euo pipefail @@ -154,12 +157,8 @@ in PREV_BUILDNUMBER="" fi '' + (wrapScript { keysDir="$1"; commands='' - if [[ "$KEYSDIR" ]]; then - echo Signing target files - ${signedTargetFilesScript { targetFiles=unsignedTargetFiles; out=signedTargetFiles.name; }} - else - echo No KEYSDIR specified. Skipping signing target files. - fi + echo Signing target files + ${signedTargetFilesScript { targetFiles=unsignedTargetFiles; out=signedTargetFiles.name; }} echo Building OTA zip ${otaScript { targetFiles=signedTargetFiles.name; out=ota.name; }} if [[ ! -z "$PREV_BUILDNUMBER" ]]; then diff --git a/modules/resources.nix b/modules/resources.nix index b9eb8244..1c92a6a0 100644 --- a/modules/resources.nix +++ b/modules/resources.nix @@ -33,7 +33,8 @@ in resources = mkOption { default = {}; type = with types; attrsOf (attrsOf (either resourceTypeGeneric resourceTypeModule)); - description = "Package resources. The first key refers to the relative path for the package, and the second key refers to the resource name"; + description = "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"; + example = literalExample "{ \"frameworks/base/core/res\".config_enableAutoPowerModes = true; }"; }; }; diff --git a/modules/signing.nix b/modules/signing.nix index 3a2cd0ef..61436d1e 100644 --- a/modules/signing.nix +++ b/modules/signing.nix @@ -43,12 +43,13 @@ in mode = mkOption { type = types.strMatching "(verity_only|vbmeta_simple|vbmeta_chained|vbmeta_chained_v2)"; default = "vbmeta_chained"; + description = "Mode of AVB signing to use."; }; fingerprint = mkOption { type = types.strMatching "[0-9A-F]{64}"; apply = toUpper; - description = "SHA256 hash of avb_pkmd.bin"; + description = "SHA256 hash of `avb_pkmd.bin`. Should be set automatically based on file under `keyStorePath` if `signing.enable = true`"; }; verityCert = mkOption { @@ -58,7 +59,7 @@ in }; apex = { - enable = mkEnableOption "APEX signing"; + enable = mkEnableOption "signing APEX packages"; packageNames = mkOption { default = []; @@ -70,7 +71,11 @@ in keyStorePath = mkOption { type = types.str; - description = "Absolute path to generated keys for signing"; + description = '' + 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"; }; }; diff --git a/modules/source.nix b/modules/source.nix index 9b816dff..8747afb4 100644 --- a/modules/source.nix +++ b/modules/source.nix @@ -45,16 +45,18 @@ let enable = mkOption { default = true; type = types.bool; - description = "Include this directory in the android build source tree"; + description = "Whether to include this directory in the android build source tree."; }; relpath = mkOption { default = name; type = types.str; + description = "Relative path under android source tree to place this directory. Defaults to attribute name."; }; src = mkOption { type = types.path; + description = "Source to use for this android source directory."; default = pkgs.runCommand "empty" {} "mkdir -p $out"; apply = src: # Maybe replace with with pkgs.applyPatches? Need patchFlags though... if (config.patches != [] || config.postPatch != "") @@ -71,11 +73,13 @@ let patches = mkOption { default = []; type = types.listOf types.path; + description = "Patches to apply to source directory."; }; postPatch = mkOption { default = ""; type = types.lines; + description = "Additional commands to run after patching source directory."; }; unpackScript = mkOption { @@ -171,42 +175,49 @@ in manifest = { url = mkOption { type = types.str; + description = "URL to repo manifest repository. Not necessary to set if using `source.dirs` directly."; }; rev = mkOption { type = types.str; + description = "Revision/tag to use from repo manifest repository."; }; sha256 = mkOption { type = types.str; + description = "Nix sha256 hash of repo manifest repository."; }; }; evalTimeFetching = mkOption { default = false; description = '' - 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. (Useful for - development) + 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) ''; }; dirs = mkOption { default = {}; type = types.attrsOf dirModule; + description = '' + 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. + ''; }; excludeGroups = mkOption { default = [ "darwin" "mips" ]; type = types.listOf types.str; - description = "project groups to exclude from source tree"; + description = "Project groups to exclude from source tree"; }; includeGroups = mkOption { default = []; type = types.listOf types.str; - description = "project groups to include in source tree (overrides excludeGroups)"; + description = "Project groups to include in source tree (overrides `excludeGroups`)"; }; unpackScript = mkOption { diff --git a/modules/webview.nix b/modules/webview.nix index 872d0910..55f5a920 100644 --- a/modules/webview.nix +++ b/modules/webview.nix @@ -7,6 +7,9 @@ with lib; { options = { webview = mkOption { + description = "Webview providers to include in Android build. Pre-specified options are `chromium`, `bromite`, and `vanadium`."; + example = literalExample "{ bromite.enable = true; }"; + type = types.attrsOf (types.submodule ({ name, config, ... }: { options = { enable = mkEnableOption "${name} webview"; @@ -27,10 +30,10 @@ with lib; type = types.bool; default = false; description = '' - 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. + 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. ''; }; @@ -38,15 +41,16 @@ with lib; type = types.bool; default = false; description = '' - 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. + 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. ''; }; apk = mkOption { type = types.path; + description = "APK file containing webview package."; }; }; })); diff --git a/release.nix b/release.nix index b9b23379..238e73dd 100644 --- a/release.nix +++ b/release.nix @@ -103,7 +103,7 @@ in kernels = lib.recurseIntoAttrs (lib.mapAttrs (name: c: c.config.build.kernel) - (lib.filterAttrs (name: c: c.config.kernel.useCustom) builtConfigs)); + (lib.filterAttrs (name: c: c.config.kernel.enable) builtConfigs)); tests = lib.recurseIntoAttrs { attestation-server = tests.attestation-server.test;