<message>Delete obsolete one-shot scripts for setting screen rotation and wallpaper, as well as related Python and shell scripts. Update the first-boot configuration to streamline the provisioning process by removing references to these scripts. This cleanup enhances maintainability and focuses on the essential steps required for the first boot experience, ensuring a more efficient setup for users.
179 lines
7.3 KiB
Bash
179 lines
7.3 KiB
Bash
#!/bin/bash
|
|
# Revision: 4
|
|
# First-boot provisioning for reTerminal DM — two-phase process.
|
|
# Phase 1 (cloud-init): hostname, packages, reTerminal DM drivers → reboot
|
|
# Phase 2 (systemd oneshot): kiosk, theme, LightDM, rotation → reboot
|
|
#
|
|
# Step implementations are downloaded from FILE_SERVER/steps/ and sourced.
|
|
# All fileserver assets live in cloud-init/fileserver/ in the repo.
|
|
#
|
|
# REQUIRES first-boot.conf — looked up in order:
|
|
# 1. <script-dir>/first-boot.conf
|
|
# 2. /tmp/first-boot.conf (cloud-init runcmd downloads it)
|
|
# 3. /etc/cm4-provisioning/first-boot.conf
|
|
# 4. /var/lib/cm4-provisioning/first-boot.conf (persisted for phase 2)
|
|
# Copy first-boot.conf.example → first-boot.conf and fill in all values.
|
|
# Disable steps: ENABLE_STEP_01=0 .. ENABLE_STEP_13=0
|
|
|
|
set -e
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
|
|
# ── Phase tracking ──────────────────────────────────────────────────────
|
|
STATE_DIR="/var/lib/cm4-provisioning"
|
|
PHASE_FILE="$STATE_DIR/first-boot-phase"
|
|
PERSISTENT_SCRIPT="$STATE_DIR/first-boot.sh"
|
|
PERSISTENT_CONF="$STATE_DIR/first-boot.conf"
|
|
PHASE2_SERVICE="cm4-first-boot-phase2"
|
|
STEPS_DIR="/tmp/first-boot-steps"
|
|
LOGFILE="/var/log/first-boot.log"
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" 2>/dev/null && pwd)"
|
|
|
|
# ── Load config (required) ──────────────────────────────────────────────
|
|
FIRST_BOOT_CONF=""
|
|
for _f in "$SCRIPT_DIR/first-boot.conf" /tmp/first-boot.conf \
|
|
/etc/cm4-provisioning/first-boot.conf "$PERSISTENT_CONF"; do
|
|
if [[ -f "$_f" ]]; then
|
|
set -a && source "$_f" && set +a
|
|
FIRST_BOOT_CONF="$_f"
|
|
break
|
|
fi
|
|
done
|
|
if [[ -z "$FIRST_BOOT_CONF" ]]; then
|
|
echo "[$(date -Iseconds)] ERROR: first-boot.conf not found. Searched:" >&2
|
|
echo " $SCRIPT_DIR/first-boot.conf" >&2
|
|
echo " /tmp/first-boot.conf" >&2
|
|
echo " /etc/cm4-provisioning/first-boot.conf" >&2
|
|
echo " $PERSISTENT_CONF" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Step enable flags (default: all enabled)
|
|
for _n in 01 02 03 04 05 06 07 08 09 10 11 12 13; do
|
|
eval "ENABLE_STEP_${_n}=\"\${ENABLE_STEP_${_n}:-1}\""
|
|
done
|
|
|
|
# ── Derived ─────────────────────────────────────────────────────────────
|
|
PI_HOME="/home/$PI_USER"
|
|
AUTOSTART="$PI_HOME/.config/autostart"
|
|
[[ "$FILE_SERVER" =~ ^(https?://[^/]+) ]] && PORTAL_BASE="${BASH_REMATCH[1]}" || PORTAL_BASE=""
|
|
[[ -f "$PHASE_FILE" ]] && CURRENT_PHASE="$(cat "$PHASE_FILE")" || CURRENT_PHASE="1"
|
|
|
|
# ── Logging ─────────────────────────────────────────────────────────────
|
|
log() { echo "[$(date -Iseconds)] $*"; }
|
|
exec > >(tee -a "$LOGFILE") 2>&1
|
|
log "=== first-boot.sh phase $CURRENT_PHASE ==="
|
|
log "Config: $FIRST_BOOT_CONF"
|
|
log "FILE_SERVER=$FILE_SERVER PI_USER=$PI_USER HOSTNAME=$HOSTNAME"
|
|
|
|
# ── Portal status API ──────────────────────────────────────────────────
|
|
report_status() {
|
|
[[ -z "$PORTAL_BASE" ]] && return 0
|
|
curl -s -X POST -H "Content-Type: application/json" \
|
|
-d "{\"phase\":\"$1\",\"message\":\"$2\",\"step\":\"$3\",\"step_name\":\"$4\",\"hostname\":\"$HOSTNAME\",\"ip\":\"$5\"}" \
|
|
"${PORTAL_BASE}/api/first-boot-status" || true
|
|
}
|
|
report_status "started" "Phase $CURRENT_PHASE started" "" "" ""
|
|
|
|
# ── Helpers (available to step scripts) ─────────────────────────────────
|
|
install_oneshot() {
|
|
local name="$1"
|
|
curl -fsSL "${FILE_SERVER}/${name}.sh" -o "$PI_HOME/${name}.sh" || { log "WARNING: ${name}.sh download failed"; return 1; }
|
|
curl -fsSL "${FILE_SERVER}/${name}.desktop" -o "$AUTOSTART/${name}.desktop" || { log "WARNING: ${name}.desktop download failed"; return 1; }
|
|
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"
|
|
}
|
|
|
|
# ── Download, source, and run a step ────────────────────────────────────
|
|
run_step() {
|
|
local n="$1" name="$2"
|
|
local val; eval "val=\"\${ENABLE_STEP_${n}}\""
|
|
val="${val//[^01]/}"
|
|
if [[ "${val:0:1}" != "1" ]]; then
|
|
log "--- Step $n: $name (disabled) ---"
|
|
return 0
|
|
fi
|
|
log "--- Step $n: $name ---"
|
|
local f="$STEPS_DIR/${n}-${name}.sh"
|
|
curl -fsSL "${FILE_SERVER}/steps/${n}-${name}.sh" -o "$f" \
|
|
|| { log "ERROR: could not download step ${n}-${name}.sh"; return 1; }
|
|
source "$f"
|
|
"step_${n}_${name}"
|
|
report_status "running" "Step $n: $name done" "$n" "$name" ""
|
|
}
|
|
|
|
# ── Phase transitions ──────────────────────────────────────────────────
|
|
phase1_reboot() {
|
|
mkdir -p "$STATE_DIR"
|
|
cp "$(readlink -f "${BASH_SOURCE[0]:-.}")" "$PERSISTENT_SCRIPT"
|
|
chmod 755 "$PERSISTENT_SCRIPT"
|
|
[[ -n "$FIRST_BOOT_CONF" && -f "$FIRST_BOOT_CONF" && "$FIRST_BOOT_CONF" != "$PERSISTENT_CONF" ]] \
|
|
&& cp "$FIRST_BOOT_CONF" "$PERSISTENT_CONF"
|
|
echo "2" > "$PHASE_FILE"
|
|
|
|
cat > "/etc/systemd/system/${PHASE2_SERVICE}.service" <<PHASE2SVC
|
|
[Unit]
|
|
Description=reTerminal DM first-boot phase 2
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=$PERSISTENT_SCRIPT
|
|
StandardOutput=journal+console
|
|
TimeoutStartSec=600
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
PHASE2SVC
|
|
systemctl daemon-reload
|
|
systemctl enable "${PHASE2_SERVICE}.service"
|
|
|
|
local ip; ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
|
report_status "phase1-done" "Phase 1 done, rebooting" "03" "reterminal_drivers" "$ip"
|
|
log "=== Phase 1 done — rebooting to activate drivers ==="
|
|
reboot
|
|
}
|
|
|
|
phase2_cleanup() {
|
|
systemctl disable "${PHASE2_SERVICE}.service" 2>/dev/null || true
|
|
rm -f "/etc/systemd/system/${PHASE2_SERVICE}.service"
|
|
systemctl daemon-reload
|
|
rm -f "$PHASE_FILE" "$PERSISTENT_SCRIPT" "$PERSISTENT_CONF"
|
|
log "Phase 2 cleanup done"
|
|
}
|
|
|
|
# ── Main ────────────────────────────────────────────────────────────────
|
|
mkdir -p "$STEPS_DIR"
|
|
|
|
if [[ "$CURRENT_PHASE" == "1" ]]; then
|
|
log "=== PHASE 1: hostname, packages, reTerminal drivers ==="
|
|
run_step 01 hostname
|
|
run_step 02 packages
|
|
run_step 03 reterminal_drivers
|
|
phase1_reboot
|
|
|
|
elif [[ "$CURRENT_PHASE" == "2" ]]; then
|
|
log "=== PHASE 2: kiosk, theme, display config ==="
|
|
run_step 04 kiosk_files
|
|
run_step 05 splash_wallpaper
|
|
run_step 06 lightdm
|
|
run_step 07 maliit
|
|
run_step 08 dark_theme
|
|
run_step 09 reapply_splash
|
|
run_step 10 cmdline
|
|
run_step 11 oneshots
|
|
run_step 12 log_permissions
|
|
phase2_cleanup
|
|
run_step 13 reboot
|
|
|
|
# Only reached if step 13 (reboot) is disabled
|
|
DEVICE_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
|
report_status "done" "First-boot complete (reboot disabled)" "13" "reboot" "$DEVICE_IP"
|
|
log "=== first-boot.sh finished (reboot disabled) ==="
|
|
|
|
else
|
|
log "ERROR: Unknown phase '$CURRENT_PHASE'"; exit 1
|
|
fi
|