Add EEPROM update functionality and UI enhancements
Implement a new feature to allow users to update the EEPROM via the dashboard, including the generation of necessary update files. Enhance the device action handling to support the 'eeprom_update' action for USB-connected devices, ensuring proper validation of boot order presets. Update the dashboard UI to include an EEPROM update option alongside existing actions, improving user experience. Modify related scripts to handle EEPROM updates effectively, including file management and error handling during the update process.
This commit is contained in:
@@ -5,12 +5,14 @@ Public home: deploy only (status, logs, how to connect). No login.
|
|||||||
Admin: login required — backups, cloud-init, portal files, set golden, users.
|
Admin: login required — backups, cloud-init, portal files, set golden, users.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
@@ -49,6 +51,12 @@ PORTAL_DESCRIPTIONS_FILE = Path(os.environ.get("CM4_PORTAL_DESCRIPTIONS_FILE", s
|
|||||||
DB_PATH = Path(os.environ.get("CM4_DASHBOARD_DB", str(BASE_DIR / "dashboard.db")))
|
DB_PATH = Path(os.environ.get("CM4_DASHBOARD_DB", str(BASE_DIR / "dashboard.db")))
|
||||||
TOGGLE_NETWORK_BOOT_SCRIPT = os.environ.get("CM4_TOGGLE_NETWORK_BOOT_SCRIPT", "/opt/cm4-provisioning/toggle-network-boot-dhcp.sh")
|
TOGGLE_NETWORK_BOOT_SCRIPT = os.environ.get("CM4_TOGGLE_NETWORK_BOOT_SCRIPT", "/opt/cm4-provisioning/toggle-network-boot-dhcp.sh")
|
||||||
DHCP_LEASES_FILE = os.environ.get("CM4_DHCP_LEASES_FILE", "/var/lib/misc/dnsmasq.leases")
|
DHCP_LEASES_FILE = os.environ.get("CM4_DHCP_LEASES_FILE", "/var/lib/misc/dnsmasq.leases")
|
||||||
|
# EEPROM update (USB boot): tools and output files (shared with host script)
|
||||||
|
EEPROM_DIR = Path(os.environ.get("CM4_EEPROM_DIR", "/opt/cm4-provisioning/eeprom"))
|
||||||
|
EEPROM_CONFIG_TOOL = EEPROM_DIR / "rpi-eeprom-config"
|
||||||
|
EEPROM_FW_FILE = EEPROM_DIR / "pieeprom.bin"
|
||||||
|
EEPROM_UPD_FILE = BASE_DIR / "pieeprom.upd"
|
||||||
|
EEPROM_SIG_FILE = BASE_DIR / "pieeprom.sig"
|
||||||
|
|
||||||
|
|
||||||
# --- Database (admin users + activity logs) ---
|
# --- Database (admin users + activity logs) ---
|
||||||
@@ -229,6 +237,72 @@ def _write_status(phase, message, progress=None):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_eeprom_update(boot_order, extra_settings=None):
|
||||||
|
"""Generate pieeprom.upd and pieeprom.sig in BASE_DIR. Returns (True, None) or (False, error_message)."""
|
||||||
|
extra_settings = extra_settings or {}
|
||||||
|
if not EEPROM_CONFIG_TOOL.is_file() or not EEPROM_FW_FILE.is_file():
|
||||||
|
return False, "EEPROM tools not installed. Run install-eeprom-tools-on-lxc.sh on the LXC."
|
||||||
|
try:
|
||||||
|
os.makedirs(BASE_DIR, exist_ok=True)
|
||||||
|
# Read current config from firmware image
|
||||||
|
out = subprocess.run(
|
||||||
|
[str(EEPROM_CONFIG_TOOL), str(EEPROM_FW_FILE)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=30,
|
||||||
|
cwd=str(EEPROM_DIR),
|
||||||
|
)
|
||||||
|
if out.returncode != 0:
|
||||||
|
return False, (out.stderr or out.stdout or "rpi-eeprom-config failed").strip()[:500]
|
||||||
|
lines = (out.stdout or "").strip().splitlines()
|
||||||
|
# Strip lines we replace
|
||||||
|
skip_keys = {"BOOT_ORDER", "NET_BOOT_MAX_RETRIES", "DHCP_TIMEOUT", "DHCP_REQ_TIMEOUT", "TFTP_IP", "NET_INSTALL_AT_POWER_ON"}
|
||||||
|
new_lines = []
|
||||||
|
for line in lines:
|
||||||
|
key = line.split("=", 1)[0].strip() if "=" in line else ""
|
||||||
|
if key in skip_keys:
|
||||||
|
continue
|
||||||
|
new_lines.append(line)
|
||||||
|
# Add our settings
|
||||||
|
new_lines.append(f"BOOT_ORDER={boot_order}")
|
||||||
|
new_lines.append("NET_BOOT_MAX_RETRIES=3")
|
||||||
|
new_lines.append("DHCP_TIMEOUT=1500")
|
||||||
|
new_lines.append("DHCP_REQ_TIMEOUT=500")
|
||||||
|
new_lines.append("NET_INSTALL_AT_POWER_ON=0")
|
||||||
|
for k, v in extra_settings.items():
|
||||||
|
new_lines.append(f"{k}={v}")
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".conf", delete=False) as f:
|
||||||
|
f.write("\n".join(new_lines) + "\n")
|
||||||
|
conf_path = f.name
|
||||||
|
try:
|
||||||
|
out2 = subprocess.run(
|
||||||
|
[str(EEPROM_CONFIG_TOOL), "--config", conf_path, "--out", str(EEPROM_UPD_FILE), str(EEPROM_FW_FILE)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=60,
|
||||||
|
cwd=str(EEPROM_DIR),
|
||||||
|
)
|
||||||
|
if out2.returncode != 0:
|
||||||
|
return False, (out2.stderr or out2.stdout or "rpi-eeprom-config --config failed").strip()[:500]
|
||||||
|
if not EEPROM_UPD_FILE.is_file() or EEPROM_UPD_FILE.stat().st_size == 0:
|
||||||
|
return False, "Generated pieeprom.upd is missing or empty"
|
||||||
|
# Write signature (sha256 hex)
|
||||||
|
h = hashlib.sha256()
|
||||||
|
with open(EEPROM_UPD_FILE, "rb") as f:
|
||||||
|
h.update(f.read())
|
||||||
|
EEPROM_SIG_FILE.write_text(h.hexdigest())
|
||||||
|
return True, None
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.unlink(conf_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return False, "Timeout running rpi-eeprom-config"
|
||||||
|
except (PermissionError, OSError) as e:
|
||||||
|
return False, str(e)
|
||||||
|
|
||||||
|
|
||||||
def read_status():
|
def read_status():
|
||||||
try:
|
try:
|
||||||
with open(STATUS_FILE, "r") as f:
|
with open(STATUS_FILE, "r") as f:
|
||||||
@@ -518,6 +592,17 @@ def api_log():
|
|||||||
return jsonify({"log": read_log_tail()})
|
return jsonify({"log": read_log_tail()})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/eeprom-presets")
|
||||||
|
def api_eeprom_presets():
|
||||||
|
"""Return available boot order presets for the Update EEPROM dropdown."""
|
||||||
|
presets = [
|
||||||
|
{"id": "0x1", "label": "eMMC only"},
|
||||||
|
{"id": "0xf21", "label": "eMMC first, then network"},
|
||||||
|
{"id": "0xf12", "label": "Network first, then eMMC"},
|
||||||
|
]
|
||||||
|
return jsonify({"presets": presets})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/pending-devices")
|
@app.route("/api/pending-devices")
|
||||||
def api_pending_devices():
|
def api_pending_devices():
|
||||||
"""Returns USB (if waiting_choice) and registered network devices so the UI can show Backup/Deploy."""
|
"""Returns USB (if waiting_choice) and registered network devices so the UI can show Backup/Deploy."""
|
||||||
@@ -532,15 +617,32 @@ def api_pending_devices():
|
|||||||
|
|
||||||
@app.route("/api/device-action", methods=["POST"])
|
@app.route("/api/device-action", methods=["POST"])
|
||||||
def api_device_action():
|
def api_device_action():
|
||||||
"""User chose Backup or Deploy for a device. source=usb | network; for network pass mac=."""
|
"""User chose Backup, Deploy, or Update EEPROM for a device. source=usb | network; for network pass mac=."""
|
||||||
body = request.get_json(force=True, silent=True) or {}
|
body = request.get_json(force=True, silent=True) or {}
|
||||||
source = (body.get("source") or "").strip().lower()
|
source = (body.get("source") or "").strip().lower()
|
||||||
action = (body.get("action") or "").strip().lower()
|
action = (body.get("action") or "").strip().lower()
|
||||||
if action not in ("backup", "deploy", "reboot"):
|
if action not in ("backup", "deploy", "reboot", "eeprom_update"):
|
||||||
return jsonify({"ok": False, "error": "action must be 'backup', 'deploy', or 'reboot'"}), 400
|
return jsonify({"ok": False, "error": "action must be 'backup', 'deploy', 'reboot', or 'eeprom_update'"}), 400
|
||||||
if action == "reboot" and source != "network":
|
if action == "reboot" and source != "network":
|
||||||
return jsonify({"ok": False, "error": "'reboot' is only for network devices"}), 400
|
return jsonify({"ok": False, "error": "'reboot' is only for network devices"}), 400
|
||||||
|
if action == "eeprom_update" and source != "usb":
|
||||||
|
return jsonify({"ok": False, "error": "'eeprom_update' is only for USB-connected devices"}), 400
|
||||||
if source == "usb":
|
if source == "usb":
|
||||||
|
if action == "eeprom_update":
|
||||||
|
boot_order = (body.get("boot_order") or "0xf21").strip().lower()
|
||||||
|
if boot_order not in ("0x1", "0xf21", "0xf12"):
|
||||||
|
return jsonify({"ok": False, "error": "boot_order must be 0x1, 0xf21, or 0xf12"}), 400
|
||||||
|
ok, err = _generate_eeprom_update(boot_order)
|
||||||
|
if not ok:
|
||||||
|
return jsonify({"ok": False, "error": err or "Failed to generate EEPROM update"}), 500
|
||||||
|
try:
|
||||||
|
os.makedirs(os.path.dirname(ACTION_REQUEST_FILE) or ".", exist_ok=True)
|
||||||
|
with open(ACTION_REQUEST_FILE, "w") as f:
|
||||||
|
f.write("eeprom_update")
|
||||||
|
_write_status("eeprom_update", "EEPROM update ready; host will write to device boot partition.")
|
||||||
|
return jsonify({"ok": True})
|
||||||
|
except (PermissionError, OSError):
|
||||||
|
return jsonify({"ok": False, "error": "Could not write action file"}), 500
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(ACTION_REQUEST_FILE) or ".", exist_ok=True)
|
os.makedirs(os.path.dirname(ACTION_REQUEST_FILE) or ".", exist_ok=True)
|
||||||
# If user requested "shrink after backup", create flag so host runs PiShrink after dd
|
# If user requested "shrink after backup", create flag so host runs PiShrink after dd
|
||||||
|
|||||||
@@ -165,7 +165,8 @@
|
|||||||
margin-bottom: 0.2rem;
|
margin-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
.device-desc { font-size: 0.9rem; color: var(--text); }
|
.device-desc { font-size: 0.9rem; color: var(--text); }
|
||||||
.device-actions { display: flex; gap: 0.5rem; flex-shrink: 0; }
|
.device-actions { display: flex; gap: 0.5rem; flex-shrink: 0; align-items: center; }
|
||||||
|
.eeprom-preset { padding: 0.35rem 0.5rem; font-size: 0.85rem; font-family: inherit; background: var(--bg-tertiary); color: var(--text); border: 1px solid var(--border); border-radius: 6px; max-width: 12rem; }
|
||||||
.btn {
|
.btn {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
@@ -557,7 +558,7 @@
|
|||||||
if (shrinkWrap) shrinkWrap.style.display = 'block';
|
if (shrinkWrap) shrinkWrap.style.display = 'block';
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.className = 'device-item';
|
el.className = 'device-item';
|
||||||
el.innerHTML = '<div class="device-info"><div class="device-type">USB boot</div><div class="device-desc">Device connected — choose Backup or Deploy</div></div><div class="device-actions"><button type="button" class="btn btn-outline" data-source="usb" data-action="backup">Backup</button><button type="button" class="btn btn-primary" data-source="usb" data-action="deploy">Deploy</button></div>';
|
el.innerHTML = '<div class="device-info"><div class="device-type">USB boot</div><div class="device-desc">Device connected — choose Backup, Deploy, or Update EEPROM</div></div><div class="device-actions"><button type="button" class="btn btn-outline" data-source="usb" data-action="backup">Backup</button><button type="button" class="btn btn-primary" data-source="usb" data-action="deploy">Deploy</button><select class="eeprom-preset" title="Boot order"><option value="0xf21">eMMC first, then network</option><option value="0x1">eMMC only</option><option value="0xf12">Network first, then eMMC</option></select><button type="button" class="btn btn-outline" data-source="usb" data-action="eeprom_update">Update EEPROM</button></div>';
|
||||||
container.appendChild(el);
|
container.appendChild(el);
|
||||||
} else {
|
} else {
|
||||||
if (shrinkWrap) shrinkWrap.style.display = 'none';
|
if (shrinkWrap) shrinkWrap.style.display = 'none';
|
||||||
@@ -581,6 +582,10 @@
|
|||||||
const mac = btn.getAttribute('data-mac');
|
const mac = btn.getAttribute('data-mac');
|
||||||
const body = { source: source, action: action };
|
const body = { source: source, action: action };
|
||||||
if (mac) body.mac = mac;
|
if (mac) body.mac = mac;
|
||||||
|
if (action === 'eeprom_update' && source === 'usb') {
|
||||||
|
const presetEl = btn.closest('.device-item') && btn.closest('.device-item').querySelector('.eeprom-preset');
|
||||||
|
body.boot_order = (presetEl && presetEl.value) ? presetEl.value : '0xf21';
|
||||||
|
}
|
||||||
const shrinkCb = document.getElementById('shrinkAfterBackup');
|
const shrinkCb = document.getElementById('shrinkAfterBackup');
|
||||||
if (action === 'backup' && shrinkCb && shrinkCb.checked) body.shrink = true;
|
if (action === 'backup' && shrinkCb && shrinkCb.checked) body.shrink = true;
|
||||||
fetch('/api/device-action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
fetch('/api/device-action', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
|
||||||
|
|||||||
@@ -152,10 +152,10 @@ if [[ -z "$target_dev" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Ask user (dashboard): Backup or Deploy?
|
# Ask user (dashboard): Backup or Deploy?
|
||||||
write_status "waiting_choice" "Device connected (USB boot mode). Choose Backup or Deploy in the dashboard." "null"
|
write_status "waiting_choice" "Device connected (USB boot mode). Choose Backup, Deploy, or Update EEPROM in the dashboard." "null"
|
||||||
echo "usb" > "$DEVICE_SOURCE_FILE" 2>/dev/null || true
|
echo "usb" > "$DEVICE_SOURCE_FILE" 2>/dev/null || true
|
||||||
echo "$target_dev" > "$CURRENT_DEVICE_FILE" 2>/dev/null || true
|
echo "$target_dev" > "$CURRENT_DEVICE_FILE" 2>/dev/null || true
|
||||||
log "Waiting for user choice (Backup or Deploy) in dashboard; timeout ${WAIT_TIMEOUT}s..."
|
log "Waiting for user choice (Backup, Deploy, or Update EEPROM) in dashboard; timeout ${WAIT_TIMEOUT}s..."
|
||||||
for (( i = 0; i < WAIT_TIMEOUT; i += 2 )); do
|
for (( i = 0; i < WAIT_TIMEOUT; i += 2 )); do
|
||||||
sleep 2
|
sleep 2
|
||||||
if [[ -f "$ACTION_REQUEST_FILE" ]]; then
|
if [[ -f "$ACTION_REQUEST_FILE" ]]; then
|
||||||
@@ -225,8 +225,48 @@ for (( i = 0; i < WAIT_TIMEOUT; i += 2 )); do
|
|||||||
else
|
else
|
||||||
write_status "error" "Flash failed" "null" "dd failed"
|
write_status "error" "Flash failed" "null" "dd failed"
|
||||||
fi
|
fi
|
||||||
|
elif [[ "$action" == "eeprom_update" ]]; then
|
||||||
|
# Dashboard has written pieeprom.upd and pieeprom.sig to BASE_DIR; copy to eMMC boot partition
|
||||||
|
PROV_DIR="$(dirname "$STATUS_FILE")"
|
||||||
|
EEPROM_UPD="$PROV_DIR/pieeprom.upd"
|
||||||
|
EEPROM_SIG="$PROV_DIR/pieeprom.sig"
|
||||||
|
if [[ ! -f "$EEPROM_UPD" || ! -f "$EEPROM_SIG" ]]; then
|
||||||
|
log "EEPROM update files not found: $EEPROM_UPD / $EEPROM_SIG"
|
||||||
|
write_status "error" "EEPROM update failed" "null" "pieeprom.upd or pieeprom.sig not found in $PROV_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
boot_part=""
|
||||||
|
for p in "${target_dev}1" "${target_dev}p1"; do
|
||||||
|
if [[ -b "$p" ]]; then
|
||||||
|
boot_part="$p"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ -z "$boot_part" ]]; then
|
||||||
|
log "No boot partition found for $target_dev (tried ...1 and ...p1)"
|
||||||
|
write_status "error" "EEPROM update failed" "null" "Boot partition not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
write_status "eeprom_update" "Writing EEPROM update to boot partition…" "null"
|
||||||
|
log "Mounting $boot_part and copying EEPROM update..."
|
||||||
|
mnt=$(mktemp -d)
|
||||||
|
if mount "$boot_part" "$mnt" 2>/dev/null; then
|
||||||
|
if cp "$EEPROM_UPD" "$mnt/pieeprom.upd" && cp "$EEPROM_SIG" "$mnt/pieeprom.sig"; then
|
||||||
|
sync
|
||||||
|
log "EEPROM update written. Remove eMMC disable jumper and power cycle to apply."
|
||||||
|
write_status "done" "EEPROM update written to boot partition. Remove eMMC disable jumper and power cycle the reTerminal to apply." "100"
|
||||||
|
rm -f "$EEPROM_UPD" "$EEPROM_SIG"
|
||||||
|
( sleep 90 && write_status "idle" "Waiting for reTerminal in boot mode or network." "null" ) &
|
||||||
|
else
|
||||||
|
write_status "error" "EEPROM update failed" "null" "Failed to copy pieeprom.upd/sig"
|
||||||
|
fi
|
||||||
|
umount "$mnt" 2>/dev/null || true
|
||||||
|
else
|
||||||
|
write_status "error" "EEPROM update failed" "null" "Could not mount boot partition"
|
||||||
|
fi
|
||||||
|
rm -rf "$mnt"
|
||||||
else
|
else
|
||||||
write_status "error" "Unknown action" "null" "action_request must be 'backup' or 'deploy'"
|
write_status "error" "Unknown action" "null" "action_request must be 'backup', 'deploy', or 'eeprom_update'"
|
||||||
fi
|
fi
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
60
emmc-provisioning/scripts/install-eeprom-tools-on-lxc.sh
Executable file
60
emmc-provisioning/scripts/install-eeprom-tools-on-lxc.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install rpi-eeprom-config and pieeprom.bin on the LXC (or host) where the dashboard runs.
|
||||||
|
# Required for "Update EEPROM" from the dashboard when a device is connected via USB boot.
|
||||||
|
# Run on the LXC: bash -s < scripts/install-eeprom-tools-on-lxc.sh
|
||||||
|
# Or: ssh root@<LXC-IP> 'bash -s' < emmc-provisioning/scripts/install-eeprom-tools-on-lxc.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
RPI_EEPROM_RAW="https://raw.githubusercontent.com/raspberrypi/rpi-eeprom/master"
|
||||||
|
EEPROM_DIR="${EEPROM_DIR:-/opt/cm4-provisioning/eeprom}"
|
||||||
|
|
||||||
|
echo "Installing EEPROM tools to $EEPROM_DIR"
|
||||||
|
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: Python 3 is required to run rpi-eeprom-config. Install it (e.g. apt install python3) and re-run."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
GET="curl -sL"
|
||||||
|
GET_O="curl -sL -o"
|
||||||
|
elif command -v wget >/dev/null 2>&1; then
|
||||||
|
GET="wget -q -O -"
|
||||||
|
GET_O="wget -q -O"
|
||||||
|
else
|
||||||
|
echo "ERROR: curl or wget required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$EEPROM_DIR"
|
||||||
|
cd "$EEPROM_DIR"
|
||||||
|
|
||||||
|
echo "Downloading rpi-eeprom-config..."
|
||||||
|
$GET_O "$EEPROM_DIR/rpi-eeprom-config" "$RPI_EEPROM_RAW/rpi-eeprom-config"
|
||||||
|
$GET_O "$EEPROM_DIR/rpi-eeprom-digest" "$RPI_EEPROM_RAW/rpi-eeprom-digest"
|
||||||
|
chmod +x "$EEPROM_DIR/rpi-eeprom-config" "$EEPROM_DIR/rpi-eeprom-digest"
|
||||||
|
|
||||||
|
echo "Finding latest BCM2711 EEPROM firmware..."
|
||||||
|
LATEST_FW=$($GET "https://api.github.com/repos/raspberrypi/rpi-eeprom/contents/firmware-2711/default" \
|
||||||
|
| grep -o '"name" *: *"pieeprom-[^"]*\.bin"' | sed 's/"name" *: *"//;s/"//' | sort | tail -1)
|
||||||
|
|
||||||
|
if [ -z "$LATEST_FW" ]; then
|
||||||
|
echo "WARNING: Could not determine latest firmware from GitHub API. Try again or download pieeprom.bin manually."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Downloading $LATEST_FW..."
|
||||||
|
$GET_O "$EEPROM_DIR/pieeprom.bin" "$RPI_EEPROM_RAW/firmware-2711/default/$LATEST_FW"
|
||||||
|
|
||||||
|
if [ ! -s "$EEPROM_DIR/pieeprom.bin" ]; then
|
||||||
|
echo "ERROR: pieeprom.bin is missing or empty"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Verifying rpi-eeprom-config..."
|
||||||
|
if ! python3 "$EEPROM_DIR/rpi-eeprom-config" "$EEPROM_DIR/pieeprom.bin" >/dev/null 2>&1; then
|
||||||
|
echo "WARNING: rpi-eeprom-config could not read pieeprom.bin (non-fatal)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done. EEPROM tools installed:"
|
||||||
|
ls -la "$EEPROM_DIR"
|
||||||
Reference in New Issue
Block a user