# Runbook - GrapheneOS build, Dialer patch, flash (Pixel 10a "stallion") The privileged recording path only works inside a self-signed OS build, so the patched Dialer is built from GrapheneOS source and flashed, never shipped as an app. This is the build side; the hands-on flashing procedure with all the brick-safety rules is `pixel10a-flash-manual.md`. Tags advance weekly - check grapheneos.org/releases for the current Stable tag and confirm it lists "Pixel 10a" before you sync a fresh tree. ## This project's build box The working tree lives on the dev machine at `/build/grapheneos` (tag `2026062800`, AOSP base `android-17.0.0_r1`, device `stallion`). Facts that cost time to rediscover: - The tool shell defines a `grep` function wrapping ugrep, which breaks `lunch`'s product parsing. Before sourcing the build env, run `unset -f grep` and put `PATH=/usr/bin:/bin:$PATH` first. - Build-matched adb/fastboot are at `/build/grapheneos/out/host/linux-x86/bin/`. - An incremental `m Dialer` after touching only Java is minutes; if the build environment changed it can invalidate the framework layer and take about an hour. Check what soong is rebuilding (`siso.INFO` shows the current module) before assuming it hung. - Dev iteration loop: build the APK, push it, reboot. No full reflash needed: ```bash cd /build/grapheneos unset -f grep; export PATH=/usr/bin:/bin:$PATH source build/envsetup.sh lunch stallion-cur-userdebug m Dialer adb root && adb remount adb push out/target/product/stallion/product/priv-app/Dialer/Dialer.apk /product/priv-app/Dialer/ adb reboot # unlock the phone once after boot so the helpdesk services start ``` ## Building from scratch ### Host requirements x86_64 Linux (Arch, Debian 12, Ubuntu 24.04/24.10 are the supported set), 32 GiB+ RAM (the LTO/CFI link step needs it), ~300 GiB disk (source with history + build output). Packages: `repo yarnpkg zip rsync git python3 gnupg openssh-client curl`, Node.js LTS for adevtool, and the 32-bit libs for Vanadium (`libc6:i386 lib32stdc++6 lib32gcc-s1` after `dpkg --add-architecture i386`). On Debian, use `yarnpkg` - the `yarn` binary is an unrelated package. ### Fetch, verify, sync ```bash TAG=2026062800 # re-check grapheneos.org/releases; must list "Pixel 10a" mkdir grapheneos-$TAG && cd grapheneos-$TAG repo init -u https://github.com/GrapheneOS/platform_manifest.git -b refs/tags/$TAG # verify the manifest tag before syncing: curl https://grapheneos.org/allowed_signers > ~/.ssh/grapheneos_allowed_signers ( cd .repo/manifests \ && git config gpg.ssh.allowedSignersFile ~/.ssh/grapheneos_allowed_signers \ && git verify-tag "$(git describe)" ) # expect: Good "git" signature ... contact@grapheneos.org repo sync -j"$(nproc)" # hours; re-run the same command to resume after a drop ``` `resources/scripts/fetch-graphene-source.sh` wraps this. ### Vendor blobs ```bash source build/envsetup.sh yarnpkg --cwd vendor/adevtool/ install # once adevtool generate-all -d stallion # downloads the stock factory image itself; no phone needed ``` ### Apply the Helpdesk patches ```bash cd /path/to/Helpdesk DIALER_TREE=/path/to/grapheneos/packages/apps/Dialer bash components/dialer-patch/apply.sh SEPOLICY_TREE=/path/to/grapheneos/system/sepolicy bash components/sepolicy-patch/apply.sh ``` Both scripts copy the overlay, apply their patches (0001-0009 for the Dialer, one for sepolicy), and are safe to re-run - already-applied patches are detected and skipped. The env vars default to the build box's `/build/grapheneos` paths. Validate sepolicy with `m selinux_policy`; compile-check the Dialer with `m Dialer`. ### Build the image ```bash lunch stallion-cur-userdebug # what the office device currently runs (test keys, unlocked) m target-files-package m otatools-package ``` Then flash per `pixel10a-flash-manual.md` (`fastboot flashall`, without `-w` unless you mean to wipe). ## On-device acceptance After any build lands on the phone, place a real call to the office number and watch the backend - the server log is the source of truth, logcat is for diagnosing a step that didn't fire: 1. Ring → `POST /calls/incoming` arrives, screen-pop shows on the console. 2. Answer → `POST /calls/answered`; recording starts within ~500 ms, with no consent dialog. 3. Hang up → `POST /calls/ended` with a sensible disconnect cause. 4. `PUT /recordings/{uuid}` → 200, and the event reaches `recording_uploaded`, then `transcribed`, then `summarised` on the console. 5. Both parties are audible in the stored file. 6. From the console: accept, mute/unmute, hold, hangup, and dial each round-trip to the phone. Point the device at the backend first (`persist.helpdesk.base_url`, credentials): see `docs/phone-connectivity.md`. The production values target `https://moje.al.army:8443/api/v1`. ## The single USB-C port Runtime uses zero USB - the phone talks to the backend over the network and the headset splitter occupies the port. USB matters only for flashing and wired adb, and it physically conflicts with the headset. So: unplug the splitter for any fastboot work, and use wireless adb for the everyday dev loop (`adb pair` / `adb connect` in Developer options). Only a full OS reflash forces the cable back on. GrapheneOS friction to expect: wireless debugging doesn't cleanly survive reboots, and USB peripherals need "allow peripherals" in settings or the headset never enumerates. For dev LAN discovery without pinning an IP, the backend host can advertise `_helpdesk._tcp` via avahi; `docs/phone-connectivity.md` has the resolution order. Enable NTP on the phone - the backend rejects requests with more than 300 s of clock skew. ## Signed release and relock (not done yet) The office device currently runs an unlocked userdebug build on test keys. Locking the bootloader requires generating our own signing keys (`development/tools/make_key` for the app/platform set, an AVB key via `avbtool`), building `stallion-cur-user`, signing with `script/finalize.sh` + `script/generate-release.sh`, provisioning `avb_pkmd.bin`, and only then `fastboot flashing lock` - after confirming the signed image boots and records while still unlocked. Never relock on test keys; that is the one real way to brick the device. Keys would be immutable after locking and must be kept in offline custody. When this happens, budget for a monthly re-flash cadence: GrapheneOS ships security updates on stable tags, and a self-signed build has to be rebuilt and re-flashed for each one. Expect attestation (Auditor-style) to report SelfSigned rather than the official keys.