Build optimization: pre-built PM3 binaries, ARM64 CI, base image caching
Replace PM3 compile-from-source in pi-gen with pre-built tarball extraction (saves 43-58 min). Merge stagePM3 into stageDangerousPi as 02-pm3-install substage, renumber all subsequent substages. Switch CI PM3 build to native ARM64 runner (ubuntu-24.04-arm64) eliminating QEMU overhead. Add weekly base-image workflow for pre-baking stages 0-2. Support PM3_TARBALL, BASE_IMAGE, and APT_PROXY env vars in build-image.sh. Also includes prior Phase 5 work: theme system, design system integration, component update system, OS updates, CI build pipeline, and test results. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
282
.plan
Normal file
282
.plan
Normal file
@@ -0,0 +1,282 @@
|
||||
# Plan: APT Optimization & PM3 Build Caching
|
||||
|
||||
## Goal
|
||||
Reduce redundant network requests and unnecessary rebuilds during pi-gen image builds.
|
||||
|
||||
---
|
||||
|
||||
## Part 1: Consolidate APT operations in stageDangerousPi
|
||||
|
||||
### 1A. Create a unified package list file for stageDangerousPi
|
||||
|
||||
**File:** `pi-gen/stageDangerousPi/00-packages` (new)
|
||||
|
||||
Move all apt packages currently installed inline across substages into a single
|
||||
pi-gen `00-packages` file at the stage root level. Pi-gen's `run_sub_stage()`
|
||||
already handles these automatically — one `apt-get install` call, one
|
||||
`Reading package lists` parse.
|
||||
|
||||
**Problem:** pi-gen processes `XX-packages` files *per sub-stage directory*, not
|
||||
at the stage level. The `run_stage()` iterates over `${STAGE_DIR}/*` sub-stage
|
||||
directories, then `run_sub_stage()` looks for `{00..99}-packages` inside each.
|
||||
There is no stage-level package consolidation built in.
|
||||
|
||||
**Revised approach:** Add a new sub-stage `00-apt-setup` that runs first (before
|
||||
`01-Wireless-AP`) and handles all package installation for the entire
|
||||
stageDangerousPi in one shot.
|
||||
|
||||
Create: `pi-gen/stageDangerousPi/00-apt-setup/00-packages`
|
||||
```
|
||||
# All packages needed across stageDangerousPi substages
|
||||
# (consolidated to avoid redundant apt-get calls)
|
||||
|
||||
# 01-Wireless-AP
|
||||
hostapd
|
||||
dnsmasq
|
||||
nftables
|
||||
nginx
|
||||
|
||||
# 03-dangerous-pi
|
||||
nodejs
|
||||
npm
|
||||
lrzsz
|
||||
|
||||
# 04-pisugar
|
||||
i2c-tools
|
||||
```
|
||||
|
||||
Note: `python3-pip` is omitted because it's already present from stage2 (Python 3.13
|
||||
is installed in stage1, pip comes with it). The conditional `if ! command -v pip3`
|
||||
in 03-dangerous-pi is dead code.
|
||||
|
||||
### 1B. Remove inline `apt-get install` calls from substage scripts
|
||||
|
||||
**Files to modify:**
|
||||
|
||||
1. `pi-gen/stageDangerousPi/01-Wireless-AP/00-run-chroot.sh`
|
||||
- Remove lines 12-16 (`apt-get install -y hostapd dnsmasq nftables nginx`)
|
||||
- Add a comment: `# Packages installed by 00-apt-setup stage`
|
||||
|
||||
2. `pi-gen/stageDangerousPi/03-dangerous-pi/00-run-chroot.sh`
|
||||
- Remove lines 17-25 (the conditional pip3 check + `apt-get update` + both
|
||||
`apt-get install` calls)
|
||||
- Add a comment noting packages come from 00-apt-setup
|
||||
|
||||
3. `pi-gen/stageDangerousPi/04-pisugar/00-run-chroot.sh`
|
||||
- Remove lines 49-50 (`apt-get update` + `apt-get install -y i2c-tools`)
|
||||
- Add a comment noting i2c-tools comes from 00-apt-setup
|
||||
|
||||
### 1C. Remove the redundant `apt-get update` from stagePM3
|
||||
|
||||
**File:** `pi-gen/stagePM3/01-proxmark3/00-run-chroot.sh`
|
||||
- Remove line 11 (`apt-get update`)
|
||||
- The metadata was just refreshed by stage0's `00-configure-apt`. Between
|
||||
stage0 and stagePM3, `copy_previous()` preserves `/var/lib/apt/lists/`
|
||||
(only `/var/cache/apt/archives` is excluded). The index is still valid.
|
||||
|
||||
**Risk assessment:** Low. The build runs in a single session — the metadata
|
||||
fetched minutes ago in stage0 hasn't changed. The build log confirms all 4
|
||||
repos returned `Hit:` (cache valid) when stagePM3 ran `apt-get update`.
|
||||
|
||||
### Estimated savings
|
||||
- 2-3 redundant `apt-get update` round-trips eliminated: ~10-15s
|
||||
- ~5 fewer `Reading package lists` parses under QEMU: ~25-40s
|
||||
- Fewer apt-get install invocations (3→0 in stageDangerousPi): ~15-20s
|
||||
- **Total: ~50-75s per build**
|
||||
|
||||
---
|
||||
|
||||
## Part 2: PM3 build caching (skip rebuild when unchanged)
|
||||
|
||||
### Problem
|
||||
The PM3 build (clone + compile firmware + client + SWIG bindings) takes
|
||||
~45-60 minutes under QEMU. Currently `from-pm3` always rebuilds it, even
|
||||
if nothing changed (same upstream commit, same patch, same build deps).
|
||||
|
||||
### Approach: Content-addressed cache key
|
||||
|
||||
Create a manifest file that captures everything that affects the PM3 build
|
||||
output. If the manifest matches what was used for the cached build, skip
|
||||
the entire PM3 compilation.
|
||||
|
||||
**Cache key inputs:**
|
||||
1. The LED PWM control patch file hash
|
||||
2. The Proxmark3 upstream git commit (pinned or HEAD)
|
||||
3. The branding sed command in the build script
|
||||
4. The build script itself (00-run-chroot.sh)
|
||||
|
||||
### Implementation
|
||||
|
||||
#### 2A. Pin the Proxmark3 upstream commit
|
||||
|
||||
**File:** `pi-gen/stagePM3/01-proxmark3/00-run-chroot.sh`
|
||||
|
||||
Currently clones `HEAD` of master — non-deterministic. Change to clone a
|
||||
specific tag or commit:
|
||||
|
||||
```bash
|
||||
PM3_VERSION="v4.20728" # Pin to known-good release
|
||||
PM3_COMMIT="" # Optional: specific commit hash
|
||||
|
||||
git clone https://github.com/RfidResearchGroup/proxmark3
|
||||
cd proxmark3
|
||||
if [ -n "$PM3_COMMIT" ]; then
|
||||
git checkout "$PM3_COMMIT"
|
||||
fi
|
||||
```
|
||||
|
||||
Add a small config file `pi-gen/stagePM3/pm3-version.conf`:
|
||||
```bash
|
||||
PM3_REPO="https://github.com/RfidResearchGroup/proxmark3"
|
||||
PM3_COMMIT="ef82d5ba1" # from the build log version string
|
||||
```
|
||||
|
||||
#### 2B. Generate and check a build manifest
|
||||
|
||||
**File:** `pi-gen/stagePM3/01-proxmark3/00-run.sh` (pre-chroot, runs on host)
|
||||
|
||||
Add manifest generation and comparison logic:
|
||||
|
||||
```bash
|
||||
# Generate cache key from all PM3 build inputs
|
||||
generate_pm3_manifest() {
|
||||
{
|
||||
# Hash of the build script itself
|
||||
sha256sum "${SCRIPT_DIR}/00-run-chroot.sh"
|
||||
# Hash of the LED patch
|
||||
sha256sum "${SCRIPT_DIR}/led-pwm-control.patch"
|
||||
# PM3 version config
|
||||
cat "${SCRIPT_DIR}/../pm3-version.conf" 2>/dev/null || echo "HEAD"
|
||||
} | sha256sum | cut -d' ' -f1
|
||||
}
|
||||
|
||||
MANIFEST=$(generate_pm3_manifest)
|
||||
CACHED_MANIFEST="${ROOTFS_DIR}/opt/dangerous-pi/.pm3-build-manifest"
|
||||
|
||||
if [ -f "$CACHED_MANIFEST" ] && [ "$(cat "$CACHED_MANIFEST")" = "$MANIFEST" ]; then
|
||||
echo "PM3 build manifest unchanged — skipping rebuild"
|
||||
# Create a SKIP file for the chroot script
|
||||
touch "${SCRIPT_DIR}/SKIP_PM3_BUILD"
|
||||
else
|
||||
echo "PM3 build manifest changed — will rebuild"
|
||||
echo " New: $MANIFEST"
|
||||
[ -f "$CACHED_MANIFEST" ] && echo " Old: $(cat "$CACHED_MANIFEST")"
|
||||
rm -f "${SCRIPT_DIR}/SKIP_PM3_BUILD"
|
||||
fi
|
||||
```
|
||||
|
||||
#### 2C. Honor the skip flag in the chroot build script
|
||||
|
||||
**File:** `pi-gen/stagePM3/01-proxmark3/00-run-chroot.sh`
|
||||
|
||||
Wrap the entire build in a check:
|
||||
|
||||
```bash
|
||||
if [ -f /tmp/SKIP_PM3_BUILD ]; then
|
||||
echo "=== Skipping Proxmark3 build (cached, manifest unchanged) ==="
|
||||
# Still verify the installation is intact
|
||||
DEFAULT_USER=$(awk -F: '$3 >= 1000 && $3 < 65534 {print $1; exit}' /etc/passwd)
|
||||
DEFAULT_HOME=$(eval echo ~${DEFAULT_USER:-pi})
|
||||
if [ -f "$DEFAULT_HOME/.pm3/proxmark3/client/proxmark3" ]; then
|
||||
echo "✓ PM3 client binary present"
|
||||
exit 0
|
||||
else
|
||||
echo "✗ PM3 binary missing despite manifest match — rebuilding"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ... existing build logic ...
|
||||
```
|
||||
|
||||
And at the end of a successful build, save the manifest:
|
||||
|
||||
```bash
|
||||
# Save build manifest for future cache checks
|
||||
mkdir -p /opt/dangerous-pi
|
||||
# (manifest value is passed via a file from the pre-chroot script)
|
||||
if [ -f /tmp/pm3-build-manifest ]; then
|
||||
cp /tmp/pm3-build-manifest /opt/dangerous-pi/.pm3-build-manifest
|
||||
fi
|
||||
```
|
||||
|
||||
#### 2D. Pass files between pre-chroot and chroot
|
||||
|
||||
**File:** `pi-gen/stagePM3/01-proxmark3/00-run.sh`
|
||||
|
||||
The pre-chroot script already copies the LED patch into `${ROOTFS_DIR}/tmp/`.
|
||||
Extend it to also copy the skip flag and manifest value:
|
||||
|
||||
```bash
|
||||
# Copy skip flag if set
|
||||
if [ -f "${SCRIPT_DIR}/SKIP_PM3_BUILD" ]; then
|
||||
touch "${ROOTFS_DIR}/tmp/SKIP_PM3_BUILD"
|
||||
rm -f "${SCRIPT_DIR}/SKIP_PM3_BUILD"
|
||||
fi
|
||||
|
||||
# Always write current manifest for the chroot to save on success
|
||||
echo "$MANIFEST" > "${ROOTFS_DIR}/tmp/pm3-build-manifest"
|
||||
```
|
||||
|
||||
#### 2E. Update build-image.sh for clarity
|
||||
|
||||
Add a `--force-pm3` flag to `from-pm3` mode that removes the cached manifest,
|
||||
forcing a rebuild even if the inputs haven't changed:
|
||||
|
||||
```bash
|
||||
from-pm3)
|
||||
# ... existing logic ...
|
||||
if [ "$FORCE_PM3" = "1" ]; then
|
||||
echo "Forcing PM3 rebuild (--force-pm3 flag)"
|
||||
# Remove cached manifest from the work directory rootfs
|
||||
rm -f "${PI_GEN_DIR}/work/${IMG_NAME}/stagePM3/rootfs/opt/dangerous-pi/.pm3-build-manifest"
|
||||
fi
|
||||
```
|
||||
|
||||
And update the usage text to document the new behavior.
|
||||
|
||||
---
|
||||
|
||||
## Part 3: File changes summary
|
||||
|
||||
### New files
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `pi-gen/stageDangerousPi/00-apt-setup/00-packages` | Consolidated package list |
|
||||
| `pi-gen/stagePM3/pm3-version.conf` | Pinned PM3 repo + commit |
|
||||
|
||||
### Modified files
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `pi-gen/stagePM3/01-proxmark3/00-run.sh` | Add manifest generation, skip flag, manifest passthrough |
|
||||
| `pi-gen/stagePM3/01-proxmark3/00-run-chroot.sh` | Remove `apt-get update`; add skip check at top; save manifest on success; pin git clone to commit |
|
||||
| `pi-gen/stageDangerousPi/01-Wireless-AP/00-run-chroot.sh` | Remove inline `apt-get install` |
|
||||
| `pi-gen/stageDangerousPi/03-dangerous-pi/00-run-chroot.sh` | Remove conditional pip check, `apt-get update`, both `apt-get install` calls |
|
||||
| `pi-gen/stageDangerousPi/04-pisugar/00-run-chroot.sh` | Remove `apt-get update` + `apt-get install` |
|
||||
| `build-image.sh` | Add `--force-pm3` flag parsing and usage docs |
|
||||
|
||||
### Not modified (left as-is)
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `pi-gen/stageDangerousPi/06-os-updates/00-packages` | Already uses file-based packages correctly |
|
||||
| `pi-gen/stageDangerousPi/03-dangerous-pi/00-run-chroot.sh` cleanup section | `apt-get clean/autoclean/autoremove` at bottom is still useful |
|
||||
| `pi-gen/stageDangerousPi/05-https-support/00-run-chroot.sh` | No apt calls to change |
|
||||
|
||||
---
|
||||
|
||||
## Risk assessment
|
||||
|
||||
| Change | Risk | Mitigation |
|
||||
|--------|------|------------|
|
||||
| Removing `apt-get update` from stagePM3 | Low — metadata inherited from stage0, same build session | Build log proves `Hit:` on all repos |
|
||||
| Consolidating packages into 00-apt-setup | Low — same packages, just installed earlier | Substage ordering (00- before 01-) guarantees execution order |
|
||||
| Removing conditional pip3 install | Low — pip3 always present from stage1/2 Python install | Verified in stage2 package lists |
|
||||
| PM3 skip logic | Medium — if manifest check has a bug, stale binary ships | Verification step checks binary exists; `--force-pm3` escape hatch |
|
||||
| Pinning PM3 commit | Low — explicit is better than implicit HEAD | Documented in pm3-version.conf; easy to bump |
|
||||
|
||||
## Testing approach
|
||||
|
||||
1. Run `./build-image.sh test` to syntax-check all modified scripts
|
||||
2. Run `./build-image.sh from-dtpi` to verify APT consolidation works (Part 1)
|
||||
3. Run `./build-image.sh from-pm3` twice — second run should skip PM3 build (Part 2)
|
||||
4. Modify `led-pwm-control.patch` slightly, rebuild — should trigger PM3 rebuild
|
||||
5. Run `./build-image.sh from-pm3 --force-pm3` — should always rebuild
|
||||
Reference in New Issue
Block a user