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