Refactor first-boot process to introduce ordered execution and new one-shot scripts

Revise the first-boot script to implement a structured approach with 13 numbered steps, allowing for better control over the execution order. Introduce two new one-shot scripts: `01-set-rotation-once.sh` and `02-set-wallpaper-once.sh`, replacing the previous single script approach. Update documentation to reflect these changes, including the new configuration options for enabling/disabling steps and the revised file structure for one-shot scripts. Enhance the dashboard to display first-boot progress, improving user feedback during the initial setup.
This commit is contained in:
nearxos
2026-02-22 16:22:44 +02:00
parent 79a7f76a12
commit 16c796b8af
15 changed files with 431 additions and 188 deletions

View File

@@ -3,7 +3,8 @@
# Runs once as user pi at first login; deletes its autostart and this script so it never runs again.
# Logs to /var/log/first-boot.log.
FIRST_BOOT_LOG="/var/log/first-boot.log"
log() { echo "[$(date -Iseconds)] [set-rotation-once] $*" >> "$FIRST_BOOT_LOG" 2>/dev/null || true; }
BASE="$(basename "$0" .sh)"
log() { echo "[$(date -Iseconds)] [$BASE] $*" >> "$FIRST_BOOT_LOG" 2>/dev/null || true; }
log "started (labwc/wlr-randr)"
log "waiting 5s for compositor ..."
@@ -26,5 +27,5 @@ else
fi
log "removing one-shot desktop and script"
rm -f /home/pi/.config/autostart/set-rotation-once.desktop /home/pi/set-rotation-once.sh
rm -f "$HOME/.config/autostart/${BASE}.desktop" "$HOME/${BASE}.sh"
log "finished"

View File

@@ -2,7 +2,8 @@
# One-shot: set desktop wallpaper for labwc (swaybg) and persist via labwc autostart, then remove self.
# Runs as user pi at first login. Logs to /var/log/first-boot.log.
FIRST_BOOT_LOG="/var/log/first-boot.log"
log() { echo "[$(date -Iseconds)] [set-wallpaper-once] $*" >> "$FIRST_BOOT_LOG" 2>/dev/null || true; }
BASE="$(basename "$0" .sh)"
log() { echo "[$(date -Iseconds)] [$BASE] $*" >> "$FIRST_BOOT_LOG" 2>/dev/null || true; }
WALLPAPER="/usr/share/rpd-wallpaper/splash.png"
LABWC_AUTOSTART="$HOME/.config/labwc/autostart"
@@ -32,5 +33,5 @@ else
fi
log "removing one-shot desktop and script"
rm -f /home/pi/.config/autostart/set-wallpaper-once.desktop /home/pi/set-wallpaper-once.sh
rm -f "$HOME/.config/autostart/${BASE}.desktop" "$HOME/${BASE}.sh"
log "finished"

View File

@@ -1,5 +1,5 @@
[Desktop Entry]
Type=Application
Name=Set rotation once
Exec=/home/pi/set-rotation-once.sh
Name=01 Set rotation once
Exec=/home/pi/01-set-rotation-once.sh
X-GNOME-Autostart-enabled=true

View File

@@ -0,0 +1,5 @@
[Desktop Entry]
Type=Application
Name=02 Set wallpaper once
Exec=/home/pi/02-set-wallpaper-once.sh
X-GNOME-Autostart-enabled=true

View File

@@ -8,6 +8,5 @@ first-boot.sh downloads these from `FILE_SERVER` (e.g. `http://10.20.50.1:5000/f
| 99-wallpaper.conf | /etc/lightdm/lightdm.conf.d/99-wallpaper.conf |
| 99-default-session.conf | /etc/lightdm/lightdm.conf.d/99-default-session.conf (rpd-labwc) |
| maliit-keyboard.desktop | /home/pi/.config/autostart/maliit-keyboard.desktop |
| set-rotation-once.desktop | /home/pi/.config/autostart/set-rotation-once.desktop (with set-rotation-once.sh) |
Wallpaper is set once during first-boot via pcmanfm config; no set-wallpaper-once one-shot.
| 01-set-rotation-once.desktop | /home/pi/.config/autostart/01-set-rotation-once.desktop (with 01-set-rotation-once.sh) |
| 02-set-wallpaper-once.desktop | /home/pi/.config/autostart/02-set-wallpaper-once.desktop (with 02-set-wallpaper-once.sh). Wallpaper is also set during first-boot via pcmanfm. |

View File

@@ -1,5 +0,0 @@
[Desktop Entry]
Type=Application
Name=Set wallpaper once
Exec=/home/pi/set-wallpaper-once.sh
X-GNOME-Autostart-enabled=true

View File

@@ -14,9 +14,9 @@ first-boot.sh downloads from **`.../files/first-boot/`** (e.g. `http://10.20.50.
| **99-wallpaper.conf** | LightDM greeter wallpaper (from `config-files/`). |
| **99-default-session.conf** | LightDM default session rpd-labwc (from `config-files/`). |
| **maliit-keyboard.desktop** | Maliit on-screen keyboard autostart (from `config-files/`). |
| **set-rotation-once.sh** + **.desktop** | One-shot: wlr-randr rotation (Left) at first login. |
| **01-set-rotation-once.sh** + **.desktop** | One-shot: wlr-randr rotation (Left) at first login. |
Desktop wallpaper is set once during first-boot via pcmanfm config (first-boot.sh); no set-wallpaper-once one-shot needed.
Desktop wallpaper is set once during first-boot via pcmanfm config (first-boot.sh). Optional one-shot: **02-set-wallpaper-once.sh**.
---
@@ -27,4 +27,4 @@ Desktop wallpaper is set once during first-boot via pcmanfm config (first-boot.s
- **plymouth-custom/custom.script** — Plymouth script that draws splash.png; host as `custom.script`.
- **lightdm/RPiSystem_dark.png** — Unused; only `splash.png` is used now.
- **start-chromium.sh** and **chromium-kiosk.desktop** live in `cloud-init/` and `cloud-init/config-files/`; `scripts/sync-portal-files-to-lxc.sh` copies them to the portal first-boot folder.
- **set-rotation-once.sh** (and its .desktop) are in `cloud-init/` and synced to `portal-files/first-boot/` by the same script.
- **01-set-rotation-once.sh**, **02-set-wallpaper-once.sh** (and their .desktop files) are in `cloud-init/` and synced to `portal-files/first-boot/` by the same script.

View File

@@ -0,0 +1,60 @@
# first-boot.conf.example
# Copy to first-boot.conf and edit. Loaded by first-boot.sh from:
# - same directory as first-boot.sh, or
# - /tmp/first-boot.conf (when run via cloud-init), or
# - /etc/cm4-provisioning/first-boot.conf
# Unset variables keep the script's built-in defaults.
# --- File server & host ---
# Base URL for first-boot assets (scripts, splash, configs). No trailing slash.
FILE_SERVER="http://10.20.50.1:5000/files/first-boot"
# Hostname set on the device.
HOSTNAME="guard"
# --- User ---
# Login user (must exist; cloud-init user-data must create the same user).
PI_USER="pi"
# --- Paths (optional overrides) ---
# First-boot log file.
LOGFILE="/var/log/first-boot.log"
# Plymouth custom theme directory.
PLYMOUTH_DIR="/usr/share/plymouth/themes/custom"
# Wallpaper path (splash.png is also copied here).
WALLPAPER_PATH="/usr/share/rpd-wallpaper/splash.png"
# --- Packages ---
# Space-separated list of packages to install. Must include: git chromium wmctrl openssh-server
# swaybg wlr-randr maliit-keyboard xinput-calibrator (for kiosk + labwc + touch).
PACKAGES="git chromium wmctrl openssh-server swaybg wlr-randr maliit-keyboard xinput-calibrator"
# --- Desktop & theme ---
# LightDM session (rpd-labwc for Wayland + labwc).
LIGHTDM_SESSION="rpd-labwc"
# GTK dark theme name (e.g. PiXnoir; fallback is Adwaita-dark if missing).
GTK_THEME_NAME="PiXnoir"
# Wallpaper mode for pcmanfm (crop, stretch, fit, center, tile, screen, color).
WALLPAPER_MODE="crop"
# --- Display (reTerminal DM) ---
# Kernel cmdline: DSI rotation. 90 = 90° clockwise; use 180 or 270 for other orientations.
DSI_ROTATE="270"
# Kernel cmdline: swiotlb size (for vc4-drm/DSI). Leave empty to skip.
SWIOTLB_SIZE="65536"
# --- reTerminal (Seeed) ---
# Device passed to reTerminal.sh (reTerminal-DM for reTerminal DM).
RETERMINAL_DEVICE="reTerminal-DM"
# Seeed overlays repo (clone URL). Leave empty to skip driver install.
RETERMINAL_REPO_URL="https://github.com/Seeed-Studio/seeed-linux-dtoverlays"
# --- One-shots ---
# Space-separated names of one-shot scripts (numbered = run order at first login). Leave empty for none.
ONESHOT_SCRIPTS="01-set-rotation-once 02-set-wallpaper-once"

View File

@@ -57,5 +57,12 @@
# --- One-shots ---
# Space-separated names of one-shot scripts to install from FILE_SERVER (each name gets name.sh + name.desktop).
# Example: "set-rotation-once set-wallpaper-once". Leave empty for none.
# ONESHOT_SCRIPTS=""
# Use numbered names so they run in order at first login. Leave empty for none.
# Example: "01-set-rotation-once 02-set-wallpaper-once"
# ONESHOT_SCRIPTS="01-set-rotation-once 02-set-wallpaper-once"
# --- Step enable flags (1 = run, 0 = skip). Default: all 1. Set in config to disable a step. ---
# 01=hostname, 02=packages, 03=kiosk_files, 04=splash_wallpaper, 05=lightdm, 06=maliit,
# 07=dark_theme, 08=reterminal_drivers, 09=reapply_splash, 10=cmdline, 11=oneshots, 12=log_permissions, 13=reboot
# ENABLE_STEP_08=0
# ENABLE_STEP_13=0

View File

@@ -16,7 +16,9 @@ Only set variables you want to change; the rest use built-in defaults. See `firs
---
## Structure (sections)
## Structure (steps and enable flags)
The script runs **13 numbered steps** (`step_01_hostname``step_13_reboot`). Disable a step by setting `ENABLE_STEP_NN=0` in config (default: all `1`). Step table: 01=hostname, 02=packages, 03=kiosk_files, 04=splash_wallpaper, 05=lightdm, 06=maliit, 07=dark_theme, 08=reterminal_drivers, 09=reapply_splash, 10=cmdline, 11=oneshots, 12=log_permissions, 13=reboot.
1. **Config & constants** — Load optional `first-boot.conf`; then `FILE_SERVER`, `PI_USER`, paths, log file (defaults or from config).
2. **Logging** — All output teed to `/var/log/first-boot.log`.
@@ -29,8 +31,6 @@ Only set variables you want to change; the rest use built-in defaults. See `firs
9. **reTerminal DM drivers** — Seeed repo clone and `reTerminal.sh`.
10. **Re-apply splash** — Set `disable_splash=0`, Plymouth theme to `custom` only, `update-initramfs`.
11. **Dark theme** — Set GTK dark theme for user `pi`: `~/.config/gtk-3.0/settings.ini` with `gtk-application-prefer-dark-theme=1` and `gtk-theme-name=PiXnoir` (Raspberry Pi OS dark theme).
12. **One-shots** — Download `set-rotation-once.sh` + `.desktop` from file server (wlr-randr for labwc). Wallpaper is set once via pcmanfm config during first-boot.
13. **Reboot.**
---

View File

@@ -3,6 +3,7 @@
# Run by cloud-init (user-data-remote-gnss.example). Run as root.
# Optional: copy first-boot.conf.example to first-boot.conf and edit; it is loaded
# from the script dir, /tmp/first-boot.conf, or /etc/cm4-provisioning/first-boot.conf.
# Set ENABLE_STEP_01=0 .. ENABLE_STEP_13=0 in config to disable a step (default: all enabled).
set -e
export DEBIAN_FRONTEND=noninteractive
@@ -24,6 +25,20 @@ SWIOTLB_SIZE="${SWIOTLB_SIZE:-65536}"
RETERMINAL_DEVICE="${RETERMINAL_DEVICE:-reTerminal-DM}"
RETERMINAL_REPO_URL="${RETERMINAL_REPO_URL:-https://github.com/Seeed-Studio/seeed-linux-dtoverlays}"
ONESHOT_SCRIPTS="${ONESHOT_SCRIPTS:-}"
# Step enable flags (1 = run, 0 = skip). Default all enabled.
ENABLE_STEP_01="${ENABLE_STEP_01:-1}"
ENABLE_STEP_02="${ENABLE_STEP_02:-1}"
ENABLE_STEP_03="${ENABLE_STEP_03:-1}"
ENABLE_STEP_04="${ENABLE_STEP_04:-1}"
ENABLE_STEP_05="${ENABLE_STEP_05:-1}"
ENABLE_STEP_06="${ENABLE_STEP_06:-1}"
ENABLE_STEP_07="${ENABLE_STEP_07:-1}"
ENABLE_STEP_08="${ENABLE_STEP_08:-1}"
ENABLE_STEP_09="${ENABLE_STEP_09:-1}"
ENABLE_STEP_10="${ENABLE_STEP_10:-1}"
ENABLE_STEP_11="${ENABLE_STEP_11:-1}"
ENABLE_STEP_12="${ENABLE_STEP_12:-1}"
ENABLE_STEP_13="${ENABLE_STEP_13:-1}"
# --- Load config file (first found) ---
FIRST_BOOT_CONF=""
@@ -40,6 +55,13 @@ done
PI_HOME="/home/$PI_USER"
AUTOSTART="$PI_HOME/.config/autostart"
# Portal base URL for first-boot status API (derived from FILE_SERVER, e.g. http://10.20.50.1:5000)
if [[ "$FILE_SERVER" =~ ^(https?://[^/]+) ]]; then
PORTAL_BASE="${BASH_REMATCH[1]}"
else
PORTAL_BASE=""
fi
# --- Logging ---
log() { echo "[$(date -Iseconds)] $*"; }
exec > >(tee -a "$LOGFILE") 2>&1
@@ -47,22 +69,31 @@ log "=== first-boot.sh started ==="
[[ -n "$FIRST_BOOT_CONF" ]] && log "Config loaded from $FIRST_BOOT_CONF" || log "Using built-in defaults (no config file found)"
log "FILE_SERVER=$FILE_SERVER PI_USER=$PI_USER HOSTNAME=$HOSTNAME 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"
# --- Report status to portal (no-op if PORTAL_BASE empty; failures ignored) ---
report_status() {
local phase="$1" message="$2" step="$3" step_name="$4" ip="$5"
[[ -z "$PORTAL_BASE" ]] && return 0
local json="{\"phase\":\"${phase}\",\"message\":\"${message}\",\"step\":\"${step}\",\"step_name\":\"${step_name}\",\"hostname\":\"${HOSTNAME}\",\"ip\":\"${ip}\"}"
curl -s -X POST -H "Content-Type: application/json" -d "$json" "${PORTAL_BASE}/api/first-boot-status" || true
}
report_status "started" "First-boot started" "" "" ""
# --- Helpers ---
# Download script + .desktop from FILE_SERVER and install as one-shot autostart (runs once at pi's first login, then deletes itself).
# --- Helper: run step if enabled ---
run_step() {
local n="$1" name="$2" enable_var="ENABLE_STEP_${n}"
if [[ "${!enable_var}" == "1" ]]; then
log "--- Step $n: $name ---"
"step_${n}_${name}" || return $?
report_status "running" "Step $n: $name completed" "$n" "$name" ""
else
log "--- Step $n: $name (disabled) ---"
fi
}
# --- Helper: install one-shot from FILE_SERVER ---
install_oneshot() {
local name="$1"
log "--- Installing one-shot: $name ---"
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
@@ -78,170 +109,205 @@ install_oneshot() {
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: $PACKAGES"
apt-get install -y -qq $PACKAGES
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"
# --- Step 01: Hostname and /etc/hosts ---
step_01_hostname() {
echo "$HOSTNAME" > /etc/hostname
hostnamectl set-hostname "$HOSTNAME" 2>/dev/null || true
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
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=$WALLPAPER_MODE" 'wallpaper_common=1' > "$PCMANFM_DESKTOP"
log "Hostname set to $HOSTNAME; /etc/hosts updated"
}
# --- Step 02: Packages ---
step_02_packages() {
log "Running apt-get update ..."
apt-get update -qq
log "Installing: $PACKAGES"
apt-get install -y -qq $PACKAGES
log "Packages installed successfully"
}
# --- Step 03: Kiosk files from file server ---
step_03_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"
}
# --- Step 04: Boot splash and wallpaper ---
step_04_splash_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
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=$WALLPAPER_MODE/" "$PCMANFM_DESKTOP" || echo "wallpaper_mode=$WALLPAPER_MODE" >> "$PCMANFM_DESKTOP"
log "WARNING: Could not download custom.plymouth/custom.script; boot splash theme may be incomplete"
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
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"
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=$WALLPAPER_MODE" '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=$WALLPAPER_MODE/" "$PCMANFM_DESKTOP" || echo "wallpaper_mode=$WALLPAPER_MODE" >> "$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=$LIGHTDM_SESSION/" /etc/lightdm/lightdm.conf
sed -i "s/^autologin-session=.*/autologin-session=$LIGHTDM_SESSION/" /etc/lightdm/lightdm.conf
log "Patched /etc/lightdm/lightdm.conf to use $LIGHTDM_SESSION"
fi
# --- Step 05: LightDM session ---
step_05_lightdm() {
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
if [[ -f /etc/lightdm/lightdm.conf ]]; then
sed -i "s/^user-session=.*/user-session=$LIGHTDM_SESSION/" /etc/lightdm/lightdm.conf
sed -i "s/^autologin-session=.*/autologin-session=$LIGHTDM_SESSION/" /etc/lightdm/lightdm.conf
log "Patched /etc/lightdm/lightdm.conf to use $LIGHTDM_SESSION"
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"
# --- Step 06: Maliit on-screen keyboard ---
step_06_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=$GTK_THEME_NAME" > "$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=$GTK_THEME_NAME/" "$GTK_SETTINGS" || echo "gtk-theme-name=$GTK_THEME_NAME" >> "$GTK_SETTINGS"
fi
# Fallback if theme not installed (e.g. older image): Adwaita-dark
log "Set dark theme ($GTK_THEME_NAME) in gtk-3.0/settings.ini"
chown -R "$PI_USER:$PI_USER" "$PI_HOME/.config"
# --- Step 07: Dark theme (GTK) ---
step_07_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=$GTK_THEME_NAME" > "$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=$GTK_THEME_NAME/" "$GTK_SETTINGS" || echo "gtk-theme-name=$GTK_THEME_NAME" >> "$GTK_SETTINGS"
fi
log "Set dark theme ($GTK_THEME_NAME) in gtk-3.0/settings.ini"
chown -R "$PI_USER:$PI_USER" "$PI_HOME/.config"
}
# --- 6. reTerminal DM drivers (Seeed) ---
if [[ -n "$RETERMINAL_REPO_URL" ]]; then
log "--- reTerminal DM drivers ---"
# --- Step 08: reTerminal DM drivers (Seeed) ---
step_08_reterminal_drivers() {
if [[ -z "$RETERMINAL_REPO_URL" ]]; then
log "Skipping reTerminal drivers (RETERMINAL_REPO_URL not set)"
return 0
fi
REPO_DIR="/tmp/seeed-linux-dtoverlays"
log "Cloning seeed-linux-dtoverlays to $REPO_DIR ..."
git clone --depth 1 "$RETERMINAL_REPO_URL" "$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_DEVICE from $REPO_DIR ..."
if ( cd "$REPO_DIR" && "$REPO_DIR/scripts/reTerminal.sh" --device "$RETERMINAL_DEVICE" ); 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_DEVICE"
fi
log "Removing $REPO_DIR"
rm -rf "$REPO_DIR"
else
log "--- Skipping reTerminal drivers (RETERMINAL_REPO_URL not set) ---"
fi
}
# --- 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
# --- Step 09: Re-apply splash and Plymouth theme ---
step_09_reapply_splash() {
CFG_PATH="/boot/firmware/config.txt"
[[ -f /boot/firmware/config.txt ]] || CFG_PATH="/boot/config.txt"
if [[ -f "$CFG_PATH" ]] && 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 [[ -n "$SWIOTLB_SIZE" ]] && ! grep -q 'swiotlb=' "$CMDLINE_PATH"; then
sed -i "s/rootwait/rootwait swiotlb=$SWIOTLB_SIZE/" "$CMDLINE_PATH"
log "Added swiotlb=$SWIOTLB_SIZE to kernel cmdline (vc4-drm / DSI)"
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
# Persistent rotation for DSI-1 (KMS): append at end of single line. 90 = 90° clockwise.
if [[ -n "$DSI_ROTATE" ]] && ! grep -q 'video=DSI-1:rotate=' "$CMDLINE_PATH"; then
sed -i "s/\$/ video=DSI-1:rotate=$DSI_ROTATE/" "$CMDLINE_PATH"
log "Added video=DSI-1:rotate=$DSI_ROTATE to kernel cmdline (DSI rotation)"
log "Running update-initramfs to apply Plymouth theme ..."
update-initramfs -u -k all 2>/dev/null || true
}
# --- Step 10: Kernel cmdline (swiotlb + DSI rotation) ---
step_10_cmdline() {
CMDLINE_PATH="/boot/firmware/cmdline.txt"
[[ -f "$CMDLINE_PATH" ]] || CMDLINE_PATH="/boot/cmdline.txt"
if [[ -f "$CMDLINE_PATH" ]]; then
if [[ -n "$SWIOTLB_SIZE" ]] && ! grep -q 'swiotlb=' "$CMDLINE_PATH"; then
sed -i "s/rootwait/rootwait swiotlb=$SWIOTLB_SIZE/" "$CMDLINE_PATH"
log "Added swiotlb=$SWIOTLB_SIZE to kernel cmdline (vc4-drm / DSI)"
fi
if [[ -n "$DSI_ROTATE" ]] && ! grep -q 'video=DSI-1:rotate=' "$CMDLINE_PATH"; then
sed -i "s/\$/ video=DSI-1:rotate=$DSI_ROTATE/" "$CMDLINE_PATH"
log "Added video=DSI-1:rotate=$DSI_ROTATE to kernel cmdline (DSI rotation)"
fi
fi
fi
}
# --- 7. One-shots (wallpaper already set in pcmanfm config above; rotation is via cmdline.txt) ---
log "--- One-shot scripts (if any) ---"
if [[ -n "$DSI_ROTATE" ]]; then
log "Rotation is set via kernel cmdline (video=DSI-1:rotate=$DSI_ROTATE)"
fi
if [[ -n "$ONESHOT_SCRIPTS" ]]; then
for _name in $ONESHOT_SCRIPTS; do
install_oneshot "$_name" || true
done
else
log "No one-shot scripts configured (ONESHOT_SCRIPTS empty)"
fi
# --- Step 11: One-shot scripts ---
step_11_oneshots() {
if [[ -n "$DSI_ROTATE" ]]; then
log "Rotation is set via kernel cmdline (video=DSI-1:rotate=$DSI_ROTATE)"
fi
if [[ -n "$ONESHOT_SCRIPTS" ]]; then
for _name in $ONESHOT_SCRIPTS; do
install_oneshot "$_name" || true
done
else
log "No one-shot scripts configured (ONESHOT_SCRIPTS empty)"
fi
}
# --- 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"
# --- Step 12: Log file permissions ---
step_12_log_permissions() {
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
# --- Step 13: Reboot ---
step_13_reboot() {
DEVICE_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
report_status "done" "First-boot complete" "13" "reboot" "$DEVICE_IP"
log "Device IP: ${DEVICE_IP:-unknown}"
log "=== first-boot.sh finished, rebooting ==="
reboot
}
# --- Main: run steps in order ---
run_step 01 hostname
run_step 02 packages
run_step 03 kiosk_files
run_step 04 splash_wallpaper
run_step 05 lightdm
run_step 06 maliit
run_step 07 dark_theme
run_step 08 reterminal_drivers
run_step 09 reapply_splash
run_step 10 cmdline
run_step 11 oneshots
run_step 12 log_permissions
run_step 13 reboot