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>
283 lines
10 KiB
Plaintext
283 lines
10 KiB
Plaintext
# 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
|