Remove deprecated one-shot scripts and update first-boot configuration for improved provisioning</message>

<message>Delete obsolete one-shot scripts for setting screen rotation and wallpaper, as well as related Python and shell scripts. Update the first-boot configuration to streamline the provisioning process by removing references to these scripts. This cleanup enhances maintainability and focuses on the essential steps required for the first boot experience, ensuring a more efficient setup for users.
This commit is contained in:
nearxos
2026-02-23 16:15:47 +02:00
parent 2d6e5aa009
commit 25bf710c67
51 changed files with 650 additions and 1192 deletions

View File

@@ -0,0 +1,5 @@
[Desktop Entry]
Type=Application
Name=01 Set rotation once
Exec=/home/pi/01-set-rotation-once.sh
X-GNOME-Autostart-enabled=true

View File

@@ -0,0 +1,53 @@
#!/bin/bash
# Revision: 2
# One-shot: set screen rotation via kanshi (same as Control Center), then remove self.
# Reads video=DSI-1:rotate=N from kernel cmdline and writes ~/.config/kanshi/config.
# Runs once as user pi at first login; deletes its autostart and this script so it never runs again.
# Logs to /var/log/first-boot.log.
FIRST_BOOT_LOG="/var/log/first-boot.log"
BASE="$(basename "$0" .sh)"
log() { echo "[$(date -Iseconds)] [$BASE] $*" >> "$FIRST_BOOT_LOG" 2>/dev/null || true; }
ROTATE="270"
for f in /boot/firmware/cmdline.txt /boot/cmdline.txt; do
if [[ -f "$f" ]]; then
val=$(grep -o 'video=DSI-1:rotate=[0-9]*' "$f" 2>/dev/null | head -1)
val="${val#*rotate=}"
if [[ "$val" =~ ^(90|180|270)$ ]]; then ROTATE="$val"; break; fi
fi
done
log "writing kanshi config with transform $ROTATE (from cmdline)"
KANSHI_DIR="$HOME/.config/kanshi"
KANSHI_CONFIG="$KANSHI_DIR/config"
mkdir -p "$KANSHI_DIR"
cat > "$KANSHI_CONFIG" << EOF
profile {
output DSI-1 enable scale 1.000000 mode 800x1280@60.000 position 0,0 transform $ROTATE
}
EOF
log "kanshi config written to $KANSHI_CONFIG"
# Set GTK dark theme (same as first-boot step 08) and force dark mode via gsettings
GTK_THEME_NAME="PiXnoir"
[[ -d /usr/share/themes/Adwaita-dark ]] && ! [[ -d /usr/share/themes/PiXnoir ]] && GTK_THEME_NAME="Adwaita-dark"
GTK_SETTINGS="$HOME/.config/gtk-3.0/settings.ini"
mkdir -p "$(dirname "$GTK_SETTINGS")"
if [[ ! -f "$GTK_SETTINGS" ]]; then
printf '%s\n' '[Settings]' 'gtk-application-prefer-dark-theme=1' "gtk-theme-name=$GTK_THEME_NAME" > "$GTK_SETTINGS"
else
grep -q '^gtk-application-prefer-dark-theme=' "$GTK_SETTINGS" && sed -i 's/^gtk-application-prefer-dark-theme=.*/gtk-application-prefer-dark-theme=1/' "$GTK_SETTINGS" || echo 'gtk-application-prefer-dark-theme=1' >> "$GTK_SETTINGS"
grep -q '^gtk-theme-name=' "$GTK_SETTINGS" && sed -i "s/^gtk-theme-name=.*/gtk-theme-name=$GTK_THEME_NAME/" "$GTK_SETTINGS" || echo "gtk-theme-name=$GTK_THEME_NAME" >> "$GTK_SETTINGS"
fi
if [[ -d /usr/share/icons/PiXtrix ]]; then
grep -q '^gtk-icon-theme-name=' "$GTK_SETTINGS" && sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=PiXtrix/' "$GTK_SETTINGS" || echo 'gtk-icon-theme-name=PiXtrix' >> "$GTK_SETTINGS"
fi
if command -v gsettings >/dev/null 2>&1; then
gsettings set org.gnome.desktop.interface gtk-theme "$GTK_THEME_NAME" 2>/dev/null || true
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' 2>/dev/null || true
fi
log "Set dark theme ($GTK_THEME_NAME) in gtk-3.0/settings.ini"
log "removing one-shot desktop and script"
rm -f "$HOME/.config/autostart/${BASE}.desktop" "$HOME/${BASE}.sh"
log "finished"

View File

@@ -0,0 +1,5 @@
[Desktop Entry]
Type=Application
Name=02 Set wallpaper once
Exec=/home/pi/02-set-wallpaper-once.sh
X-GNOME-Autostart-enabled=true

View File

@@ -0,0 +1,38 @@
#!/bin/bash
# Revision: 2
# One-shot: set desktop wallpaper for labwc (swaybg) and persist via labwc autostart, then remove self.
# Runs as user pi at first login. Logs to /var/log/first-boot.log.
FIRST_BOOT_LOG="/var/log/first-boot.log"
BASE="$(basename "$0" .sh)"
log() { echo "[$(date -Iseconds)] [$BASE] $*" >> "$FIRST_BOOT_LOG" 2>/dev/null || true; }
WALLPAPER="/usr/share/rpd-wallpaper/splash.png"
LABWC_AUTOSTART="$HOME/.config/labwc/autostart"
log "started (labwc/swaybg)"
log "waiting 8s for compositor ..."
sleep 8
if [[ ! -f "$WALLPAPER" ]]; then
log "WARNING: wallpaper not found $WALLPAPER"
else
mkdir -p "$(dirname "$LABWC_AUTOSTART")"
if [[ ! -f "$LABWC_AUTOSTART" ]]; then
echo '#!/bin/sh' > "$LABWC_AUTOSTART"
chmod +x "$LABWC_AUTOSTART"
fi
if ! grep -q 'swaybg.*splash.png' "$LABWC_AUTOSTART" 2>/dev/null; then
echo "swaybg -i $WALLPAPER -m fill &" >> "$LABWC_AUTOSTART"
log "added swaybg to labwc autostart"
fi
if command -v swaybg &>/dev/null; then
log "applying wallpaper now: $WALLPAPER"
swaybg -i "$WALLPAPER" -m fill &
else
log "WARNING: swaybg not installed"
fi
fi
log "removing one-shot desktop and script"
rm -f "$HOME/.config/autostart/${BASE}.desktop" "$HOME/${BASE}.sh"
log "finished"

View File

@@ -0,0 +1,3 @@
[Seat:*]
user-session=rpd-labwc
autologin-session=rpd-labwc

View File

@@ -0,0 +1,3 @@
[greeter]
wallpaper=/usr/share/rpd-wallpaper/splash.png
wallpaper_mode=crop

View File

@@ -0,0 +1,35 @@
# fileserver/
All files in this directory are synced to the portal's file server and served
at `http://<portal>:5000/files/first-boot/`. The `first-boot.sh` script
downloads them during provisioning via the `FILE_SERVER` variable.
Sync to the LXC with:
```bash
../scripts/sync-portal-files-to-lxc.sh root@<lxc-ip>
```
## Layout
| Path | Purpose |
|------|---------|
| `steps/01-hostname.sh``steps/13-reboot.sh` | Step scripts sourced by `first-boot.sh` |
| `start-chromium.sh` | Chromium kiosk launcher |
| `five-tap-close-chromium.py` | 5-tap overlay to close Chromium |
| `chromium-kiosk.desktop` | Autostart for Chromium kiosk |
| `chromium-kiosk-launcher.desktop` | Desktop icon to restart Chromium |
| `five-tap-close-chromium.desktop` | Autostart for the 5-tap overlay |
| `set-rotation-at-login.sh` / `.desktop` | Per-login kanshi rotation from cmdline |
| `01-set-rotation-once.sh` / `.desktop` | One-shot: first-login rotation + dark theme |
| `02-set-wallpaper-once.sh` / `.desktop` | One-shot: wallpaper via swaybg |
| `fix-reterminal-display.sh` | Utility: re-apply DSI driver fix |
| `99-default-session.conf` | LightDM → rpd-labwc session |
| `99-wallpaper.conf` | LightDM greeter wallpaper |
| `maliit-keyboard.desktop` | Maliit on-screen keyboard autostart |
| `gtk.css` | GTK3 CSS override (dark menus/popovers) |
| `wf-panel-pi.ini` | Dark taskbar config |
| `panel-theme.css` | Dark taskbar CSS |
| `custom.plymouth` / `custom.script` | Plymouth boot splash theme |
| `splash.png` | Boot splash + wallpaper image (binary) |
| `start-here.png` | Taskbar start button icon (binary, optional) |

View File

@@ -0,0 +1,9 @@
[Desktop Entry]
Type=Application
Name=Start Chromium Kiosk
Comment=Start Chromium in kiosk mode (use if the browser was closed)
Exec=/home/pi/start-chromium.sh
Icon=chromium-browser
Terminal=false
Categories=Utility;Kiosk;
StartupNotify=false

View File

@@ -0,0 +1,7 @@
[Desktop Entry]
Type=Application
Name=Chromium Fullscreen
Exec=/home/pi/start-chromium.sh
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true

View File

@@ -0,0 +1,8 @@
[Plymouth Theme]
Name=Custom Splash
Description=Custom boot splash screen
ModuleName=script
[script]
ImageDir=/usr/share/plymouth/themes/custom
ScriptFile=/usr/share/plymouth/themes/custom/custom.script

View File

@@ -0,0 +1,40 @@
screen_width = Window.GetWidth();
screen_height = Window.GetHeight();
theme_image = Image("splash.png");
image_width = theme_image.GetWidth();
image_height = theme_image.GetHeight();
scale_x = image_width / screen_width;
scale_y = image_height / screen_height;
if (scale_x > 1 || scale_y > 1)
{
if (scale_x > scale_y)
{
resized_image = theme_image.Scale(screen_width, image_height / scale_x);
image_x = 0;
image_y = (screen_height - ((image_height * screen_width) / image_width)) / 2;
}
else
{
resized_image = theme_image.Scale(image_width / scale_y, screen_height);
image_x = (screen_width - ((image_width * screen_height) / image_height)) / 2;
image_y = 0;
}
}
else
{
resized_image = theme_image.Scale(image_width, image_height);
image_x = (screen_width - image_width) / 2;
image_y = (screen_height - image_height) / 2;
}
if (Plymouth.GetMode() != "shutdown")
{
sprite = Sprite(resized_image);
sprite.SetPosition(image_x, image_y, -100);
}
fun message_callback(text) {
}

View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=Five-tap close Chromium
Comment=5 taps in top-right corner close Chromium kiosk (hidden at startup)
Exec=/home/pi/five-tap-close-chromium.py
Hidden=false
NoDisplay=true
X-GNOME-Autostart-enabled=true

View File

@@ -0,0 +1,106 @@
#!/usr/bin/env python3
# 5 taps in the top-right corner of the screen close Chromium (kiosk).
# Run from session autostart. Requires: python3, PyGObject (Gtk), Wayland or X11.
import logging
import subprocess
import sys
LOG_TAG = "five-tap"
logging.basicConfig(
stream=sys.stderr,
level=logging.DEBUG,
format=f"{LOG_TAG}: %(message)s",
)
log = logging.getLogger(LOG_TAG)
try:
import gi
gi.require_version("Gdk", "3.0")
gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, Gtk, GLib
except Exception as e:
log.error("need PyGObject Gtk: %s", e)
sys.exit(1)
CORNER_SIZE = 80
TAP_WINDOW_SEC = 2.0
CHROMIUM_KILL_CMD = ["pkill", "-f", "chromium"]
def get_screen_size():
display = Gdk.Display.get_default()
if not display:
log.warning("no display found, using fallback 800x1280")
return 800, 1280
monitor = display.get_monitor(0) if display.get_n_monitors() else None
if monitor:
geom = monitor.get_geometry()
log.info("screen size: %dx%d", geom.width, geom.height)
return geom.width, geom.height
log.warning("no monitor found, using fallback 800x1280")
return 800, 1280
def on_button_press(widget, event, data):
count, reset_timer = data
count[0] += 1
log.debug("tap %d of 5 at (%.0f, %.0f)", count[0], event.x_root, event.y_root)
if reset_timer[0]:
GLib.source_remove(reset_timer[0])
if count[0] >= 5:
count[0] = 0
log.info("5 taps reached — killing chromium")
try:
result = subprocess.run(CHROMIUM_KILL_CMD, timeout=2, capture_output=True)
log.info("pkill returncode=%d", result.returncode)
except Exception as e:
log.error("pkill failed: %s", e)
if reset_timer[0]:
GLib.source_remove(reset_timer[0])
reset_timer[0] = None
return
def reset():
log.debug("tap count reset (%.1fs timeout)", TAP_WINDOW_SEC)
count[0] = 0
reset_timer[0] = None
return False
reset_timer[0] = GLib.timeout_add(int(TAP_WINDOW_SEC * 1000), reset)
return False
def main():
win = Gtk.Window()
win.set_decorated(False)
win.set_resizable(False)
win.set_default_size(CORNER_SIZE, CORNER_SIZE)
win.set_skip_taskbar_hint(True)
win.set_skip_pager_hint(True)
win.set_keep_above(True)
win.set_opacity(0.01)
win.set_accept_focus(False)
win.set_focus_on_map(False)
# Allow closing from compositor / taskbar
win.connect("destroy", Gtk.main_quit)
# Count 5 taps
count = [0]
reset_timer = [None]
win.connect("button-press-event", on_button_press, (count, reset_timer))
# Touch events often come as button-press with button=1
win.set_events(
win.get_events()
| Gdk.EventMask.BUTTON_PRESS_MASK
| Gdk.EventMask.TOUCH_MASK
)
win.realize()
w, h = get_screen_size()
x = max(0, w - CORNER_SIZE)
win.move(x, 0)
log.info("window %dx%d positioned at x=%d y=0", CORNER_SIZE, CORNER_SIZE, x)
win.show_all()
log.info("listening for taps (5 within %.1fs to kill chromium)", TAP_WINDOW_SEC)
Gtk.main()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,49 @@
/* User GTK3 override: force start menu and all menus/popovers to full dark.
* Colors match Adwaita-dark (menu/popover #2d2d2d, text #e0e0e0).
* Placed in ~/.config/gtk-3.0/gtk.css.
*/
/* Adwaita-dark menu/popover background */
menu,
.menu,
menubar,
.menubar,
popover,
.popover,
popover.menu {
background-color: #2d2d2d !important;
background: #2d2d2d !important;
color: #e0e0e0 !important;
}
/* Inner content (often the cause of light background) */
menu contents,
.menu contents,
popover contents,
popover list,
popover flowbox,
popover box {
background-color: #2d2d2d !important;
background: #2d2d2d !important;
color: #e0e0e0 !important;
}
menu menuitem,
.menu menuitem,
menubar menuitem,
popover modelbutton,
popover menuitem {
background-color: transparent !important;
background: transparent !important;
color: #e0e0e0 !important;
}
menu menuitem:hover,
.menu menuitem:hover,
menubar menuitem:hover,
popover modelbutton:hover,
popover menuitem:hover {
background-color: rgba(255, 255, 255, 0.1) !important;
background: rgba(255, 255, 255, 0.1) !important;
color: #ffffff !important;
}

View File

@@ -0,0 +1,5 @@
[Desktop Entry]
Type=Application
Name=Maliit Keyboard
Exec=maliit-keyboard -r
X-GNOME-Autostart-enabled=true

View File

@@ -0,0 +1,42 @@
/* Minimal dark taskbar flat, touch-friendly
* Colors match Adwaita-dark (window bg #242424) so taskbar fits dark mode.
* Icon theme PiXtrix (start-here) set in gtk-3.0/settings.ini.
*/
window {
background-color: transparent;
}
/* Flat bar: Adwaita-dark window bg so theme is consistent */
window box {
background-color: #242424;
border-radius: 0;
margin: 0;
padding: 4px 8px;
border: none;
box-shadow: none;
}
/* Touch-friendly buttons: min 32px, clear hit area */
button {
background: transparent;
border: none;
border-radius: 4px;
padding: 6px;
min-width: 32px;
min-height: 32px;
color: #e0e0e0;
}
button:hover {
background: rgba(255, 255, 255, 0.08);
}
button:active {
background: rgba(255, 255, 255, 0.12);
}
label {
color: #e0e0e0;
font-size: 11pt;
}

View File

@@ -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

View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Revision: 2
# Set screen rotation via kanshi (same as Control Center). Reads video=DSI-1:rotate=N
# from kernel cmdline and writes ~/.config/kanshi/config. Kanshi auto-reloads when the file changes.
# Also sets GTK dark theme (PiXnoir / Adwaita-dark). Runs from autostart when user pi logs in; does not remove itself.
ROTATE="270"
for f in /boot/firmware/cmdline.txt /boot/cmdline.txt; do
[[ -f "$f" ]] || continue
val=$(grep -o 'video=DSI-1:rotate=[0-9]*' "$f" 2>/dev/null | head -1)
val="${val#*rotate=}"
[[ "$val" =~ ^(90|180|270)$ ]] && ROTATE="$val" && break
done
KANSHI_DIR="$HOME/.config/kanshi"
KANSHI_CONFIG="$KANSHI_DIR/config"
mkdir -p "$KANSHI_DIR"
cat > "$KANSHI_CONFIG" << EOF
profile {
output DSI-1 enable scale 1.000000 mode 800x1280@60.000 position 0,0 transform $ROTATE
}
EOF
# Set GTK dark theme (same as first-boot step 08) and force dark mode via gsettings
GTK_THEME_NAME="PiXnoir"
[[ -d /usr/share/themes/Adwaita-dark ]] && ! [[ -d /usr/share/themes/PiXnoir ]] && GTK_THEME_NAME="Adwaita-dark"
GTK_SETTINGS="$HOME/.config/gtk-3.0/settings.ini"
mkdir -p "$(dirname "$GTK_SETTINGS")"
if [[ ! -f "$GTK_SETTINGS" ]]; then
printf '%s\n' '[Settings]' 'gtk-application-prefer-dark-theme=1' "gtk-theme-name=$GTK_THEME_NAME" > "$GTK_SETTINGS"
else
grep -q '^gtk-application-prefer-dark-theme=' "$GTK_SETTINGS" && sed -i 's/^gtk-application-prefer-dark-theme=.*/gtk-application-prefer-dark-theme=1/' "$GTK_SETTINGS" || echo 'gtk-application-prefer-dark-theme=1' >> "$GTK_SETTINGS"
grep -q '^gtk-theme-name=' "$GTK_SETTINGS" && sed -i "s/^gtk-theme-name=.*/gtk-theme-name=$GTK_THEME_NAME/" "$GTK_SETTINGS" || echo "gtk-theme-name=$GTK_THEME_NAME" >> "$GTK_SETTINGS"
fi
# Force dark mode system-wide (GNOME/GTK apps that read gsettings)
if command -v gsettings >/dev/null 2>&1; then
gsettings set org.gnome.desktop.interface gtk-theme "$GTK_THEME_NAME" 2>/dev/null || true
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark' 2>/dev/null || true
fi
# Preserve icon theme (PiXtrix = custom start-here)
if [[ -d /usr/share/icons/PiXtrix ]]; then
grep -q '^gtk-icon-theme-name=' "$GTK_SETTINGS" && sed -i 's/^gtk-icon-theme-name=.*/gtk-icon-theme-name=PiXtrix/' "$GTK_SETTINGS" || echo 'gtk-icon-theme-name=PiXtrix' >> "$GTK_SETTINGS"
fi

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@@ -0,0 +1,43 @@
#!/bin/bash
# Revision: 2
# Start Chromium in app mode. Optional env vars:
# CHROMIUM_APP_URL - URL to open (default: http://127.0.0.1:8080)
# CHROMIUM_MODE - "kiosk" (default, fills whole screen) or "fullscreen"
#
# Touch long-press → right-click: In Chromium on Wayland, long-press often does *not*
# open the context menu (Chromium handles touch itself). Elsewhere (e.g. desktop) it may
# work. For long-press right-click *inside* Chromium, use evdev-right-click-emulation
# at the input layer so all apps (including Chromium) receive real right-click events:
# https://github.com/PeterCxy/evdev-right-click-emulation
#
# Disable keyring prompts
export GNOME_KEYRING_CONTROL=""
# Wayland + labwc compositor.
export GDK_BACKEND=wayland
# Wait for compositor
for i in {1..60}; do
if [ -S "${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/$WAYLAND_DISPLAY" ]; then
pgrep -x labwc >/dev/null 2>&1 && break
fi
sleep 0.5
done
# URL to open in Chromium (app mode)
#CHROMIUM_APP_URL="${CHROMIUM_APP_URL:-http://127.0.0.1:8080}"
CHROMIUM_APP_URL="${CHROMIUM_APP_URL:-https://tototheo.com}"
# Mode: "kiosk" (fills whole screen, no taskbar gap) or "fullscreen"
CHROMIUM_MODE="${CHROMIUM_MODE:-kiosk}"
CHROMIUM_OPTS="--noerrdialogs --disable-infobars --disable-session-crashed-bubble --disable-restore-session-state --no-first-run --password-store=basic --use-mock-keychain --ozone-platform=wayland --enable-features=WaylandWindowDecorations --disable-features=UseChromeOSDirectVideoDecoder --app=${CHROMIUM_APP_URL}"
case "${CHROMIUM_MODE}" in
kiosk) CHROMIUM_OPTS="--kiosk ${CHROMIUM_OPTS}" ;;
*) CHROMIUM_OPTS="--start-fullscreen ${CHROMIUM_OPTS}" ;;
esac
sleep 3
/usr/bin/chromium $CHROMIUM_OPTS &
# Keep script running
wait

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,12 @@
#!/bin/bash
# Step 01: Set hostname and /etc/hosts
step_01_hostname() {
echo "$HOSTNAME" > /etc/hostname
hostnamectl set-hostname "$HOSTNAME" 2>/dev/null || true
if ! grep -q "127.0.1.1[[:space:]]*$HOSTNAME" /etc/hosts 2>/dev/null; then
sed -i "/127.0.1.1[[:space:]].*$/d" /etc/hosts
echo "127.0.1.1 $HOSTNAME" >> /etc/hosts
fi
log "Hostname set to $HOSTNAME"
}

View File

@@ -0,0 +1,10 @@
#!/bin/bash
# Step 02: Install packages
step_02_packages() {
log "Running apt-get update ..."
apt-get update -qq
log "Installing: $PACKAGES"
apt-get install -y -qq $PACKAGES
log "Packages installed"
}

View File

@@ -0,0 +1,19 @@
#!/bin/bash
# Step 03: Install reTerminal DM drivers (Seeed device-tree overlays)
step_03_reterminal_drivers() {
if [[ -z "$RETERMINAL_REPO_URL" ]]; then
log "Skipping reTerminal drivers (RETERMINAL_REPO_URL not set)"
return 0
fi
local repo="/tmp/seeed-linux-dtoverlays"
log "Cloning seeed-linux-dtoverlays ..."
git clone --depth 1 "$RETERMINAL_REPO_URL" "$repo"
log "Running reTerminal.sh --device $RETERMINAL_DEVICE ..."
if ( cd "$repo" && "$repo/scripts/reTerminal.sh" --device "$RETERMINAL_DEVICE" ); then
log "reTerminal DM drivers installed (reboot will apply)"
else
log "WARNING: reTerminal.sh failed; display/touch may still work"
fi
rm -rf "$repo"
}

View File

@@ -0,0 +1,36 @@
#!/bin/bash
# Step 04: Download kiosk scripts and launcher files from file server
step_04_kiosk_files() {
mkdir -p "$AUTOSTART" "$PI_HOME/Desktop" "$PI_HOME/.local/share/applications"
curl -fsSL "${FILE_SERVER}/start-chromium.sh" -o "$PI_HOME/start-chromium.sh"
curl -fsSL "${FILE_SERVER}/chromium-kiosk.desktop" -o "$AUTOSTART/chromium-kiosk.desktop"
chmod 755 "$PI_HOME/start-chromium.sh"
chmod 644 "$AUTOSTART/chromium-kiosk.desktop"
chown "$PI_USER:$PI_USER" "$PI_HOME/start-chromium.sh" "$AUTOSTART/chromium-kiosk.desktop"
# Desktop launcher icon
if curl -fsSL "${FILE_SERVER}/chromium-kiosk-launcher.desktop" -o /tmp/chromium-kiosk-launcher.desktop 2>/dev/null; then
sed "s|/home/pi|$PI_HOME|g" /tmp/chromium-kiosk-launcher.desktop > "$PI_HOME/Desktop/Chromium Kiosk.desktop"
sed "s|/home/pi|$PI_HOME|g" /tmp/chromium-kiosk-launcher.desktop > "$PI_HOME/.local/share/applications/chromium-kiosk-launcher.desktop"
chmod 755 "$PI_HOME/Desktop/Chromium Kiosk.desktop"
chmod 644 "$PI_HOME/.local/share/applications/chromium-kiosk-launcher.desktop"
chown "$PI_USER:$PI_USER" "$PI_HOME/Desktop/Chromium Kiosk.desktop" "$PI_HOME/.local/share/applications/chromium-kiosk-launcher.desktop"
rm -f /tmp/chromium-kiosk-launcher.desktop
log "Chromium kiosk launcher installed"
fi
# 5-tap close Chromium overlay
if curl -fsSL "${FILE_SERVER}/five-tap-close-chromium.py" -o "$PI_HOME/five-tap-close-chromium.py" 2>/dev/null; then
chmod 755 "$PI_HOME/five-tap-close-chromium.py"
if curl -fsSL "${FILE_SERVER}/five-tap-close-chromium.desktop" -o "$AUTOSTART/five-tap-close-chromium.desktop" 2>/dev/null; then
sed -i "s|/home/pi|$PI_HOME|g" "$AUTOSTART/five-tap-close-chromium.desktop"
chmod 644 "$AUTOSTART/five-tap-close-chromium.desktop"
chown "$PI_USER:$PI_USER" "$PI_HOME/five-tap-close-chromium.py" "$AUTOSTART/five-tap-close-chromium.desktop"
log "5-tap close Chromium installed"
fi
fi
log "Kiosk files installed"
}

View File

@@ -0,0 +1,63 @@
#!/bin/bash
# Step 05: Boot splash (Plymouth) and desktop wallpaper
step_05_splash_wallpaper() {
mkdir -p "$PLYMOUTH_DIR" /usr/share/rpd-wallpaper
if ! curl -fsSL "${FILE_SERVER}/splash.png" -o "$PLYMOUTH_DIR/splash.png"; then
log "WARNING: Could not download splash.png"; return 0
fi
cp "$PLYMOUTH_DIR/splash.png" "$WALLPAPER_PATH"
chmod 644 "$PLYMOUTH_DIR/splash.png" "$WALLPAPER_PATH"
# Plymouth theme
if curl -fsSL "${FILE_SERVER}/custom.plymouth" -o "$PLYMOUTH_DIR/custom.plymouth" \
&& curl -fsSL "${FILE_SERVER}/custom.script" -o "$PLYMOUTH_DIR/custom.script"; then
chmod 644 "$PLYMOUTH_DIR/custom.plymouth" "$PLYMOUTH_DIR/custom.script"
log "Plymouth theme installed"
fi
grep -q '^Theme=custom' /etc/plymouth/plymouthd.conf 2>/dev/null \
|| printf '%s\n' '[Daemon]' 'Theme=custom' >> /etc/plymouth/plymouthd.conf
log "Running update-initramfs ..."
update-initramfs -u -k all 2>/dev/null || true
# LightDM wallpaper
mkdir -p /etc/lightdm/lightdm.conf.d
curl -fsSL "${FILE_SERVER}/99-wallpaper.conf" -o /etc/lightdm/lightdm.conf.d/99-wallpaper.conf 2>/dev/null || true
# Desktop wallpaper (pcmanfm)
for PROFILE in LXDE-pi default; do
local conf="$PI_HOME/.config/pcmanfm/$PROFILE/desktop-items-0.conf"
mkdir -p "$(dirname "$conf")"
if [[ ! -f "$conf" ]]; then
printf '%s\n' '[*]' "wallpaper=$WALLPAPER_PATH" "wallpaper_mode=$WALLPAPER_MODE" 'wallpaper_common=1' 'show_trash=0' > "$conf"
else
grep -q '^wallpaper=' "$conf" && sed -i "s|^wallpaper=.*|wallpaper=$WALLPAPER_PATH|" "$conf" || echo "wallpaper=$WALLPAPER_PATH" >> "$conf"
grep -q '^wallpaper_mode=' "$conf" && sed -i "s/^wallpaper_mode=.*/wallpaper_mode=$WALLPAPER_MODE/" "$conf" || echo "wallpaper_mode=$WALLPAPER_MODE" >> "$conf"
grep -q '^show_trash=' "$conf" && sed -i 's/^show_trash=.*/show_trash=0/' "$conf" || echo 'show_trash=0' >> "$conf"
fi
chown -R "$PI_USER:$PI_USER" "$(dirname "$conf")"
done
# Taskbar start-here icon
if curl -fsSL "${FILE_SERVER}/start-here.png" -o /tmp/start-here.png 2>/dev/null; then
local icons="/usr/share/icons/PiXtrix"
if [[ -d "$icons/32x32/places" ]]; then
for s in 16 24 32 48 64 96; do
local dest="$icons/${s}x${s}/places/start-here.png"
[[ -d "$(dirname "$dest")" ]] || continue
rm -f "$dest"
if command -v convert >/dev/null 2>&1; then
convert /tmp/start-here.png -resize "${s}x${s}" "$dest" 2>/dev/null || cp /tmp/start-here.png "$dest"
else
cp /tmp/start-here.png "$dest"
fi
chmod 644 "$dest"
done
log "Taskbar start-here icon installed"
fi
rm -f /tmp/start-here.png
fi
log "Splash and wallpaper configured"
}

View File

@@ -0,0 +1,39 @@
#!/bin/bash
# Step 06: LightDM session config + DSI panel wait service
step_06_lightdm() {
mkdir -p /etc/lightdm/lightdm.conf.d
curl -fsSL "${FILE_SERVER}/99-default-session.conf" \
-o /etc/lightdm/lightdm.conf.d/99-default-session.conf 2>/dev/null || true
if [[ -f /etc/lightdm/lightdm.conf ]]; then
sed -i "s/^user-session=.*/user-session=$LIGHTDM_SESSION/" /etc/lightdm/lightdm.conf
sed -i "s/^autologin-session=.*/autologin-session=$LIGHTDM_SESSION/" /etc/lightdm/lightdm.conf
log "LightDM session set to $LIGHTDM_SESSION"
fi
# Wait for DSI panel to report "connected" before LightDM starts (max 30s)
mkdir -p /etc/systemd/system/lightdm.service.d
cat > /etc/systemd/system/cm4-await-display.service << 'AWAITSVC'
[Unit]
Description=Wait for reTerminal DM DSI panel before LightDM
Before=lightdm.service
DefaultDependencies=no
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'echo "Waiting for DSI panel..."; for i in $(seq 1 30); do for f in /sys/class/drm/card*-DSI-1/status; do [ -f "$f" ] && [ "$(cat "$f" 2>/dev/null)" = "connected" ] && echo "DSI connected." && exit 0; done; sleep 1; done; echo "DSI timeout (30s), starting anyway."'
TimeoutStartSec=35
[Install]
WantedBy=graphical.target
AWAITSVC
printf '%s\n' '[Unit]' 'After=cm4-await-display.service' \
> /etc/systemd/system/lightdm.service.d/99-await-display.conf
systemctl daemon-reload
systemctl enable cm4-await-display.service 2>/dev/null || true
log "cm4-await-display.service installed (wait for DSI, max 30s)"
}

View File

@@ -0,0 +1,10 @@
#!/bin/bash
# Step 07: Maliit on-screen keyboard autostart
step_07_maliit() {
mkdir -p "$AUTOSTART"
curl -fsSL "${FILE_SERVER}/maliit-keyboard.desktop" \
-o "$AUTOSTART/maliit-keyboard.desktop" 2>/dev/null \
&& log "maliit-keyboard.desktop installed" \
|| log "WARNING: Could not download maliit-keyboard.desktop"
}

View File

@@ -0,0 +1,48 @@
#!/bin/bash
# Step 08: Dark theme (GTK3 settings, GTK CSS, taskbar theme)
step_08_dark_theme() {
local theme_name="$GTK_THEME_NAME"
[[ ! -d "/usr/share/themes/$theme_name" ]] && [[ -d /usr/share/themes/Adwaita-dark ]] \
&& theme_name="Adwaita-dark" && log "Using Adwaita-dark (PiXnoir not found)"
local icon_theme=""
[[ -d /usr/share/icons/PiXtrix ]] && icon_theme="PiXtrix"
# GTK3 settings
local gtk_ini="$PI_HOME/.config/gtk-3.0/settings.ini"
mkdir -p "$(dirname "$gtk_ini")"
if [[ ! -f "$gtk_ini" ]]; then
{ printf '%s\n' '[Settings]' 'gtk-application-prefer-dark-theme=1' "gtk-theme-name=$theme_name"
[[ -n "$icon_theme" ]] && echo "gtk-icon-theme-name=$icon_theme"
} > "$gtk_ini"
else
_set_ini() { grep -q "^$1=" "$gtk_ini" && sed -i "s/^$1=.*/$1=$2/" "$gtk_ini" || echo "$1=$2" >> "$gtk_ini"; }
_set_ini gtk-application-prefer-dark-theme 1
_set_ini gtk-theme-name "$theme_name"
[[ -n "$icon_theme" ]] && _set_ini gtk-icon-theme-name "$icon_theme"
fi
log "GTK theme: $theme_name" && [[ -n "$icon_theme" ]] && log "Icon theme: $icon_theme"
# GTK4/Libadwaita dark preference
if [[ -f "$PI_HOME/.profile" ]] && ! grep -q 'ADW_DEBUG_COLOR_SCHEME' "$PI_HOME/.profile" 2>/dev/null; then
printf '\n%s\n' 'export ADW_DEBUG_COLOR_SCHEME=prefer-dark' >> "$PI_HOME/.profile"
chown "$PI_USER:$PI_USER" "$PI_HOME/.profile"
log "Added prefer-dark to .profile"
fi
# Custom GTK CSS (dark menus/popovers)
curl -fsSL "${FILE_SERVER}/gtk.css" -o "$PI_HOME/.config/gtk-3.0/gtk.css" 2>/dev/null \
&& log "gtk.css installed" || true
# Taskbar theme
local panel_dir="$PI_HOME/.config/wf-panel-pi"
mkdir -p "$panel_dir"
if curl -fsSL "${FILE_SERVER}/wf-panel-pi.ini" -o "$panel_dir/wf-panel-pi.ini" 2>/dev/null; then
sed -i "s|/home/pi|$PI_HOME|g" "$panel_dir/wf-panel-pi.ini"
curl -fsSL "${FILE_SERVER}/panel-theme.css" -o "$panel_dir/panel-theme.css" 2>/dev/null || true
log "Taskbar theme installed"
fi
chown -R "$PI_USER:$PI_USER" "$PI_HOME/.config"
}

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# Step 09: Re-apply Plymouth splash theme after driver install
step_09_reapply_splash() {
local cfg="/boot/firmware/config.txt"
[[ -f "$cfg" ]] || cfg="/boot/config.txt"
if [[ -f "$cfg" ]] && grep -q '^disable_splash=1' "$cfg"; then
sed -i 's/^disable_splash=1$/disable_splash=0/' "$cfg"
log "Set disable_splash=0"
fi
if [[ -f /etc/plymouth/plymouthd.conf ]]; then
sed -i '/^Theme=/d; /^\[Daemon\]$/d' /etc/plymouth/plymouthd.conf
grep -q '^\[Daemon\]' /etc/plymouth/plymouthd.conf || echo '[Daemon]' >> /etc/plymouth/plymouthd.conf
echo 'Theme=custom' >> /etc/plymouth/plymouthd.conf
fi
log "Running update-initramfs ..."
update-initramfs -u -k all 2>/dev/null || true
}

View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Step 10: Kernel command line (swiotlb + DSI rotation)
step_10_cmdline() {
local cmdline="/boot/firmware/cmdline.txt"
[[ -f "$cmdline" ]] || cmdline="/boot/cmdline.txt"
[[ -f "$cmdline" ]] || { log "WARNING: cmdline.txt not found"; return 0; }
if [[ -n "$SWIOTLB_SIZE" ]] && ! grep -q 'swiotlb=' "$cmdline"; then
sed -i "s/rootwait/rootwait swiotlb=$SWIOTLB_SIZE/" "$cmdline"
log "Added swiotlb=$SWIOTLB_SIZE to cmdline"
fi
if [[ -n "$DSI_ROTATE" ]] && ! grep -q 'video=DSI-1:rotate=' "$cmdline"; then
sed -i "s/\$/ video=DSI-1:rotate=$DSI_ROTATE/" "$cmdline"
log "Added video=DSI-1:rotate=$DSI_ROTATE to cmdline"
fi
}

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# Step 11: Install one-shot and per-login scripts
step_11_oneshots() {
# Rotation: install set-rotation-at-login (re-applies kanshi config every login)
if [[ -n "$DSI_ROTATE" ]]; then
log "Rotation $DSI_ROTATE set via cmdline"
if curl -fsSL "${FILE_SERVER}/set-rotation-at-login.sh" -o "$PI_HOME/set-rotation-at-login.sh" 2>/dev/null; then
chmod 755 "$PI_HOME/set-rotation-at-login.sh"
chown "$PI_USER:$PI_USER" "$PI_HOME/set-rotation-at-login.sh"
if curl -fsSL "${FILE_SERVER}/set-rotation-at-login.desktop" -o /tmp/sral.desktop 2>/dev/null; then
sed "s|/home/pi|$PI_HOME|g" /tmp/sral.desktop > "$AUTOSTART/set-rotation-at-login.desktop"
chown "$PI_USER:$PI_USER" "$AUTOSTART/set-rotation-at-login.desktop"
rm -f /tmp/sral.desktop
log "set-rotation-at-login installed"
fi
fi
fi
# Additional one-shots from config
if [[ -n "$ONESHOT_SCRIPTS" ]]; then
for _name in $ONESHOT_SCRIPTS; do
install_oneshot "$_name" || true
done
else
log "No additional one-shot scripts"
fi
}

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# Step 12: Make log file appendable by the pi user
step_12_log_permissions() {
chmod 666 "$LOGFILE"
log "Log $LOGFILE now appendable by $PI_USER"
}

View File

@@ -0,0 +1,11 @@
#!/bin/bash
# Step 13: Report completion and reboot
step_13_reboot() {
local ip
ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
report_status "done" "First-boot complete" "13" "reboot" "$ip"
log "Device IP: ${ip:-unknown}"
log "=== first-boot.sh finished, rebooting ==="
reboot
}

View File

@@ -0,0 +1,20 @@
# wf-panel-pi minimal dark taskbar, touch-friendly
# Copy to ~/.config/wf-panel-pi/wf-panel-pi.ini (user pi)
# Set css_path to panel-theme.css. Restart panel: pkill wf-panel-pi
# Icon theme (start-here) is set in gtk-3.0/settings.ini (PiXtrix) so custom icon from first-boot is used.
[panel]
css_path = /home/pi/.config/wf-panel-pi/panel-theme.css
# Left: start menu (uses start-here from PiXtrix), launchers, window list
widgets_left = smenu spacing0 spacing2 launchers spacing4 window-list
widgets_center = none
# Right: tray, network, volume, clock, power (minimal set, touch-friendly)
widgets_right = tray spacing2 netman spacing2 volumepulse spacing2 clock spacing2 power
# Touch-friendly size (32px min target)
icon_size = 32
minimal_height = 36
position = top
# Match Adwaita-dark window bg (#242424) so taskbar fits dark theme
background_color = #242424FF
autohide = false
autohide_duration = 300