#!/bin/bash # Rebuild AXP Resurection from a LineageOS 23.2 sunfish tree on rom-dev01. # # This is the OTA entry point. It always ships: # - Magisk 30.7 preroot + release-keys (009 bacon) # - wave 4 deblob (AXP_DEBLOB_WAVE4=1) # - hardened_malloc 2 GiB (012), after a libc link check # - AVB flags 0 (WITH_AVB=true) # # It does not download the signed nightly zip. The source is the repo tree # at LINEAGE_TREE (default /srv/rom/src/lineage), branch lineage-23.2. # # Usage (host only, detached): # LOS_SYNC=1 /srv/rom/artifacts/bin/rom-detach bacon009ota -- \ # bash /path/to/AXP_Resurection/scripts/009-rebuild-from-los.sh # # LOS_SYNC=1 # Clean project worktrees, repo sync -c -j8 --force-sync, then check out every project # revision from LOS_MANIFEST. LOS_MANIFEST is mandatory in this mode. # LOS_MANIFEST=/path/to/build-manifest.xml # Exact official nightly build-manifest.xml. After pinning, write repo # manifest -r and fail if any common project revision differs. # LOS_DATE=YYYYMMDD # Nightly date used as LINEAGE_BUILD_DATE, so the zip is named # AXP-Resurection-Pro-23.2--sunfish.zip. # # Current tree (2026-10-10) was synced 2026-09-13 (vanilla bacon 2026-09-14), # kernel reset each bacon to 28f9290ae067. That is NOT # lineage-23.2-20261008-nightly-sunfish. Set LOS_SYNC=1 to move the tree # forward, then compare manifests before trusting the zip. set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) TREE=${LINEAGE_TREE:-/srv/rom/src/lineage} ART=/srv/rom/artifacts/009-axp-los23-port PROJECT_SOURCE=${AXP_PROJECT_SOURCE:-} if [[ ! -d "$TREE/.repo" ]]; then echo "FAIL: $TREE is not a repo checkout" >&2 exit 1 fi cd "$TREE" # rom-dev01: if bacon is started as root, git refuses android-build-owned trees. export HOME="${HOME:-/home/android-build}" mkdir -p "$ART" STAMP=$(date -u +%Y%m%dT%H%M%SZ) if [[ "${LOS_SYNC:-0}" == 1 ]]; then if [[ -z "${LOS_MANIFEST:-}" || ! -f "$LOS_MANIFEST" ]]; then echo "FAIL: LOS_SYNC=1 requires an existing LOS_MANIFEST" >&2 exit 1 fi echo "=== clean existing project worktrees $(date -Is) ===" repo forall -c 'git reset --hard -q HEAD; git clean -fdq' echo "=== repo sync -c -j8 --force-sync $(date -Is) ===" # force-sync is needed when upstream changed a project's object-store # association. Project worktrees were reset above; our patch source is # staged outside the repo tree. repo sync -c -j8 --force-sync echo "=== pin projects to $(basename "$LOS_MANIFEST") $(date -Is) ===" python3 - "$LOS_MANIFEST" <<'PY' import pathlib import subprocess import sys import xml.etree.ElementTree as ET root = ET.parse(sys.argv[1]).getroot() missing = [] count = 0 for el in root.iter("project"): path = pathlib.Path(el.get("path") or el.get("name") or "") rev = el.get("revision") if not path or not rev: continue if not (path / ".git").exists(): missing.append(str(path)) continue probe = subprocess.run( ["git", "-C", str(path), "cat-file", "-e", f"{rev}^{{commit}}"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) if probe.returncode: sys.exit(f"FAIL: nightly revision unavailable in {path}: {rev}") subprocess.run( ["git", "-C", str(path), "checkout", "--detach", "-q", rev], check=True, ) subprocess.run(["git", "-C", str(path), "clean", "-fdq"], check=True) count += 1 print(f"OK: pinned {count} projects; {len(missing)} nightly-only paths absent locally") for path in missing[:20]: print(f"MISSING_LOCAL {path}") if len(missing) > 20: print(f"... {len(missing) - 20} more missing paths") PY fi MANIFEST_OUT="$ART/manifest-${STAMP}.xml" repo manifest -r -o "$MANIFEST_OUT" echo "OK: wrote $MANIFEST_OUT" if [[ -n "${LOS_MANIFEST:-}" ]]; then if [[ ! -f "$LOS_MANIFEST" ]]; then echo "FAIL: LOS_MANIFEST not a file: $LOS_MANIFEST" >&2 exit 1 fi echo "=== manifest diff vs $LOS_MANIFEST ===" python3 - "$MANIFEST_OUT" "$LOS_MANIFEST" <<'PY' import sys import xml.etree.ElementTree as ET def revs(path): root = ET.parse(path).getroot() out = {} for el in root.iter("project"): name = el.get("name") or el.get("path") rev = el.get("revision") if name and rev: out[name] = rev return out local, nightly = revs(sys.argv[1]), revs(sys.argv[2]) only_l = sorted(set(local) - set(nightly)) only_n = sorted(set(nightly) - set(local)) differ = sorted(n for n in local.keys() & nightly.keys() if local[n] != nightly[n]) print(f"projects local={len(local)} nightly={len(nightly)} differ={len(differ)} only_local={len(only_l)} only_nightly={len(only_n)}") for n in differ[:40]: print(f"DIFF {n}\n local {local[n]}\n nightly {nightly[n]}") if len(differ) > 40: print(f"... {len(differ) - 40} more diffs") if differ: sys.exit("FAIL: local source revisions do not match the pinned nightly") PY fi if [[ -n "${LOS_DATE:-}" ]]; then [[ "$LOS_DATE" =~ ^[0-9]{8}$ ]] || { echo "FAIL: LOS_DATE must be YYYYMMDD" >&2; exit 1; } echo "$LOS_DATE" > "$TREE/.axp-los-date" echo "OK: build name date = LOS nightly $LOS_DATE" fi if [[ -n "$PROJECT_SOURCE" ]]; then if [[ ! -d "$PROJECT_SOURCE/vendor/sunfish-a16" ]]; then echo "FAIL: AXP_PROJECT_SOURCE lacks vendor/sunfish-a16: $PROJECT_SOURCE" >&2 exit 1 fi echo "=== install current AXP working tree into source tree ===" rsync -a --delete --exclude .git \ "$PROJECT_SOURCE/vendor/sunfish-a16/" "$TREE/vendor/sunfish-a16/" fi echo "=== mandatory ship: wave4 + hardened_malloc + release-keys bacon ===" export AXP_DEBLOB_WAVE4=1 export AXP_AVB_FLAGS=0 export WITH_AVB=true exec bash "$SCRIPT_DIR/009-bacon-hardened-malloc.sh"