<message>Modify the first-boot.sh script to include an additional step for managing screen brightness during the provisioning process. Update user-data.bootstrap to improve DNS configuration by ensuring NetworkManager manages /etc/resolv.conf correctly, and remove obsolete scripts related to systemd-resolved. Enhance documentation to reflect these changes and clarify the setup process for users, improving overall network boot functionality and user experience.
180 lines
7.4 KiB
Bash
180 lines
7.4 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 14; 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 --connect-timeout 5 --max-time 10 -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
|
|
run_step 14 screen_brightness
|
|
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
|