Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ jobs:
- name: Test exfatprogs image metadata diagnostics
run: python3 tests/exfat_adapter_metadata_test.py

- name: Test unmodified original vold CI-only baseline
run: python3 tests/vold_baseline_ci_test.py

- name: Test pdx235 Custota metadata preparation
run: bash tests/pdx235_custota_metadata_test.sh

Expand Down
150 changes: 150 additions & 0 deletions .github/workflows/vold-baseline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: Qualify original LineageOS pdx235 vold (no patch)

# Intentionally manual and nonpublishing. workflow_dispatch is available
# once this *baseline-only* workflow is merged to the default branch.
on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: unmodified-pdx235-vold-baseline
cancel-in-progress: false

jobs:
prerequisites:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Require a trusted dedicated large build runner
shell: bash
env:
BASELINE_READY: ${{ vars.MODOS_VOLD_BASELINE_RUNNER_READY }}
SCRATCH: ${{ vars.MODOS_LINEAGE_LARGE_SCRATCH }}
SOURCE_REF: ${{ github.ref }}
REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
[[ "$REPOSITORY" == '0cwa/ModOS' && "$SOURCE_REF" == refs/heads/main ]] || {
echo '::error::Unmodified vold source build is restricted to the repository main branch'
exit 1
}
[[ "$BASELINE_READY" == 'true' && "$SCRATCH" == /* ]] || {
echo '::error::Configure MODOS_VOLD_BASELINE_RUNNER_READY=true and MODOS_LINEAGE_LARGE_SCRATCH with an absolute private scratch path'
exit 1
}

unmodified_vold:
needs: prerequisites
runs-on: [self-hosted, linux, x64, modos-lineage-large]
timeout-minutes: 360
permissions:
contents: read
env:
MODOS_LINEAGE_LARGE_SCRATCH: ${{ vars.MODOS_LINEAGE_LARGE_SCRATCH }}
steps:
- name: Checkout pinned source and tests
uses: actions/checkout@v7
with:
persist-credentials: false
fetch-depth: 0

- name: Validate isolated host and reserve scratch workspace
shell: bash
run: |
set -euo pipefail
for binary in repo avbroot curl unzip openssl sha256sum debugfs git python3 realpath; do
command -v "$binary" >/dev/null || {
echo "::error::Missing required executable: $binary"
exit 1
}
done
path="$MODOS_LINEAGE_LARGE_SCRATCH"
[[ "$path" == /* && -d "$path" && ! -L "$path" ]] || {
echo '::error::Scratch must be an existing real absolute directory'
exit 1
}
path="$(realpath -e -- "$path")"
free="$(df -PB1 "$path" | awk 'END {print $4}')"
(( free >= 250000000000 )) || {
echo '::error::At least 250 GB free space is required before syncing the complete 1,243-project tree'
exit 1
}
workspace="$(mktemp -d "$path/modos-original-vold.XXXXXXXX")"
echo "VOLD_SCRATCH=$path" >> "$GITHUB_ENV"
echo "VOLD_WORK=$workspace" >> "$GITHUB_ENV"

- name: Fetch only the immutable official October 16 source OTA
shell: bash
run: |
set -euo pipefail
url="$(python3 - <<'PY'
import json
from pathlib import Path
lock = json.loads(Path('locks/lineage-vold-baseline-20261016.json').read_text())
assert lock['source_ota_url'] == (
'https://mirrorbits.lineageos.org/full/pdx235/20261016/'
'lineage-24.0-20261016-nightly-pdx235-signed.zip'
)
print(lock['source_ota_url'])
PY
)"
curl --fail --location --retry 3 --connect-timeout 30 \
--proto '=https' --proto-redir '=https' \
--output "$VOLD_WORK/source.zip" "$url"

- name: Authenticate official publisher and extract original vold
shell: bash
run: |
set -euo pipefail
bash src/ci/extract_original_vold.sh "$VOLD_WORK/source.zip" "$VOLD_WORK/reference"

- name: Synchronize the exact 1,243-project original manifest
shell: bash
run: |
set -euo pipefail
python3 src/ci/build_original_vold.py sync \
--source-root "$VOLD_WORK/source" --jobs 4

- name: Build unmodified vold and require byte-exact equality
shell: bash
run: |
set -euo pipefail
python3 src/ci/build_original_vold.py compare \
--source-root "$VOLD_WORK/source" \
--reference-vold "$VOLD_WORK/reference/original-vold" \
--output-dir "$VOLD_WORK/baseline" --jobs 4
python3 - <<'PY'
import json
import os
from pathlib import Path
p = Path(os.environ['VOLD_WORK']) / 'baseline' / 'baseline-report.json'
data = json.loads(p.read_text())
assert data['byte_identical'] is True
with Path(os.environ['GITHUB_STEP_SUMMARY']).open('a') as report:
report.write('## Unmodified vold reproduced byte-for-byte\n')
report.write(f"Source OTA SHA-256: `{data['official_ota_sha256']}`\n\n")
report.write(f"Original vold SHA-256: `{data['reference_sha256']}`\n")
PY

- name: Retain small comparison receipt only
if: always() && env.VOLD_WORK != ''
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: original-pdx235-vold-reproducibility-report
path: ${{ env.VOLD_WORK }}/baseline/baseline-report.json
if-no-files-found: warn
retention-days: 14

- name: Remove original OTA, complete tree, and binary from trusted runner
if: always() && env.VOLD_WORK != ''
shell: bash
run: |
set -euo pipefail
[[ "$VOLD_WORK" == "$VOLD_SCRATCH"/modos-original-vold.* ]] || {
echo '::error::Refusing scratch cleanup outside the isolated baseline workspace'
exit 1
}
rm -rf -- "$VOLD_WORK"
91 changes: 91 additions & 0 deletions docs/vold-baseline-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# First gate: reproduce LineageOS pdx235's original vold in CI

This is a **separate, nonpublishing, unmodified-source** workflow. It does
not use or import the exFAT timeout patch, the ModOS vold injection module,
OTA signing secrets, or any publishing workflow.

A patched vold must never be promoted on the basis of synthetic ELF fixtures
or mere compatibility. First prove that a clean build of the **unmodified**
source produces the identical executable found in the cryptographically
authenticated original LineageOS OTA.

## Exact pinned inputs

- Source: `lineage-24.0-20261016-nightly-pdx235-signed.zip`.
- Official source OTA SHA-256:
`8af46661a1440e98018bb2abb155317d6d4ae54e051160f0786b75c1e6806c82`.
- Complete 1,243-project release manifest:
`locks/lineage-pdx235-20261016-build-manifest.xml`.
- Source lock: `locks/lineage-vold-baseline-20261016.json`.
- Exact build: `lineage_pdx235-userdebug`, then `m vold`, without
applying any patch or introducing a replacement binary.

These pins were recorded from signed ModOS source-verification runs
[#39632433468](https://github.com/0cwa/ModOS/actions/runs/39632433468)
and [#39678546023](https://github.com/0cwa/ModOS/actions/runs/39678546023).
The complete OTA is reauthenticated during each real baseline run:
downloaded bytes must match the digest, the embedded certificate's key must
match the independently pinned LineageOS official publisher key, the ZIP
certificate must be unique, and `avbroot ota verify` must succeed.

The original `system.img` is extracted read-only, then its
`/system/bin/vold` is recovered as the reference executable. A full,
clean, exact-revision LineageOS source checkout builds another unmodified
`vold` for the **same** pinned product. The resulting report includes
the reference and built SHA-256s, byte counts and first differing offset.

Only `byte_identical: true` is success. The workflow fails on a mismatch
and uploads a diagnostic JSON receipt, rather than bypassing the check.

## Runner provisioning (required before first real run)

The GitHub Actions workflow
[`vold-baseline.yml`](../.github/workflows/vold-baseline.yml) is
manual-only (`workflow_dispatch`) and runs on the repository's `main`
branch. A regular 14 GB GitHub-hosted Ubuntu runner is too small.

The repository owner must provision a **trusted, isolated Linux x64
self-hosted runner**, labeled `modos-lineage-large`, with a dedicated
scratch filesystem offering at least **250 GB free** and sufficient CPU/RAM
for an Android 17 `m vold` build. It needs LineageOS/Android build
prerequisites, the `repo` tool, Git, Python 3, curl, OpenSSL, unzip,
`debugfs`, and a verified `avbroot` binary supporting `ota verify` and
`ota extract`. Install and verify these dependencies through the host's
normal trusted administration process, not from an unreviewed workflow PR.

Configure two repository variables:

- `MODOS_LINEAGE_LARGE_SCRATCH` — absolute path to that real, dedicated
scratch directory on the self-hosted runner.
- `MODOS_VOLD_BASELINE_RUNNER_READY=true` — set only when the owner has
confirmed the runner is isolated, sufficiently provisioned and trusted.

The workflow's GitHub token has **read-only** repository permission and does
not receive signing keys or publishing secrets. Concurrent baseline jobs
are serialized. A failed preflight stops before allocating the source
workspace; all scratch content is deleted after uploading the small
comparison report. Do not register an untrusted shared self-hosted runner
for this purpose.

Once the baseline-only PR has passed ordinary CI and been merged,
open **Actions → Qualify original LineageOS pdx235 vold (no patch) →
Run workflow** on `main`. Any missing runner, unavailable pinned OTA,
manifest mismatch, build failure, or byte mismatch is a failure, not an
approval. The full source tree or reference image is never uploaded.

## After the first authentic success

Review the actual run URL and download its
`original-pdx235-vold-reproducibility-report` artifact. Record the exact
source, manifest and reference SHA-256, plus the CI commit and run ID.
Verify the report contains `byte_identical: true` with equal SHA-256s.
Independent assessment of the real reference extraction and Android
build remains essential. Do not infer these properties from synthetic tests.

**Only then** return to the separate draft
[timeout module PR #157](https://github.com/0cwa/ModOS/pull/157) to
integrate the changed two-line timeout, repeat a clean build, check ABI,
prepare a nonpublishing OTA, and qualify real device boot, storage and
exFAT behavior. Never enable scheduled pdx235 builds or force-publish merely
because the original-build proof passes. Newer LineageOS nightlies require
their own source/binary compatibility evidence.
Loading
Loading