Files
nearxos 0844adbcbe Update cloud-init scripts and documentation for enhanced DNS management and provisioning steps</message>
<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.
2026-03-06 14:45:23 +02:00

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