Monorepo for Aesthetic.Computer aesthetic.computer
at main 489 lines 17 kB view raw
1#!/bin/bash 2# make-kiosk-usb.sh — Create a FedAC live kiosk USB 3# 4# Boots Fedora 43 straight into Firefox kiosk mode at kidlisp.com. 5# No install, no GRUB menu, no desktop access. Plug in, boot, see kidlisp.com fullscreen. 6# 7# How it works: 8# 1. Flash Fedora Workstation Live ISO to USB 9# 2. Extract squashfs rootfs from live partition 10# 3. Inject kiosk autostart + dconf settings into rootfs 11# 4. Repack squashfs, replace on USB 12# 5. Patch EFI partition: instant-boot GRUB + theme 13# 14# Usage: 15# sudo bash fedac/scripts/make-kiosk-usb.sh /dev/sdX 16# 17# Options: 18# --iso <path> Use a local ISO instead of downloading 19# --no-eject Don't eject when done 20# --yes Skip confirmation prompts 21# 22# Requirements: 23# squashfs-tools (unsquashfs, mksquashfs) 24# ~5GB free temp space 25 26set -euo pipefail 27 28RED='\033[0;31m' 29GREEN='\033[0;32m' 30YELLOW='\033[1;33m' 31CYAN='\033[0;36m' 32PURPLE='\033[0;35m' 33NC='\033[0m' 34 35SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 36FEDAC_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" 37OVERLAY_DIR="$FEDAC_DIR/overlays/kiosk" 38 39# Fedora 43 Workstation 40FEDORA_VERSION="43" 41FEDORA_RELEASE="43-1.6" 42FEDORA_ISO_NAME="Fedora-Workstation-Live-${FEDORA_RELEASE}.x86_64.iso" 43FEDORA_URL="https://dl.fedoraproject.org/pub/fedora/linux/releases/${FEDORA_VERSION}/Workstation/x86_64/iso/${FEDORA_ISO_NAME}" 44FEDORA_SHA256="2a4a16c009244eb5ab2198700eb04103793b62407e8596f30a3e0cc8ac294d77" 45CDLABEL="Fedora-WS-Live-${FEDORA_VERSION}" 46CACHE_DIR="${HOME}/Downloads" 47 48usage() { 49 echo -e "${PURPLE}FedAC Kiosk USB Creator${NC}" 50 echo "" 51 echo "Creates a live-only USB that boots straight into Firefox kiosk at kidlisp.com." 52 echo "" 53 echo "Usage: sudo $0 <device> [options]" 54 echo "" 55 echo " <device> USB block device (e.g., /dev/sdb)" 56 echo "" 57 echo "Options:" 58 echo " --iso <path> Use existing ISO instead of downloading" 59 echo " --no-eject Don't eject the USB when done" 60 echo " --yes Skip confirmation prompts" 61 echo " --help Show this help" 62 exit 1 63} 64 65cleanup() { 66 echo -e "\n${YELLOW}Cleaning up...${NC}" 67 # Unmount anything we mounted 68 [ -n "${LIVE_MOUNT:-}" ] && mountpoint -q "$LIVE_MOUNT" 2>/dev/null && umount "$LIVE_MOUNT" 2>/dev/null || true 69 [ -n "${EFI_MOUNT:-}" ] && mountpoint -q "$EFI_MOUNT" 2>/dev/null && umount "$EFI_MOUNT" 2>/dev/null || true 70 # Remove temp dirs (but not the squashfs work dir — it's huge, leave for manual cleanup on error) 71 [ -n "${LIVE_MOUNT:-}" ] && [ -d "$LIVE_MOUNT" ] && rmdir "$LIVE_MOUNT" 2>/dev/null || true 72 [ -n "${EFI_MOUNT:-}" ] && [ -d "$EFI_MOUNT" ] && rmdir "$EFI_MOUNT" 2>/dev/null || true 73} 74trap cleanup EXIT 75 76# ── Parse args ── 77DEVICE="" 78ISO_PATH="" 79DO_EJECT=true 80SKIP_CONFIRM=false 81 82while [ $# -gt 0 ]; do 83 case "$1" in 84 --iso) ISO_PATH="$2"; shift 2 ;; 85 --no-eject) DO_EJECT=false; shift ;; 86 --yes) SKIP_CONFIRM=true; shift ;; 87 --help|-h) usage ;; 88 /dev/*) DEVICE="$1"; shift ;; 89 *) echo -e "${RED}Unknown arg: $1${NC}"; usage ;; 90 esac 91done 92 93[ -n "$DEVICE" ] || { echo -e "${RED}Error: No device specified${NC}"; usage; } 94[ -b "$DEVICE" ] || { echo -e "${RED}Error: $DEVICE is not a block device${NC}"; exit 1; } 95 96# Must be root 97if [ "$(id -u)" -ne 0 ]; then 98 echo -e "${RED}Error: Must run as root (sudo)${NC}" 99 exit 1 100fi 101 102# Safety: refuse system disks 103case "$DEVICE" in 104 /dev/nvme0n1|/dev/vda|/dev/xvda) 105 echo -e "${RED}REFUSED: $DEVICE is likely your system disk.${NC}" 106 exit 1 107 ;; 108esac 109 110# Check for squashfs-tools 111if ! command -v unsquashfs &>/dev/null || ! command -v mksquashfs &>/dev/null; then 112 echo -e "${RED}Error: squashfs-tools not installed${NC}" 113 echo "Install with: sudo dnf install squashfs-tools" 114 exit 1 115fi 116 117echo -e "${PURPLE}╔══════════════════════════════════════════╗${NC}" 118echo -e "${PURPLE}║ FedAC Kiosk USB Creator ║${NC}" 119echo -e "${PURPLE}║ Live boot → Firefox → kidlisp.com ║${NC}" 120echo -e "${PURPLE}╚══════════════════════════════════════════╝${NC}" 121echo "" 122 123# ══════════════════════════════════════════ 124# Step 1: Get the ISO 125# ══════════════════════════════════════════ 126echo -e "${CYAN}[1/7] Getting Fedora ${FEDORA_VERSION} ISO...${NC}" 127 128if [ -n "$ISO_PATH" ]; then 129 if [ ! -f "$ISO_PATH" ]; then 130 echo -e "${RED}ISO not found: $ISO_PATH${NC}" 131 exit 1 132 fi 133 echo -e " Using: ${GREEN}$ISO_PATH${NC}" 134else 135 ISO_PATH="${CACHE_DIR}/${FEDORA_ISO_NAME}" 136 if [ -f "$ISO_PATH" ]; then 137 echo -e " Found cached: ${GREEN}$ISO_PATH${NC}" 138 else 139 echo -e " Downloading ${FEDORA_ISO_NAME} (~2.6 GB)..." 140 mkdir -p "$CACHE_DIR" 141 curl -L --progress-bar -o "$ISO_PATH" "$FEDORA_URL" 142 echo -e " ${GREEN}Downloaded${NC}" 143 fi 144fi 145 146# ══════════════════════════════════════════ 147# Step 2: Verify checksum 148# ══════════════════════════════════════════ 149echo -e "${CYAN}[2/7] Verifying checksum...${NC}" 150ACTUAL_SHA=$(sha256sum "$ISO_PATH" | cut -d' ' -f1) 151if [ "$ACTUAL_SHA" = "$FEDORA_SHA256" ]; then 152 echo -e " ${GREEN}SHA256 OK${NC}" 153else 154 echo -e " ${YELLOW}WARNING: Checksum mismatch${NC}" 155 echo " Expected: $FEDORA_SHA256" 156 echo " Got: $ACTUAL_SHA" 157 echo " (Continuing — may be a different Fedora build)" 158fi 159 160# ── Confirm ── 161echo "" 162echo -e "Target: ${YELLOW}$DEVICE${NC}" 163lsblk "$DEVICE" 2>/dev/null || true 164echo "" 165 166if [ "$SKIP_CONFIRM" = false ]; then 167 echo -e "${RED}ALL DATA ON $DEVICE WILL BE DESTROYED${NC}" 168 read -p "Continue? [y/N] " confirm 169 if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then 170 echo "Aborted." 171 exit 0 172 fi 173fi 174 175# ══════════════════════════════════════════ 176# Step 3: Flash ISO to USB 177# ══════════════════════════════════════════ 178echo -e "${CYAN}[3/7] Flashing ISO to $DEVICE...${NC}" 179 180# Unmount any mounted partitions 181for part in "${DEVICE}"*; do 182 umount "$part" 2>/dev/null || true 183done 184 185dd if="$ISO_PATH" of="$DEVICE" bs=4M status=progress oflag=sync 2>&1 186sync 187echo -e " ${GREEN}Flash complete${NC}" 188 189# Wait for kernel to re-read partition table 190echo -e " Waiting for partitions..." 191sleep 2 192partprobe "$DEVICE" 2>/dev/null || true 193sleep 2 194 195# ══════════════════════════════════════════ 196# Step 4: Inject kiosk config into squashfs 197# ══════════════════════════════════════════ 198echo -e "${CYAN}[4/7] Injecting kiosk config into squashfs rootfs...${NC}" 199 200# Find and mount the live partition (partition 3 on Fedora live USB — the large ISO9660/ext4) 201LIVE_PART="" 202for part in "${DEVICE}3" "${DEVICE}p3" "${DEVICE}1" "${DEVICE}p1"; do 203 if [ -b "$part" ]; then 204 # Check if this partition has LiveOS/squashfs.img 205 LIVE_MOUNT=$(mktemp -d /tmp/fedac-live-XXXX) 206 if mount -o ro "$part" "$LIVE_MOUNT" 2>/dev/null; then 207 if [ -f "$LIVE_MOUNT/LiveOS/squashfs.img" ]; then 208 LIVE_PART="$part" 209 echo -e " Found squashfs on ${GREEN}$part${NC}" 210 break 211 fi 212 umount "$LIVE_MOUNT" 213 fi 214 rmdir "$LIVE_MOUNT" 2>/dev/null || true 215 LIVE_MOUNT="" 216 fi 217done 218 219if [ -z "$LIVE_PART" ]; then 220 echo -e "${RED}Could not find LiveOS/squashfs.img on any partition${NC}" 221 echo "Partitions on $DEVICE:" 222 lsblk "$DEVICE" 223 exit 1 224fi 225 226# Set up work directory for squashfs manipulation 227WORK_DIR=$(mktemp -d /tmp/fedac-squash-XXXX) 228echo -e " Work dir: $WORK_DIR" 229 230# Copy squashfs.img to work dir (need to modify it) 231echo -e " Copying squashfs.img to work dir..." 232cp "$LIVE_MOUNT/LiveOS/squashfs.img" "$WORK_DIR/squashfs.img" 233 234# Unmount live partition (we'll remount rw later) 235umount "$LIVE_MOUNT" 236 237# Extract squashfs 238echo -e " Extracting squashfs (this takes a minute)..." 239cd "$WORK_DIR" 240unsquashfs -d squashfs-root squashfs.img 241 242# The squashfs contains LiveOS/rootfs.img (an ext4 image) 243ROOTFS_IMG="$WORK_DIR/squashfs-root/LiveOS/rootfs.img" 244if [ ! -f "$ROOTFS_IMG" ]; then 245 echo -e "${RED}No LiveOS/rootfs.img inside squashfs${NC}" 246 ls -la "$WORK_DIR/squashfs-root/" "$WORK_DIR/squashfs-root/LiveOS/" 2>/dev/null || true 247 exit 1 248fi 249 250# Mount rootfs.img (ext4) read-write 251ROOTFS_MOUNT=$(mktemp -d /tmp/fedac-rootfs-XXXX) 252mount -o loop "$ROOTFS_IMG" "$ROOTFS_MOUNT" 253echo -e " ${GREEN}Rootfs mounted${NC}" 254 255# ── Inject kiosk files ── 256 257# 1. Firefox kiosk autostart for liveuser 258AUTOSTART_DIR="$ROOTFS_MOUNT/home/liveuser/.config/autostart" 259mkdir -p "$AUTOSTART_DIR" 260cp "$OVERLAY_DIR/firefox-kiosk.desktop" "$AUTOSTART_DIR/" 261# Set ownership (liveuser is UID 1000 on Fedora live) 262chown -R 1000:1000 "$ROOTFS_MOUNT/home/liveuser/.config" 263echo -e " ${GREEN}Injected firefox-kiosk.desktop${NC}" 264 265# 2. dconf settings (disable idle, screensaver, lock, notifications) 266mkdir -p "$ROOTFS_MOUNT/etc/dconf/db/local.d" 267mkdir -p "$ROOTFS_MOUNT/etc/dconf/profile" 268cp "$OVERLAY_DIR/00-kiosk" "$ROOTFS_MOUNT/etc/dconf/db/local.d/00-kiosk" 269cp "$OVERLAY_DIR/dconf-profile-user" "$ROOTFS_MOUNT/etc/dconf/profile/user" 270echo -e " ${GREEN}Injected dconf kiosk settings${NC}" 271 272# 3. Compile dconf database (run dconf update inside the rootfs via chroot) 273# We need to compile the local dconf db so GNOME picks it up 274if [ -x "$ROOTFS_MOUNT/usr/bin/dconf" ]; then 275 # Use a minimal chroot to run dconf compile 276 chroot "$ROOTFS_MOUNT" /usr/bin/dconf compile /etc/dconf/db/local /etc/dconf/db/local.d 2>/dev/null || true 277 echo -e " ${GREEN}Compiled dconf database${NC}" 278else 279 # Fallback: just place the keyfiles, GNOME will compile at runtime 280 echo -e " ${YELLOW}dconf binary not in rootfs, will compile at boot${NC}" 281fi 282 283# 4. Disable GNOME initial setup (it would block kiosk boot) 284mkdir -p "$ROOTFS_MOUNT/home/liveuser/.config" 285echo "yes" > "$ROOTFS_MOUNT/home/liveuser/.config/gnome-initial-setup-done" 286chown 1000:1000 "$ROOTFS_MOUNT/home/liveuser/.config/gnome-initial-setup-done" 287echo -e " ${GREEN}Disabled GNOME initial setup${NC}" 288 289# 5. Create a systemd service to disable screen blanking at runtime 290cat > "$ROOTFS_MOUNT/etc/systemd/system/disable-blanking.service" << 'SVCEOF' 291[Unit] 292Description=Disable screen blanking for kiosk 293After=graphical.target 294 295[Service] 296Type=oneshot 297ExecStart=/usr/bin/bash -c 'DISPLAY=:0 xset s off; DISPLAY=:0 xset -dpms; DISPLAY=:0 xset s noblank' 298ExecStart=/usr/bin/bash -c 'dbus-send --system --print-reply --dest=org.freedesktop.login1 /org/freedesktop/login1 org.freedesktop.login1.Manager.SetWallMessage string:"" boolean:false 2>/dev/null || true' 299 300[Install] 301WantedBy=graphical.target 302SVCEOF 303 304# Enable it via symlink 305mkdir -p "$ROOTFS_MOUNT/etc/systemd/system/graphical.target.wants" 306ln -sf /etc/systemd/system/disable-blanking.service "$ROOTFS_MOUNT/etc/systemd/system/graphical.target.wants/disable-blanking.service" 307echo -e " ${GREEN}Created disable-blanking service${NC}" 308 309# ── Unmount rootfs and repack squashfs ── 310echo -e " Unmounting rootfs..." 311umount "$ROOTFS_MOUNT" 312rmdir "$ROOTFS_MOUNT" 313 314echo -e " Repacking squashfs (this takes a few minutes)..." 315rm -f "$WORK_DIR/squashfs-new.img" 316mksquashfs "$WORK_DIR/squashfs-root" "$WORK_DIR/squashfs-new.img" -comp xz -b 1M -Xdict-size 100% 317echo -e " ${GREEN}Squashfs repacked${NC}" 318 319# ══════════════════════════════════════════ 320# Step 5: Replace squashfs on USB 321# ══════════════════════════════════════════ 322echo -e "${CYAN}[5/7] Replacing squashfs on USB...${NC}" 323 324# Remount live partition read-write 325LIVE_MOUNT=$(mktemp -d /tmp/fedac-live-XXXX) 326mount "$LIVE_PART" "$LIVE_MOUNT" 327 328# Check space 329NEW_SIZE=$(stat -c%s "$WORK_DIR/squashfs-new.img") 330OLD_SIZE=$(stat -c%s "$LIVE_MOUNT/LiveOS/squashfs.img") 331echo -e " Old squashfs: $(numfmt --to=iec $OLD_SIZE)" 332echo -e " New squashfs: $(numfmt --to=iec $NEW_SIZE)" 333 334# Replace 335cp "$WORK_DIR/squashfs-new.img" "$LIVE_MOUNT/LiveOS/squashfs.img" 336sync 337echo -e " ${GREEN}Squashfs replaced on USB${NC}" 338 339umount "$LIVE_MOUNT" 340rmdir "$LIVE_MOUNT" 341LIVE_MOUNT="" 342 343# Clean up work dir 344echo -e " Cleaning up work dir..." 345rm -rf "$WORK_DIR" 346 347# ══════════════════════════════════════════ 348# Step 6: Patch EFI partition 349# ══════════════════════════════════════════ 350echo -e "${CYAN}[6/7] Patching EFI partition...${NC}" 351 352EFI_PART="" 353for part in "${DEVICE}2" "${DEVICE}p2"; do 354 if [ -b "$part" ]; then 355 EFI_PART="$part" 356 break 357 fi 358done 359 360if [ -z "$EFI_PART" ]; then 361 echo -e "${YELLOW}Could not find EFI partition — skipping GRUB patch${NC}" 362else 363 EFI_MOUNT=$(mktemp -d /tmp/fedac-efi-XXXX) 364 mount "$EFI_PART" "$EFI_MOUNT" 365 366 # Install GRUB theme 367 THEME_SRC="$FEDAC_DIR/grub-theme" 368 THEME_DST="$EFI_MOUNT/EFI/BOOT/fedac-theme" 369 mkdir -p "$THEME_DST" 370 371 for asset in theme.txt background.png DejaVuSansBold36.pf2 DejaVuSans18.pf2 DejaVuSans10.pf2 select_c.png menu_c.png; do 372 if [ -f "$THEME_SRC/$asset" ]; then 373 cp "$THEME_SRC/$asset" "$THEME_DST/" 374 fi 375 done 376 377 # Generate theme assets if missing 378 NEED_GENERATE=false 379 for asset in background.png DejaVuSansBold36.pf2 DejaVuSans18.pf2 DejaVuSans10.pf2; do 380 [ ! -f "$THEME_DST/$asset" ] && NEED_GENERATE=true 381 done 382 383 if [ "$NEED_GENERATE" = true ] && [ -x "$THEME_SRC/prepare-theme.sh" ]; then 384 echo -e " Generating theme assets..." 385 bash "$THEME_SRC/prepare-theme.sh" 2>/dev/null || true 386 for asset in background.png DejaVuSansBold36.pf2 DejaVuSans18.pf2 DejaVuSans10.pf2 select_c.png menu_c.png; do 387 [ -f "$THEME_SRC/$asset" ] && cp "$THEME_SRC/$asset" "$THEME_DST/" 388 done 389 fi 390 echo -e " ${GREEN}Theme installed${NC}" 391 392 # Write kiosk GRUB config — instant boot, no menu choices 393 GRUB_CFG="" 394 for candidate in \ 395 "$EFI_MOUNT/EFI/BOOT/grub.cfg" \ 396 "$EFI_MOUNT/EFI/fedora/grub.cfg" \ 397 "$EFI_MOUNT/boot/grub2/grub.cfg"; do 398 if [ -f "$candidate" ]; then 399 GRUB_CFG="$candidate" 400 break 401 fi 402 done 403 404 if [ -n "$GRUB_CFG" ]; then 405 cp "$GRUB_CFG" "${GRUB_CFG}.orig" 406 407 cat > "$GRUB_CFG" << 'GRUBEOF' 408# FedAC Kiosk — Live boot to Firefox fullscreen 409# Auto-generated by fedac/scripts/make-kiosk-usb.sh 410 411set default=0 412set timeout=0 413 414# ── Graphics modules ── 415if [ "$grub_platform" == "efi" ]; then 416 insmod efi_gop 417 insmod efi_uga 418fi 419insmod all_video 420insmod gzio 421insmod part_gpt 422insmod ext2 423insmod png 424insmod font 425 426# ── Save EFI partition root before search changes it ── 427set efi_root=$root 428 429# ── Load fonts from EFI partition ── 430loadfont ($efi_root)/EFI/BOOT/fedac-theme/DejaVuSansBold36.pf2 431loadfont ($efi_root)/EFI/BOOT/fedac-theme/DejaVuSans18.pf2 432loadfont ($efi_root)/EFI/BOOT/fedac-theme/DejaVuSans10.pf2 433 434# ── Switch to graphical terminal ── 435set gfxmode=auto 436set gfxpayload=keep 437terminal_input console 438terminal_output gfxterm 439 440# ── Load theme from EFI partition ── 441if [ -f ($efi_root)/EFI/BOOT/fedac-theme/theme.txt ]; then 442 set theme=($efi_root)/EFI/BOOT/fedac-theme/theme.txt 443else 444 set color_normal=magenta/black 445 set color_highlight=white/magenta 446fi 447 448# ── Find Fedora live partition (changes $root) ── 449search --file --set=root /boot/0x4da30161 450 451menuentry "FedAC Kiosk" --class fedora { 452 linux ($root)/boot/x86_64/loader/linux quiet rhgb root=live:CDLABEL=FEDAC_CDLABEL rd.live.image 453 initrd ($root)/boot/x86_64/loader/initrd 454} 455GRUBEOF 456 457 sed -i "s/FEDAC_CDLABEL/${CDLABEL}/g" "$GRUB_CFG" 458 echo -e " ${GREEN}GRUB patched — instant kiosk boot (0s timeout)${NC}" 459 else 460 echo -e " ${YELLOW}No grub.cfg found on EFI — skipping${NC}" 461 fi 462 463 sync 464 umount "$EFI_MOUNT" 465 rmdir "$EFI_MOUNT" 466 EFI_MOUNT="" 467fi 468 469# ══════════════════════════════════════════ 470# Step 7: Finalize 471# ══════════════════════════════════════════ 472echo -e "${CYAN}[7/7] Finalizing...${NC}" 473sync 474 475if [ "$DO_EJECT" = true ]; then 476 eject "$DEVICE" 2>/dev/null || true 477 echo -e " ${GREEN}Ejected${NC}" 478fi 479 480echo "" 481echo -e "${GREEN}╔══════════════════════════════════════════╗${NC}" 482echo -e "${GREEN}║ FedAC Kiosk USB Ready ║${NC}" 483echo -e "${GREEN}╚══════════════════════════════════════════╝${NC}" 484echo "" 485echo -e "Boot flow:" 486echo -e " Power on → GRUB (instant) → Fedora live → GNOME auto-login" 487echo -e "${PURPLE}Firefox fullscreen → kidlisp.com${NC}" 488echo "" 489echo "Plug into target machine → boot from USB → done."