Files
reterminal-dm4/emmc-provisioning/cloud-init/first-boot.sh
nearxos 5238d457e8 Update boot order configuration for eMMC first, then network
Modify the first-boot script and documentation to set the EEPROM boot order to 0xf21, prioritizing eMMC boot followed by network boot. Adjust network boot settings for faster failure on DHCP timeouts and update related scripts and documentation to reflect these changes. Enhance the rescue script to directly modify EEPROM settings without requiring a chroot into eMMC, streamlining the recovery process for devices stuck in network-only boot. Update relevant documentation to ensure clarity on the new boot order and its implications.
2026-02-21 15:05:17 +02:00

307 lines
16 KiB
Bash

#!/bin/bash
# First-boot: packages, Chromium kiosk, rpd-labwc + touch, reTerminal DM drivers.
# Run by cloud-init (user-data-remote-gnss.example). Run as root.
set -e
export DEBIAN_FRONTEND=noninteractive
# --- Constants ---
# All first-boot assets live in portal-files/first-boot/ on the file server.
FILE_SERVER="http://10.130.60.141:5000/files/first-boot"
HOSTNAME="guard"
PI_USER="pi"
PI_HOME="/home/$PI_USER"
AUTOSTART="$PI_HOME/.config/autostart"
LOGFILE="/var/log/first-boot.log"
PLYMOUTH_DIR="/usr/share/plymouth/themes/custom"
WALLPAPER_PATH="/usr/share/rpd-wallpaper/splash.png"
# --- Logging ---
log() { echo "[$(date -Iseconds)] $*"; }
exec > >(tee -a "$LOGFILE") 2>&1
log "=== first-boot.sh started ==="
log "FILE_SERVER=$FILE_SERVER PI_USER=$PI_USER LOGFILE=$LOGFILE"
# --- 0. Hostname and /etc/hosts (avoids "unable to resolve host" with sudo) ---
log "--- Hostname: $HOSTNAME ---"
echo "$HOSTNAME" > /etc/hostname
hostnamectl set-hostname "$HOSTNAME" 2>/dev/null || true
# Ensure hostname resolves so sudo and other tools don't warn
if ! grep -q "127.0.1.1[[:space:]]*$HOSTNAME" /etc/hosts 2>/dev/null; then
sed -i "/127.0.1.1[[:space:]].*$/d" /etc/hosts
echo "127.0.1.1 $HOSTNAME" >> /etc/hosts
fi
log "Hostname set to $HOSTNAME; /etc/hosts updated"
# --- Helpers ---
# Download script + .desktop from FILE_SERVER and install as one-shot autostart (runs once at pi's first login, then deletes itself).
install_oneshot() {
local name="$1"
log "--- Installing one-shot: $name ---"
if curl -fsSL "${FILE_SERVER}/${name}.sh" -o "$PI_HOME/${name}.sh"; then
log "Downloaded ${name}.sh to $PI_HOME/${name}.sh"
else
log "WARNING: Could not download ${name}.sh"; return 1
fi
if curl -fsSL "${FILE_SERVER}/${name}.desktop" -o "$AUTOSTART/${name}.desktop"; then
log "Downloaded ${name}.desktop to $AUTOSTART/${name}.desktop"
else
log "WARNING: Could not download ${name}.desktop"; return 1
fi
chmod 755 "$PI_HOME/${name}.sh" && chmod 644 "$AUTOSTART/${name}.desktop"
chown "$PI_USER:$PI_USER" "$PI_HOME/${name}.sh" "$AUTOSTART/${name}.desktop"
log "One-shot $name installed (will run at first login and then remove itself)"
}
# --- 1. Packages ---
log "--- Installing packages ---"
log "Running apt-get update ..."
apt-get update -qq
log "Installing: git chromium wmctrl openssh-server swaybg wlr-randr maliit-keyboard xinput-calibrator rpi-eeprom"
apt-get install -y -qq git chromium wmctrl openssh-server \
swaybg wlr-randr maliit-keyboard xinput-calibrator rpi-eeprom
log "Packages installed successfully"
# --- 2. Dirs and kiosk files from file server ---
log "--- Kiosk files ---"
log "Creating $AUTOSTART"
mkdir -p "$AUTOSTART"
log "Downloading start-chromium.sh from ${FILE_SERVER}/start-chromium.sh"
curl -fsSL "${FILE_SERVER}/start-chromium.sh" -o "$PI_HOME/start-chromium.sh"
log "Downloading chromium-kiosk.desktop from ${FILE_SERVER}/chromium-kiosk.desktop"
curl -fsSL "${FILE_SERVER}/chromium-kiosk.desktop" -o "$AUTOSTART/chromium-kiosk.desktop"
chmod 755 "$PI_HOME/start-chromium.sh" && chmod 644 "$AUTOSTART/chromium-kiosk.desktop"
chown -R "$PI_USER:$PI_USER" "$PI_HOME/start-chromium.sh" "$AUTOSTART/chromium-kiosk.desktop"
log "Kiosk files installed under $PI_HOME and $AUTOSTART"
# --- 3. Boot splash and wallpaper (splash.png + Plymouth theme from file server) ---
log "--- Boot splash and wallpaper ---"
log "Creating $PLYMOUTH_DIR and /usr/share/rpd-wallpaper"
mkdir -p "$PLYMOUTH_DIR" /usr/share/rpd-wallpaper
if curl -fsSL "${FILE_SERVER}/splash.png" -o "$PLYMOUTH_DIR/splash.png"; then
log "Downloaded splash.png; copying to $WALLPAPER_PATH"
cp "$PLYMOUTH_DIR/splash.png" "$WALLPAPER_PATH"
chmod 644 "$PLYMOUTH_DIR/splash.png" "$WALLPAPER_PATH"
if curl -fsSL "${FILE_SERVER}/custom.plymouth" -o "$PLYMOUTH_DIR/custom.plymouth" \
&& curl -fsSL "${FILE_SERVER}/custom.script" -o "$PLYMOUTH_DIR/custom.script"; then
chmod 644 "$PLYMOUTH_DIR/custom.plymouth" "$PLYMOUTH_DIR/custom.script"
log "Plymouth theme files (custom.plymouth, custom.script) installed"
else
log "WARNING: Could not download custom.plymouth/custom.script; boot splash theme may be incomplete"
fi
grep -q '^Theme=custom' /etc/plymouth/plymouthd.conf 2>/dev/null || printf '%s\n' '[Daemon]' 'Theme=custom' >> /etc/plymouth/plymouthd.conf
log "Running update-initramfs (may take a moment) ..."
update-initramfs -u -k all 2>/dev/null || true
mkdir -p /etc/lightdm/lightdm.conf.d
curl -fsSL "${FILE_SERVER}/99-wallpaper.conf" -o /etc/lightdm/lightdm.conf.d/99-wallpaper.conf 2>/dev/null || log "WARNING: Could not download 99-wallpaper.conf"
# Set desktop wallpaper once via pcmanfm config (rpd-labwc uses pcmanfm-pi; profile LXDE-pi or default)
for PROFILE in LXDE-pi default; do
PCMANFM_DESKTOP="$PI_HOME/.config/pcmanfm/$PROFILE/desktop-items-0.conf"
mkdir -p "$(dirname "$PCMANFM_DESKTOP")"
if [[ ! -f "$PCMANFM_DESKTOP" ]]; then
printf '%s\n' '[*]' "wallpaper=$WALLPAPER_PATH" 'wallpaper_mode=crop' 'wallpaper_common=1' > "$PCMANFM_DESKTOP"
else
grep -q '^wallpaper=' "$PCMANFM_DESKTOP" && sed -i "s|^wallpaper=.*|wallpaper=$WALLPAPER_PATH|" "$PCMANFM_DESKTOP" || echo "wallpaper=$WALLPAPER_PATH" >> "$PCMANFM_DESKTOP"
grep -q '^wallpaper_mode=' "$PCMANFM_DESKTOP" && sed -i 's/^wallpaper_mode=.*/wallpaper_mode=crop/' "$PCMANFM_DESKTOP" || echo 'wallpaper_mode=crop' >> "$PCMANFM_DESKTOP"
fi
chown -R "$PI_USER:$PI_USER" "$(dirname "$PCMANFM_DESKTOP")"
done
log "Set desktop wallpaper via pcmanfm config (LXDE-pi and default)"
log "Splash and wallpaper set from file server"
else
log "WARNING: Could not download splash.png"
fi
# --- 4. LightDM: rpd-labwc session + configs from file server ---
log "--- LightDM session (rpd-labwc) ---"
mkdir -p /etc/lightdm/lightdm.conf.d
if curl -fsSL "${FILE_SERVER}/99-default-session.conf" -o /etc/lightdm/lightdm.conf.d/99-default-session.conf 2>/dev/null; then
log "99-default-session.conf installed"
else
log "WARNING: Could not download 99-default-session.conf"
fi
# Raspberry Pi OS may apply main lightdm.conf after .conf.d; force session in main config too
if [[ -f /etc/lightdm/lightdm.conf ]]; then
sed -i 's/^user-session=.*/user-session=rpd-labwc/' /etc/lightdm/lightdm.conf
sed -i 's/^autologin-session=.*/autologin-session=rpd-labwc/' /etc/lightdm/lightdm.conf
log "Patched /etc/lightdm/lightdm.conf to use rpd-labwc"
fi
# --- 5. Maliit on-screen keyboard (from file server) ---
log "--- Maliit ---"
mkdir -p "$AUTOSTART" "$PI_HOME/.config"
curl -fsSL "${FILE_SERVER}/maliit-keyboard.desktop" -o "$AUTOSTART/maliit-keyboard.desktop" 2>/dev/null && log "maliit-keyboard.desktop installed" || log "WARNING: Could not download maliit-keyboard.desktop"
# --- 5b. Dark theme (GTK + prefer dark for apps) ---
log "--- Dark theme ---"
GTK_SETTINGS="$PI_HOME/.config/gtk-3.0/settings.ini"
mkdir -p "$(dirname "$GTK_SETTINGS")"
if [[ ! -f "$GTK_SETTINGS" ]]; then
printf '%s\n' '[Settings]' 'gtk-application-prefer-dark-theme=1' 'gtk-theme-name=PiXnoir' > "$GTK_SETTINGS"
else
grep -q '^gtk-application-prefer-dark-theme=' "$GTK_SETTINGS" && sed -i 's/^gtk-application-prefer-dark-theme=.*/gtk-application-prefer-dark-theme=1/' "$GTK_SETTINGS" || echo 'gtk-application-prefer-dark-theme=1' >> "$GTK_SETTINGS"
grep -q '^gtk-theme-name=' "$GTK_SETTINGS" && sed -i 's/^gtk-theme-name=.*/gtk-theme-name=PiXnoir/' "$GTK_SETTINGS" || echo 'gtk-theme-name=PiXnoir' >> "$GTK_SETTINGS"
fi
# Fallback if PiXnoir not installed (e.g. older image): Adwaita-dark
log "Set dark theme (PiXnoir) in gtk-3.0/settings.ini"
chown -R "$PI_USER:$PI_USER" "$PI_HOME/.config"
# --- 6. reTerminal DM drivers (Seeed) ---
log "--- reTerminal DM drivers ---"
REPO_DIR="/tmp/seeed-linux-dtoverlays"
log "Cloning seeed-linux-dtoverlays to $REPO_DIR ..."
git clone --depth 1 https://github.com/Seeed-Studio/seeed-linux-dtoverlays "$REPO_DIR"
# Script must run from repo root (it uses pwd for MOD_PATH). On bookworm+ --compat-kernel is not supported.
log "Running reTerminal.sh --device reTerminal-DM from $REPO_DIR ..."
if ( cd "$REPO_DIR" && "$REPO_DIR/scripts/reTerminal.sh" --device reTerminal-DM ); then
log "reTerminal DM drivers installed (reboot will apply)"
else
log "WARNING: reTerminal.sh failed (see log above). Display/touch may still work; you can retry later with: cd $REPO_DIR && sudo ./scripts/reTerminal.sh --device reTerminal-DM"
fi
log "Removing $REPO_DIR"
rm -rf "$REPO_DIR"
# --- 6b. Re-apply splash and display (Seeed script sets disable_splash=1 and can duplicate Plymouth theme) ---
log "--- Re-applying boot splash and Plymouth theme ---"
CFG_PATH="/boot/firmware/config.txt"
[[ -f /boot/firmware/config.txt ]] || CFG_PATH="/boot/config.txt"
if [[ -f "$CFG_PATH" ]]; then
if grep -q '^disable_splash=1' "$CFG_PATH"; then
sed -i 's/^disable_splash=1$/disable_splash=0/' "$CFG_PATH"
log "Set disable_splash=0 so Plymouth splash is shown"
fi
fi
# Ensure Plymouth uses our custom theme only (single [Daemon], Theme=custom)
if [[ -f /etc/plymouth/plymouthd.conf ]]; then
sed -i '/^Theme=/d' /etc/plymouth/plymouthd.conf
sed -i '/^\[Daemon\]$/d' /etc/plymouth/plymouthd.conf
grep -q '^\[Daemon\]' /etc/plymouth/plymouthd.conf || echo '[Daemon]' >> /etc/plymouth/plymouthd.conf
echo 'Theme=custom' >> /etc/plymouth/plymouthd.conf
log "Plymouth theme set to custom only"
fi
log "Running update-initramfs to apply Plymouth theme ..."
update-initramfs -u -k all 2>/dev/null || true
# --- 6b2. Kernel cmdline: swiotlb + DSI rotation (KMS, persistent across reboots) ---
CMDLINE_PATH="/boot/firmware/cmdline.txt"
[[ -f "$CMDLINE_PATH" ]] || CMDLINE_PATH="/boot/cmdline.txt"
if [[ -f "$CMDLINE_PATH" ]]; then
if ! grep -q 'swiotlb=' "$CMDLINE_PATH"; then
sed -i 's/rootwait/rootwait swiotlb=65536/' "$CMDLINE_PATH"
log "Added swiotlb=65536 to kernel cmdline (vc4-drm / DSI)"
fi
# Persistent rotation for DSI-1 (KMS): append at end of single line. 90 = 90° clockwise.
if ! grep -q 'video=DSI-1:rotate=' "$CMDLINE_PATH"; then
sed -i 's/$/ video=DSI-1:rotate=90/' "$CMDLINE_PATH"
log "Added video=DSI-1:rotate=90 to kernel cmdline (DSI rotation)"
fi
fi
# --- 6c. CM4: enable rpi-eeprom-update so boot order can be set ---
# On CM4, rpi-eeprom-update is disabled by default. Enable it by setting flags in /etc/default/rpi-eeprom-update.
# We use the bootloader method (pieeprom.upd file placed in /boot/firmware), NOT flashrom.
# NOTE: Do NOT add dtoverlay=audremap or dtoverlay=spi-gpio40-45 to config.txt.
# Those are only needed for the flashrom (direct SPI) method, and audremap CONFLICTS with
# the reTerminal DM display backlight (both use GPIO13 PWM).
# See: https://github.com/raspberrypi/usbboot , /etc/default/rpi-eeprom-update
log "--- CM4 EEPROM update enable (for boot order) ---"
EEPROM_DEFAULT="/etc/default/rpi-eeprom-update"
if [[ ! -f "$EEPROM_DEFAULT" ]]; then
touch "$EEPROM_DEFAULT"
log "Created $EEPROM_DEFAULT"
fi
grep -q '^RPI_EEPROM_USE_FLASHROM=' "$EEPROM_DEFAULT" && sed -i 's/^RPI_EEPROM_USE_FLASHROM=.*/RPI_EEPROM_USE_FLASHROM=1/' "$EEPROM_DEFAULT" || echo 'RPI_EEPROM_USE_FLASHROM=1' >> "$EEPROM_DEFAULT"
grep -q '^CM4_ENABLE_RPI_EEPROM_UPDATE=' "$EEPROM_DEFAULT" && sed -i 's/^CM4_ENABLE_RPI_EEPROM_UPDATE=.*/CM4_ENABLE_RPI_EEPROM_UPDATE=1/' "$EEPROM_DEFAULT" || echo 'CM4_ENABLE_RPI_EEPROM_UPDATE=1' >> "$EEPROM_DEFAULT"
log "Set RPI_EEPROM_USE_FLASHROM=1 and CM4_ENABLE_RPI_EEPROM_UPDATE=1 in $EEPROM_DEFAULT"
# --- 6d. Boot order: eMMC/SD first, then network, then restart (0xf21) ---
# BOOT_ORDER nibbles (right-to-left): 1=SD/eMMC, 2=network (TFTP), f=restart loop.
# On CM4, rpi-eeprom-update -l only works after reboot (once 6c is applied). So we try now; if it fails, a one-shot runs after next boot.
log "--- Boot order (0xf21: eMMC/SD first, then network, restart) ---"
BOOTCONF="/tmp/first-boot-eeprom-conf.txt"
BOOT_ORDER_SET=0
if command -v rpi-eeprom-config >/dev/null 2>&1 && command -v rpi-eeprom-update >/dev/null 2>&1; then
if PEE="$(rpi-eeprom-update -l 2>/dev/null)" && [[ -n "$PEE" ]] && [[ -f "$PEE" ]]; then
rpi-eeprom-config "$PEE" > "$BOOTCONF" 2>/dev/null || true
fi
if [[ -s "$BOOTCONF" ]]; then
sed -i 's/^BOOT_ORDER=.*/BOOT_ORDER=0xf21/' "$BOOTCONF"
grep -q '^BOOT_ORDER=' "$BOOTCONF" || echo 'BOOT_ORDER=0xf21' >> "$BOOTCONF"
# Limit network boot: 3 retries, 1500ms DHCP timeout (fail fast to eMMC)
sed -i '/^NET_BOOT_MAX_RETRIES=/d; /^DHCP_TIMEOUT=/d; /^DHCP_REQ_TIMEOUT=/d; /^TFTP_IP=/d; /^NET_INSTALL_AT_POWER_ON=/d' "$BOOTCONF"
echo 'NET_BOOT_MAX_RETRIES=3' >> "$BOOTCONF"
echo 'DHCP_TIMEOUT=1500' >> "$BOOTCONF"
echo 'DHCP_REQ_TIMEOUT=500' >> "$BOOTCONF"
echo 'NET_INSTALL_AT_POWER_ON=0' >> "$BOOTCONF"
if rpi-eeprom-config --apply "$BOOTCONF" 2>/dev/null; then
log "Boot order set to 0xf21 (eMMC first, then network, restart); EEPROM update scheduled for next reboot"
@ BOOT_ORDER_SET=1
else
log "WARNING: rpi-eeprom-config --apply failed; boot order unchanged"
fi
else
log "rpi-eeprom-update -l did not return a config (on CM4 this is normal until after reboot with 6c applied); scheduling one-shot to set boot order after next boot"
fi
rm -f "$BOOTCONF"
else
log "rpi-eeprom-config/rpi-eeprom-update not found; skipping boot order (not a Pi4/CM4 or package missing)"
fi
# If boot order was not set (e.g. CM4 first boot), install a one-shot systemd service to set it after reboot
if [[ "$BOOT_ORDER_SET" -eq 0 ]] && command -v rpi-eeprom-config >/dev/null 2>&1 && command -v rpi-eeprom-update >/dev/null 2>&1; then
ONCE_SCRIPT="/usr/local/bin/set-cm4-boot-order-once.sh"
ONCE_SVC="/etc/systemd/system/set-cm4-boot-order-once.service"
cat > "$ONCE_SCRIPT" << 'SETBOOTEOF'
#!/bin/bash
# One-shot: set BOOT_ORDER=0xf21 (eMMC first, then network) when rpi-eeprom-update becomes available (e.g. after CM4 enable and reboot).
BOOTCONF="/tmp/eeprom-boot-order-once.txt"
if PEE="$(rpi-eeprom-update -l 2>/dev/null)" && [[ -n "$PEE" ]] && [[ -f "$PEE" ]]; then
rpi-eeprom-config "$PEE" > "$BOOTCONF" 2>/dev/null
if [[ -s "$BOOTCONF" ]]; then
sed -i 's/^BOOT_ORDER=.*/BOOT_ORDER=0xf21/' "$BOOTCONF"
grep -q '^BOOT_ORDER=' "$BOOTCONF" || echo 'BOOT_ORDER=0xf21' >> "$BOOTCONF"
sed -i '/^NET_BOOT_MAX_RETRIES=/d; /^DHCP_TIMEOUT=/d; /^DHCP_REQ_TIMEOUT=/d; /^TFTP_IP=/d; /^NET_INSTALL_AT_POWER_ON=/d' "$BOOTCONF"
echo 'NET_BOOT_MAX_RETRIES=3' >> "$BOOTCONF"
echo 'DHCP_TIMEOUT=1500' >> "$BOOTCONF"
echo 'DHCP_REQ_TIMEOUT=500' >> "$BOOTCONF"
echo 'NET_INSTALL_AT_POWER_ON=0' >> "$BOOTCONF"
if rpi-eeprom-config --apply "$BOOTCONF" 2>/dev/null; then
echo "Boot order set to 0xf21 (eMMC first, then network)"
fi
fi
rm -f "$BOOTCONF"
fi
systemctl disable set-cm4-boot-order-once.service 2>/dev/null || true
rm -f /etc/systemd/system/set-cm4-boot-order-once.service
rm -f "$0"
SETBOOTEOF
chmod 755 "$ONCE_SCRIPT"
cat > "$ONCE_SVC" << 'SVCEOF'
[Unit]
Description=Set CM4 boot order once (eMMC first, then network)
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/set-cm4-boot-order-once.sh
RemainAfterExit=no
[Install]
WantedBy=multi-user.target
SVCEOF
systemctl enable set-cm4-boot-order-once.service 2>/dev/null && log "Enabled set-cm4-boot-order-once.service to set boot order after next boot"
fi
# --- 7. One-shots (wallpaper already set in pcmanfm config above; rotation is via cmdline.txt) ---
log "--- One-shot scripts (if any) ---"
# Rotation is set persistently in cmdline.txt (video=DSI-1:rotate=90), not via one-shot script.
log "Rotation is set via kernel cmdline (video=DSI-1:rotate=90)"
# --- 8. Allow pi to append to first-boot.log (for one-shot scripts) ---
chmod 666 "$LOGFILE"
log "Log file $LOGFILE is now appendable by user $PI_USER for one-shot scripts"
# --- 9. Reboot ---
log "=== first-boot.sh finished, rebooting ==="
reboot