From 79a7f76a12607ffe1e7fc7476c43b7126957bc94 Mon Sep 17 00:00:00 2001 From: nearxos Date: Sat, 21 Feb 2026 22:22:40 +0200 Subject: [PATCH] Enhance first-boot script and documentation with config file support Introduce a configuration file (`first-boot.conf`) to allow users to override default script variables without modifying the script directly. Update the first-boot script to load this config file from multiple locations, enhancing flexibility in customization. Revise documentation to guide users on how to utilize the config file for setting variables like FILE_SERVER, HOSTNAME, and PACKAGES, improving the overall user experience during first boot operations. --- .../cloud-init/first-boot.conf.example | 61 +++++++++ emmc-provisioning/cloud-init/first-boot.md | 14 ++- emmc-provisioning/cloud-init/first-boot.sh | 116 ++++++++++++------ .../cloud-init/user-data-remote-gnss.example | 9 +- 4 files changed, 157 insertions(+), 43 deletions(-) create mode 100644 emmc-provisioning/cloud-init/first-boot.conf.example diff --git a/emmc-provisioning/cloud-init/first-boot.conf.example b/emmc-provisioning/cloud-init/first-boot.conf.example new file mode 100644 index 0000000..088406c --- /dev/null +++ b/emmc-provisioning/cloud-init/first-boot.conf.example @@ -0,0 +1,61 @@ +# 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 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="" diff --git a/emmc-provisioning/cloud-init/first-boot.md b/emmc-provisioning/cloud-init/first-boot.md index a92c336..fd4118c 100644 --- a/emmc-provisioning/cloud-init/first-boot.md +++ b/emmc-provisioning/cloud-init/first-boot.md @@ -4,9 +4,21 @@ This script runs once on first boot via cloud-init (see `user-data-remote-gnss.e --- +## Config file (first-boot.conf) + +You can override script behaviour without editing `first-boot.sh` by using a **config file**. Copy `first-boot.conf.example` to `first-boot.conf`, edit the variables you need, and ensure the script can load it. The script looks for a config file in this order: + +1. **Same directory as the script** — e.g. if you host both at `.../first-boot/first-boot.sh` and `.../first-boot/first-boot.conf`. +2. **`/tmp/first-boot.conf`** — when run via cloud-init, add a runcmd line to download your config to `/tmp/first-boot.conf` before running the script. +3. **`/etc/cm4-provisioning/first-boot.conf`** — for host-side or pre-written config. + +Only set variables you want to change; the rest use built-in defaults. See `first-boot.conf.example` for all options (FILE_SERVER, HOSTNAME, PI_USER, PACKAGES, LIGHTDM_SESSION, GTK_THEME_NAME, WALLPAPER_MODE, DSI_ROTATE, SWIOTLB_SIZE, RETERMINAL_DEVICE, RETERMINAL_REPO_URL, ONESHOT_SCRIPTS, etc.). + +--- + ## Structure (sections) -1. **Constants** — `FILE_SERVER`, `PI_USER`, paths, log file. +1. **Config & constants** — Load optional `first-boot.conf`; then `FILE_SERVER`, `PI_USER`, paths, log file (defaults or from config). 2. **Logging** — All output tee’d to `/var/log/first-boot.log`. 3. **Helpers** — `install_oneshot(name)` downloads `${name}.sh` from the file server and installs it as a one-shot autostart (runs once at pi’s first login, then deletes itself). 4. **Packages** — git, Chromium, wmctrl, SSH, swaybg, wlr-randr, maliit, xinput-calibrator. diff --git a/emmc-provisioning/cloud-init/first-boot.sh b/emmc-provisioning/cloud-init/first-boot.sh index 1eb9fcf..8a6b34c 100644 --- a/emmc-provisioning/cloud-init/first-boot.sh +++ b/emmc-provisioning/cloud-init/first-boot.sh @@ -1,26 +1,51 @@ #!/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. +# 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 -e export DEBIAN_FRONTEND=noninteractive -# --- Constants --- -# All first-boot assets live in portal-files/first-boot/ on the file server. -FILE_SERVER="http://10.20.50.1:5000/files/first-boot" -HOSTNAME="guard" -PI_USER="pi" +# --- Defaults (overridden by first-boot.conf if present) --- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" 2>/dev/null && pwd)" +FILE_SERVER="${FILE_SERVER:-http://10.20.50.1:5000/files/first-boot}" +HOSTNAME="${HOSTNAME:-guard}" +PI_USER="${PI_USER:-pi}" +LOGFILE="${LOGFILE:-/var/log/first-boot.log}" +PLYMOUTH_DIR="${PLYMOUTH_DIR:-/usr/share/plymouth/themes/custom}" +WALLPAPER_PATH="${WALLPAPER_PATH:-/usr/share/rpd-wallpaper/splash.png}" +PACKAGES="${PACKAGES:-git chromium wmctrl openssh-server swaybg wlr-randr maliit-keyboard xinput-calibrator}" +LIGHTDM_SESSION="${LIGHTDM_SESSION:-rpd-labwc}" +GTK_THEME_NAME="${GTK_THEME_NAME:-PiXnoir}" +WALLPAPER_MODE="${WALLPAPER_MODE:-crop}" +DSI_ROTATE="${DSI_ROTATE:-90}" +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:-}" + +# --- Load config file (first found) --- +FIRST_BOOT_CONF="" +for _f in "$SCRIPT_DIR/first-boot.conf" /tmp/first-boot.conf /etc/cm4-provisioning/first-boot.conf; do + if [[ -f "$_f" ]]; then + # shellcheck source=first-boot.conf.example + set -a && source "$_f" && set +a + FIRST_BOOT_CONF="$_f" + break + fi +done + +# --- Derived paths --- 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" +[[ -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 ---" @@ -57,9 +82,8 @@ install_oneshot() { 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" -apt-get install -y -qq git chromium wmctrl openssh-server \ - swaybg wlr-randr maliit-keyboard xinput-calibrator +log "Installing: $PACKAGES" +apt-get install -y -qq $PACKAGES log "Packages installed successfully" # --- 2. Dirs and kiosk files from file server --- @@ -99,10 +123,10 @@ if curl -fsSL "${FILE_SERVER}/splash.png" -o "$PLYMOUTH_DIR/splash.png"; then 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" + 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=crop/' "$PCMANFM_DESKTOP" || echo 'wallpaper_mode=crop' >> "$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 @@ -122,9 +146,9 @@ else 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" + 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) --- @@ -137,29 +161,33 @@ 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" + 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=PiXnoir/' "$GTK_SETTINGS" || echo 'gtk-theme-name=PiXnoir' >> "$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 PiXnoir not installed (e.g. older image): Adwaita-dark -log "Set dark theme (PiXnoir) in gtk-3.0/settings.ini" +# 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" # --- 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)" +if [[ -n "$RETERMINAL_REPO_URL" ]]; then + log "--- reTerminal DM drivers ---" + 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 "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" + log "--- Skipping reTerminal drivers (RETERMINAL_REPO_URL not set) ---" 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 ---" @@ -186,21 +214,29 @@ update-initramfs -u -k all 2>/dev/null || true 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)" + 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 # 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)" + 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 # --- 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)" +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" diff --git a/emmc-provisioning/cloud-init/user-data-remote-gnss.example b/emmc-provisioning/cloud-init/user-data-remote-gnss.example index bfa6332..50a04b9 100644 --- a/emmc-provisioning/cloud-init/user-data-remote-gnss.example +++ b/emmc-provisioning/cloud-init/user-data-remote-gnss.example @@ -6,7 +6,10 @@ # 1. Generate a password hash: mkpasswd -m sha-512 'YourPassword' or openssl passwd -6 'YourPassword' # Paste the full output into the passwd: line below. # 2. Host first-boot.sh (same dir as this repo: cloud-init/first-boot.sh) at FIRST_BOOT_URL. -# 3. To use a different username than "pi", replace every "pi" in this file and in first-boot.sh. +# 3. Optional: copy first-boot.conf.example to first-boot.conf, edit variables, and host it +# as first-boot.conf; then add a runcmd line to download it to /tmp/first-boot.conf before +# running first-boot.sh so the script loads your config. +# 4. To use a different username than "pi", set PI_USER in first-boot.conf and create that user below. package_update: true package_upgrade: false @@ -31,7 +34,9 @@ runcmd: - systemctl enable ssh - systemctl start ssh - curl -fsSL "http://10.20.50.1:5000/files/first-boot.sh" -o /tmp/first-boot.sh + # Optional: download config to override FILE_SERVER, HOSTNAME, PACKAGES, etc. + # - curl -fsSL "http://10.20.50.1:5000/files/first-boot.conf" -o /tmp/first-boot.conf - chmod +x /tmp/first-boot.sh - /tmp/first-boot.sh - # - rm -f /tmp/first-boot.sh + # - rm -f /tmp/first-boot.sh /tmp/first-boot.conf - cloud-init single --name cc_final_message