Monorepo for Aesthetic.Computer
aesthetic.computer
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."