Image by LineageOS.

I have been building my own lineage images for a while but, I have to say this is by far the simplest method. Sit down and eat, no cleaning up the kitchen. This is a quick guide for building lineage with the lineageos4microg/docker-lineage-cicd docker container.

Why?

  1. Cross platform, docker runs almost anywhere. So if you are stuck on windows, no need to boot up a VM and potentially lose on performance.
  2. No routine required. No need to schedule running breakfast and brunch, just run the container.
  3. No need to pull binary blobs from your phone. The container is configured to automatically pull necessary drivers and vendor blobs from https://github.com/TheMuppets/manifests.
  4. Supports embedding custom packages like FDroidPrivilegedExtension, MozillaNlpBackend, GsfProxy, GMSCore, Microg etc.
  5. Build multiple device versions at once, even with different branches.

Operational security disclaimer:

This guide is for relatively advanced users who understand the security trade offs that comes with building unofficially with third party tools. We are pulling configuration files and source code data from numerous sources. Failure to understand certain decisions, trade offs, special configurations may put your security at risk. I strongly advise you to do your own research, read the docker file, verify the sources etc.

I used DigitalOcean for this build:

  • Standard droplet
  • 4 x 6CPU cores
  • 320GB SSD disk
  • 16GB RAM
  • 6TB bandwidth

The whole build costed me $0.60 for 4 hours of accounted VPS usage.

TIP:

Launch your VPS in a location where you can expect good bandwidth, the lineage os and AOSP source code is <40GB, so the closer you are to the GitHub, Google CDNs the faster you can finish downloading source code. At least 8GB of RAM is recommended. It is possible to optimize the build process by playing around with JACK’s RAM allowance and higher CCACHE, the default is 50G. Launch the build in GNU Screen, this will keep the build runnnig even when you log off SSH so you can come back after a long ass coffee break.

Install docker

The official Docker guides are well-written: Linux (Ubuntu, Debian, CentOS and Fedora are officially supported) Windows 10/Windows Server 2016 64bit Mac OS El Capitan 10.11 or newer

Optional: Install Screen

# Arch/Manjaro
$  ~ pacman -S screen
# Debian/Ubuntu
$  ~ apt install screen

This will create a new screen named ‘lineage_build’ and drop us into that shell

screen -S lineage_build

To get out of this press CTRL + A then D.

To get back in the shell type

screen -R

Run the Docker image

Build for bacon on lineage 16 with full fdroid packages.

docker run \
-e "BRANCH_NAME=lineage-16.0" \
-e "DEVICE_LIST=bacon" \
-e "SIGN_BUILDS=true" \
-e "CUSTOM_PACKAGES=FDroid FDroidPrivilegedExtension" \
-v "/home/user/lineage:/srv/src" \
-v "/home/user/zips:/srv/zips" \
-v "/home/user/logs:/srv/logs" \
-v "/home/user/cache:/srv/ccache" \
-v "/home/user/keys:/srv/keys" \
-v "/home/user/manifests:/srv/local_manifests" \
lineageos4microg/docker-lineage-cicd

Wait for it to build

Digest: sha256:8c76f4ec6b2d193dd80c84b805e3769c2d810f52d9fd38e7c71e047ecaef7aea
Status: Downloaded newer image for lineageos4microg/docker-lineage-cicd:latest
Set cache size limit to 50.0 GB
>> [Fri Jul 12 13:17:09 UTC 2019] Branch:  lineage-16.0
>> [Fri Jul 12 13:17:09 UTC 2019] Devices: bacon,
>> [Fri Jul 12 13:17:09 UTC 2019] (Re)initializing branch repository
>> [Fri Jul 12 13:17:16 UTC 2019] Copying '/srv/local_manifests/*.xml' to '.repo/local_manifests/'
>> [Fri Jul 12 13:17:16 UTC 2019] Syncing branch repository
>> [Fri Jul 12 13:48:37 UTC 20zsh19] Setting "UNOFFICIAL" as release type
>> [Fri Jul 12 13:48:37 UTC 20zsh19] Adding OTA URL overlay (for custom URL )
>> [Fri Jul 12 13:48:37 UTC 20zsh19] Using OpenJDK 8
>> [Fri Jul 12 13:48:38 UTC 20zsh19] Preparing build environment
>> [Fri Jul 12 13:48:38 UTC 2019] Starting build for bacon, lineage-16.0 branch
>> [Fri Jul 12 16:42:45 UTC 2019] Moving build artifacts for bacon to '/srv/zips/bacon'
>> [Fri Jul 12 16:42:48 UTC 2019] Finishing build for bacon
>> [Fri Jul 12 16:42:48 UTC 2019] Cleaning source dir for device bacon

VPS usage during the build During the build

Change the directory to build output

[email protected]:~#cd /home/user/zips
[email protected]:/home/user/zips# cd bacon/
[email protected]:/home/user/zips/bacon# ls
lineage-16.0-20190712-UNOFFICIAL-bacon.zip
lineage-16.0-20190712-UNOFFICIAL-bacon.zip.md5sum
lineage-16.0-20190712-UNOFFICIAL-bacon.zip.sha256sumd

Enjoy

This was made possible by lineageos4microg team.

Check them out: https://github.com/lineageos4microg

Original project and additonal instructions: https://hub.docker.com/r/lineageos4microg/docker-lineage-cicd

The dockerfile

FROM debian:stretch
MAINTAINER Nicola Corna <[email protected]>

# Environment variables
#######################

ENV MIRROR_DIR /srv/mirror
ENV SRC_DIR /srv/src
ENV TMP_DIR /srv/tmp
ENV CCACHE_DIR /srv/ccache
ENV ZIP_DIR /srv/zips
ENV LMANIFEST_DIR /srv/local_manifests
ENV DELTA_DIR /srv/delta
ENV KEYS_DIR /srv/keys
ENV LOGS_DIR /srv/logs
ENV USERSCRIPTS_DIR /srv/userscripts

ENV DEBIAN_FRONTEND noninteractive
ENV USER root

# Configurable environment variables
####################################

# By default we want to use CCACHE, you can disable this
# WARNING: disabling this may slow down a lot your builds!
ENV USE_CCACHE 1

# ccache maximum size. It should be a number followed by an optional suffix: k,
# M, G, T (decimal), Ki, Mi, Gi or Ti (binary). The default suffix is G. Use 0
# for no limit.
ENV CCACHE_SIZE 50G

# Environment for the LineageOS branches name
# See https://github.com/LineageOS/android_vendor_cm/branches for possible options
ENV BRANCH_NAME 'cm-14.1'

# Environment for the device list (separate by comma if more than one)
# eg. DEVICE_LIST=hammerhead,bullhead,angler
ENV DEVICE_LIST ''

# Release type string
ENV RELEASE_TYPE 'UNOFFICIAL'

# OTA URL that will be used inside CMUpdater
# Use this in combination with LineageOTA to make sure your device can auto-update itself from this buildbot
ENV OTA_URL ''

# User identity
ENV USER_NAME 'LineageOS Buildbot'
ENV USER_MAIL '[email protected]'

# Include proprietary files, downloaded automatically from github.com/TheMuppets/
# Only some branches are supported
ENV INCLUDE_PROPRIETARY true

# Mount an overlay filesystem over the source dir to do each build on a clean source
ENV BUILD_OVERLAY false

# Clone the full LineageOS mirror (> 200 GB)
ENV LOCAL_MIRROR false

# If you want to preserve old ZIPs set this to 'false'
ENV CLEAN_OUTDIR false

# Change this cron rule to what fits best for you
# Use 'now' to start the build immediately
# For example, '0 10 * * *' means 'Every day at 10:00 UTC'
ENV CRONTAB_TIME 'now'

# Clean artifacts output after each build
ENV CLEAN_AFTER_BUILD true

# Provide root capabilities builtin inside the ROM (see http://lineageos.org/Update-and-Build-Prep/)
ENV WITH_SU false

# Provide a default JACK configuration in order to avoid out-of-memory issues
ENV ANDROID_JACK_VM_ARGS "-Dfile.encoding=UTF-8 -XX:+TieredCompilation -Xmx4G"

# Custom packages to be installed
ENV CUSTOM_PACKAGES ''

# Sign the builds with the keys in $KEYS_DIR
ENV SIGN_BUILDS false

# When SIGN_BUILDS = true but no keys have been provided, generate a new set with this subject
ENV KEYS_SUBJECT '/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/[email protected]'

# Move the resulting zips to $ZIP_DIR/$codename instead of $ZIP_DIR/
ENV ZIP_SUBDIR true

# Write the verbose logs to $LOGS_DIR/$codename instead of $LOGS_DIR/
ENV LOGS_SUBDIR true

# Apply the MicroG's signature spoofing patch
# Valid values are "no", "yes" (for the original MicroG's patch) and
# "restricted" (to grant the permission only to the system privileged apps).
#
# The original ("yes") patch allows user apps to gain the ability to spoof
# themselves as other apps, which can be a major security threat. Using the
# restricted patch and embedding the apps that requires it as system privileged
# apps is a much secure option. See the README.md ("Custom mode") for an
# example.
ENV SIGNATURE_SPOOFING "no"

# Generate delta files
ENV BUILD_DELTA false

# Delete old zips in $ZIP_DIR, keep only the N latest one (0 to disable)
ENV DELETE_OLD_ZIPS 0

# Delete old deltas in $DELTA_DIR, keep only the N latest one (0 to disable)
ENV DELETE_OLD_DELTAS 0

# Delete old logs in $LOGS_DIR, keep only the N latest one (0 to disable)
ENV DELETE_OLD_LOGS 0

# Create a JSON file that indexes the build zips at the end of the build process
# (for the updates in OpenDelta). The file will be created in $ZIP_DIR with the
# specified name; leave empty to skip it.
# Requires ZIP_SUBDIR.
ENV OPENDELTA_BUILDS_JSON ''

# You can optionally specify a USERSCRIPTS_DIR volume containing these scripts:
#  * begin.sh, run at the very beginning
#  * before.sh, run after the syncing and patching, before starting the builds
#  * pre-build.sh, run before the build of every device 
#  * post-build.sh, run after the build of every device
#  * end.sh, run at the very end
# Each script will be run in $SRC_DIR and must be owned and writeable only by
# root

# Create Volume entry points
############################
VOLUME $MIRROR_DIR
VOLUME $SRC_DIR
VOLUME $TMP_DIR
VOLUME $CCACHE_DIR
VOLUME $ZIP_DIR
VOLUME $LMANIFEST_DIR
VOLUME $DELTA_DIR
VOLUME $KEYS_DIR
VOLUME $LOGS_DIR
VOLUME $USERSCRIPTS_DIR

# Copy required files
#####################
COPY src/ /root/

# Create missing directories
############################
RUN mkdir -p $MIRROR_DIR
RUN mkdir -p $SRC_DIR
RUN mkdir -p $TMP_DIR
RUN mkdir -p $CCACHE_DIR
RUN mkdir -p $ZIP_DIR
RUN mkdir -p $LMANIFEST_DIR
RUN mkdir -p $DELTA_DIR
RUN mkdir -p $KEYS_DIR
RUN mkdir -p $LOGS_DIR
RUN mkdir -p $USERSCRIPTS_DIR

# Install build dependencies
############################
RUN echo 'deb http://deb.debian.org/debian sid main' >> /etc/apt/sources.list
RUN echo 'deb http://deb.debian.org/debian experimental main' >> /etc/apt/sources.list
COPY apt_preferences /etc/apt/preferences
RUN apt-get -qq update
RUN apt-get -qqy upgrade

RUN apt-get install -y bc bison bsdmainutils build-essential ccache cgpt cron \
      curl flex g++-multilib gcc-multilib git gnupg gperf imagemagick kmod \
      lib32ncurses5-dev lib32readline-dev lib32z1-dev libesd0-dev liblz4-tool \
      libncurses5-dev libsdl1.2-dev libssl-dev libwxgtk3.0-dev libxml2 \
      libxml2-utils lsof lzop maven openjdk-7-jdk openjdk-8-jdk pngcrush \
      procps python rsync schedtool squashfs-tools wget xdelta3 xsltproc yasm \
      zip zlib1g-dev

RUN curl https://storage.googleapis.com/git-repo-downloads/repo > /usr/local/bin/repo
RUN chmod a+x /usr/local/bin/repo

# Download and build delta tools
################################
RUN cd /root/ && \
        mkdir delta && \
        git clone --depth=1 https://github.com/omnirom/android_packages_apps_OpenDelta.git OpenDelta && \
        gcc -o delta/zipadjust OpenDelta/jni/zipadjust.c OpenDelta/jni/zipadjust_run.c -lz && \
        cp OpenDelta/server/minsignapk.jar OpenDelta/server/opendelta.sh delta/ && \
        chmod +x delta/opendelta.sh && \
        rm -rf OpenDelta/ && \
        sed -i -e 's|^\s*HOME=.*|HOME=/root|; \
                   s|^\s*BIN_XDELTA=.*|BIN_XDELTA=xdelta3|; \
                   s|^\s*FILE_MATCH=.*|FILE_MATCH=lineage-\*.zip|; \
                   s|^\s*PATH_CURRENT=.*|PATH_CURRENT=$SRC_DIR/out/target/product/$DEVICE|; \
                   s|^\s*PATH_LAST=.*|PATH_LAST=$SRC_DIR/delta_last/$DEVICE|; \
                   s|^\s*KEY_X509=.*|KEY_X509=$KEYS_DIR/releasekey.x509.pem|; \
                   s|^\s*KEY_PK8=.*|KEY_PK8=$KEYS_DIR/releasekey.pk8|; \
                   s|publish|$DELTA_DIR|g' /root/delta/opendelta.sh

# Set the work directory
########################
WORKDIR $SRC_DIR

# Allow redirection of stdout to docker logs
############################################
RUN ln -sf /proc/1/fd/1 /var/log/docker.log

# Set the entry point to init.sh
################################
ENTRYPOINT /root/init.sh