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:
@@ -1,73 +0,0 @@
|
|||||||
# Taskbar (start menu) icon
|
|
||||||
|
|
||||||
The icon you see in the taskbar that opens the application menu is the **start-here** icon from the current icon theme. On Raspberry Pi OS with rpd-labwc (wf-panel-pi + wfplug-menu), that icon is resolved by name: **`start-here`** in the **places** context.
|
|
||||||
|
|
||||||
On the default PiXtrix theme, `start-here` is a symlink to the Raspberry Pi logo (`rpi.png`). To show a different icon, replace **start-here** with your own image in the same theme (or in a custom theme that overrides it).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## How to change it
|
|
||||||
|
|
||||||
### Option 1: Replace on the device (one-off)
|
|
||||||
|
|
||||||
After first-boot, SSH in and replace the icon files. The panel uses **PiXtrix** by default; start-here there is under **places**:
|
|
||||||
|
|
||||||
- Paths: `/usr/share/icons/PiXtrix/<size>/places/start-here.png`
|
|
||||||
- Sizes present on Raspberry Pi OS: **16, 24, 32, 48, 64, 96** (and optionally 22 for legacy).
|
|
||||||
|
|
||||||
**Steps (as root):**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backup originals (currently symlinks to rpi.png)
|
|
||||||
for s in 16 24 32 48 64 96; do
|
|
||||||
[ -L /usr/share/icons/PiXtrix/${s}x${s}/places/start-here.png ] && \
|
|
||||||
cp -P /usr/share/icons/PiXtrix/${s}x${s}/places/start-here.png /usr/share/icons/PiXtrix/${s}x${s}/places/start-here.png.bak
|
|
||||||
done
|
|
||||||
|
|
||||||
# Replace with your PNG (one size – e.g. 32x32 – then scale for others, or use a scalable SVG and generate PNGs)
|
|
||||||
# Example: copy your icon to 32x32 (panel default icon_size is 32)
|
|
||||||
cp /path/to/your-icon.png /usr/share/icons/PiXtrix/32x32/places/start-here.png
|
|
||||||
# Remove symlink first if it was a link:
|
|
||||||
rm -f /usr/share/icons/PiXtrix/32x32/places/start-here.png
|
|
||||||
cp /path/to/your-icon.png /usr/share/icons/PiXtrix/32x32/places/start-here.png
|
|
||||||
|
|
||||||
# Repeat for other sizes, or use ImageMagick to scale:
|
|
||||||
for s in 16 24 48 64 96; do
|
|
||||||
convert /path/to/your-icon.png -resize ${s}x${s} /usr/share/icons/PiXtrix/${s}x${s}/places/start-here.png
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
Then restart the panel (or log out and back in):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pkill wf-panel-pi
|
|
||||||
# Panel is usually restarted by the session; if not, log out and in again.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Deploy via first-boot (file server)
|
|
||||||
|
|
||||||
**first-boot.sh** (step 05) already does this: if **`start-here.png`** is present in the first-boot folder on the file server (same URL as splash.png, e.g. `http://10.20.50.1:5000/files/first-boot/start-here.png`), it is downloaded and installed as the taskbar start button icon in PiXtrix (all sizes 16, 24, 32, 48, 64, 96). If ImageMagick (`convert`) is installed, the icon is resized per size; otherwise the same image is copied to each size. Place **start-here.png** in your portal first-boot folder (e.g. next to splash.png) and re-provision or run first-boot; no extra config needed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Specs for the new icon
|
|
||||||
|
|
||||||
So that it looks correct in the taskbar and in the menu, use these specs:
|
|
||||||
|
|
||||||
| Property | Value |
|
|
||||||
|----------------|--------|
|
|
||||||
| **Icon name** | Must be installed as **start-here** (in **places** context) so the panel finds it. |
|
|
||||||
| **Format** | PNG (preferred for raster), or SVG if you generate PNGs per size. |
|
|
||||||
| **Sizes** | 16×16, 24×24, 32×32, 48×48, 64×64, 96×96 px (panel default is 32px). |
|
|
||||||
| **Aspect** | Square (1∶1). The panel shows it in a square cell. |
|
|
||||||
| **Background** | Transparent (PNG with alpha) so it fits any panel background. |
|
|
||||||
| **Content** | Simple, recognizable at 16–32 px (logo or symbol). Avoid fine detail. |
|
|
||||||
| **Theme path** | `PiXtrix/<size>x<size>/places/start-here.png` (or override via a custom theme). |
|
|
||||||
|
|
||||||
### Recommendation
|
|
||||||
|
|
||||||
- **Source asset**: One PNG at **32×32** or **64×64** (or one SVG), then generate the other sizes to avoid scaling artifacts.
|
|
||||||
- **Design**: High contrast, clear at 24–32 px; works on both light and dark panel (or match your panel colour).
|
|
||||||
- **File server name**: e.g. `taskbar-icon.png` or `start-here.png`; first-boot script would download it and install to the paths above.
|
|
||||||
|
|
||||||
If you want, the next step is to add a small script (and optional first-boot wiring) that downloads `taskbar-icon.png` from the file server and installs it as **start-here** in all required sizes.
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
# Taskbar (wf-panel-pi) editing and custom theme
|
|
||||||
|
|
||||||
You can edit the taskbar to your liking and create a custom theme. The panel is **wf-panel-pi** (GTK3); configuration is in **`~/.config/wf-panel-pi/wf-panel-pi.ini`** and optional **custom CSS** via the **css_path** option.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Editing the taskbar (INI config)
|
|
||||||
|
|
||||||
Config file: **`~/.config/wf-panel-pi/wf-panel-pi.ini`** (user `pi`). If the file is empty or missing, the panel uses built-in defaults. Options (from `panel-pi.xml`):
|
|
||||||
|
|
||||||
| Option | Type | Default | Description |
|
|
||||||
|--------|------|---------|-------------|
|
|
||||||
| **css_path** | string | (empty) | Path to a custom CSS file for the panel (GTK CSS). |
|
|
||||||
| **widgets_left** | string | `smenu spacing0 spacing4 launchers spacing8 window-list` | Widget names for the left section (space-separated). |
|
|
||||||
| **widgets_center** | string | `none` | Widgets in the center. |
|
|
||||||
| **widgets_right** | string | `tray power ejecter updater ... clock ... batt squeek` | Widgets on the right. |
|
|
||||||
| **icon_size** | int | 32 | Icon size in pixels. |
|
|
||||||
| **minimal_height** | int | 24 | Panel height (min). |
|
|
||||||
| **autohide** | bool | false | Hide panel until cursor touches edge. |
|
|
||||||
| **autohide_duration** | int | 300 | Animation duration (ms) when showing/hiding. |
|
|
||||||
| **position** | string | top | `top` or `bottom`. |
|
|
||||||
| **background_color** | string | #EDECEBFF | Panel background (ARGB hex). |
|
|
||||||
| **layer** | string | bottom | `top`, `bottom`, `overlay`, `background`. |
|
|
||||||
| **monitor** | string | 0 | Monitor index. |
|
|
||||||
| **edge_offset** | int | 20 | Pixels from edge to trigger autohide. |
|
|
||||||
| **gestures_touch_only** | bool | false | Restrict gestures to touch. |
|
|
||||||
|
|
||||||
### Widget names (for widgets_left / _center / _right)
|
|
||||||
|
|
||||||
- **Left:** `smenu` (start menu), `spacing0` … `spacing8` (spacers), `launchers`, `window-list`
|
|
||||||
- **Right:** `tray`, `power`, `ejecter`, `updater`, `connect`, `bluetooth`, `netman`, `volumepulse`, `clock`, `batt`, `squeek`, plus `spacing2` etc.
|
|
||||||
|
|
||||||
Example: minimal left (only menu + window list), right (clock + tray only):
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[panel]
|
|
||||||
widgets_left = smenu spacing4 window-list
|
|
||||||
widgets_center = none
|
|
||||||
widgets_right = spacing2 clock spacing2 tray
|
|
||||||
icon_size = 28
|
|
||||||
minimal_height = 28
|
|
||||||
position = bottom
|
|
||||||
background_color = #2D2D2DFF
|
|
||||||
```
|
|
||||||
|
|
||||||
Save as `~/.config/wf-panel-pi/wf-panel-pi.ini` for user `pi`, then restart the panel: `pkill wf-panel-pi` (session will respawn it, or log out/in).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Custom theme (CSS + INI)
|
|
||||||
|
|
||||||
The panel supports **GTK 3 CSS** via **css_path**. You can:
|
|
||||||
|
|
||||||
1. Set **css_path** in `wf-panel-pi.ini` to a full path to your `.css` file (e.g. `~/.config/wf-panel-pi/panel-theme.css`).
|
|
||||||
2. Use GTK CSS to style the panel window and its children (colors, borders, font, padding, etc.).
|
|
||||||
|
|
||||||
### INI
|
|
||||||
|
|
||||||
```ini
|
|
||||||
[panel]
|
|
||||||
css_path = /home/pi/.config/wf-panel-pi/panel-theme.css
|
|
||||||
background_color = #1E1E1EFF
|
|
||||||
icon_size = 28
|
|
||||||
minimal_height = 28
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example CSS (panel-theme.css)
|
|
||||||
|
|
||||||
GTK panel often uses a single top-level window; you can target it and inner boxes. Example (dark, rounded, with padding):
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* Custom taskbar theme – dark, subtle border */
|
|
||||||
window {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main panel box */
|
|
||||||
window box {
|
|
||||||
background-color: rgba(30, 30, 30, 0.95);
|
|
||||||
border-radius: 12px;
|
|
||||||
margin: 4px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons / plugin containers */
|
|
||||||
button,
|
|
||||||
button:hover,
|
|
||||||
button:active {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 4px;
|
|
||||||
color: #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Labels (e.g. clock) */
|
|
||||||
label {
|
|
||||||
color: #e0e0e0;
|
|
||||||
font-size: 11pt;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Paths: use an **absolute path** in **css_path** (e.g. `/home/pi/.config/wf-panel-pi/panel-theme.css`). Expand `~` yourself when writing the INI (the panel may not expand it).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Deploying a custom theme via first-boot
|
|
||||||
|
|
||||||
To apply a custom taskbar theme on every provisioned device:
|
|
||||||
|
|
||||||
1. **On the file server** (e.g. first-boot folder): add:
|
|
||||||
- **wf-panel-pi.ini** – INI with your `[panel]` options and `css_path` pointing to the CSS file path on the device.
|
|
||||||
- **panel-theme.css** – your GTK CSS.
|
|
||||||
|
|
||||||
2. **In first-boot** (or a one-shot script): for user `pi`:
|
|
||||||
- Create `~/.config/wf-panel-pi/` if needed.
|
|
||||||
- Copy or download **wf-panel-pi.ini** to `~/.config/wf-panel-pi/wf-panel-pi.ini`.
|
|
||||||
- Copy or download **panel-theme.css** to e.g. `~/.config/wf-panel-pi/panel-theme.css`.
|
|
||||||
- In the INI, set `css_path = /home/pi/.config/wf-panel-pi/panel-theme.css` (or use `$PI_HOME` in the script).
|
|
||||||
- `chown -R pi:pi ~/.config/wf-panel-pi`.
|
|
||||||
|
|
||||||
3. **Restart panel** after first login (or rely on next login): `pkill wf-panel-pi` (optional in script; session often restarts it).
|
|
||||||
|
|
||||||
Example first-boot snippet (run as root, after `$PI_HOME` is set):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PANEL_CONF="$PI_HOME/.config/wf-panel-pi"
|
|
||||||
mkdir -p "$PANEL_CONF"
|
|
||||||
curl -fsSL "${FILE_SERVER}/wf-panel-pi.ini" -o "$PANEL_CONF/wf-panel-pi.ini"
|
|
||||||
curl -fsSL "${FILE_SERVER}/panel-theme.css" -o "$PANEL_CONF/panel-theme.css"
|
|
||||||
sed -i "s|/home/pi|$PI_HOME|g" "$PANEL_CONF/wf-panel-pi.ini"
|
|
||||||
chown -R "$PI_USER:$PI_USER" "$PANEL_CONF"
|
|
||||||
```
|
|
||||||
|
|
||||||
Ensure the INI uses the same path as where you save the CSS (e.g. `css_path = /home/pi/.config/wf-panel-pi/panel-theme.css` or `$PI_HOME/...` if you substitute in the script).
|
|
||||||
|
|
||||||
**Example theme files** in this repo: `config-files/wf-panel-pi.ini` and `config-files/panel-theme.css` (dark, rounded panel). Host them on the file server and deploy with the snippet above.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Summary
|
|
||||||
|
|
||||||
| Goal | Where | How |
|
|
||||||
|------|--------|-----|
|
|
||||||
| Change layout (widgets, position, size) | `~/.config/wf-panel-pi/wf-panel-pi.ini` | Edit `[panel]` options. |
|
|
||||||
| Change colors / style | Same INI + custom CSS | Set **css_path** and write GTK CSS. |
|
|
||||||
| Custom theme on all devices | File server + first-boot | Deploy **wf-panel-pi.ini** and **panel-theme.css** and copy to `~/.config/wf-panel-pi/` for `pi`. |
|
|
||||||
|
|
||||||
After any change, restart the panel (`pkill wf-panel-pi`) or log out and back in so the new theme is applied.
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Config files for first-boot (file server)
|
|
||||||
|
|
||||||
first-boot.sh downloads these from `FILE_SERVER` (e.g. `http://10.20.50.1:5000/files/first-boot`) and installs them to the paths below. Upload each file into the **first-boot** subfolder of portal-files (e.g. `/var/lib/cm4-provisioning/portal-files/first-boot/`).
|
|
||||||
|
|
||||||
| File on server | Destination on device |
|
|
||||||
|----------------|------------------------|
|
|
||||||
| chromium-kiosk.desktop | /home/pi/.config/autostart/chromium-kiosk.desktop (with start-chromium.sh) |
|
|
||||||
| 99-wallpaper.conf | /etc/lightdm/lightdm.conf.d/99-wallpaper.conf |
|
|
||||||
| 99-default-session.conf | /etc/lightdm/lightdm.conf.d/99-default-session.conf (rpd-labwc) |
|
|
||||||
| maliit-keyboard.desktop | /home/pi/.config/autostart/maliit-keyboard.desktop |
|
|
||||||
| 01-set-rotation-once.desktop | /home/pi/.config/autostart/01-set-rotation-once.desktop (with 01-set-rotation-once.sh; writes kanshi config + dark theme) |
|
|
||||||
| 02-set-wallpaper-once.desktop | /home/pi/.config/autostart/02-set-wallpaper-once.desktop (with 02-set-wallpaper-once.sh). Wallpaper is also set during first-boot via pcmanfm. |
|
|
||||||
| wf-panel-pi.ini | Optional: /home/pi/.config/wf-panel-pi/wf-panel-pi.ini (custom taskbar layout and css_path). See TASKBAR-THEME.md. |
|
|
||||||
| panel-theme.css | Optional: /home/pi/.config/wf-panel-pi/panel-theme.css (custom taskbar CSS). Deploy with wf-panel-pi.ini; see TASKBAR-THEME.md. |
|
|
||||||
| gtk.css | /home/pi/.config/gtk-3.0/gtk.css (user GTK3 override; forces start menu dropdown and all menus/popovers to full dark). |
|
|
||||||
| chromium-kiosk-launcher.desktop | Desktop icon + application menu: "Start Chromium Kiosk" (run start-chromium.sh when browser was closed). |
|
|
||||||
| five-tap-close-chromium.desktop | /home/pi/.config/autostart/ (with five-tap-close-chromium.py). 5 taps in top-right corner close Chromium. |
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
[General]
|
|
||||||
ForceFontDPI=120
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
[Windows]
|
|
||||||
BorderlessMaximizedWindows=true
|
|
||||||
[Plugins]
|
|
||||||
touchpointsEnabled=true
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Files for the file server
|
|
||||||
|
|
||||||
first-boot.sh downloads from **`.../files/first-boot/`** (e.g. `http://10.20.50.1:5000/files/first-boot`). Put all first-boot assets in a **first-boot** subfolder of portal-files so provisioning works.
|
|
||||||
|
|
||||||
## Required files (host under `.../files/first-boot/`)
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|--------|
|
|
||||||
| **start-chromium.sh** | Chromium kiosk launcher (executable). |
|
|
||||||
| **chromium-kiosk.desktop** | Autostart entry for the kiosk. |
|
|
||||||
| **splash.png** | Boot splash + login + desktop wallpaper (single image). |
|
|
||||||
| **custom.plymouth** | Plymouth theme config (from `plymouth-custom/`). |
|
|
||||||
| **custom.script** | Plymouth script that displays splash.png (from `plymouth-custom/`). |
|
|
||||||
| **99-wallpaper.conf** | LightDM greeter wallpaper (from `config-files/`). |
|
|
||||||
| **99-default-session.conf** | LightDM default session rpd-labwc (from `config-files/`). |
|
|
||||||
| **maliit-keyboard.desktop** | Maliit on-screen keyboard autostart (from `config-files/`). |
|
|
||||||
| **01-set-rotation-once.sh** + **.desktop** | One-shot: writes ~/.config/kanshi/config with rotation from cmdline and sets GTK dark theme at first login. |
|
|
||||||
|
|
||||||
Desktop wallpaper is set once during first-boot via pcmanfm config (first-boot.sh). Optional one-shot: **02-set-wallpaper-once.sh**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What’s in this folder
|
|
||||||
|
|
||||||
- **plymouth-custom/splash.png** — Example splash image; host as `splash.png`.
|
|
||||||
- **plymouth-custom/custom.plymouth** — Plymouth theme definition; host as `custom.plymouth`.
|
|
||||||
- **plymouth-custom/custom.script** — Plymouth script that draws splash.png; host as `custom.script`.
|
|
||||||
- **lightdm/RPiSystem_dark.png** — Unused; only `splash.png` is used now.
|
|
||||||
- **start-chromium.sh** and **chromium-kiosk.desktop** live in `cloud-init/` and `cloud-init/config-files/`; `scripts/sync-portal-files-to-lxc.sh` copies them to the portal first-boot folder.
|
|
||||||
- **01-set-rotation-once.sh**, **02-set-wallpaper-once.sh** (and their .desktop files) are in `cloud-init/` and synced to `portal-files/first-boot/` by the same script.
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 460 KiB |
35
emmc-provisioning/cloud-init/fileserver/README.md
Normal file
35
emmc-provisioning/cloud-init/fileserver/README.md
Normal 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) |
|
||||||
@@ -2,16 +2,25 @@
|
|||||||
# 5 taps in the top-right corner of the screen close Chromium (kiosk).
|
# 5 taps in the top-right corner of the screen close Chromium (kiosk).
|
||||||
# Run from session autostart. Requires: python3, PyGObject (Gtk), Wayland or X11.
|
# Run from session autostart. Requires: python3, PyGObject (Gtk), Wayland or X11.
|
||||||
|
|
||||||
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
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:
|
try:
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Gdk", "3.0")
|
gi.require_version("Gdk", "3.0")
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gdk, Gtk, GLib
|
from gi.repository import Gdk, Gtk, GLib
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
sys.stderr.write("five-tap-close-chromium: need PyGObject Gtk: %s\n" % e)
|
log.error("need PyGObject Gtk: %s", e)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
CORNER_SIZE = 80
|
CORNER_SIZE = 80
|
||||||
@@ -22,30 +31,37 @@ CHROMIUM_KILL_CMD = ["pkill", "-f", "chromium"]
|
|||||||
def get_screen_size():
|
def get_screen_size():
|
||||||
display = Gdk.Display.get_default()
|
display = Gdk.Display.get_default()
|
||||||
if not display:
|
if not display:
|
||||||
|
log.warning("no display found, using fallback 800x1280")
|
||||||
return 800, 1280
|
return 800, 1280
|
||||||
monitor = display.get_monitor(0) if display.get_n_monitors() else None
|
monitor = display.get_monitor(0) if display.get_n_monitors() else None
|
||||||
if monitor:
|
if monitor:
|
||||||
geom = monitor.get_geometry()
|
geom = monitor.get_geometry()
|
||||||
|
log.info("screen size: %dx%d", geom.width, geom.height)
|
||||||
return geom.width, geom.height
|
return geom.width, geom.height
|
||||||
|
log.warning("no monitor found, using fallback 800x1280")
|
||||||
return 800, 1280
|
return 800, 1280
|
||||||
|
|
||||||
|
|
||||||
def on_button_press(widget, event, data):
|
def on_button_press(widget, event, data):
|
||||||
count, reset_timer = data
|
count, reset_timer = data
|
||||||
count[0] += 1
|
count[0] += 1
|
||||||
|
log.debug("tap %d of 5 at (%.0f, %.0f)", count[0], event.x_root, event.y_root)
|
||||||
if reset_timer[0]:
|
if reset_timer[0]:
|
||||||
GLib.source_remove(reset_timer[0])
|
GLib.source_remove(reset_timer[0])
|
||||||
if count[0] >= 5:
|
if count[0] >= 5:
|
||||||
count[0] = 0
|
count[0] = 0
|
||||||
|
log.info("5 taps reached — killing chromium")
|
||||||
try:
|
try:
|
||||||
subprocess.run(CHROMIUM_KILL_CMD, timeout=2, capture_output=True)
|
result = subprocess.run(CHROMIUM_KILL_CMD, timeout=2, capture_output=True)
|
||||||
except Exception:
|
log.info("pkill returncode=%d", result.returncode)
|
||||||
pass
|
except Exception as e:
|
||||||
|
log.error("pkill failed: %s", e)
|
||||||
if reset_timer[0]:
|
if reset_timer[0]:
|
||||||
GLib.source_remove(reset_timer[0])
|
GLib.source_remove(reset_timer[0])
|
||||||
reset_timer[0] = None
|
reset_timer[0] = None
|
||||||
return
|
return
|
||||||
def reset():
|
def reset():
|
||||||
|
log.debug("tap count reset (%.1fs timeout)", TAP_WINDOW_SEC)
|
||||||
count[0] = 0
|
count[0] = 0
|
||||||
reset_timer[0] = None
|
reset_timer[0] = None
|
||||||
return False
|
return False
|
||||||
@@ -77,11 +93,12 @@ def main():
|
|||||||
| Gdk.EventMask.TOUCH_MASK
|
| Gdk.EventMask.TOUCH_MASK
|
||||||
)
|
)
|
||||||
win.realize()
|
win.realize()
|
||||||
# Position top-right (after realization so we have a display)
|
|
||||||
w, h = get_screen_size()
|
w, h = get_screen_size()
|
||||||
x = max(0, w - CORNER_SIZE)
|
x = max(0, w - CORNER_SIZE)
|
||||||
win.move(x, 0)
|
win.move(x, 0)
|
||||||
|
log.info("window %dx%d positioned at x=%d y=0", CORNER_SIZE, CORNER_SIZE, x)
|
||||||
win.show_all()
|
win.show_all()
|
||||||
|
log.info("listening for taps (5 within %.1fs to kill chromium)", TAP_WINDOW_SEC)
|
||||||
Gtk.main()
|
Gtk.main()
|
||||||
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 205 KiB |
BIN
emmc-provisioning/cloud-init/fileserver/start-here.png
Normal file
BIN
emmc-provisioning/cloud-init/fileserver/start-here.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
12
emmc-provisioning/cloud-init/fileserver/steps/01-hostname.sh
Normal file
12
emmc-provisioning/cloud-init/fileserver/steps/01-hostname.sh
Normal 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"
|
||||||
|
}
|
||||||
10
emmc-provisioning/cloud-init/fileserver/steps/02-packages.sh
Normal file
10
emmc-provisioning/cloud-init/fileserver/steps/02-packages.sh
Normal 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"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
39
emmc-provisioning/cloud-init/fileserver/steps/06-lightdm.sh
Normal file
39
emmc-provisioning/cloud-init/fileserver/steps/06-lightdm.sh
Normal 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)"
|
||||||
|
}
|
||||||
10
emmc-provisioning/cloud-init/fileserver/steps/07-maliit.sh
Normal file
10
emmc-provisioning/cloud-init/fileserver/steps/07-maliit.sh
Normal 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"
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
17
emmc-provisioning/cloud-init/fileserver/steps/10-cmdline.sh
Normal file
17
emmc-provisioning/cloud-init/fileserver/steps/10-cmdline.sh
Normal 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
|
||||||
|
}
|
||||||
28
emmc-provisioning/cloud-init/fileserver/steps/11-oneshots.sh
Normal file
28
emmc-provisioning/cloud-init/fileserver/steps/11-oneshots.sh
Normal 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
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
11
emmc-provisioning/cloud-init/fileserver/steps/13-reboot.sh
Normal file
11
emmc-provisioning/cloud-init/fileserver/steps/13-reboot.sh
Normal 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
|
||||||
|
}
|
||||||
@@ -1,66 +1,36 @@
|
|||||||
# Revision: 2
|
# first-boot.conf — loaded by first-boot.sh (required)
|
||||||
# first-boot.conf (or .example)
|
|
||||||
# Copy to first-boot.conf and edit. Loaded by first-boot.sh from:
|
|
||||||
# - same directory as first-boot.sh, or
|
|
||||||
# - /tmp/first-boot.conf (when run via cloud-init), or
|
|
||||||
# - /etc/cm4-provisioning/first-boot.conf
|
|
||||||
# Unset variables keep the script's built-in defaults.
|
|
||||||
|
|
||||||
# --- File server & host ---
|
# --- File server & host ---
|
||||||
# Base URL for first-boot assets (scripts, splash, configs). No trailing slash.
|
|
||||||
FILE_SERVER="http://10.20.50.1:5000/files/first-boot"
|
FILE_SERVER="http://10.20.50.1:5000/files/first-boot"
|
||||||
|
|
||||||
# Hostname set on the device.
|
|
||||||
HOSTNAME="guard"
|
HOSTNAME="guard"
|
||||||
|
|
||||||
# --- User ---
|
# --- User ---
|
||||||
# Login user (must exist; cloud-init user-data must create the same user).
|
|
||||||
PI_USER="pi"
|
PI_USER="pi"
|
||||||
|
|
||||||
# --- Paths (optional overrides) ---
|
|
||||||
# First-boot log file.
|
|
||||||
LOGFILE="/var/log/first-boot.log"
|
|
||||||
|
|
||||||
# Plymouth custom theme directory.
|
|
||||||
PLYMOUTH_DIR="/usr/share/plymouth/themes/custom"
|
|
||||||
|
|
||||||
# Wallpaper path (splash.png is also copied here).
|
|
||||||
WALLPAPER_PATH="/usr/share/rpd-wallpaper/splash.png"
|
|
||||||
|
|
||||||
# --- Packages ---
|
# --- Packages ---
|
||||||
# Space-separated list of packages to install. Must include: git chromium wmctrl openssh-server
|
PACKAGES="git chromium wmctrl openssh-server swaybg wlr-randr maliit-keyboard xinput-calibrator python3-gi python3-gi-cairo"
|
||||||
# swaybg wlr-randr maliit-keyboard xinput-calibrator (for kiosk + labwc + touch).
|
|
||||||
PACKAGES="git chromium wmctrl openssh-server swaybg wlr-randr maliit-keyboard xinput-calibrator"
|
|
||||||
|
|
||||||
# --- Desktop & theme ---
|
# --- Desktop & theme ---
|
||||||
# LightDM session (rpd-labwc for Wayland + labwc).
|
|
||||||
LIGHTDM_SESSION="rpd-labwc"
|
LIGHTDM_SESSION="rpd-labwc"
|
||||||
|
|
||||||
# GTK dark theme name (e.g. PiXnoir; fallback is Adwaita-dark if missing).
|
|
||||||
GTK_THEME_NAME="PiXnoir"
|
GTK_THEME_NAME="PiXnoir"
|
||||||
|
|
||||||
# Wallpaper mode for pcmanfm (crop, stretch, fit, center, tile, screen, color).
|
|
||||||
WALLPAPER_MODE="crop"
|
WALLPAPER_MODE="crop"
|
||||||
|
|
||||||
# --- Display (reTerminal DM) ---
|
# --- Paths ---
|
||||||
# Kernel cmdline: DSI rotation. 90 = 90° clockwise; use 180 or 270 for other orientations.
|
PLYMOUTH_DIR="/usr/share/plymouth/themes/custom"
|
||||||
DSI_ROTATE="270"
|
WALLPAPER_PATH="/usr/share/rpd-wallpaper/splash.png"
|
||||||
|
|
||||||
# Kernel cmdline: swiotlb size (for vc4-drm/DSI). Leave empty to skip.
|
# --- Display (reTerminal DM) ---
|
||||||
|
DSI_ROTATE="270"
|
||||||
SWIOTLB_SIZE="65536"
|
SWIOTLB_SIZE="65536"
|
||||||
|
|
||||||
# --- reTerminal (Seeed) ---
|
# --- reTerminal (Seeed) ---
|
||||||
# Device passed to reTerminal.sh (reTerminal-DM for reTerminal DM).
|
|
||||||
RETERMINAL_DEVICE="reTerminal-DM"
|
RETERMINAL_DEVICE="reTerminal-DM"
|
||||||
|
|
||||||
# Seeed overlays repo (clone URL). Leave empty to skip driver install.
|
|
||||||
RETERMINAL_REPO_URL="https://github.com/Seeed-Studio/seeed-linux-dtoverlays"
|
RETERMINAL_REPO_URL="https://github.com/Seeed-Studio/seeed-linux-dtoverlays"
|
||||||
|
|
||||||
# --- One-shots ---
|
# --- One-shots ---
|
||||||
# Space-separated names of one-shot scripts (numbered = run order at first login). Leave empty for none.
|
|
||||||
ONESHOT_SCRIPTS="01-set-rotation-once 02-set-wallpaper-once"
|
ONESHOT_SCRIPTS="01-set-rotation-once 02-set-wallpaper-once"
|
||||||
|
|
||||||
# --- Step enable flags (1 = run, 0 = skip). All enabled below; set to 0 to disable a step. ---
|
# --- Step enable flags (1 = run, 0 = skip) ---
|
||||||
ENABLE_STEP_01=1
|
ENABLE_STEP_01=1
|
||||||
ENABLE_STEP_02=1
|
ENABLE_STEP_02=1
|
||||||
ENABLE_STEP_03=1
|
ENABLE_STEP_03=1
|
||||||
|
|||||||
@@ -1,70 +1,46 @@
|
|||||||
# first-boot.conf.example
|
# first-boot.conf — REQUIRED by first-boot.sh
|
||||||
# Copy to first-boot.conf and edit. Loaded by first-boot.sh from:
|
# Copy to first-boot.conf and edit. Loaded from (first found):
|
||||||
# - same directory as first-boot.sh, or
|
# 1. same directory as first-boot.sh
|
||||||
# - /tmp/first-boot.conf (when run via cloud-init), or
|
# 2. /tmp/first-boot.conf (cloud-init runcmd downloads it)
|
||||||
# - /etc/cm4-provisioning/first-boot.conf
|
# 3. /etc/cm4-provisioning/first-boot.conf
|
||||||
# Unset variables keep the script's built-in defaults.
|
# 4. /var/lib/cm4-provisioning/first-boot.conf (persisted for phase 2)
|
||||||
|
|
||||||
# --- File server & host ---
|
# --- File server & host ---
|
||||||
# Base URL for first-boot assets (scripts, splash, configs). No trailing slash.
|
FILE_SERVER="http://10.20.50.1:5000/files/first-boot"
|
||||||
# FILE_SERVER="http://10.20.50.1:5000/files/first-boot"
|
HOSTNAME="guard"
|
||||||
|
|
||||||
# Hostname set on the device.
|
|
||||||
# HOSTNAME="guard"
|
|
||||||
|
|
||||||
# --- User ---
|
# --- User ---
|
||||||
# Login user (must exist; cloud-init user-data must create the same user).
|
PI_USER="pi"
|
||||||
# PI_USER="pi"
|
|
||||||
|
|
||||||
# --- Paths (optional overrides) ---
|
|
||||||
# First-boot log file.
|
|
||||||
# LOGFILE="/var/log/first-boot.log"
|
|
||||||
|
|
||||||
# Plymouth custom theme directory.
|
|
||||||
# PLYMOUTH_DIR="/usr/share/plymouth/themes/custom"
|
|
||||||
|
|
||||||
# Wallpaper path (splash.png is also copied here).
|
|
||||||
# WALLPAPER_PATH="/usr/share/rpd-wallpaper/splash.png"
|
|
||||||
|
|
||||||
# --- Packages ---
|
# --- Packages ---
|
||||||
# Space-separated list of packages to install. Must include: git chromium wmctrl openssh-server
|
PACKAGES="git chromium wmctrl openssh-server swaybg wlr-randr maliit-keyboard xinput-calibrator python3-gi python3-gi-cairo"
|
||||||
# swaybg wlr-randr maliit-keyboard xinput-calibrator (for kiosk + labwc + touch).
|
|
||||||
# PACKAGES="git chromium wmctrl openssh-server swaybg wlr-randr maliit-keyboard xinput-calibrator"
|
|
||||||
|
|
||||||
# --- Desktop & theme ---
|
# --- Desktop & theme ---
|
||||||
# LightDM session (rpd-labwc for Wayland + labwc).
|
LIGHTDM_SESSION="rpd-labwc"
|
||||||
# LIGHTDM_SESSION="rpd-labwc"
|
GTK_THEME_NAME="PiXnoir"
|
||||||
|
WALLPAPER_MODE="crop"
|
||||||
|
|
||||||
# GTK dark theme name (e.g. PiXnoir; fallback is Adwaita-dark if missing).
|
# --- Paths ---
|
||||||
# GTK_THEME_NAME="PiXnoir"
|
PLYMOUTH_DIR="/usr/share/plymouth/themes/custom"
|
||||||
|
WALLPAPER_PATH="/usr/share/rpd-wallpaper/splash.png"
|
||||||
# Wallpaper mode for pcmanfm (crop, stretch, fit, center, tile, screen, color).
|
|
||||||
# WALLPAPER_MODE="crop"
|
|
||||||
|
|
||||||
# --- Display (reTerminal DM) ---
|
# --- Display (reTerminal DM) ---
|
||||||
# Kernel cmdline: DSI rotation. 90 = 90° clockwise; 180 or 270 for other orientations.
|
DSI_ROTATE="270"
|
||||||
# At login, ~/.config/kanshi/config is written with this transform (same as Control Center).
|
SWIOTLB_SIZE="65536"
|
||||||
# DSI_ROTATE="270"
|
|
||||||
|
|
||||||
# Kernel cmdline: swiotlb size (for vc4-drm/DSI). Leave empty to skip.
|
|
||||||
# SWIOTLB_SIZE="65536"
|
|
||||||
|
|
||||||
# --- reTerminal (Seeed) ---
|
# --- reTerminal (Seeed) ---
|
||||||
# Device passed to reTerminal.sh (reTerminal-DM for reTerminal DM).
|
RETERMINAL_DEVICE="reTerminal-DM"
|
||||||
# RETERMINAL_DEVICE="reTerminal-DM"
|
RETERMINAL_REPO_URL="https://github.com/Seeed-Studio/seeed-linux-dtoverlays"
|
||||||
|
|
||||||
# Seeed overlays repo (clone URL). Leave empty to skip driver install.
|
|
||||||
# RETERMINAL_REPO_URL="https://github.com/Seeed-Studio/seeed-linux-dtoverlays"
|
|
||||||
|
|
||||||
# --- One-shots ---
|
# --- One-shots ---
|
||||||
# Space-separated names of one-shot scripts to install from FILE_SERVER (each name gets name.sh + name.desktop).
|
# Space-separated names of one-shot scripts (each needs name.sh + name.desktop on file server).
|
||||||
# Use numbered names so they run in order at first login. Leave empty for none.
|
# Leave empty for none.
|
||||||
# Example: "01-set-rotation-once 02-set-wallpaper-once"
|
ONESHOT_SCRIPTS=""
|
||||||
# ONESHOT_SCRIPTS="01-set-rotation-once 02-set-wallpaper-once"
|
|
||||||
|
|
||||||
# --- Step enable flags (1 = run, 0 = skip). All steps enabled by default. Set ENABLE_STEP_NN=0 to disable. ---
|
# --- Step enable flags (1 = run, 0 = skip) ---
|
||||||
# 01=hostname, 02=packages, 03=kiosk_files, 04=splash_wallpaper, 05=lightdm, 06=maliit,
|
# Phase 1 (cloud-init → reboot): 01=hostname, 02=packages, 03=reterminal_drivers
|
||||||
# 07=dark_theme, 08=reterminal_drivers, 09=reapply_splash, 10=cmdline, 11=oneshots, 12=log_permissions, 13=reboot
|
# Phase 2 (after reboot → reboot): 04=kiosk_files, 05=splash_wallpaper, 06=lightdm,
|
||||||
# Example: uncomment to disable a step
|
# 07=maliit, 08=dark_theme, 09=reapply_splash, 10=cmdline, 11=oneshots,
|
||||||
|
# 12=log_permissions, 13=reboot
|
||||||
# ENABLE_STEP_08=0
|
# ENABLE_STEP_08=0
|
||||||
# ENABLE_STEP_13=0
|
# ENABLE_STEP_13=0
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
# first-boot.sh — Documentation
|
|
||||||
|
|
||||||
This script runs once on first boot via cloud-init (see `user-data-remote-gnss.example`). It installs packages, configures a Chromium kiosk with rpd-labwc (Raspberry Pi Desktop + labwc) and touch support, and installs the reTerminal DM display/touch drivers. It must run as **root**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Config file (first-boot.conf)
|
|
||||||
|
|
||||||
You can override script behaviour without editing `first-boot.sh` by using a **config file**. Copy `first-boot.conf.example` to `first-boot.conf`, edit the variables you need, and ensure the script can load it. The script looks for a config file in this order:
|
|
||||||
|
|
||||||
1. **Same directory as the script** — e.g. if you host both at `.../first-boot/first-boot.sh` and `.../first-boot/first-boot.conf`.
|
|
||||||
2. **`/tmp/first-boot.conf`** — when run via cloud-init, add a runcmd line to download your config to `/tmp/first-boot.conf` before running the script.
|
|
||||||
3. **`/etc/cm4-provisioning/first-boot.conf`** — for host-side or pre-written config.
|
|
||||||
|
|
||||||
Only set variables you want to change; the rest use built-in defaults. See `first-boot.conf.example` for all options (FILE_SERVER, HOSTNAME, PI_USER, PACKAGES, LIGHTDM_SESSION, GTK_THEME_NAME, WALLPAPER_MODE, DSI_ROTATE, SWIOTLB_SIZE, RETERMINAL_DEVICE, RETERMINAL_REPO_URL, ONESHOT_SCRIPTS, etc.).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Structure (steps and enable flags)
|
|
||||||
|
|
||||||
The script runs **13 numbered steps** (`step_01_hostname` … `step_13_reboot`). Disable a step by setting `ENABLE_STEP_NN=0` in config (default: all `1`). Step table: 01=hostname, 02=packages, 03=kiosk_files, 04=splash_wallpaper, 05=lightdm, 06=maliit, 07=dark_theme, 08=reterminal_drivers, 09=reapply_splash, 10=cmdline, 11=oneshots, 12=log_permissions, 13=reboot.
|
|
||||||
|
|
||||||
1. **Config & constants** — Load optional `first-boot.conf`; then `FILE_SERVER`, `PI_USER`, paths, log file (defaults or from config).
|
|
||||||
2. **Logging** — All output tee’d to `/var/log/first-boot.log`.
|
|
||||||
3. **Helpers** — `install_oneshot(name)` downloads `${name}.sh` from the file server and installs it as a one-shot autostart (runs once at pi’s first login, then deletes itself).
|
|
||||||
4. **Packages** — git, Chromium, wmctrl, SSH, swaybg, wlr-randr, maliit, xinput-calibrator.
|
|
||||||
5. **Kiosk files** — Download `start-chromium.sh` and `chromium-kiosk.desktop`; create autostart dir.
|
|
||||||
6. **Boot splash and wallpaper** — Download `splash.png`; install Plymouth custom theme; copy image for LightDM and desktop.
|
|
||||||
7. **LightDM** — Download `99-default-session.conf` (rpd-labwc) and `99-wallpaper.conf` to `/etc/lightdm/lightdm.conf.d/`.
|
|
||||||
8. **Maliit** — Download `maliit-keyboard.desktop` from file server to pi’s autostart.
|
|
||||||
9. **reTerminal DM drivers** — Seeed repo clone and `reTerminal.sh`.
|
|
||||||
10. **Re-apply splash** — Set `disable_splash=0`, Plymouth theme to `custom` only, `update-initramfs`.
|
|
||||||
11. **Dark theme** — Set GTK dark theme for user `pi`: `~/.config/gtk-3.0/settings.ini` with `gtk-application-prefer-dark-theme=1` and `gtk-theme-name=PiXnoir` (Raspberry Pi OS dark theme).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Script header and environment
|
|
||||||
|
|
||||||
- **`set -e`** — Exit immediately if any command fails.
|
|
||||||
- **`DEBIAN_FRONTEND=noninteractive`** — Prevents apt from asking questions (assumes default or automatic answers).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Logging
|
|
||||||
|
|
||||||
All script output (stdout and stderr) is appended to **`/var/log/first-boot.log`** so you can review what ran after first boot (e.g. over SSH: `cat /var/log/first-boot.log`).
|
|
||||||
|
|
||||||
- **`LOGFILE`** — Path of the log file (`/var/log/first-boot.log`).
|
|
||||||
- **`log "..."`** — Prints a timestamped line (ISO format); used for section headers.
|
|
||||||
- **`exec > >(tee -a "$LOGFILE") 2>&1`** — Sends all subsequent stdout and stderr to both the console and the log file.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Packages
|
|
||||||
|
|
||||||
Installs the software needed for the rest of the script and for the kiosk:
|
|
||||||
|
|
||||||
| Package | Purpose |
|
|
||||||
|--------|---------|
|
|
||||||
| **git** | Clone the Seeed Linux DTOverlays repo for reTerminal DM drivers. |
|
|
||||||
| **chromium-browser** | Full-screen kiosk browser. |
|
|
||||||
| **wmctrl** | Window control; used to force Chromium into fullscreen. |
|
|
||||||
| **openssh-server** | SSH access (often also enabled in user-data). |
|
|
||||||
| **swaybg** | Wallpaper for labwc (Wayland); used by one-shot and labwc autostart. |
|
|
||||||
| **kanshi** | Display rotation: ~/.config/kanshi/config is written at login with transform from cmdline (same as Control Center). The same login scripts also set GTK dark theme (~/.config/gtk-3.0/settings.ini). |
|
|
||||||
| **maliit-keyboard** | On-screen keyboard for touch input. |
|
|
||||||
| **xinput-calibrator** | Touchscreen calibration (optional; run manually if needed). |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Autostart directory
|
|
||||||
|
|
||||||
Creates `/home/pi/.config/autostart` so that `.desktop` files placed there are started when user `pi` logs into the graphical session.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Chromium kiosk files (from file server)
|
|
||||||
|
|
||||||
Downloads from `FILE_SERVER` (no local creation):
|
|
||||||
|
|
||||||
- **`FILE_SERVER`** — Base URL for first-boot assets (default: `http://10.20.50.1:5000/files/first-boot`). All first-boot files are served from a **first-boot** subfolder on the file server. Change this if your server or path is different.
|
|
||||||
- **`start-chromium.sh`** — Downloaded to `/home/pi/start-chromium.sh`, made executable (755), owned by `pi`. Waits for the desktop, then starts Chromium in fullscreen. When the session is Wayland (rpd-labwc), Chromium runs with `--ozone-platform=wayland` so **touch long-press produces right-click (context menu)** like the rest of the desktop; on X11 it falls back to `--ozone-platform=x11` and uses `wmctrl` to force fullscreen.
|
|
||||||
- **`chromium-kiosk.desktop`** — Downloaded to `/home/pi/.config/autostart/chromium-kiosk.desktop`, mode 644, owned by `pi`. This autostart entry runs `start-chromium.sh` when `pi` logs in.
|
|
||||||
|
|
||||||
Ensure the `.desktop` file on the server has `Exec=/home/pi/start-chromium.sh` (or the path you use on the device).
|
|
||||||
|
|
||||||
**Touch long-press → right-click in Chromium:** Under Wayland (rpd-labwc), long-press may work for right-click elsewhere (e.g. desktop) but **not inside Chromium**—Chromium gets touch events directly and does not implement long-press-as-right-click. To get context menu on long-press in the browser (and everywhere), use **evdev-right-click-emulation** at the input layer so long-press is converted to right-click before any app sees it: [evdev-right-click-emulation](https://github.com/PeterCxy/evdev-right-click-emulation). Build, install the binary (e.g. to `/usr/local/bin/evdev-rce`), and run it as a systemd service at boot (works on both X11 and Wayland).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Boot splash and wallpaper (single image from file server)
|
|
||||||
|
|
||||||
A **single image** (`splash.png`) is used for the boot splash, login screen, and desktop wallpaper. Host it at **`${FILE_SERVER}/splash.png`** (e.g. `http://10.20.50.1:5000/files/splash.png`).
|
|
||||||
|
|
||||||
- **Plymouth (boot splash):** Downloads `splash.png`, `custom.plymouth`, and `custom.script` from the file server → installs to `/usr/share/plymouth/themes/custom/` → sets `Theme=custom` in `/etc/plymouth/plymouthd.conf` (single `[Daemon]` section) → runs `update-initramfs -u`. If any download fails, logs a warning and continues. **Note:** On reTerminal DM the DSI panel can initialize a few seconds into boot, so the Plymouth splash may appear briefly or after a short black screen; this is normal for DSI displays.
|
|
||||||
- **LightDM (login screen):** Copies the same image to `/usr/share/rpd-wallpaper/splash.png` and writes `/etc/lightdm/lightdm.conf.d/99-wallpaper.conf` with `wallpaper=...` and `wallpaper_mode=crop`.
|
|
||||||
- **Desktop wallpaper:** First-boot sets the wallpaper once by writing pcmanfm config: `~/.config/pcmanfm/LXDE-pi/desktop-items-0.conf` and `default/desktop-items-0.conf` with `wallpaper=/usr/share/rpd-wallpaper/splash.png` and `wallpaper_mode=crop`. pcmanfm-pi (rpd-labwc desktop) reads this and shows the wallpaper; no autostart script. To set or change wallpaper manually: `pcmanfm --set-wallpaper /path/to/image.png --wallpaper-mode=crop` (modes: crop, stretch, fit, center, tile, screen, color).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## LightDM: default session (rpd-labwc)
|
|
||||||
|
|
||||||
Writes `/etc/lightdm/lightdm.conf.d/99-default-session.conf` so the display manager (LightDM) uses the **rpd-labwc** session (Raspberry Pi Desktop with labwc Wayland compositor). The script also patches `/etc/lightdm/lightdm.conf` so `user-session` and `autologin-session` are `rpd-labwc`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## On-screen keyboard (Maliit)
|
|
||||||
|
|
||||||
Creates `/home/pi/.config/autostart/maliit-keyboard.desktop` so that **Maliit** (`maliit-keyboard -r`) starts when `pi` logs in. This gives an on-screen keyboard for touch-only use.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Ownership for pi’s config
|
|
||||||
|
|
||||||
Runs `chown -R pi:pi /home/pi/.config` so all files under `pi`’s config directory are owned by `pi`. Ensures the desktop session runs as `pi` without permission issues.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## reTerminal DM: Seeed display/touch drivers
|
|
||||||
|
|
||||||
Installs the official Seeed drivers for the reTerminal DM so the display and touch work:
|
|
||||||
|
|
||||||
1. Clones **https://github.com/Seeed-Studio/seeed-linux-dtoverlays** into `/tmp/seeed-linux-dtoverlays` (`--depth 1` for a shallow clone).
|
|
||||||
2. Runs **`scripts/reTerminal.sh --device reTerminal-DM`** to install device-tree overlays and any required firmware/config for the reTerminal DM.
|
|
||||||
3. Removes the clone from `/tmp`.
|
|
||||||
|
|
||||||
These changes take effect after a reboot.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Screen rotation (portrait → landscape, 90° clockwise)
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
- **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** — Kernel cmdline rotates at boot; at login ~/.config/kanshi/config is written with the same transform (kanshi applies it; same as Control Center).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Dark theme
|
|
||||||
|
|
||||||
First-boot sets a dark GTK theme for user **pi** via **`~/.config/gtk-3.0/settings.ini`** with **`gtk-application-prefer-dark-theme=1`** and **`gtk-theme-name=PiXnoir`**. On older images use **Adwaita-dark** if PiXnoir is missing.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Reboot
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Customisation
|
|
||||||
|
|
||||||
- **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** — 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`.
|
|
||||||
@@ -1,448 +1,178 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Revision: 2
|
# Revision: 4
|
||||||
# First-boot: packages, Chromium kiosk, rpd-labwc + touch, reTerminal DM drivers.
|
# First-boot provisioning for reTerminal DM — two-phase process.
|
||||||
# Run by cloud-init (user-data-remote-gnss.example). Run as root.
|
# Phase 1 (cloud-init): hostname, packages, reTerminal DM drivers → reboot
|
||||||
# Optional: copy first-boot.conf.example to first-boot.conf and edit; it is loaded
|
# Phase 2 (systemd oneshot): kiosk, theme, LightDM, rotation → reboot
|
||||||
# from the script dir, /tmp/first-boot.conf, or /etc/cm4-provisioning/first-boot.conf.
|
#
|
||||||
# Set ENABLE_STEP_01=0 .. ENABLE_STEP_13=0 in config to disable a step (default: all enabled).
|
# 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
|
set -e
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
# --- Defaults (overridden by first-boot.conf if present) ---
|
# ── 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)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-.}")" 2>/dev/null && pwd)"
|
||||||
FILE_SERVER="${FILE_SERVER:-http://10.20.50.1:5000/files/first-boot}"
|
|
||||||
HOSTNAME="${HOSTNAME:-guard}"
|
|
||||||
PI_USER="${PI_USER:-pi}"
|
|
||||||
LOGFILE="${LOGFILE:-/var/log/first-boot.log}"
|
|
||||||
PLYMOUTH_DIR="${PLYMOUTH_DIR:-/usr/share/plymouth/themes/custom}"
|
|
||||||
WALLPAPER_PATH="${WALLPAPER_PATH:-/usr/share/rpd-wallpaper/splash.png}"
|
|
||||||
PACKAGES="${PACKAGES:-git chromium wmctrl openssh-server swaybg wlr-randr maliit-keyboard xinput-calibrator python3-gi python3-gi-cairo}"
|
|
||||||
LIGHTDM_SESSION="${LIGHTDM_SESSION:-rpd-labwc}"
|
|
||||||
GTK_THEME_NAME="${GTK_THEME_NAME:-PiXnoir}"
|
|
||||||
WALLPAPER_MODE="${WALLPAPER_MODE:-crop}"
|
|
||||||
DSI_ROTATE="${DSI_ROTATE:-90}"
|
|
||||||
SWIOTLB_SIZE="${SWIOTLB_SIZE:-65536}"
|
|
||||||
RETERMINAL_DEVICE="${RETERMINAL_DEVICE:-reTerminal-DM}"
|
|
||||||
RETERMINAL_REPO_URL="${RETERMINAL_REPO_URL:-https://github.com/Seeed-Studio/seeed-linux-dtoverlays}"
|
|
||||||
ONESHOT_SCRIPTS="${ONESHOT_SCRIPTS:-}"
|
|
||||||
|
|
||||||
# --- Load config file first (first found); then defaults apply only to unset vars ---
|
# ── Load config (required) ──────────────────────────────────────────────
|
||||||
FIRST_BOOT_CONF=""
|
FIRST_BOOT_CONF=""
|
||||||
for _f in "$SCRIPT_DIR/first-boot.conf" /tmp/first-boot.conf /etc/cm4-provisioning/first-boot.conf; do
|
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
|
if [[ -f "$_f" ]]; then
|
||||||
# shellcheck source=first-boot.conf.example
|
|
||||||
set -a && source "$_f" && set +a
|
set -a && source "$_f" && set +a
|
||||||
FIRST_BOOT_CONF="$_f"
|
FIRST_BOOT_CONF="$_f"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
if [[ -z "$FIRST_BOOT_CONF" ]]; then
|
||||||
# Step enable flags (1 = run, 0 = skip). Default all enabled; only set if not already set by config.
|
echo "[$(date -Iseconds)] ERROR: first-boot.conf not found. Searched:" >&2
|
||||||
ENABLE_STEP_01="${ENABLE_STEP_01:-1}"
|
echo " $SCRIPT_DIR/first-boot.conf" >&2
|
||||||
ENABLE_STEP_02="${ENABLE_STEP_02:-1}"
|
echo " /tmp/first-boot.conf" >&2
|
||||||
ENABLE_STEP_03="${ENABLE_STEP_03:-1}"
|
echo " /etc/cm4-provisioning/first-boot.conf" >&2
|
||||||
ENABLE_STEP_04="${ENABLE_STEP_04:-1}"
|
echo " $PERSISTENT_CONF" >&2
|
||||||
ENABLE_STEP_05="${ENABLE_STEP_05:-1}"
|
exit 1
|
||||||
ENABLE_STEP_06="${ENABLE_STEP_06:-1}"
|
|
||||||
ENABLE_STEP_07="${ENABLE_STEP_07:-1}"
|
|
||||||
ENABLE_STEP_08="${ENABLE_STEP_08:-1}"
|
|
||||||
ENABLE_STEP_09="${ENABLE_STEP_09:-1}"
|
|
||||||
ENABLE_STEP_10="${ENABLE_STEP_10:-1}"
|
|
||||||
ENABLE_STEP_11="${ENABLE_STEP_11:-1}"
|
|
||||||
ENABLE_STEP_12="${ENABLE_STEP_12:-1}"
|
|
||||||
ENABLE_STEP_13="${ENABLE_STEP_13:-1}"
|
|
||||||
|
|
||||||
# --- Derived paths ---
|
|
||||||
PI_HOME="/home/$PI_USER"
|
|
||||||
AUTOSTART="$PI_HOME/.config/autostart"
|
|
||||||
|
|
||||||
# Portal base URL for first-boot status API (derived from FILE_SERVER, e.g. http://10.20.50.1:5000)
|
|
||||||
if [[ "$FILE_SERVER" =~ ^(https?://[^/]+) ]]; then
|
|
||||||
PORTAL_BASE="${BASH_REMATCH[1]}"
|
|
||||||
else
|
|
||||||
PORTAL_BASE=""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --- Logging ---
|
# 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)] $*"; }
|
log() { echo "[$(date -Iseconds)] $*"; }
|
||||||
exec > >(tee -a "$LOGFILE") 2>&1
|
exec > >(tee -a "$LOGFILE") 2>&1
|
||||||
log "=== first-boot.sh started ==="
|
log "=== first-boot.sh phase $CURRENT_PHASE ==="
|
||||||
[[ -n "$FIRST_BOOT_CONF" ]] && log "Config loaded from $FIRST_BOOT_CONF" || log "Using built-in defaults (no config file found)"
|
log "Config: $FIRST_BOOT_CONF"
|
||||||
log "FILE_SERVER=$FILE_SERVER PI_USER=$PI_USER HOSTNAME=$HOSTNAME LOGFILE=$LOGFILE"
|
log "FILE_SERVER=$FILE_SERVER PI_USER=$PI_USER HOSTNAME=$HOSTNAME"
|
||||||
|
|
||||||
# --- Report status to portal (no-op if PORTAL_BASE empty; failures ignored) ---
|
# ── Portal status API ──────────────────────────────────────────────────
|
||||||
report_status() {
|
report_status() {
|
||||||
local phase="$1" message="$2" step="$3" step_name="$4" ip="$5"
|
|
||||||
[[ -z "$PORTAL_BASE" ]] && return 0
|
[[ -z "$PORTAL_BASE" ]] && return 0
|
||||||
local json="{\"phase\":\"${phase}\",\"message\":\"${message}\",\"step\":\"${step}\",\"step_name\":\"${step_name}\",\"hostname\":\"${HOSTNAME}\",\"ip\":\"${ip}\"}"
|
curl -s -X POST -H "Content-Type: application/json" \
|
||||||
curl -s -X POST -H "Content-Type: application/json" -d "$json" "${PORTAL_BASE}/api/first-boot-status" || true
|
-d "{\"phase\":\"$1\",\"message\":\"$2\",\"step\":\"$3\",\"step_name\":\"$4\",\"hostname\":\"$HOSTNAME\",\"ip\":\"$5\"}" \
|
||||||
|
"${PORTAL_BASE}/api/first-boot-status" || true
|
||||||
}
|
}
|
||||||
report_status "started" "First-boot started" "" "" ""
|
report_status "started" "Phase $CURRENT_PHASE started" "" "" ""
|
||||||
|
|
||||||
# --- Helper: run step if enabled (accepts "1" or "0"; strips CR/LF/whitespace) ---
|
# ── Helpers (available to step scripts) ─────────────────────────────────
|
||||||
run_step() {
|
|
||||||
local n="$1" name="$2"
|
|
||||||
local enable_var="ENABLE_STEP_${n}" val
|
|
||||||
val="${!enable_var}"
|
|
||||||
val="${val//[^01]/}"
|
|
||||||
val="${val:0:1}"
|
|
||||||
if [[ "$val" == "1" ]]; then
|
|
||||||
log "--- Step $n: $name ---"
|
|
||||||
"step_${n}_${name}" || return $?
|
|
||||||
report_status "running" "Step $n: $name completed" "$n" "$name" ""
|
|
||||||
else
|
|
||||||
log "--- Step $n: $name (disabled) ---"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Helper: install one-shot from FILE_SERVER ---
|
|
||||||
install_oneshot() {
|
install_oneshot() {
|
||||||
local name="$1"
|
local name="$1"
|
||||||
log "Installing one-shot: $name"
|
curl -fsSL "${FILE_SERVER}/${name}.sh" -o "$PI_HOME/${name}.sh" || { log "WARNING: ${name}.sh download failed"; return 1; }
|
||||||
if curl -fsSL "${FILE_SERVER}/${name}.sh" -o "$PI_HOME/${name}.sh"; then
|
curl -fsSL "${FILE_SERVER}/${name}.desktop" -o "$AUTOSTART/${name}.desktop" || { log "WARNING: ${name}.desktop download failed"; return 1; }
|
||||||
log "Downloaded ${name}.sh to $PI_HOME/${name}.sh"
|
chmod 755 "$PI_HOME/${name}.sh"
|
||||||
else
|
chmod 644 "$AUTOSTART/${name}.desktop"
|
||||||
log "WARNING: Could not download ${name}.sh"; return 1
|
|
||||||
fi
|
|
||||||
if curl -fsSL "${FILE_SERVER}/${name}.desktop" -o "$AUTOSTART/${name}.desktop"; then
|
|
||||||
log "Downloaded ${name}.desktop to $AUTOSTART/${name}.desktop"
|
|
||||||
else
|
|
||||||
log "WARNING: Could not download ${name}.desktop"; return 1
|
|
||||||
fi
|
|
||||||
chmod 755 "$PI_HOME/${name}.sh" && chmod 644 "$AUTOSTART/${name}.desktop"
|
|
||||||
chown "$PI_USER:$PI_USER" "$PI_HOME/${name}.sh" "$AUTOSTART/${name}.desktop"
|
chown "$PI_USER:$PI_USER" "$PI_HOME/${name}.sh" "$AUTOSTART/${name}.desktop"
|
||||||
log "One-shot $name installed (will run at first login and then remove itself)"
|
log "One-shot $name installed"
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Step 01: Hostname and /etc/hosts ---
|
# ── Download, source, and run a step ────────────────────────────────────
|
||||||
step_01_hostname() {
|
run_step() {
|
||||||
echo "$HOSTNAME" > /etc/hostname
|
local n="$1" name="$2"
|
||||||
hostnamectl set-hostname "$HOSTNAME" 2>/dev/null || true
|
local val; eval "val=\"\${ENABLE_STEP_${n}}\""
|
||||||
if ! grep -q "127.0.1.1[[:space:]]*$HOSTNAME" /etc/hosts 2>/dev/null; then
|
val="${val//[^01]/}"
|
||||||
sed -i "/127.0.1.1[[:space:]].*$/d" /etc/hosts
|
if [[ "${val:0:1}" != "1" ]]; then
|
||||||
echo "127.0.1.1 $HOSTNAME" >> /etc/hosts
|
log "--- Step $n: $name (disabled) ---"
|
||||||
fi
|
|
||||||
log "Hostname set to $HOSTNAME; /etc/hosts updated"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 02: 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 successfully"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 03: reTerminal DM drivers (Seeed) ---
|
|
||||||
step_03_reterminal_drivers() {
|
|
||||||
if [[ -z "$RETERMINAL_REPO_URL" ]]; then
|
|
||||||
log "Skipping reTerminal drivers (RETERMINAL_REPO_URL not set)"
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
REPO_DIR="/tmp/seeed-linux-dtoverlays"
|
log "--- Step $n: $name ---"
|
||||||
log "Cloning seeed-linux-dtoverlays to $REPO_DIR ..."
|
local f="$STEPS_DIR/${n}-${name}.sh"
|
||||||
git clone --depth 1 "$RETERMINAL_REPO_URL" "$REPO_DIR"
|
curl -fsSL "${FILE_SERVER}/steps/${n}-${name}.sh" -o "$f" \
|
||||||
log "Running reTerminal.sh --device $RETERMINAL_DEVICE from $REPO_DIR ..."
|
|| { log "ERROR: could not download step ${n}-${name}.sh"; return 1; }
|
||||||
if ( cd "$REPO_DIR" && "$REPO_DIR/scripts/reTerminal.sh" --device "$RETERMINAL_DEVICE" ); then
|
source "$f"
|
||||||
log "reTerminal DM drivers installed (reboot will apply)"
|
"step_${n}_${name}"
|
||||||
else
|
report_status "running" "Step $n: $name done" "$n" "$name" ""
|
||||||
log "WARNING: reTerminal.sh failed (see log above). Display/touch may still work; you can retry later with: cd $REPO_DIR && sudo ./scripts/reTerminal.sh --device $RETERMINAL_DEVICE"
|
|
||||||
fi
|
|
||||||
rm -rf "$REPO_DIR"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Step 04: Kiosk files from file server ---
|
# ── Phase transitions ──────────────────────────────────────────────────
|
||||||
step_04_kiosk_files() {
|
phase1_reboot() {
|
||||||
log "Creating $AUTOSTART"
|
mkdir -p "$STATE_DIR"
|
||||||
mkdir -p "$AUTOSTART" "$PI_HOME/Desktop" "$PI_HOME/.local/share/applications"
|
cp "$(readlink -f "${BASH_SOURCE[0]:-.}")" "$PERSISTENT_SCRIPT"
|
||||||
log "Downloading start-chromium.sh from ${FILE_SERVER}/start-chromium.sh"
|
chmod 755 "$PERSISTENT_SCRIPT"
|
||||||
curl -fsSL "${FILE_SERVER}/start-chromium.sh" -o "$PI_HOME/start-chromium.sh"
|
[[ -n "$FIRST_BOOT_CONF" && -f "$FIRST_BOOT_CONF" && "$FIRST_BOOT_CONF" != "$PERSISTENT_CONF" ]] \
|
||||||
log "Downloading chromium-kiosk.desktop from ${FILE_SERVER}/chromium-kiosk.desktop"
|
&& cp "$FIRST_BOOT_CONF" "$PERSISTENT_CONF"
|
||||||
curl -fsSL "${FILE_SERVER}/chromium-kiosk.desktop" -o "$AUTOSTART/chromium-kiosk.desktop"
|
echo "2" > "$PHASE_FILE"
|
||||||
chmod 755 "$PI_HOME/start-chromium.sh" && chmod 644 "$AUTOSTART/chromium-kiosk.desktop"
|
|
||||||
chown -R "$PI_USER:$PI_USER" "$PI_HOME/start-chromium.sh" "$AUTOSTART/chromium-kiosk.desktop"
|
|
||||||
# Launcher icon: desktop + application menu (start Chromium when killed)
|
|
||||||
if curl -fsSL "${FILE_SERVER}/chromium-kiosk-launcher.desktop" -o "$PI_HOME/chromium-kiosk-launcher.desktop" 2>/dev/null; then
|
|
||||||
sed "s|/home/pi|$PI_HOME|g" "$PI_HOME/chromium-kiosk-launcher.desktop" > "$PI_HOME/Desktop/Chromium Kiosk.desktop"
|
|
||||||
cp "$PI_HOME/chromium-kiosk-launcher.desktop" "$PI_HOME/.local/share/applications/"
|
|
||||||
sed -i "s|/home/pi|$PI_HOME|g" "$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"
|
|
||||||
log "Chromium kiosk launcher icon installed on desktop and in application menu"
|
|
||||||
fi
|
|
||||||
# 5 taps in top-right corner close Chromium (Python + Gtk)
|
|
||||||
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 top-right close Chromium installed (autostart)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
log "Kiosk files installed under $PI_HOME and $AUTOSTART"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 05: Boot splash and wallpaper ---
|
cat > "/etc/systemd/system/${PHASE2_SERVICE}.service" <<PHASE2SVC
|
||||||
step_05_splash_wallpaper() {
|
|
||||||
log "Creating $PLYMOUTH_DIR and /usr/share/rpd-wallpaper"
|
|
||||||
mkdir -p "$PLYMOUTH_DIR" /usr/share/rpd-wallpaper
|
|
||||||
if curl -fsSL "${FILE_SERVER}/splash.png" -o "$PLYMOUTH_DIR/splash.png"; then
|
|
||||||
log "Downloaded splash.png; copying to $WALLPAPER_PATH"
|
|
||||||
cp "$PLYMOUTH_DIR/splash.png" "$WALLPAPER_PATH"
|
|
||||||
chmod 644 "$PLYMOUTH_DIR/splash.png" "$WALLPAPER_PATH"
|
|
||||||
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 files (custom.plymouth, custom.script) installed"
|
|
||||||
else
|
|
||||||
log "WARNING: Could not download custom.plymouth/custom.script; boot splash theme may be incomplete"
|
|
||||||
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 (may take a moment) ..."
|
|
||||||
update-initramfs -u -k all 2>/dev/null || true
|
|
||||||
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 || log "WARNING: Could not download 99-wallpaper.conf"
|
|
||||||
for PROFILE in LXDE-pi default; do
|
|
||||||
PCMANFM_DESKTOP="$PI_HOME/.config/pcmanfm/$PROFILE/desktop-items-0.conf"
|
|
||||||
mkdir -p "$(dirname "$PCMANFM_DESKTOP")"
|
|
||||||
if [[ ! -f "$PCMANFM_DESKTOP" ]]; then
|
|
||||||
printf '%s\n' '[*]' "wallpaper=$WALLPAPER_PATH" "wallpaper_mode=$WALLPAPER_MODE" 'wallpaper_common=1' 'show_trash=0' > "$PCMANFM_DESKTOP"
|
|
||||||
else
|
|
||||||
grep -q '^wallpaper=' "$PCMANFM_DESKTOP" && sed -i "s|^wallpaper=.*|wallpaper=$WALLPAPER_PATH|" "$PCMANFM_DESKTOP" || echo "wallpaper=$WALLPAPER_PATH" >> "$PCMANFM_DESKTOP"
|
|
||||||
grep -q '^wallpaper_mode=' "$PCMANFM_DESKTOP" && sed -i "s/^wallpaper_mode=.*/wallpaper_mode=$WALLPAPER_MODE/" "$PCMANFM_DESKTOP" || echo "wallpaper_mode=$WALLPAPER_MODE" >> "$PCMANFM_DESKTOP"
|
|
||||||
grep -q '^show_trash=' "$PCMANFM_DESKTOP" && sed -i 's/^show_trash=.*/show_trash=0/' "$PCMANFM_DESKTOP" || echo 'show_trash=0' >> "$PCMANFM_DESKTOP"
|
|
||||||
fi
|
|
||||||
chown -R "$PI_USER:$PI_USER" "$(dirname "$PCMANFM_DESKTOP")"
|
|
||||||
done
|
|
||||||
log "Set desktop wallpaper via pcmanfm config (LXDE-pi and default)"
|
|
||||||
log "Splash and wallpaper set from file server"
|
|
||||||
else
|
|
||||||
log "WARNING: Could not download splash.png"
|
|
||||||
fi
|
|
||||||
# Optional: taskbar start button icon from file server (start-here.png)
|
|
||||||
if curl -fsSL "${FILE_SERVER}/start-here.png" -o /tmp/start-here.png; then
|
|
||||||
PIXTRIX_PLACES="/usr/share/icons/PiXtrix"
|
|
||||||
if [[ -d "$PIXTRIX_PLACES/32x32/places" ]]; then
|
|
||||||
for s in 16 24 32 48 64 96; do
|
|
||||||
DEST="$PIXTRIX_PLACES/${s}x${s}/places/start-here.png"
|
|
||||||
if [[ -d "$(dirname "$DEST")" ]]; then
|
|
||||||
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 && true
|
|
||||||
else
|
|
||||||
cp /tmp/start-here.png "$DEST"
|
|
||||||
fi
|
|
||||||
chmod 644 "$DEST"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
log "Taskbar start button icon (start-here.png) installed from file server"
|
|
||||||
else
|
|
||||||
log "WARNING: PiXtrix theme places dir not found; skipped taskbar icon"
|
|
||||||
fi
|
|
||||||
rm -f /tmp/start-here.png
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 06: LightDM session ---
|
|
||||||
step_06_lightdm() {
|
|
||||||
mkdir -p /etc/lightdm/lightdm.conf.d
|
|
||||||
if curl -fsSL "${FILE_SERVER}/99-default-session.conf" -o /etc/lightdm/lightdm.conf.d/99-default-session.conf 2>/dev/null; then
|
|
||||||
log "99-default-session.conf installed"
|
|
||||||
else
|
|
||||||
log "WARNING: Could not download 99-default-session.conf"
|
|
||||||
fi
|
|
||||||
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 "Patched /etc/lightdm/lightdm.conf to use $LIGHTDM_SESSION"
|
|
||||||
fi
|
|
||||||
# Delay LightDM on first boot after provisioning so reTerminal DM DSI panel has time to init (avoids black screen on first reboot)
|
|
||||||
mkdir -p /etc/systemd/system/lightdm.service.d
|
|
||||||
cat > /etc/systemd/system/cm4-await-display.service << 'AWAITSVC'
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Wait for reTerminal DM display on first boot after provisioning
|
Description=reTerminal DM first-boot phase 2
|
||||||
Before=lightdm.service
|
After=network-online.target
|
||||||
DefaultDependencies=no
|
Wants=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
RemainAfterExit=yes
|
ExecStart=$PERSISTENT_SCRIPT
|
||||||
ExecStart=/bin/sh -c 'if [ -f /var/lib/cm4-provisioning/await-display ]; then echo "Waiting 18s for DSI panel..."; sleep 18; rm -f /var/lib/cm4-provisioning/await-display; fi'
|
StandardOutput=journal+console
|
||||||
|
TimeoutStartSec=600
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=graphical.target
|
WantedBy=multi-user.target
|
||||||
AWAITSVC
|
PHASE2SVC
|
||||||
printf '%s\n' '[Unit]' 'After=cm4-await-display.service' > /etc/systemd/system/lightdm.service.d/99-await-display.conf
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable cm4-await-display.service 2>/dev/null || true
|
systemctl enable "${PHASE2_SERVICE}.service"
|
||||||
log "Installed cm4-await-display.service (delays LightDM on first reboot after provisioning)"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 07: Maliit on-screen keyboard ---
|
local ip; ip="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||||
step_07_maliit() {
|
report_status "phase1-done" "Phase 1 done, rebooting" "03" "reterminal_drivers" "$ip"
|
||||||
mkdir -p "$AUTOSTART" "$PI_HOME/.config"
|
log "=== Phase 1 done — rebooting to activate drivers ==="
|
||||||
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"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 08: Dark theme (GTK + taskbar) ---
|
|
||||||
step_08_dark_theme() {
|
|
||||||
# Use Adwaita-dark if PiXnoir is not installed (e.g. some Raspberry Pi OS images)
|
|
||||||
local theme_name="$GTK_THEME_NAME"
|
|
||||||
if [[ ! -d "/usr/share/themes/${theme_name}" ]]; then
|
|
||||||
if [[ -d /usr/share/themes/Adwaita-dark ]]; then
|
|
||||||
theme_name="Adwaita-dark"
|
|
||||||
log "PiXnoir not found; using Adwaita-dark for dark theme"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
GTK_SETTINGS="$PI_HOME/.config/gtk-3.0/settings.ini"
|
|
||||||
mkdir -p "$(dirname "$GTK_SETTINGS")"
|
|
||||||
# PiXtrix holds the custom start-here icon (installed in step 05); set so taskbar shows it
|
|
||||||
local icon_theme="PiXtrix"
|
|
||||||
[[ ! -d /usr/share/icons/PiXtrix ]] && icon_theme=""
|
|
||||||
if [[ ! -f "$GTK_SETTINGS" ]]; 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_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=$theme_name/" "$GTK_SETTINGS" || echo "gtk-theme-name=$theme_name" >> "$GTK_SETTINGS"
|
|
||||||
if [[ -n "$icon_theme" ]]; then
|
|
||||||
grep -q '^gtk-icon-theme-name=' "$GTK_SETTINGS" && sed -i "s/^gtk-icon-theme-name=.*/gtk-icon-theme-name=$icon_theme/" "$GTK_SETTINGS" || echo "gtk-icon-theme-name=$icon_theme" >> "$GTK_SETTINGS"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
log "Set dark theme ($theme_name) in gtk-3.0/settings.ini" && [[ -n "$icon_theme" ]] && log "Icon theme $icon_theme (custom start-here from step 05)"
|
|
||||||
# Ensure .profile exports prefer-dark for Libadwaita/GTK4 apps (session dark mode)
|
|
||||||
if [[ -f "$PI_HOME/.profile" ]] && ! grep -q 'ADW_DEBUG_COLOR_SCHEME\|COLOR_SCHEME.*prefer-dark' "$PI_HOME/.profile" 2>/dev/null; then
|
|
||||||
echo '' >> "$PI_HOME/.profile"
|
|
||||||
echo '# Prefer dark mode for GTK4/Libadwaita apps (cm4-provisioning first-boot)' >> "$PI_HOME/.profile"
|
|
||||||
echo 'export ADW_DEBUG_COLOR_SCHEME=prefer-dark' >> "$PI_HOME/.profile"
|
|
||||||
chown "$PI_USER:$PI_USER" "$PI_HOME/.profile"
|
|
||||||
log "Added prefer-dark export to .profile"
|
|
||||||
fi
|
|
||||||
# User GTK CSS: force start menu dropdown and all menus/popovers to full dark (matches Adwaita-dark palette)
|
|
||||||
if curl -fsSL "${FILE_SERVER}/gtk.css" -o "$PI_HOME/.config/gtk-3.0/gtk.css" 2>/dev/null; then
|
|
||||||
log "gtk.css installed (dark menu/popover override)"
|
|
||||||
fi
|
|
||||||
# Deploy dark taskbar (wf-panel-pi) so panel is dark and matches theme
|
|
||||||
local panel_conf="$PI_HOME/.config/wf-panel-pi"
|
|
||||||
mkdir -p "$panel_conf"
|
|
||||||
if curl -fsSL "${FILE_SERVER}/wf-panel-pi.ini" -o "$panel_conf/wf-panel-pi.ini" 2>/dev/null; then
|
|
||||||
sed -i "s|/home/pi|$PI_HOME|g" "$panel_conf/wf-panel-pi.ini"
|
|
||||||
if curl -fsSL "${FILE_SERVER}/panel-theme.css" -o "$panel_conf/panel-theme.css" 2>/dev/null; then
|
|
||||||
log "Taskbar theme (wf-panel-pi.ini, panel-theme.css) installed"
|
|
||||||
else
|
|
||||||
log "WARNING: Could not download panel-theme.css"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "WARNING: Could not download wf-panel-pi.ini (taskbar will use defaults)"
|
|
||||||
fi
|
|
||||||
chown -R "$PI_USER:$PI_USER" "$PI_HOME/.config"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 09: Re-apply splash and Plymouth theme ---
|
|
||||||
step_09_reapply_splash() {
|
|
||||||
CFG_PATH="/boot/firmware/config.txt"
|
|
||||||
[[ -f /boot/firmware/config.txt ]] || CFG_PATH="/boot/config.txt"
|
|
||||||
if [[ -f "$CFG_PATH" ]] && grep -q '^disable_splash=1' "$CFG_PATH"; then
|
|
||||||
sed -i 's/^disable_splash=1$/disable_splash=0/' "$CFG_PATH"
|
|
||||||
log "Set disable_splash=0 so Plymouth splash is shown"
|
|
||||||
fi
|
|
||||||
if [[ -f /etc/plymouth/plymouthd.conf ]]; then
|
|
||||||
sed -i '/^Theme=/d' /etc/plymouth/plymouthd.conf
|
|
||||||
sed -i '/^\[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
|
|
||||||
log "Plymouth theme set to custom only"
|
|
||||||
fi
|
|
||||||
log "Running update-initramfs to apply Plymouth theme ..."
|
|
||||||
update-initramfs -u -k all 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 10: Kernel cmdline (swiotlb + DSI rotation) ---
|
|
||||||
step_10_cmdline() {
|
|
||||||
CMDLINE_PATH="/boot/firmware/cmdline.txt"
|
|
||||||
[[ -f "$CMDLINE_PATH" ]] || CMDLINE_PATH="/boot/cmdline.txt"
|
|
||||||
if [[ -f "$CMDLINE_PATH" ]]; then
|
|
||||||
if [[ -n "$SWIOTLB_SIZE" ]] && ! grep -q 'swiotlb=' "$CMDLINE_PATH"; then
|
|
||||||
sed -i "s/rootwait/rootwait swiotlb=$SWIOTLB_SIZE/" "$CMDLINE_PATH"
|
|
||||||
log "Added swiotlb=$SWIOTLB_SIZE to kernel cmdline (vc4-drm / DSI)"
|
|
||||||
fi
|
|
||||||
if [[ -n "$DSI_ROTATE" ]] && ! grep -q 'video=DSI-1:rotate=' "$CMDLINE_PATH"; then
|
|
||||||
sed -i "s/\$/ video=DSI-1:rotate=$DSI_ROTATE/" "$CMDLINE_PATH"
|
|
||||||
log "Added video=DSI-1:rotate=$DSI_ROTATE to kernel cmdline (DSI rotation)"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 11: One-shot scripts ---
|
|
||||||
step_11_oneshots() {
|
|
||||||
if [[ -n "$DSI_ROTATE" ]]; then
|
|
||||||
log "Rotation is set via kernel cmdline (video=DSI-1:rotate=$DSI_ROTATE)"
|
|
||||||
# Install set-rotation-at-login to write ~/.config/kanshi/config at every login (same as Control Center)
|
|
||||||
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/set-rotation-at-login.desktop 2>/dev/null; then
|
|
||||||
sed "s|/home/pi|$PI_HOME|g" /tmp/set-rotation-at-login.desktop > "$AUTOSTART/set-rotation-at-login.desktop"
|
|
||||||
chown "$PI_USER:$PI_USER" "$AUTOSTART/set-rotation-at-login.desktop"
|
|
||||||
log "Installed set-rotation-at-login (re-applies rotation from cmdline every login)"
|
|
||||||
fi
|
|
||||||
rm -f /tmp/set-rotation-at-login.desktop
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
if [[ -n "$ONESHOT_SCRIPTS" ]]; then
|
|
||||||
for _name in $ONESHOT_SCRIPTS; do
|
|
||||||
install_oneshot "$_name" || true
|
|
||||||
done
|
|
||||||
else
|
|
||||||
log "No one-shot scripts configured (ONESHOT_SCRIPTS empty)"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 12: Log file permissions ---
|
|
||||||
step_12_log_permissions() {
|
|
||||||
chmod 666 "$LOGFILE"
|
|
||||||
log "Log file $LOGFILE is now appendable by user $PI_USER for one-shot scripts"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Step 13: Reboot ---
|
|
||||||
step_13_reboot() {
|
|
||||||
DEVICE_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
|
||||||
report_status "done" "First-boot complete" "13" "reboot" "$DEVICE_IP"
|
|
||||||
log "Device IP: ${DEVICE_IP:-unknown}"
|
|
||||||
# Flag for cm4-await-display.service: on next boot delay LightDM so DSI panel can init (avoids black screen)
|
|
||||||
mkdir -p /var/lib/cm4-provisioning
|
|
||||||
touch /var/lib/cm4-provisioning/await-display
|
|
||||||
log "=== first-boot.sh finished, rebooting ==="
|
|
||||||
reboot
|
reboot
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Main: run steps in order ---
|
phase2_cleanup() {
|
||||||
run_step 01 hostname
|
systemctl disable "${PHASE2_SERVICE}.service" 2>/dev/null || true
|
||||||
run_step 02 packages
|
rm -f "/etc/systemd/system/${PHASE2_SERVICE}.service"
|
||||||
run_step 03 reterminal_drivers
|
systemctl daemon-reload
|
||||||
run_step 04 kiosk_files
|
rm -f "$PHASE_FILE" "$PERSISTENT_SCRIPT" "$PERSISTENT_CONF"
|
||||||
run_step 05 splash_wallpaper
|
log "Phase 2 cleanup done"
|
||||||
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
|
|
||||||
run_step 13 reboot
|
|
||||||
|
|
||||||
# If reboot was disabled, still report done and device IP so the portal shows completion
|
# ── Main ────────────────────────────────────────────────────────────────
|
||||||
_step13_val="${ENABLE_STEP_13:-1}"
|
mkdir -p "$STEPS_DIR"
|
||||||
_step13_val="${_step13_val//[^01]/}"
|
|
||||||
_step13_val="${_step13_val:0:1}"
|
if [[ "$CURRENT_PHASE" == "1" ]]; then
|
||||||
if [[ "$_step13_val" != "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}')"
|
DEVICE_IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||||
report_status "done" "First-boot complete (reboot disabled)" "13" "reboot" "$DEVICE_IP"
|
report_status "done" "First-boot complete (reboot disabled)" "13" "reboot" "$DEVICE_IP"
|
||||||
log "Device IP: ${DEVICE_IP:-unknown}"
|
|
||||||
log "=== first-boot.sh finished (reboot disabled) ==="
|
log "=== first-boot.sh finished (reboot disabled) ==="
|
||||||
|
|
||||||
|
else
|
||||||
|
log "ERROR: Unknown phase '$CURRENT_PHASE'"; exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Revision: 2
|
|
||||||
# One-time fix for reTerminal DM after first-boot: splash, Plymouth theme, rotation, wallpaper.
|
|
||||||
# Run on the device as root (e.g. sudo bash fix-reterminal-display.sh).
|
|
||||||
# Or run over SSH: ssh pi@DEVICE_IP 'sudo bash -s' < fix-reterminal-display.sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
PI_USER="${PI_USER:-pi}"
|
|
||||||
PI_HOME="/home/$PI_USER"
|
|
||||||
CFG_PATH="/boot/firmware/config.txt"
|
|
||||||
[[ -f "$CFG_PATH" ]] || CFG_PATH="/boot/config.txt"
|
|
||||||
|
|
||||||
echo "=== Fixing boot splash (disable_splash=0) ==="
|
|
||||||
if [[ -f "$CFG_PATH" ]]; then
|
|
||||||
sed -i 's/^disable_splash=1$/disable_splash=0/' "$CFG_PATH" || true
|
|
||||||
grep -q '^disable_splash=' "$CFG_PATH" || echo 'disable_splash=0' >> "$CFG_PATH"
|
|
||||||
echo "Done. config: $(grep disable_splash "$CFG_PATH")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "=== Fixing Plymouth theme (custom only, no duplicate [Daemon]) ==="
|
|
||||||
if [[ -f /etc/plymouth/plymouthd.conf ]]; then
|
|
||||||
sed -i '/^Theme=/d' /etc/plymouth/plymouthd.conf
|
|
||||||
sed -i '/^\[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
|
|
||||||
echo "Done. plymouthd.conf Theme: $(grep Theme= /etc/plymouth/plymouthd.conf)"
|
|
||||||
fi
|
|
||||||
update-initramfs -u -k all 2>/dev/null || true
|
|
||||||
|
|
||||||
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 "=== Reboot to apply splash, initramfs and rotation ==="
|
|
||||||
echo " sudo reboot"
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#cloud-config
|
|
||||||
# Cloud-init user-data for reTerminal DM4 golden image (eMMC).
|
|
||||||
# Copy to the boot (FAT32) partition of your image as 'user-data'.
|
|
||||||
# Raspberry Pi OS uses NoCloud: meta-data, user-data, network-config on boot partition.
|
|
||||||
|
|
||||||
package_update: true
|
|
||||||
package_upgrade: false
|
|
||||||
|
|
||||||
packages:
|
|
||||||
- chromium-browser
|
|
||||||
- wmctrl
|
|
||||||
# - python3-pip # uncomment if you need Flask/other apps
|
|
||||||
|
|
||||||
# Optional: set hostname from serial or leave default
|
|
||||||
# hostname: reterminal-%s # %s = first column of meta-data instance-id if set
|
|
||||||
|
|
||||||
# Optional: enable I2C/SPI for reTerminal peripherals (LED, buzzer, etc.)
|
|
||||||
# Uncomment if your image does not already enable these:
|
|
||||||
# write_files:
|
|
||||||
# - path: /boot/firmware/config.txt.d/99-reterminal.txt
|
|
||||||
# content: |
|
|
||||||
# dtparam=i2c_arm=on
|
|
||||||
# dtparam=spi=on
|
|
||||||
|
|
||||||
# Run once on first boot (e.g. copy kiosk scripts, start Chromium on boot)
|
|
||||||
runcmd:
|
|
||||||
# Example: ensure Chromium kiosk autostart
|
|
||||||
# - systemctl enable chromium-kiosk
|
|
||||||
- cloud-init single --name cc_final_message
|
|
||||||
|
|
||||||
# Power state after first boot (optional)
|
|
||||||
# power_state:
|
|
||||||
# mode: reboot
|
|
||||||
# delay: 1
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
#cloud-config
|
|
||||||
# Example: create user (pi) with password, enable SSH, install KDE Plasma with touch options,
|
|
||||||
# set KDE as default GUI, and deploy Chromium kiosk autostart.
|
|
||||||
# Uses start-chromium.sh and chromium-kiosk.desktop from this project (emmc-provisioning/cloud-init/).
|
|
||||||
#
|
|
||||||
# 1. Generate a password hash on a Linux host:
|
|
||||||
# mkpasswd -m sha-512 'YourPassword'
|
|
||||||
# or: openssl passwd -6 'YourPassword'
|
|
||||||
# Paste the full output (e.g. $6$...) into the passwd: line below.
|
|
||||||
# 2. To use a different username than "pi", replace every "pi" in this file.
|
|
||||||
# 3. To change the kiosk URL, edit the --app=... line in the start-chromium.sh content below.
|
|
||||||
|
|
||||||
package_update: true
|
|
||||||
package_upgrade: false
|
|
||||||
|
|
||||||
packages:
|
|
||||||
- chromium-browser
|
|
||||||
- wmctrl
|
|
||||||
- openssh-server
|
|
||||||
# KDE Plasma + touchscreen
|
|
||||||
- kde-plasma-desktop
|
|
||||||
- maliit-keyboard
|
|
||||||
- xinput-calibrator
|
|
||||||
|
|
||||||
# Create user and set password (use hash from mkpasswd -m sha-512 or openssl passwd -6)
|
|
||||||
users:
|
|
||||||
- name: pi
|
|
||||||
groups: [adm, sudo, video]
|
|
||||||
lock_passwd: false
|
|
||||||
passwd: "$6$7xWGhGc6d1lJx1dU$4E8r1mkzVj51bjEbfzdP8wPxso..C36LbXkqU/X4oBGq94aGFMSrZb0PVI8zs/Om1Jm97/D..Apy2HTdCn3FV1"
|
|
||||||
shell: /bin/bash
|
|
||||||
|
|
||||||
# Enable SSH (allow password auth so you can log in with the user above)
|
|
||||||
write_files:
|
|
||||||
- path: /etc/ssh/sshd_config.d/99-cloud-init.conf
|
|
||||||
content: |
|
|
||||||
PasswordAuthentication yes
|
|
||||||
PermitRootLogin no
|
|
||||||
- path: /home/pi/start-chromium.sh
|
|
||||||
content: |
|
|
||||||
#!/bin/bash
|
|
||||||
export GNOME_KEYRING_CONTROL=""
|
|
||||||
export DISPLAY=:0
|
|
||||||
export GDK_BACKEND=x11
|
|
||||||
unset WAYLAND_DISPLAY
|
|
||||||
for i in {1..60}; do
|
|
||||||
if xset q >/dev/null 2>&1 || [ -n "$DISPLAY" ]; then
|
|
||||||
if pgrep -x plasma_session >/dev/null 2>&1 || pgrep -x kwin_x11 >/dev/null 2>&1 || pgrep -x pcmanfm >/dev/null 2>&1 || pgrep -x lxsession >/dev/null 2>&1; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
sleep 0.5
|
|
||||||
done
|
|
||||||
sleep 5
|
|
||||||
/usr/bin/chromium --start-fullscreen --noerrdialogs --disable-infobars --disable-session-crashed-bubble --disable-restore-session-state --no-first-run --password-store=basic --use-mock-keychain --ozone-platform=x11 --disable-features=UseChromeOSDirectVideoDecoder --app=http://127.0.0.1:8080 &
|
|
||||||
sleep 3
|
|
||||||
for i in {1..10}; do
|
|
||||||
WINDOW_ID=$(wmctrl -l 2>/dev/null | grep -i chromium | head -1 | awk '{print $1}')
|
|
||||||
if [ -n "$WINDOW_ID" ]; then
|
|
||||||
wmctrl -i -r "$WINDOW_ID" -b add,fullscreen 2>/dev/null
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 0.5
|
|
||||||
done
|
|
||||||
wait
|
|
||||||
owner: pi:pi
|
|
||||||
permissions: "0755"
|
|
||||||
- path: /home/pi/.config/autostart/chromium-kiosk.desktop
|
|
||||||
content: |
|
|
||||||
[Desktop Entry]
|
|
||||||
Type=Application
|
|
||||||
Name=Chromium Fullscreen
|
|
||||||
Exec=/home/pi/start-chromium.sh
|
|
||||||
Hidden=false
|
|
||||||
NoDisplay=false
|
|
||||||
X-GNOME-Autostart-enabled=true
|
|
||||||
owner: pi:pi
|
|
||||||
permissions: "0644"
|
|
||||||
# KDE Plasma: switch to KDE as default session (X11 for Chromium compatibility)
|
|
||||||
- path: /etc/lightdm/lightdm.conf.d/99-default-session.conf
|
|
||||||
content: |
|
|
||||||
[Seat:*]
|
|
||||||
user-session=plasmax11
|
|
||||||
permissions: "0644"
|
|
||||||
# KDE touch-friendly: UI scale and input (for pi after first login)
|
|
||||||
- path: /home/pi/.config/kdeglobals
|
|
||||||
content: |
|
|
||||||
[General]
|
|
||||||
ForceFontDPI=120
|
|
||||||
owner: pi:pi
|
|
||||||
permissions: "0644"
|
|
||||||
- path: /home/pi/.config/kwinrc
|
|
||||||
content: |
|
|
||||||
[Windows]
|
|
||||||
BorderlessMaximizedWindows=true
|
|
||||||
[Plugins]
|
|
||||||
touchpointsEnabled=true
|
|
||||||
owner: pi:pi
|
|
||||||
permissions: "0644"
|
|
||||||
# Start on-screen keyboard (maliit) with KDE for touch input
|
|
||||||
- path: /home/pi/.config/autostart/maliit-keyboard.desktop
|
|
||||||
content: |
|
|
||||||
[Desktop Entry]
|
|
||||||
Type=Application
|
|
||||||
Name=Maliit Keyboard
|
|
||||||
Exec=maliit-keyboard -r
|
|
||||||
X-GNOME-Autostart-enabled=true
|
|
||||||
owner: pi:pi
|
|
||||||
permissions: "0644"
|
|
||||||
|
|
||||||
runcmd:
|
|
||||||
- mkdir -p /home/pi/.config/autostart
|
|
||||||
- chown -R pi:pi /home/pi/.config
|
|
||||||
# Set KDE Plasma (X11) as default session so next boot uses KDE
|
|
||||||
- update-alternatives --set x-session-manager /usr/bin/startplasma-x11 2>/dev/null || true
|
|
||||||
- systemctl enable ssh
|
|
||||||
- systemctl start ssh
|
|
||||||
- cloud-init single --name cc_final_message
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#cloud-config
|
|
||||||
# Example user-data that downloads and runs a script from a file server.
|
|
||||||
# Use this so you can change first-boot behaviour without rebuilding the golden image.
|
|
||||||
# Copy to boot partition as 'user-data' and set BOOTSTRAP_URL to your script URL.
|
|
||||||
|
|
||||||
package_update: true
|
|
||||||
package_upgrade: false
|
|
||||||
|
|
||||||
packages:
|
|
||||||
- curl # or wget if you prefer
|
|
||||||
|
|
||||||
# Download script from file server and run it (runcmd runs after network is up)
|
|
||||||
runcmd:
|
|
||||||
- curl -fsSL "http://YOUR_FILE_SERVER/provisioning/bootstrap.sh" -o /tmp/bootstrap.sh
|
|
||||||
- chmod +x /tmp/bootstrap.sh
|
|
||||||
- /tmp/bootstrap.sh
|
|
||||||
# Optional: remove after run
|
|
||||||
# - rm -f /tmp/bootstrap.sh
|
|
||||||
|
|
||||||
# Optional: finish cloud-init
|
|
||||||
# - cloud-init single --name cc_final_message
|
|
||||||
@@ -1,19 +1,22 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Revision: 2
|
# Revision: 4
|
||||||
# Sync portal (file server) content from the repo to the LXC.
|
# Sync portal (file server) content from the repo to the LXC.
|
||||||
# Updates /var/lib/cm4-provisioning/portal-files/ so first-boot and the
|
|
||||||
# dashboard /files/ serve the same scripts and assets as in the repo.
|
|
||||||
#
|
#
|
||||||
# Files first-boot.sh downloads from FILE_SERVER (../files/first-boot/):
|
# All files that first-boot.sh downloads live in cloud-init/fileserver/.
|
||||||
# Required: start-chromium.sh, chromium-kiosk.desktop, splash.png,
|
# This script rsyncs that folder to the LXC's portal-files directory so
|
||||||
# custom.plymouth, custom.script, 99-wallpaper.conf,
|
# the dashboard /files/ endpoint serves them.
|
||||||
# 99-default-session.conf, maliit-keyboard.desktop
|
#
|
||||||
# One-shots (if ONESHOT_SCRIPTS set): <name>.sh + <name>.desktop
|
# Expected layout on the LXC after sync:
|
||||||
# e.g. 01-set-rotation-once.sh, 01-set-rotation-once.desktop,
|
# /var/lib/cm4-provisioning/portal-files/
|
||||||
# 02-set-wallpaper-once.sh, 02-set-wallpaper-once.desktop
|
# ├── first-boot.sh ← cloud-init runcmd downloads this
|
||||||
# Optional: first-boot.conf (downloaded to /tmp by cloud-init runcmd)
|
# ├── first-boot.conf ← cloud-init runcmd downloads this (required)
|
||||||
# Note: splash.png is not in the repo; add it to plymouth-custom/ or upload
|
# ├── first-boot.conf.example ← reference
|
||||||
# via the portal if you want a custom boot splash.
|
# └── first-boot/ ← FILE_SERVER points here
|
||||||
|
# ├── steps/
|
||||||
|
# │ ├── 01-hostname.sh … 13-reboot.sh
|
||||||
|
# ├── start-chromium.sh
|
||||||
|
# ├── splash.png
|
||||||
|
# └── ...
|
||||||
#
|
#
|
||||||
# Usage: ./sync-portal-files-to-lxc.sh [user@lxc_ip]
|
# Usage: ./sync-portal-files-to-lxc.sh [user@lxc_ip]
|
||||||
# Example: ./sync-portal-files-to-lxc.sh root@10.130.60.141
|
# Example: ./sync-portal-files-to-lxc.sh root@10.130.60.141
|
||||||
@@ -23,45 +26,109 @@ LXC="${1:-root@10.130.60.141}"
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
REPO_DIR="$(dirname "$SCRIPT_DIR")"
|
REPO_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
CLOUDINIT_DIR="$REPO_DIR/cloud-init"
|
CLOUDINIT_DIR="$REPO_DIR/cloud-init"
|
||||||
|
FILESERVER_DIR="$CLOUDINIT_DIR/fileserver"
|
||||||
REMOTE_PORTAL="/var/lib/cm4-provisioning/portal-files"
|
REMOTE_PORTAL="/var/lib/cm4-provisioning/portal-files"
|
||||||
REMOTE_FIRST_BOOT="${REMOTE_PORTAL}/first-boot"
|
REMOTE_FIRST_BOOT="${REMOTE_PORTAL}/first-boot"
|
||||||
|
|
||||||
if [[ ! -d "$CLOUDINIT_DIR" ]]; then
|
# Files we sync to the portal root (outside first-boot/)
|
||||||
echo "Error: cloud-init dir not found: $CLOUDINIT_DIR"
|
PORTAL_ROOT_FILES=(first-boot.sh first-boot.conf first-boot.conf.example)
|
||||||
|
|
||||||
|
# ── Validate local files ────────────────────────────────────────────────
|
||||||
|
if [[ ! -d "$FILESERVER_DIR" ]]; then
|
||||||
|
echo "Error: fileserver dir not found: $FILESERVER_DIR"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
if [[ ! -f "$CLOUDINIT_DIR/first-boot.sh" ]]; then
|
||||||
|
echo "Error: first-boot.sh not found in $CLOUDINIT_DIR"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$CLOUDINIT_DIR/first-boot.conf" ]]; then
|
||||||
|
echo "Warning: first-boot.conf not found — first-boot.sh requires it!"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Syncing portal files to $LXC ($REMOTE_PORTAL) ..."
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ Sync portal files → $LXC"
|
||||||
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── Ensure rsync on LXC ────────────────────────────────────────────────
|
||||||
ssh "$LXC" "command -v rsync >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y rsync)"
|
ssh "$LXC" "command -v rsync >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y rsync)"
|
||||||
ssh "$LXC" "mkdir -p $REMOTE_FIRST_BOOT"
|
ssh "$LXC" "mkdir -p $REMOTE_FIRST_BOOT"
|
||||||
|
|
||||||
# --- Portal root (URL /files/...) ---
|
# ── Dry-run: show what will change ──────────────────────────────────────
|
||||||
# first-boot.sh: cloud-init runcmd downloads this
|
echo "── Preview (dry-run) ──────────────────────────────────────────"
|
||||||
rsync -avz "$CLOUDINIT_DIR/first-boot.sh" "$LXC:$REMOTE_PORTAL/"
|
echo ""
|
||||||
# Optional config: cloud-init can download to /tmp/first-boot.conf before running first-boot.sh
|
echo "Portal root ($REMOTE_PORTAL/):"
|
||||||
rsync -avz "$CLOUDINIT_DIR/first-boot.conf.example" "$LXC:$REMOTE_PORTAL/"
|
for f in "${PORTAL_ROOT_FILES[@]}"; do
|
||||||
[[ -f "$CLOUDINIT_DIR/first-boot.conf" ]] && rsync -avz "$CLOUDINIT_DIR/first-boot.conf" "$LXC:$REMOTE_PORTAL/" || true
|
if [[ -f "$CLOUDINIT_DIR/$f" ]]; then
|
||||||
|
echo " ✓ $f"
|
||||||
|
else
|
||||||
|
echo " ✗ $f (not found locally, will skip)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo "first-boot/ assets ($REMOTE_FIRST_BOOT/):"
|
||||||
|
CHANGES=$(rsync -avzn --delete "$FILESERVER_DIR/" "$LXC:$REMOTE_FIRST_BOOT/" 2>&1 | grep -v '^\(sending\|sent\|total\|$\|building\|\.d\.\.\.\.\.\.\.\.\.\)' | head -40)
|
||||||
|
if [[ -z "$CHANGES" ]]; then
|
||||||
|
echo " (no changes)"
|
||||||
|
else
|
||||||
|
echo "$CHANGES" | sed 's/^/ /'
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
# --- first-boot/ (URL /files/first-boot/...) ---
|
read -rp "Proceed with sync? [Y/n] " CONFIRM
|
||||||
# Config files: LightDM, Maliit, Chromium kiosk, one-shot .desktop files
|
CONFIRM="${CONFIRM:-Y}"
|
||||||
rsync -avz --exclude='README.md' \
|
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
|
||||||
"$CLOUDINIT_DIR/config-files/" \
|
echo "Aborted."
|
||||||
"$LXC:$REMOTE_FIRST_BOOT/"
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Kiosk and scripts
|
# ── Sync portal root files ──────────────────────────────────────────────
|
||||||
rsync -avz \
|
echo ""
|
||||||
"$CLOUDINIT_DIR/start-chromium.sh" \
|
echo "── Syncing portal root files ──────────────────────────────────"
|
||||||
"$CLOUDINIT_DIR/five-tap-close-chromium.py" \
|
for f in "${PORTAL_ROOT_FILES[@]}"; do
|
||||||
"$CLOUDINIT_DIR/01-set-rotation-once.sh" \
|
if [[ -f "$CLOUDINIT_DIR/$f" ]]; then
|
||||||
"$CLOUDINIT_DIR/02-set-wallpaper-once.sh" \
|
rsync -avz "$CLOUDINIT_DIR/$f" "$LXC:$REMOTE_PORTAL/"
|
||||||
"$CLOUDINIT_DIR/set-rotation-at-login.sh" \
|
fi
|
||||||
"$CLOUDINIT_DIR/fix-reterminal-display.sh" \
|
done
|
||||||
"$LXC:$REMOTE_FIRST_BOOT/"
|
|
||||||
|
|
||||||
# Plymouth theme (custom.plymouth, custom.script; add splash.png to this dir or upload via portal)
|
# ── Sync fileserver/ → first-boot/ (with --delete) ──────────────────────
|
||||||
rsync -avz \
|
echo ""
|
||||||
"$CLOUDINIT_DIR/files-from-guard/plymouth-custom/" \
|
echo "── Syncing fileserver/ → first-boot/ ──────────────────────────"
|
||||||
"$LXC:$REMOTE_FIRST_BOOT/"
|
rsync -avz --delete "$FILESERVER_DIR/" "$LXC:$REMOTE_FIRST_BOOT/"
|
||||||
|
|
||||||
|
# ── Check for extra files in portal root ────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo "── Checking for extra files in portal root ────────────────────"
|
||||||
|
|
||||||
|
REMOTE_FILES=$(ssh "$LXC" "find $REMOTE_PORTAL -maxdepth 1 -not -path $REMOTE_PORTAL -printf '%f\n' 2>/dev/null" | sort)
|
||||||
|
EXPECTED_FILES=$(printf '%s\n' "${PORTAL_ROOT_FILES[@]}" "first-boot" | sort)
|
||||||
|
EXTRA_FILES=$(comm -23 <(echo "$REMOTE_FILES") <(echo "$EXPECTED_FILES"))
|
||||||
|
|
||||||
|
if [[ -z "$EXTRA_FILES" ]]; then
|
||||||
|
echo " No extra files found. Portal root is clean."
|
||||||
|
else
|
||||||
|
echo " Extra files/folders found in $REMOTE_PORTAL/:"
|
||||||
|
echo "$EXTRA_FILES" | while read -r f; do
|
||||||
|
SIZE=$(ssh "$LXC" "du -sh '$REMOTE_PORTAL/$f' 2>/dev/null | cut -f1" 2>/dev/null || echo "?")
|
||||||
|
TYPE="file"
|
||||||
|
ssh "$LXC" "[ -d '$REMOTE_PORTAL/$f' ]" 2>/dev/null && TYPE="dir"
|
||||||
|
echo " $f ($TYPE, $SIZE)"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
read -rp " Remove extra files? [y/N] " REMOVE
|
||||||
|
if [[ "$REMOVE" =~ ^[Yy]$ ]]; then
|
||||||
|
echo "$EXTRA_FILES" | while read -r f; do
|
||||||
|
echo " Removing: $f"
|
||||||
|
ssh "$LXC" "rm -rf '$REMOTE_PORTAL/$f'"
|
||||||
|
done
|
||||||
|
echo " Done."
|
||||||
|
else
|
||||||
|
echo " Kept extra files."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "══════════════════════════════════════════════════════════════"
|
||||||
echo "Done. Portal files at http://$(echo "$LXC" | cut -d@ -f2):5000/files/"
|
echo "Done. Portal files at http://$(echo "$LXC" | cut -d@ -f2):5000/files/"
|
||||||
echo "Note: Add splash.png to $REMOTE_FIRST_BOOT/ (or plymouth-custom/) if you want a custom boot splash."
|
echo "══════════════════════════════════════════════════════════════"
|
||||||
|
|||||||
Reference in New Issue
Block a user