<message>Modify the first-boot configuration to include the gir1.2-gtklayershell-0.1 package for improved GTK layer shell support. Update the first-boot script to enhance the portal status reporting with connection timeouts. Additionally, implement a restart mechanism for the kanshi service in rotation scripts to ensure immediate application of configuration changes. Introduce a Chromium kiosk extension to disable text selection, improving user experience in kiosk mode. These changes streamline the setup process and enhance the overall functionality of the kiosk environment.
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 --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
|
|
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
|