lame subtitle

This is my blog. Do not expect frequent content.


Building LineageOS for Pixel 3

29 April 2019 23:20 -04:00 / Permalink

Recently, Google offered a half-off special on their phones. Normally, this wouldn't tempt me, but my Pixel XL has some screen lifting, and I opted to trade it in. Plus, $399 USD for a Pixel 3 is pretty good. Despite my misgivings, I wanted the XL, but it was only available in pink. Google also chose form over function and removed the 3.5mm port, which I use every day in my car (I have a car from '09 with only an aux jack). I ordered it. Offering the deal for only one day was effective, as I'm sure they know.

In addition, I've been running with no Google on my Pixel XL, and that really didn't mesh well with using Google Fi as my cell carrier (you literally cannot get a better deal in the US). So, I figured I'd give it a go and install microG with LineageOS so I could have a more usable phone at least.

It turns out that, while the Pixel and Pixel XL are supported by Lineage, the Pixel 3 and 3 XL are not supported by Lineage, nor are the 2 or 2 XL. Finally, it has come to what was, really, inevitable: I would have to build LineageOS. Boy, was I not looking forward to this, but I wasn't really down to sacrifice my personal values and beliefs completely and use stock Google spyware.

After a little searching, I found docker-lineage-cicd, a project by "lineageos4microg", which had just been updated for LineageOS 16.0 (Android 9). This was great, since I really didn't want to install a bunch of crap on my system to build Lineage. It was also great because I didn't have to look up the build process and mess around with things. It supports building for unsupported devices, so long as you do a little research and have the necessary source files. It turns out that Lineage has a device repo and a kernel repo already existing for the Pixel 3 and 3 XL, so I was basically all set.

Going in, there were a few things I knew I wanted.


As said, I wanted to add microG, which is an open-source implementation of proprietary Google software that runs on Android, enabling Google Play Services features. My phone was falling apart, metaphorically, because I didn't have the Google Fi app. At first, I just couldn't receive MMS (both media and group text messages) and I couldn't access visual voicemail. SMS messages also had some base64 appended to the end of them, but at least I could read them. Recently, I've been just not getting calls at all. I had someone tell me they called me seven times before I picked up, but I only got the last call.

microG at least gives me greater control over what data I'm sending to Google while enabling me to actually have a functioning cell phone. It was a compromise I would have to make, lest I become essentially unable to be reached over the phone, which, at best, is an inconvenience, and at worst screws me out of a job interview or urgent news, etc.


WireGuard was another must for me. It has a userspace implementation, which is great, but it only allows one tunnel to be active at a time and is slightly less performant, affecting battery life.

For me, I mostly just wanted multiple tunnels up at the same time. I have a normal tunnel I use for all traffic, which goes to a VPS with a pi-hole, which I use for ad blocking and the security and slight privacy benefits that come with using your own VPS. I also have a tunnel that can only access my home LAN, which is greatly useful when I'm at work and need to access my Raspberry Pi or want to listen to music, watch movies/TV, etc. on my Jellyfin instance when I'm not at home.

With the WireGuard userspace implementation, I had to disconnect from my normal VPN to use Jellyfin, which I didn't want to do. Building the kernel module into the ROM would enable me to use multiple tunnels at once and get slightly better battery life, so why not?

Setting up

With these two conditions in mind, I looked at the readme of the Docker image and scrolled to the final example. It shows how to build for an unsupported device, so I fumbled and guessed my way around what was necessary. There's no common repo necessary for Google devices, which I pretty much just guessed on. Either the build would fail or it wouldn't, which, incidentally, was my mindset for the whole blasted project.

I needed to make a custom manifest for my device, so I popped blueline.xml into my local_manifests directory and looked up a few repos. The device and kernel repos linked above were perfect, so I popped them into the manifest:

<project name="LineageOS/android_device_google_crosshatch" path="device/google/crosshatch" remote="github" />
<project name="LineageOS/android_kernel_google_crosshatch" path="kernel/google/crosshatch" remote="github" />

Note: Don't follow these instructions. I'll post what worked at the bottom, but this is documenting the process I took, and there were some mistakes.

As an aside, for some reason, the Pixel 3 (codename blueline) files are all contained in the Pixel 3 XL (codename crosshatch) repos, which was a huge source of confusion for me and one of the biggest wastes of time as I tried to mess with it to get the build process to actually work.

Anyway, the readme has its own instructions for adding microG, so I plopped that in the manifest, as well.

<project name="lineageos4microg/android_prebuilts_prebuiltapks" path="prebuilts/prebuiltapks" remote="github" revision="master" />

Finally, I looked up how to include WireGuard into a ROM. I was afraid it would involve building a custom kernel with manual patches, but there's actually a really easy way to add it to a local manifest, which was awesome.

<remote name="zx2c4" fetch="" />
<project remote="zx2c4" name="android_kernel_wireguard" path="kernel/wireguard" revision="master" sync-s="true" />

Bam. Job done. Next, I cloned the Lineage sources so I could build. You know what's ridiculous? The size of the Android and Lineage sources. Seriously. What the fuck, honestly? I think it came out to 63 gigabytes. I get why it's that big, but come on, people.


Anyway, after running repo sync and waiting forever, it finished. I generated some build keys and set the Docker image to work. Unfortunately, the Docker image needed to add some repos to clone, so I think I spent a solid three more hours waiting for another repo sync to finish. I think I was nearing 80 GB at that point. Fortunately, I'm working on a ~40% full 512 GB SSD, so I've got some room.

Once the repos are synced, the build starts. Or, well, not really. It tries to start, but I'm told that it can't find the files for blueline anywhere and promptly quits. I spent about an hour trying to figure out what the hell is going on. Eventually, after just giving up and trying random things, I realised that there's no revision attribute on the device and kernel repos in the manifest. I changed them to the below, and we're in business.

<project name="LineageOS/android_device_google_crosshatch" path="device/google/crosshatch" revision="lineage-16.0" remote="github" />
<project name="LineageOS/android_kernel_google_crosshatch" path="kernel/google/crosshatch" revision="lineage-16.0" remote="github" />

The build took a long time, and my computer is basically unusable during it. So much so, in fact, that Wayland crashed when I try to use my computer during the build (16 GB RAM in this machine). I eventually just closed everything I can and let the computer go, watching Futurama on my TV while I wait.

The build failed, and I scrolled up the logs to check out why. I see what is characteristic of an OOM error: process killed. Directly above I see LTO vmlinux.o, so clearly the computer ran out of memory while attempting to link-time optimise the kernel. I slapped on a new swap file First 1 GB, I think, then it ran out of memory on a different build and I upped it to 16 GB. I was incredulous that I was running out of memory, honestly. to appease the build.

I'm pretty sure the build succeeded here, after many trials and tribulations. Sweet.


I downloaded TWRP for my phone, I downloaded the factory OTA files, and I got to work. This whole time I had just barely set up my new phone: not inserting my SIM (nor downloading an eSIM), not connecting my Google account, and only connecting wifi. So, I unlocked my phone, turned it off, then booted into fastboot mode. I flashed the latest OTA to both slots, then I booted TWRP and installed Lineage using my build. Pretty standard.

I restarted and sent a picture to a Telegram group of friends: "Hope this works." I saw the LineageOS boot animation and figured I was home free. Oh, how wrong I was. The phone eventually shut off and rebooted into Lineage's recovery with the cheerful, slightly cut off message starting with "Android could not be started" (or something very similar to that). :(

Now, my first instinct was to find logs as to why, but let me tell you, it is HARD to find these. I am a noob and uninitiated, and I think some of my problems stemmed from not mounting system while I was in TWRP while looking for these logs, but I couldn't find anything and that didn't occur to me until after I already had it working. I spent a long time, several hours, trying to figure out where the logs were, if they even existed. In the meantime, I built four new builds of the ROM, each taking about two hours, each with slightly different parameters. I built as close to stock Lineage as possible, and it still wouldn't boot.

Eventually, my friend pulled some paths for logs out of nowhere, and I found some logs showing why the boot was failing. It turns out that not including Google Apps makes it so a certain file isn't installed. The file defines the permissions for a few private applications, and the system won't boot without those permissions. I installed the file, but it still didn't work. I had to change a system property to ignore missing private app permissions (which I still haven't turned off, but I think I can now). This got the ROM to boot!

I flashed my original build and made the changes, and it booted up. I'm fairly certain I skipped a few flashes and boots and messing around with permissions and properties, but it didn't take very long for me to get there. At this point, I could see the finish line.

Getting it to work

I wasn't quite ready to set up my phone normally yet, so I went through setup only enabling wifi. The first thing I noticed is that the Google Fi app wouldn't open. microG said everything was dandy, but Google Fi complained that Google Play Services needed updating. Lo and behold, when checking the version numbers necessary, microG was way, way, way behind. Like "how does it even count as functional?" behind. Fortunately, Nanolx has a cool archive of his forks of projects built into flashable packages. He has a much more up-to-date version specified for microG, so flashing that would fix my issue.

Long story short: boot once, then flash, or else you're not gonna be able to boot.

I reflashed my ROM and then flashed his FDroid package and microG package, installing both as system apps. I'm pretty sure, at this point, this is the flash I'm using currently.

I started installing things. First up was WireGuard from FDroid. WireGuard said it was using the userspace implementation, which was really vexing. This is actually the last thing I got working (today!), and it's annoying. It turns out that even when you enable root, you have to restart the phone, not just WireGuard, to get WireGuard to ask for permission to use the kernel module. After that, it's easy sailing.

I set up Google Fi and got it to eventually agree to activate my phone and work as expected. The only two snags in setting everything up I had were as follows:

  • Discord with microG just doesn't work. Relevant GitHub issue.
  • Authy crashes when the cellphone number field is tapped. Closed, annoying GitHub issue.

Eventually, I had to use Titanium Backup to transfer Authy from my old phone to my new phone, where it works fine now. Not sure what I'm going to do in the future.

Everything else works, and I no longer have to deal with the various issues I used to have on my Pixel XL, so everything appears to have worked out. I'm fairly satisfied with how it all turned out, even if it was painful and made me feel really stupid at times. At least now if I need to build again, I know how to do it, so I can rent a very powerful AWS spot instance to build on quickly.

But seriously, Lineage, could you at least offer unsupported builds for these phones? I know you have the source ready. Newbies like me would appreciate it. :)

Thanks for reading this rambling post that just peters out at the end. I said I'd post working stuff down here!
<?xml version="1.0" encoding="UTF-8"?>
  <!-- Pixel 3 -->
  <project name="LineageOS/android_device_google_crosshatch" path="device/google/crosshatch" revision="lineage-16.0" remote="github" />
  <project name="LineageOS/android_kernel_google_crosshatch" path="kernel/google/crosshatch" revision="lineage-16.0" remote="github" />
  <!-- WireGuard -->
  <remote name="zx2c4" fetch="" />
  <project remote="zx2c4" name="android_kernel_wireguard" path="kernel/wireguard" revision="master" sync-s="true" />
  <!-- microG and F-Droid -->
  <!-- too old; use instead -->
  <!--<project name="lineageos4microg/android_prebuilts_prebuiltapks" path="prebuilts/prebuiltapks" remote="github" revision="master" />-->
sudo docker run --rm \
    -e "BRANCH_NAME=lineage-16.0" \
    -e "DEVICE_LIST=blueline" \
    -e "SIGN_BUILDS=true" \
    -e "SIGNATURE_SPOOFING=restricted" \
    -v "/path/to/lineage:/srv/src" \
    -v "/path/to/zips:/srv/zips" \
    -v "/path/to/logs:/srv/logs" \
    -v "/path/to/cache:/srv/ccache" \
    -v "/path/to/keys:/srv/keys" \
    -v "/path/to/manifests:/srv/local_manifests" \