A small helpdesk system: an office Pixel running a patched GrapheneOS Dialer answers technician calls, records both call legs as separate channels, and a Ruby backend transcribes them through Whisper and files an AI summary against the caller. Squashed to a single commit for sharing. No credentials are included; secrets live outside the repo in /etc/helpdesk/env on the server or a gitignored .claude/env.local locally. See .claude/env.local.example for the shape. Start at README.md, then docs/architecture.md.
188 lines
16 KiB
Markdown
188 lines
16 KiB
Markdown
# Pixel 10a "stallion" - safe flash manual (self-built GrapheneOS, bootloader stays UNLOCKED)
|
|
|
|
A validation flash of a self-built userdebug/test-keys image. The bootloader **stays UNLOCKED** - we
|
|
are deliberately NOT locking it (locking on test keys is the one real brick). The procedure was
|
|
verified against the actual build artifacts on 2026-07-09; the facts that make it safe:
|
|
|
|
- Always use the **build-matched** `fastboot`/`adb` from the tree that produced the image you're
|
|
flashing: `<tree>/out/host/linux-x86/bin/`. On this build box that's `/build/grapheneos/out/…`
|
|
(the 2026-07 full-image build lives at `/scratch/graphene-out/…`). No system platform-tools are
|
|
installed, on purpose.
|
|
- `fastboot-info.txt` contains NO `flash bootloader` or `flash radio` line → `flashall` never touches
|
|
firmware → no anti-rollback brick vector from this flash.
|
|
- `android-info.txt` is the require gate (`board=stallion` plus exact `version-bootloader` /
|
|
`version-baseband` strings - the 2026-07 build required `stallion-17.0-15199480` /
|
|
`g5400i-260317-260429-B-15308590`). The match is exact string equality and aborts safely on any
|
|
mismatch, older or newer. Read your build's own file before flashing.
|
|
- Flash sequence driven by `fastboot-info.txt`: boot/init_boot/dtbo/vendor_kernel_boot/pvmfw/
|
|
vendor_boot → vbmeta → reboot fastbootd → update-super → system/system_dlkm/system_ext/product/
|
|
vendor/vendor_dlkm.
|
|
|
|
This is written for two people: **[YOU]** is whoever holds the phone and presses its buttons; **[ME]**
|
|
is whoever drives the terminal on the build box. Fine if that's the same person, or a human and an
|
|
assistant.
|
|
|
|
---
|
|
|
|
## Before we start - what YOU need
|
|
|
|
All items below are **phone-side [YOU] actions** unless noted.
|
|
|
|
- [ ] **[YOU] Charge the phone** to a comfortable level (ideally 50%+). A power loss mid-flash can hard-brick it.
|
|
- [ ] **[YOU] A known-good USB-C DATA cable** - the one that shipped with the device is safest. Connect it **directly** to a rear/motherboard USB port on the build box. **No USB hub, no front-panel port.** Bad cables/hubs are the single most common cause of flashing failures. ([grapheneos.org/install/cli](https://grapheneos.org/install/cli))
|
|
- [ ] **[YOU] Headset splitter UNPLUGGED** for the entire session. The Pixel 10a has one USB-C port; a headset splitter puts it in audio-accessory mode and fastboot/adb will **not enumerate at all** (the CC pins carry analog audio, not USB). ([source.android.com/docs/core/interaction/accessories/headset/usb-adapter](https://source.android.com/docs/core/interaction/accessories/headset/usb-adapter))
|
|
- [ ] **[YOU] Enable Developer options:** Settings → About phone → tap **Build number** 7 times.
|
|
- [ ] **[YOU] Enable USB debugging:** Settings → System → Developer options → **USB debugging** ON. (When you first plug in, tap **Allow** on the RSA-key prompt.)
|
|
- [ ] **[YOU] Enable OEM unlocking:** Settings → System → Developer options → **OEM unlocking** ON. This requires internet (the stock OS checks the device wasn't sold carrier-locked). Without it, `fastboot flashing unlock` is refused. ([grapheneos.org/install/cli](https://grapheneos.org/install/cli), [source.android.com/.../locking_unlocking](https://source.android.com/docs/core/architecture/bootloader/locking_unlocking))
|
|
- [ ] **[YOU] Back up anything you care about.** Unlocking the bootloader triggers a **mandatory factory data reset - ALL data is erased**, including photos, videos, and app data. This is unavoidable and expected. ([source.android.com/.../locking_unlocking](https://source.android.com/docs/core/architecture/bootloader/locking_unlocking))
|
|
- [ ] **[ME] (PC-side, no phone action) Rescue kit staged locally** - I will pre-download and verify the **official Google stock stallion factory image + full-OTA zip** and the **official GrapheneOS stallion release** before we begin, so recovery needs no network.
|
|
|
|
---
|
|
|
|
## Golden rules (don't brick it)
|
|
|
|
1. **NEVER run `fastboot flashing lock`.** These images are signed with the AVB **public test key**; a locked bootloader can't verify them and drops to AVB RED = unbootable = brick. Stay UNLOCKED (orange). ([grapheneos.org/build](https://grapheneos.org/build), [source.android.com/.../boot-flow](https://source.android.com/docs/security/features/verifiedboot/boot-flow))
|
|
2. **NEVER turn the OEM-unlocking toggle OFF.** If it's off while the OS is unbootable, `fastboot flashing unlock` is refused = hard brick with no recovery. Keep it ON throughout. ([grapheneos.org/install/cli](https://grapheneos.org/install/cli))
|
|
3. **NEVER downgrade firmware.** Do **not** flash an older bootloader/radio than the phone already has. Tensor anti-rollback is eFuse-enforced and permanent, **even while unlocked**. For this flash we leave stock firmware alone (see rule 4). ([developers.google.com/android/images](https://developers.google.com/android/images))
|
|
4. **`flashall` does NOT flash firmware - and we do NOT flash the self-built firmware.** We flash only the OS + vbmeta, leaving Google's stock bootloader/radio in place. (Our `fastboot-info.txt` contains no `flash bootloader`/`flash radio` line, confirming this.)
|
|
5. **NEVER interrupt a flash.** No unplugging, no button presses, no Ctrl-C, no host sleep. A partial firmware/`update-super` write is a classic brick. Never run `snapshot-update cancel`. ([grapheneos.org/install/cli](https://grapheneos.org/install/cli))
|
|
6. **NEVER use `--force`** to push past a `requirements not met!` abort. That check is a real guardrail (exact string match on bootloader/baseband); forcing past a firmware mismatch can leave the device non-booting.
|
|
|
|
---
|
|
|
|
## The procedure
|
|
|
|
I will use the build-matched tools (`fastboot` **37.0.0-2026070800**) at `/scratch/graphene-out/host/linux-x86/bin/` - no system platform-tools are installed, and these exactly match the build. udev/permissions may require me to run `fastboot` under `sudo`.
|
|
|
|
### Step 1 - [YOU] Complete the "Before we start" checklist
|
|
|
|
Confirm: phone charged, headset splitter unplugged, USB-C data cable direct to a rear port, Developer options + **USB debugging** + **OEM unlocking** all ON, data backed up. Tell me when done.
|
|
|
|
### Step 2 - [ME] Prepare the PC environment and verify tools
|
|
|
|
```bash
|
|
# point at the out/ of the tree that built the image you're flashing:
|
|
export OUT_TREE=/build/grapheneos/out # or /scratch/graphene-out for the 2026-07 image
|
|
export PATH=$OUT_TREE/host/linux-x86/bin:$PATH
|
|
export ANDROID_PRODUCT_OUT=$OUT_TREE/target/product/stallion
|
|
fastboot --version # must report the version your build shipped (2026-07 image: 37.0.0-2026070800)
|
|
```
|
|
|
|
### Step 3 - [ME] Reboot the phone to the bootloader
|
|
|
|
With the phone booted and USB debugging authorized:
|
|
|
|
```bash
|
|
adb reboot bootloader
|
|
```
|
|
|
|
(Alternative if adb isn't authorized - **[YOU]** power off, then hold **Volume-Down** while powering on.)
|
|
|
|
### Step 4 - [YOU] Confirm bootloader mode on screen
|
|
|
|
The screen shows a **red warning triangle** and the words **"Fastboot Mode"**. Tell me you see it. ([grapheneos.org/install/cli](https://grapheneos.org/install/cli))
|
|
|
|
### Step 5 - [ME] Confirm the PC sees the phone
|
|
|
|
```bash
|
|
fastboot devices # must list a serial - if blank, I'll retry under sudo
|
|
fastboot getvar product # must return the stallion product string
|
|
```
|
|
|
|
If no serial appears, we STOP and fix the USB link (cable/port/permissions) before anything else.
|
|
|
|
### Step 6 - [ME] Read current device state (read-only, non-destructive) - **MANDATORY pre-flash check**
|
|
|
|
```bash
|
|
fastboot getvar unlocked
|
|
fastboot flashing get_unlock_ability
|
|
fastboot getvar current-slot
|
|
fastboot getvar version-bootloader
|
|
fastboot getvar version-baseband
|
|
```
|
|
|
|
**What I check (this is the anti-rollback / mismatch gate):**
|
|
- This build requires **exactly** `version-bootloader=stallion-17.0-15199480` and `version-baseband=g5400i-260317-260429-B-15308590` (from `android-info.txt`). The require check is **exact string equality** - it aborts on *any* mismatch, older **or** newer, and does **not** do version math.
|
|
- Because we are **not** flashing firmware, the requirement is that the phone's current stock firmware **matches** these strings. If it differs in either direction, we **STOP** and reconcile firmware from the official Google stallion factory image before flashing the OS - we do **not** `--force`.
|
|
- **Anti-rollback STOP condition:** if the phone's current bootloader/baseband is **newer** than this build's, do **not** proceed to flash anything firmware-related; downgrading Tensor firmware is a permanent brick. ([developers.google.com/android/images](https://developers.google.com/android/images))
|
|
- `unlocked` should read `yes` and `get_unlock_ability` should confirm unlockable. Tell me what the **on-screen "Device state"** line says as a cross-check.
|
|
|
|
### Step 7 - [ME/YOU] Unlock the bootloader **only if not already unlocked** - **DATA WIPE POINT**
|
|
|
|
If Step 6 already shows `unlocked: yes`, **skip this step** (no-op).
|
|
|
|
Otherwise **[ME]** runs:
|
|
|
|
```bash
|
|
fastboot flashing unlock
|
|
```
|
|
|
|
**[YOU]** - the phone shows a warning prompt. Use a **Volume** button to move the selection to **"UNLOCK THE BOOTLOADER"**, then press **Power** to confirm. **This wipes ALL data (factory reset).** ([grapheneos.org/install/cli](https://grapheneos.org/install/cli), [source.android.com/.../locking_unlocking](https://source.android.com/docs/core/architecture/bootloader/locking_unlocking))
|
|
|
|
### Step 8 - [ME] Flash the OS via `flashall` - **DATA WIPE POINT (`-w`)**
|
|
|
|
This is the correct path for our raw `m` output (there is **no** signed factory zip / `flash-all.sh`). `flashall` reads `fastboot-info.txt` and drives the exact sequence: flash `boot`/`init_boot`/`dtbo`/`vendor_kernel_boot`/`pvmfw`/`vendor_boot` → `flash --apply-vbmeta vbmeta` → **auto-reboots into fastbootd** → `update-super` (repartitions `super` from `super_empty.img`) → flash the six logical partitions (`system`, `system_dlkm`, `system_ext`, `product`, `vendor`, `vendor_dlkm`) → erase userdata/metadata. **This does NOT flash bootloader or radio.** ([source.android.com/docs/setup/build/running](https://source.android.com/docs/setup/build/running))
|
|
|
|
```bash
|
|
fastboot flashall -w
|
|
```
|
|
|
|
- `-w` wipes userdata/metadata - correct for a first validation flash.
|
|
- **[YOU] Do NOT touch the phone, cable, or terminal** until it finishes. The phone will reboot itself into **fastbootd** mid-sequence - **that is normal**, do not interrupt it. ([grapheneos.org/install/cli](https://grapheneos.org/install/cli))
|
|
- On this A/B device, `flashall` sets the flashed slot active and by default also populates the secondary slot - desirable so both slots hold this build. I will **not** pass `--skip-secondary`, and will **not** manually mark the other slot active.
|
|
- If it aborts with `requirements not met!`, we **STOP** (see rule 6 / Step 6) - I will not `--force`.
|
|
|
|
### Step 9 - [ME] Trigger first boot
|
|
|
|
```bash
|
|
fastboot reboot
|
|
```
|
|
|
|
### Step 10 - [YOU] Confirm first boot and verified-boot state
|
|
|
|
- The phone shows the **orange "device is unlocked / can't be verified"** warning for ~10 seconds, then boots. **This is the expected, supported state** for an unlocked test-keys build - **not** an error, and **not** a reason to lock. (It is strictly ORANGE, not yellow.) ([source.android.com/.../boot-flow](https://source.android.com/docs/security/features/verifiedboot/boot-flow), [grapheneos.org/build](https://grapheneos.org/build))
|
|
- Let it reach the setup wizard / home screen. Tell me it booted.
|
|
- **[YOU] IMPORTANT:** if the setup wizard offers a final toggle that would **disable OEM unlocking** (often checked by default), **leave OEM unlocking ENABLED** - do not accept disabling it. ([grapheneos.org/install/cli](https://grapheneos.org/install/cli))
|
|
|
|
### Step 11 - [ME] Verify the running build
|
|
|
|
Once booted with USB debugging re-authorized (**[YOU]** tap **Allow** on the RSA prompt):
|
|
|
|
```bash
|
|
adb shell getprop ro.build.fingerprint
|
|
# expect: google/stallion/stallion:17/CP2A.260605.012/2026070800:userdebug/test-keys
|
|
```
|
|
|
|
Match confirms the self-built image is running. Done.
|
|
|
|
---
|
|
|
|
## If something goes wrong
|
|
|
|
**Why you're safe:** the bootloader is **UNLOCKED** and OEM-unlocking is **ON**. That means `fastboot` still works even when Android won't boot, so essentially every bad flash here is a recoverable **soft brick** - you just re-flash a known-good image. An unlocked + OEM-unlock-on device is recoverable in software. ([grapheneos.org/install/cli](https://grapheneos.org/install/cli))
|
|
|
|
**Recovery path, in order:**
|
|
1. **[YOU]** Power off, hold **Volume-Down** while powering on → back to Fastboot Mode (red triangle).
|
|
2. **[ME]** Re-run the OS flash: `fastboot flashall -w` (drives `update-super` + fastbootd correctly - never hand-flash `system`/`vendor` from bootloader mode; they only flash in fastbootd).
|
|
3. **[ME/YOU]** If the flashed slot won't boot, prefer **re-flashing the good image to the active slot** over switching slots. (Slot-switching can trigger an anti-rollback fallback if the other slot holds older firmware.)
|
|
4. **Rescue to stock:** flash the pre-staged official **Google stallion factory image / full-OTA** ([developers.google.com/android/images](https://developers.google.com/android/images)), or use **flash.android.com** (Android Flash Tool, works from Fastboot Mode even when the OS won't boot - needs OEM unlocking, which we have). ([source.android.com/docs/setup/test/flash](https://source.android.com/docs/setup/test/flash)) Then re-flash the self-built image.
|
|
5. **Only if truly hard-bricked** (no display, no fastboot, no charge): Tensor SoCs have **no public EDL/unbrick route** → Google RMA. This is why rules 1-5 above matter more than any recovery step.
|
|
|
|
**Do NOT, during recovery:** run `fastboot flashing lock`, disable OEM unlocking, flash older firmware, or interrupt a write.
|
|
|
|
---
|
|
|
|
## Known unknowns (confirm live before/at the relevant step)
|
|
|
|
- **[Step 6, CRITICAL] Device anti-rollback / firmware match for the 10a specifically.** Google's May-2026 ARB advisory names Pixel 10 / 10 Pro / 10 Pro XL / 10 Pro Fold - the **10a "stallion" is NOT on that published list**, so its ARB status is **unverified from docs**. The controlling fact is the **on-device value**, not the press release. We must read `version-bootloader`/`version-baseband` (both slots if in doubt) at Step 6 and confirm the phone is **not newer** than this build before proceeding. If newer → STOP.
|
|
- **[Step 6] `fastboot getvar unlocked` exact behavior on stallion** is unverified; that's why Step 6 also uses `flashing get_unlock_ability` and the on-screen "Device state" readout as cross-checks.
|
|
- **[Step 6/8] Firmware string match.** Since we don't flash firmware, the stock bootloader/radio on the phone must equal the `android-info.txt` require strings. If they don't match, `flashall` will abort (correctly). We confirm live and, if needed, reconcile via the official Google factory image - **never** `--force`.
|
|
- **[Step 8] Secondary-slot / vendor_kernel_boot handling** on stallion - `flashall` populates both slots by default; before executing I'll cross-check the sequence against `fastboot-info.txt` (already read: no firmware lines present) and, if a firmware reconcile is ever needed, against the official stallion factory `flash-all` command list.
|
|
- **[Out of scope now - future relock only] AVB vbmeta rollback index enforcement while unlocked** is documented for LOCKED but ambiguous for UNLOCKED. We stay unlocked and use `flash --apply-vbmeta vbmeta` (verity/verification stay enabled, matching the build's `Flags:0`); we do **not** add `--disable-verity`/`--disable-verification`, and we do **not** flash a vbmeta with a lower rollback index than the device stored. Relock is a separate, deferred, higher-risk step requiring your own release/AVB keys - **not this pass**.
|
|
|
|
---
|
|
|
|
The files that decide everything above live in the build output: `fastboot-info.txt` (the flash
|
|
sequence; confirms no firmware lines) and `android-info.txt` (the require gate) under
|
|
`$OUT_TREE/target/product/stallion/`, with the matching tools in `$OUT_TREE/host/linux-x86/bin/`.
|
|
Read them for the tree you're about to flash; don't trust a manual older than your build.
|