diff --git a/emmc-provisioning/cloud-init/config-files/set-rotation-at-login.desktop b/emmc-provisioning/cloud-init/config-files/set-rotation-at-login.desktop new file mode 100644 index 0000000..607ad51 --- /dev/null +++ b/emmc-provisioning/cloud-init/config-files/set-rotation-at-login.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Type=Application +Name=Set rotation at login +Exec=/home/pi/set-rotation-at-login.sh +X-GNOME-Autostart-enabled=true diff --git a/emmc-provisioning/cloud-init/first-boot.md b/emmc-provisioning/cloud-init/first-boot.md index 6273192..4ea784b 100644 --- a/emmc-provisioning/cloud-init/first-boot.md +++ b/emmc-provisioning/cloud-init/first-boot.md @@ -119,14 +119,13 @@ These changes take effect after a reboot. --- -## Screen rotation (portrait → landscape, “Left”) +## Screen rotation (portrait → landscape, 90° clockwise) -The reTerminal DM default is portrait. Rotation is set using **wlr-randr** (labwc/Wayland), so nothing is written to `/boot/firmware/config.txt`. +The reTerminal DM default is portrait. Rotation is set **persistently** via the kernel command line (KMS driver), so it survives reboots and does not depend on the desktop session. -- **One-shot autostart** — A small script runs once when user `pi` first logs into the graphical session: - - **`/home/pi/set-rotation-once.sh`** — Waits 5 seconds, detects the output name with `wlr-randr`, then runs `wlr-randr --output --transform 270` (Left / 90° counter-clockwise). Falls back to `DSI-1` if detection fails. - - **`/home/pi/.config/autostart/set-rotation-once.desktop`** — Autostart entry that runs the script once at first login. Both the script and this desktop file **delete themselves** after a successful run, so rotation is applied only on first boot and never again. -- **Effect** — Same as choosing “Left” in display settings. Rotation applies after the first login; no reboot needed for rotation (only for the Seeed drivers). +- **Kernel cmdline** — First-boot appends **`video=DSI-1:rotate=90`** to **`/boot/firmware/cmdline.txt`** (or `/boot/cmdline.txt`). The file must remain **one single line** with no line breaks. +- **90° clockwise** — The value `90` gives 90° clockwise rotation. Other valid values: `180`, `270` (90° counter-clockwise). +- **Effect** — Rotation is applied by the kernel at boot; no one-shot script or wlr-randr needed. --- @@ -144,7 +143,7 @@ If **`rpi-eeprom-config`** and **`rpi-eeprom-update`** are present (Pi 4/CM4), t ## Reboot -Runs **`reboot`** so the kernel and display stack load the new Seeed drivers. After reboot, the screen and touch work; on **pi**’s first login the one-shot sets rotation to “Left” (landscape), and the Chromium kiosk and Maliit start via autostart. +Runs **`reboot`** so the kernel and display stack load the new Seeed drivers and the cmdline rotation. After reboot, the screen and touch work with rotation applied (video=DSI-1:rotate=90), and the Chromium kiosk and Maliit start via autostart on **pi**'s login. --- @@ -153,5 +152,5 @@ Runs **`reboot`** so the kernel and display stack load the new Seeed drivers. Af - **File server** — Edit `FILE_SERVER` if your assets are served from another host/port. Host all files listed in **`files-from-guard/README.md`** and **`config-files/README.md`** (kiosk, splash, Plymouth, LightDM, Maliit, one-shots and their .desktop files). - **Kiosk URL** — The URL Chromium opens is defined in `start-chromium.sh` on your file server (e.g. `--app=http://127.0.0.1:8080`); change it there. - **User** — If you use a user other than `pi`, replace `pi` in this script and in the files on the file server (paths and ownership). -- **Screen rotation** — The one-shot runs `wlr-randr --output --transform 270` (Left). To use another orientation, change `270` to `90` (right), `180` (inverted), or `normal`. +- **Screen rotation** — Set in `/boot/firmware/cmdline.txt`: `video=DSI-1:rotate=90` (90° clockwise). Use `180` or `270` for other orientations. Keep the file one single line. - **Desktop wallpaper** — First-boot writes `wallpaper=` and `wallpaper_mode=crop` into pcmanfm’s `desktop-items-0.conf`. To change later: `pcmanfm --set-wallpaper /path/to/image --wallpaper-mode=crop`. **Other pcmanfm options:** `pcmanfm --desktop` (start desktop manager), `pcmanfm --desktop-pref` (open desktop preferences GUI), `pcmanfm --desktop-off` (stop desktop manager), `pcmanfm -w FILE` (short form of --set-wallpaper). Wallpaper modes: `crop`, `stretch`, `fit`, `center`, `tile`, `screen`, `color`. diff --git a/emmc-provisioning/cloud-init/first-boot.sh b/emmc-provisioning/cloud-init/first-boot.sh index f5326d8..88263ec 100644 --- a/emmc-provisioning/cloud-init/first-boot.sh +++ b/emmc-provisioning/cloud-init/first-boot.sh @@ -182,12 +182,19 @@ fi log "Running update-initramfs to apply Plymouth theme ..." update-initramfs -u -k all 2>/dev/null || true -# --- 6b2. Kernel cmdline: swiotlb for vc4-drm (avoids "swiotlb buffer is full" / blank DSI on CM4) --- +# --- 6b2. Kernel cmdline: swiotlb + DSI rotation (KMS, persistent across reboots) --- CMDLINE_PATH="/boot/firmware/cmdline.txt" [[ -f "$CMDLINE_PATH" ]] || CMDLINE_PATH="/boot/cmdline.txt" -if [[ -f "$CMDLINE_PATH" ]] && ! grep -q 'swiotlb=' "$CMDLINE_PATH"; then - sed -i 's/rootwait/rootwait swiotlb=65536/' "$CMDLINE_PATH" - log "Added swiotlb=65536 to kernel cmdline (vc4-drm / DSI)" +if [[ -f "$CMDLINE_PATH" ]]; then + if ! grep -q 'swiotlb=' "$CMDLINE_PATH"; then + sed -i 's/rootwait/rootwait swiotlb=65536/' "$CMDLINE_PATH" + log "Added swiotlb=65536 to kernel cmdline (vc4-drm / DSI)" + fi + # Persistent rotation for DSI-1 (KMS): append at end of single line. 90 = 90° clockwise. + if ! grep -q 'video=DSI-1:rotate=' "$CMDLINE_PATH"; then + sed -i 's/$/ video=DSI-1:rotate=90/' "$CMDLINE_PATH" + log "Added video=DSI-1:rotate=90 to kernel cmdline (DSI rotation)" + fi fi # --- 6c. CM4: enable rpi-eeprom-update so boot order can be set --- @@ -274,10 +281,10 @@ SVCEOF systemctl enable set-cm4-boot-order-once.service 2>/dev/null && log "Enabled set-cm4-boot-order-once.service to set boot order after next boot" fi -# --- 7. One-shots (rotation at first login; wallpaper already set in pcmanfm config above) --- -log "--- One-shot scripts (run at pi first login) ---" -install_oneshot set-rotation-once || true -log "One-shots will append to $LOGFILE when they run at first login" +# --- 7. One-shots (wallpaper already set in pcmanfm config above; rotation is via cmdline.txt) --- +log "--- One-shot scripts (if any) ---" +# Rotation is set persistently in cmdline.txt (video=DSI-1:rotate=90), not via one-shot script. +log "Rotation is set via kernel cmdline (video=DSI-1:rotate=90)" # --- 8. Allow pi to append to first-boot.log (for one-shot scripts) --- chmod 666 "$LOGFILE" diff --git a/emmc-provisioning/cloud-init/fix-reterminal-display.sh b/emmc-provisioning/cloud-init/fix-reterminal-display.sh index 2492c35..21fc928 100644 --- a/emmc-provisioning/cloud-init/fix-reterminal-display.sh +++ b/emmc-provisioning/cloud-init/fix-reterminal-display.sh @@ -26,11 +26,18 @@ if [[ -f /etc/plymouth/plymouthd.conf ]]; then fi update-initramfs -u -k all 2>/dev/null || true -echo "=== Rotation and wallpaper (rpd-labwc / labwc Wayland) ===" -echo "To set rotation and wallpaper now (in a labwc session), run as $PI_USER:" -echo " wlr-randr --output \$(wlr-randr | awk '/^[A-Za-z0-9_-]+ /{print \$1; exit}') --transform 270" +echo "=== Rotation (persistent via kernel cmdline) ===" +CMDLINE_PATH="/boot/firmware/cmdline.txt" +[[ -f "$CMDLINE_PATH" ]] || CMDLINE_PATH="/boot/cmdline.txt" +if [[ -f "$CMDLINE_PATH" ]] && ! grep -q 'video=DSI-1:rotate=' "$CMDLINE_PATH"; then + sed -i 's/$/ video=DSI-1:rotate=90/' "$CMDLINE_PATH" + echo "Added video=DSI-1:rotate=90 to $CMDLINE_PATH (90° clockwise). Reboot to apply." +else + echo "Rotation already set in cmdline or file missing. Current: $(grep -o 'video=DSI-1:rotate=[^ ]*' "$CMDLINE_PATH" 2>/dev/null || true)" +fi +echo "" +echo "=== Wallpaper (in labwc session, as $PI_USER) ===" echo " pcmanfm --set-wallpaper /usr/share/rpd-wallpaper/splash.png --wallpaper-mode=crop" echo "" -echo "Or ensure one-shots run at next login (they are in autostart if still present)." -echo "=== Reboot to apply splash and initramfs ===" +echo "=== Reboot to apply splash, initramfs and rotation ===" echo " sudo reboot" diff --git a/emmc-provisioning/cloud-init/set-rotation-at-login.sh b/emmc-provisioning/cloud-init/set-rotation-at-login.sh new file mode 100644 index 0000000..e422877 --- /dev/null +++ b/emmc-provisioning/cloud-init/set-rotation-at-login.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Set reTerminal DM (labwc/Wayland) rotation to Left at every login. +# Runs from autostart when user pi logs in; does not remove itself. +# Use this when wlr-randr transform does not persist across reboots. +sleep 5 +OUTPUT="" +if command -v wlr-randr &>/dev/null; then + OUTPUT=$(wlr-randr 2>/dev/null | awk '/^[A-Za-z0-9_-]+ /{print $1; exit}') +fi +[[ -z "$OUTPUT" ]] && OUTPUT="DSI-1" +if [[ -n "$OUTPUT" ]] && command -v wlr-randr &>/dev/null; then + wlr-randr --output "$OUTPUT" --transform 270 +fi diff --git a/emmc-provisioning/dashboard/app.py b/emmc-provisioning/dashboard/app.py index a2f1672..81516df 100644 --- a/emmc-provisioning/dashboard/app.py +++ b/emmc-provisioning/dashboard/app.py @@ -20,7 +20,13 @@ from flask import Flask, render_template, jsonify, request, send_file, redirect, from werkzeug.security import generate_password_hash, check_password_hash +try: + from itsdangerous import BadSignature +except ImportError: + BadSignature = Exception # noqa: disable invalid-name + app = Flask(__name__) +# Set CM4_DASHBOARD_SECRET_KEY in env (e.g. via /opt/cm4-provisioning/dashboard.env) so sessions persist across restarts app.secret_key = os.environ.get("CM4_DASHBOARD_SECRET_KEY", os.urandom(24).hex()) # --- Paths --- @@ -90,7 +96,16 @@ def admin_log(action, details=None): def require_admin(f): @wraps(f) def wrapped(*args, **kwargs): - if not session.get("admin_logged_in"): + try: + logged_in = session.get("admin_logged_in") + except (BadSignature, ValueError, OSError): + # Invalid/rotated secret key or corrupted session cookie → treat as not logged in + try: + session.clear() + except Exception: + pass + logged_in = False + if not logged_in: if request.is_json or request.path.startswith("/api/"): return jsonify({"ok": False, "error": "Login required"}), 401 return redirect(url_for("login", next=request.url)) @@ -154,6 +169,26 @@ def no_cache(response): response.headers["Pragma"] = "no-cache" return response + +@app.errorhandler(500) +def handle_500(err): + """Log 500 and return a simple response so the user is not left with a blank error.""" + import traceback + try: + traceback.print_exc() + except Exception: + pass + if request.path.startswith("/api/"): + return jsonify({"ok": False, "error": "Internal server error"}), 500 + return ( + "Error" + "

Something went wrong

Try logging in again or going home.

" + "", + 500, + {"Content-Type": "text/html; charset=utf-8"}, + ) + + # Default cloud-init user-data for Raspberry Pi OS (NoCloud on boot partition) DEFAULT_USER_DATA = """#cloud-config package_update: true