Compare commits
2
Commits
ccf73d41bd
...
c1370edc3e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1370edc3e | ||
|
|
21a0768c8c |
@@ -1,6 +1,6 @@
|
|||||||
# Ubuntu Based Kiosk
|
# Ubuntu Based Kiosk
|
||||||
|
|
||||||
**Current Version:** 1.0.3 (check script header for latest version)
|
**Current Version:** 2.0.0 (check script header for latest version)
|
||||||
**Built with Claude Sonnet 4.6 AI assistance**
|
**Built with Claude Sonnet 4.6 AI assistance**
|
||||||
**License:** GPL v3 - Keep derivatives open source
|
**License:** GPL v3 - Keep derivatives open source
|
||||||
**Repository:** https://github.com/outis1one/ubuntu-based-kiosk/
|
**Repository:** https://github.com/outis1one/ubuntu-based-kiosk/
|
||||||
@@ -47,12 +47,9 @@ Home/office kiosk for reusing old hardware, displaying:
|
|||||||
# Configure WiFi if no ethernet available
|
# Configure WiFi if no ethernet available
|
||||||
# Enable SSH during installation
|
# Enable SSH during installation
|
||||||
|
|
||||||
# Download and run the latest installer
|
# Download and run the installer
|
||||||
LATEST=$(curl -fsSL https://api.github.com/repos/outis1one/ubuntu-based-kiosk/contents \
|
wget https://github.com/outis1one/ubuntu-based-kiosk/raw/main/ubuntu-based-kiosk.sh
|
||||||
| grep -oP 'ubuntu-based-kiosk-v[0-9.]+\.sh' \
|
chmod +x ubuntu-based-kiosk.sh && ./ubuntu-based-kiosk.sh
|
||||||
| grep -v beta | sort -V | tail -1)
|
|
||||||
wget "https://github.com/outis1one/ubuntu-based-kiosk/raw/main/$LATEST"
|
|
||||||
chmod +x "$LATEST" && ./"$LATEST"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The installer will guide you through configuration during setup.
|
The installer will guide you through configuration during setup.
|
||||||
@@ -68,13 +65,10 @@ If the kiosk machine can't reach GitHub directly (no browser, restrictive proxy,
|
|||||||
**On a machine with internet access:**
|
**On a machine with internet access:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Option A: download just the latest installer script
|
# Option A: download just the installer script
|
||||||
LATEST=$(curl -fsSL https://api.github.com/repos/outis1one/ubuntu-based-kiosk/contents \
|
wget https://github.com/outis1one/ubuntu-based-kiosk/raw/main/ubuntu-based-kiosk.sh
|
||||||
| grep -oP 'ubuntu-based-kiosk-v[0-9.]+\.sh' \
|
|
||||||
| grep -v beta | sort -V | tail -1)
|
|
||||||
wget "https://github.com/outis1one/ubuntu-based-kiosk/raw/main/$LATEST"
|
|
||||||
|
|
||||||
# Option B: download the whole repo as a ZIP (includes all installer versions and addon scripts)
|
# Option B: download the whole repo as a ZIP (includes install.sh, addon scripts, and older archived installer versions)
|
||||||
wget https://github.com/outis1one/ubuntu-based-kiosk/archive/refs/heads/main.zip
|
wget https://github.com/outis1one/ubuntu-based-kiosk/archive/refs/heads/main.zip
|
||||||
unzip main.zip
|
unzip main.zip
|
||||||
```
|
```
|
||||||
@@ -83,8 +77,8 @@ Copy the downloaded `.sh` file (or the extracted ZIP contents) to a USB drive, t
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Mount the USB drive and copy the script over, then:
|
# Mount the USB drive and copy the script over, then:
|
||||||
chmod +x ubuntu-based-kiosk-v*.sh
|
chmod +x ubuntu-based-kiosk.sh
|
||||||
./ubuntu-based-kiosk-v*.sh
|
./ubuntu-based-kiosk.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The kiosk machine still needs a working internet connection (ethernet, or WiFi configured during Ubuntu install) for the script to complete.
|
The kiosk machine still needs a working internet connection (ethernet, or WiFi configured during Ubuntu install) for the script to complete.
|
||||||
@@ -160,7 +154,7 @@ After running the addon it prints the full server-side setup, but the summary is
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
ssh user@kiosk-machine
|
ssh user@kiosk-machine
|
||||||
./$(ls ubuntu-based-kiosk-v*.sh | sort -V | tail -1)
|
./ubuntu-based-kiosk.sh
|
||||||
# Addons → 5. Authelia Auto-Login
|
# Addons → 5. Authelia Auto-Login
|
||||||
# Enter your Authelia URL, username, and password when prompted
|
# Enter your Authelia URL, username, and password when prompted
|
||||||
```
|
```
|
||||||
@@ -591,7 +585,7 @@ smb://WORKGROUP/COMPUTER/PrinterName
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run installer script again to access menu
|
# Run installer script again to access menu
|
||||||
./$(ls ubuntu-based-kiosk-v*.sh | sort -V | tail -1)
|
./ubuntu-based-kiosk.sh
|
||||||
|
|
||||||
# Menu structure:
|
# Menu structure:
|
||||||
# 1. Core Settings - Sites, WiFi, schedules, passwords, full reinstall, complete uninstall
|
# 1. Core Settings - Sites, WiFi, schedules, passwords, full reinstall, complete uninstall
|
||||||
@@ -606,7 +600,7 @@ The Easy Asterisk Intercom addon provides voice communication capabilities to yo
|
|||||||
|
|
||||||
**Access the addon menu:**
|
**Access the addon menu:**
|
||||||
```bash
|
```bash
|
||||||
./$(ls ubuntu-based-kiosk-v*.sh | sort -V | tail -1)
|
./ubuntu-based-kiosk.sh
|
||||||
# Select: 2) Addons
|
# Select: 2) Addons
|
||||||
# Then: 4) Easy Asterisk Intercom
|
# Then: 4) Easy Asterisk Intercom
|
||||||
```
|
```
|
||||||
@@ -636,7 +630,7 @@ asterisk -rvvv
|
|||||||
systemctl restart asterisk
|
systemctl restart asterisk
|
||||||
|
|
||||||
# Configure intercom (rerun installation to update)
|
# Configure intercom (rerun installation to update)
|
||||||
./$(ls ubuntu-based-kiosk-v*.sh | sort -V | tail -1)
|
./ubuntu-based-kiosk.sh
|
||||||
# Select: 2) Addons → 4) Easy Asterisk Intercom
|
# Select: 2) Addons → 4) Easy Asterisk Intercom
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1020,7 +1014,7 @@ Full system cleanup that removes all kiosk components and restores the system to
|
|||||||
**Access:**
|
**Access:**
|
||||||
```bash
|
```bash
|
||||||
# Core Settings menu → option 11
|
# Core Settings menu → option 11
|
||||||
./$(ls ubuntu-based-kiosk-v*.sh | sort -V | tail -1)
|
./ubuntu-based-kiosk.sh
|
||||||
# Choose: Core Settings → Complete Uninstall
|
# Choose: Core Settings → Complete Uninstall
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1168,11 +1162,55 @@ See the LICENSE file in the repository for full terms.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Modular Management (new, in progress)
|
||||||
|
|
||||||
|
The 12,000+ line single-file installer works, but every menu lives in the
|
||||||
|
same file as everything else, which makes small changes risky. We're
|
||||||
|
pulling the *menu system* out into small, independently editable files as
|
||||||
|
groundwork for the planned web-based GUI (same modules will back both the
|
||||||
|
terminal menu and the web UI, so they can't drift apart).
|
||||||
|
|
||||||
|
**What's here so far:**
|
||||||
|
- `lib/menu.sh` — a generic numbered-menu framework (auto-numbers entries,
|
||||||
|
always offers `0` to exit/return, validated input helpers). Menu files
|
||||||
|
just declare their labels and handler functions; they don't hand-roll
|
||||||
|
`echo`/`case` loops.
|
||||||
|
- `lib/config.sh` — the single place that reads/writes `config.json`.
|
||||||
|
- `menus/sites.sh` — **Sites & Page Timing**, fully migrated: add, edit,
|
||||||
|
delete, and reorder pages, and set the duration/timing mode
|
||||||
|
(auto-rotate / manual / hidden) and home page — as a working proof of
|
||||||
|
concept for this approach.
|
||||||
|
- `menus/display.sh` — **Display & Interaction**: touch gesture mode,
|
||||||
|
link navigation security, and the on-screen pause/keyboard/navigation
|
||||||
|
button toggles. A second proof of concept covering a different menu
|
||||||
|
shape (settings toggles vs. the list CRUD in Sites).
|
||||||
|
- `install.sh` — entry point for the modular tool. Run it against an
|
||||||
|
*already-installed* kiosk:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/outis1one/ubuntu-based-kiosk/
|
||||||
|
cd ubuntu-based-kiosk
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This does **not** yet replace first-time installation — that's still the
|
||||||
|
single-file script above (`Quick Install`). The rest of Core
|
||||||
|
Settings/Addons/Advanced will move into `menus/*.sh` the same way, one
|
||||||
|
menu at a time, and `install.sh` will eventually take over the whole
|
||||||
|
`show_main_menu` from the legacy script.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Project Status & Future Plans
|
## Project Status & Future Plans
|
||||||
|
|
||||||
**Current Version:** 1.0.3
|
**Current Version:** 2.0.0
|
||||||
|
|
||||||
**Recent Updates (v1.0.3):**
|
**Recent Updates (v2.0.0):**
|
||||||
|
- **Modular management path:** new `lib/menu.sh` (reusable numbered-menu framework: auto-numbered entries, `0` always exits/returns) and `lib/config.sh` (single load/save for `config.json`), with menus migrating into `menus/*.sh` one at a time — **Sites & Page Timing** and **Display & Interaction** are migrated so far. Run via `./install.sh` after cloning the repo, against an already-installed kiosk (see "Modular Management" below). Groundwork for the planned web-based GUI, which will share this same `lib/config.sh` layer.
|
||||||
|
- **Bug fix:** the old Sites menu could save `config.json` without first loading swipe/navigation/lockout settings, silently resetting them to script defaults.
|
||||||
|
- **Bug fix:** reordering sites had an off-by-one that left the moved site one slot short of the requested position.
|
||||||
|
- **Renamed installer:** the main script is now `ubuntu-based-kiosk.sh` (no version number in the filename), updated in place going forward. Released versions are tracked via git history and this changelog instead of the filename; older `ubuntu-based-kiosk-v*.sh` / `install_kiosk_*.sh` files remain in the repo as archived releases.
|
||||||
|
|
||||||
|
**Previous (v1.0.3):**
|
||||||
- **HDMI/external display mirroring:** any connected display beyond the primary (e.g. HDMI-out to a monitor/TV) is now mirrored automatically at the primary's exact resolution — generating a custom `cvt` mode if the external display doesn't natively list it — both at kiosk login/boot and live on plug/unplug via a new udev-triggered `kiosk-hotplug.service`. Previously the external output was left inactive even when detected by X, and would otherwise mirror at its own native resolution instead of matching the kiosk panel
|
- **HDMI/external display mirroring:** any connected display beyond the primary (e.g. HDMI-out to a monitor/TV) is now mirrored automatically at the primary's exact resolution — generating a custom `cvt` mode if the external display doesn't natively list it — both at kiosk login/boot and live on plug/unplug via a new udev-triggered `kiosk-hotplug.service`. Previously the external output was left inactive even when detected by X, and would otherwise mirror at its own native resolution instead of matching the kiosk panel
|
||||||
- **HDMI audio routing:** audio now follows the same hotplug event — the default PipeWire sink automatically switches to the HDMI audio output when an external display is connected/mirrored, and back to the built-in sink when it's disconnected (`kiosk-audio-route.sh`)
|
- **HDMI audio routing:** audio now follows the same hotplug event — the default PipeWire sink automatically switches to the HDMI audio output when an external display is connected/mirrored, and back to the built-in sink when it's disconnected (`kiosk-audio-route.sh`)
|
||||||
- **Package install:** installer now also installs `net-tools` and `ncdu` (alongside the already-installed `curl` and `git`)
|
- **Package install:** installer now also installs `net-tools` and `ncdu` (alongside the already-installed `curl` and `git`)
|
||||||
|
|||||||
Executable
+79
@@ -0,0 +1,79 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# install.sh - Modular management entry point for Ubuntu Based Kiosk.
|
||||||
|
#
|
||||||
|
# This is NOT yet the full system installer - that is still the big
|
||||||
|
# single-file script (ubuntu-based-kiosk-v1.0.3.sh etc) documented in
|
||||||
|
# Readme.md, and first-time provisioning of a new kiosk still goes through
|
||||||
|
# it. This entry point is the start of pulling the *menu system* out of
|
||||||
|
# that 12k-line file into small, independently editable modules under
|
||||||
|
# lib/ and menus/, so a change to (say) the Sites menu can't accidentally
|
||||||
|
# break WiFi setup or the uninstaller three thousand lines away.
|
||||||
|
#
|
||||||
|
# Today this wires up Sites & Page Timing (menus/sites.sh) and Display &
|
||||||
|
# Interaction (menus/display.sh). The rest of Core Settings/Addons/Advanced
|
||||||
|
# will move over the same way, one menus/*.sh file at a time.
|
||||||
|
#
|
||||||
|
# Usage (once the kiosk has already been installed):
|
||||||
|
# git clone <repo>
|
||||||
|
# cd ubuntu-based-kiosk
|
||||||
|
# ./install.sh
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# shellcheck source=lib/menu.sh
|
||||||
|
source "$SCRIPT_DIR/lib/menu.sh"
|
||||||
|
# shellcheck source=lib/config.sh
|
||||||
|
source "$SCRIPT_DIR/lib/config.sh"
|
||||||
|
# shellcheck source=menus/sites.sh
|
||||||
|
source "$SCRIPT_DIR/menus/sites.sh"
|
||||||
|
# shellcheck source=menus/display.sh
|
||||||
|
source "$SCRIPT_DIR/menus/display.sh"
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Preflight
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
if [[ "$(whoami)" == "kiosk" ]]; then
|
||||||
|
log_error "Cannot run as user 'kiosk'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $EUID -eq 0 ]]; then
|
||||||
|
log_error "Run as a regular user with sudo privileges, not as root"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v jq &>/dev/null; then
|
||||||
|
log_error "jq is required but not installed. Run: sudo apt-get install -y jq"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! is_kiosk_installed; then
|
||||||
|
echo
|
||||||
|
log_error "No installed kiosk found at ${KIOSK_DIR}."
|
||||||
|
echo
|
||||||
|
echo "This tool manages an already-installed kiosk. To provision a new"
|
||||||
|
echo "one for the first time, use the full installer instead - see"
|
||||||
|
echo "Readme.md ('Quick Install') for the current download command."
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Top-level menu
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
main_menu_builder() {
|
||||||
|
MENU_LABELS=("Sites & Page Timing" "Display & Interaction")
|
||||||
|
MENU_HANDLERS=(sites_menu display_menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
main_menu_status() {
|
||||||
|
echo "Managing kiosk at: ${KIOSK_DIR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_menu "UBUNTU BASED KIOSK - MANAGEMENT" main_menu_builder main_menu_status "Exit"
|
||||||
+202
@@ -0,0 +1,202 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# lib/config.sh - config.json read/write for the kiosk Electron app.
|
||||||
|
#
|
||||||
|
# Every menu that touches sites/timing/settings works against the same
|
||||||
|
# in-memory bash arrays (URLS, DURS, USERS, PASSES, NAMES, ...) and the same
|
||||||
|
# two functions below. Load once when a menu opens, save after each change.
|
||||||
|
#
|
||||||
|
# IMPORTANT: save_config() rewrites config.json from these globals in full.
|
||||||
|
# Any menu that calls save_config() MUST call load_existing_config() first
|
||||||
|
# (even if it only touches sites), otherwise settings it doesn't know about
|
||||||
|
# (swipe mode, navigation security, lockout, etc) get silently reset to
|
||||||
|
# script defaults. This bit the old single-file Sites menu, which read
|
||||||
|
# tabs directly via jq but never loaded the rest of the settings - fixed
|
||||||
|
# here by making load_existing_config() the one canonical loader.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
: "${KIOSK_USER:=kiosk}"
|
||||||
|
: "${KIOSK_HOME:=/home/${KIOSK_USER}}"
|
||||||
|
: "${KIOSK_DIR:=${KIOSK_HOME}/kiosk-app}"
|
||||||
|
: "${CONFIG_PATH:=${KIOSK_DIR}/config.json}"
|
||||||
|
|
||||||
|
# Site/tab arrays
|
||||||
|
declare -a URLS=()
|
||||||
|
declare -a DURS=()
|
||||||
|
declare -a USERS=()
|
||||||
|
declare -a PASSES=()
|
||||||
|
declare -a NAMES=()
|
||||||
|
|
||||||
|
# Other top-level config.json settings we must round-trip even though the
|
||||||
|
# Sites menu doesn't edit most of them.
|
||||||
|
AUTOSWITCH="true"
|
||||||
|
SWIPE_MODE="dual"
|
||||||
|
ALLOW_NAVIGATION="same-origin"
|
||||||
|
HOME_TAB_INDEX=-1
|
||||||
|
INACTIVITY_TIMEOUT=120
|
||||||
|
ENABLE_PAUSE_BUTTON="true"
|
||||||
|
ENABLE_KEYBOARD_BUTTON="true"
|
||||||
|
ENABLE_NAV_BUTTON="true"
|
||||||
|
ENABLE_PASSWORD_PROTECTION="false"
|
||||||
|
LOCKOUT_PASSWORD=""
|
||||||
|
LOCKOUT_TIMEOUT=0
|
||||||
|
LOCKOUT_AT_TIME=""
|
||||||
|
LOCKOUT_ACTIVE_START=""
|
||||||
|
LOCKOUT_ACTIVE_END=""
|
||||||
|
REQUIRE_PASSWORD_ON_BOOT="false"
|
||||||
|
|
||||||
|
kiosk_user_exists() {
|
||||||
|
id "$KIOSK_USER" &>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
is_kiosk_installed() {
|
||||||
|
kiosk_user_exists && sudo -u "$KIOSK_USER" test -f "$KIOSK_DIR/main.js" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
is_service_active() {
|
||||||
|
local service="$1"
|
||||||
|
systemctl is-active --quiet "$service" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load every setting config.json has into the bash globals above.
|
||||||
|
# Safe to call with no existing config file - leaves script defaults in place.
|
||||||
|
load_existing_config() {
|
||||||
|
if ! sudo -u "$KIOSK_USER" test -f "$CONFIG_PATH" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
URLS=()
|
||||||
|
DURS=()
|
||||||
|
USERS=()
|
||||||
|
PASSES=()
|
||||||
|
NAMES=()
|
||||||
|
|
||||||
|
local tab_count
|
||||||
|
tab_count=$(sudo -u "$KIOSK_USER" jq -r '.tabs | length' "$CONFIG_PATH" 2>/dev/null || echo "0")
|
||||||
|
if [[ "$tab_count" -gt 0 ]]; then
|
||||||
|
for ((i = 0; i < tab_count; i++)); do
|
||||||
|
URLS+=("$(sudo -u "$KIOSK_USER" jq -r ".tabs[$i].url" "$CONFIG_PATH")")
|
||||||
|
DURS+=("$(sudo -u "$KIOSK_USER" jq -r ".tabs[$i].duration" "$CONFIG_PATH")")
|
||||||
|
USERS+=("$(sudo -u "$KIOSK_USER" jq -r ".tabs[$i].username // empty" "$CONFIG_PATH")")
|
||||||
|
PASSES+=("$(sudo -u "$KIOSK_USER" jq -r ".tabs[$i].password // empty" "$CONFIG_PATH")")
|
||||||
|
NAMES+=("$(sudo -u "$KIOSK_USER" jq -r ".tabs[$i].name // empty" "$CONFIG_PATH")")
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
HOME_TAB_INDEX=$(sudo -u "$KIOSK_USER" jq -r '.homeTabIndex // -1' "$CONFIG_PATH" 2>/dev/null || echo "-1")
|
||||||
|
INACTIVITY_TIMEOUT=$(sudo -u "$KIOSK_USER" jq -r '.inactivityTimeout // 120' "$CONFIG_PATH" 2>/dev/null || echo "120")
|
||||||
|
ALLOW_NAVIGATION=$(sudo -u "$KIOSK_USER" jq -r '.allowNavigation // "same-origin"' "$CONFIG_PATH" 2>/dev/null || echo "same-origin")
|
||||||
|
SWIPE_MODE=$(sudo -u "$KIOSK_USER" jq -r '.swipeMode // "dual"' "$CONFIG_PATH" 2>/dev/null || echo "dual")
|
||||||
|
|
||||||
|
local pause_btn keyboard_btn nav_btn password_enabled boot_password
|
||||||
|
pause_btn=$(sudo -u "$KIOSK_USER" jq -r '.enablePauseButton // true' "$CONFIG_PATH" 2>/dev/null)
|
||||||
|
[[ "$pause_btn" == "true" ]] && ENABLE_PAUSE_BUTTON="true" || ENABLE_PAUSE_BUTTON="false"
|
||||||
|
|
||||||
|
keyboard_btn=$(sudo -u "$KIOSK_USER" jq -r '.enableKeyboardButton // true' "$CONFIG_PATH" 2>/dev/null)
|
||||||
|
[[ "$keyboard_btn" == "true" ]] && ENABLE_KEYBOARD_BUTTON="true" || ENABLE_KEYBOARD_BUTTON="false"
|
||||||
|
|
||||||
|
nav_btn=$(sudo -u "$KIOSK_USER" jq -r '.enableNavButton // true' "$CONFIG_PATH" 2>/dev/null)
|
||||||
|
[[ "$nav_btn" == "true" ]] && ENABLE_NAV_BUTTON="true" || ENABLE_NAV_BUTTON="false"
|
||||||
|
|
||||||
|
password_enabled=$(sudo -u "$KIOSK_USER" jq -r '.enablePasswordProtection // false' "$CONFIG_PATH" 2>/dev/null)
|
||||||
|
[[ "$password_enabled" == "true" ]] && ENABLE_PASSWORD_PROTECTION="true" || ENABLE_PASSWORD_PROTECTION="false"
|
||||||
|
|
||||||
|
LOCKOUT_PASSWORD=$(sudo -u "$KIOSK_USER" jq -r '.lockoutPassword // ""' "$CONFIG_PATH" 2>/dev/null || echo "")
|
||||||
|
LOCKOUT_TIMEOUT=$(sudo -u "$KIOSK_USER" jq -r '.lockoutTimeout // 0' "$CONFIG_PATH" 2>/dev/null || echo "0")
|
||||||
|
LOCKOUT_AT_TIME=$(sudo -u "$KIOSK_USER" jq -r '.lockoutAtTime // ""' "$CONFIG_PATH" 2>/dev/null || echo "")
|
||||||
|
LOCKOUT_ACTIVE_START=$(sudo -u "$KIOSK_USER" jq -r '.lockoutActiveStart // ""' "$CONFIG_PATH" 2>/dev/null || echo "")
|
||||||
|
LOCKOUT_ACTIVE_END=$(sudo -u "$KIOSK_USER" jq -r '.lockoutActiveEnd // ""' "$CONFIG_PATH" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
boot_password=$(sudo -u "$KIOSK_USER" jq -r '.requirePasswordOnBoot // false' "$CONFIG_PATH" 2>/dev/null)
|
||||||
|
[[ "$boot_password" == "true" ]] && REQUIRE_PASSWORD_ON_BOOT="true" || REQUIRE_PASSWORD_ON_BOOT="false"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write every bash global back out to config.json, then offer to reload the
|
||||||
|
# kiosk display so the change takes effect immediately.
|
||||||
|
save_config() {
|
||||||
|
if ! kiosk_user_exists; then
|
||||||
|
log_error "Kiosk user doesn't exist - run the full installer first"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo mkdir -p "$KIOSK_DIR"
|
||||||
|
sudo chown -R "$KIOSK_USER:$KIOSK_USER" "$KIOSK_DIR"
|
||||||
|
|
||||||
|
local tmp
|
||||||
|
tmp=$(mktemp)
|
||||||
|
|
||||||
|
local dual_json="false"
|
||||||
|
[[ "$SWIPE_MODE" == "dual" ]] && dual_json="true"
|
||||||
|
|
||||||
|
local pause_btn_json="true"
|
||||||
|
[[ "$ENABLE_PAUSE_BUTTON" == "false" ]] && pause_btn_json="false"
|
||||||
|
|
||||||
|
local keyboard_btn_json="true"
|
||||||
|
[[ "$ENABLE_KEYBOARD_BUTTON" == "false" ]] && keyboard_btn_json="false"
|
||||||
|
|
||||||
|
local nav_btn_json="true"
|
||||||
|
[[ "$ENABLE_NAV_BUTTON" == "false" ]] && nav_btn_json="false"
|
||||||
|
|
||||||
|
local password_json="false"
|
||||||
|
[[ "$ENABLE_PASSWORD_PROTECTION" == "true" ]] && password_json="true"
|
||||||
|
|
||||||
|
local boot_password_json="false"
|
||||||
|
[[ "$REQUIRE_PASSWORD_ON_BOOT" == "true" ]] && boot_password_json="true"
|
||||||
|
|
||||||
|
jq -n \
|
||||||
|
--argjson autoswitch true \
|
||||||
|
--argjson enableTouch true \
|
||||||
|
--argjson dualSwipe "$dual_json" \
|
||||||
|
--arg swipeMode "$SWIPE_MODE" \
|
||||||
|
--arg allowNavigation "$ALLOW_NAVIGATION" \
|
||||||
|
--argjson homeTabIndex "${HOME_TAB_INDEX:--1}" \
|
||||||
|
--argjson inactivityTimeout "${INACTIVITY_TIMEOUT:-120}" \
|
||||||
|
--argjson enablePauseButton "$pause_btn_json" \
|
||||||
|
--argjson enableKeyboardButton "$keyboard_btn_json" \
|
||||||
|
--argjson enableNavButton "$nav_btn_json" \
|
||||||
|
--argjson enablePasswordProtection "$password_json" \
|
||||||
|
--arg lockoutPassword "${LOCKOUT_PASSWORD:-}" \
|
||||||
|
--argjson lockoutTimeout "${LOCKOUT_TIMEOUT:-0}" \
|
||||||
|
--arg lockoutAtTime "${LOCKOUT_AT_TIME:-}" \
|
||||||
|
--arg lockoutActiveStart "${LOCKOUT_ACTIVE_START:-}" \
|
||||||
|
--arg lockoutActiveEnd "${LOCKOUT_ACTIVE_END:-}" \
|
||||||
|
--argjson requirePasswordOnBoot "$boot_password_json" \
|
||||||
|
'{autoswitch:$autoswitch,enableTouch:$enableTouch,dualSwipe:$dualSwipe,swipeMode:$swipeMode,allowNavigation:$allowNavigation,homeTabIndex:$homeTabIndex,inactivityTimeout:$inactivityTimeout,enablePauseButton:$enablePauseButton,enableKeyboardButton:$enableKeyboardButton,enableNavButton:$enableNavButton,enablePasswordProtection:$enablePasswordProtection,lockoutPassword:$lockoutPassword,lockoutTimeout:$lockoutTimeout,lockoutAtTime:$lockoutAtTime,lockoutActiveStart:$lockoutActiveStart,lockoutActiveEnd:$lockoutActiveEnd,requirePasswordOnBoot:$requirePasswordOnBoot,tabs:[]}' > "$tmp"
|
||||||
|
|
||||||
|
if [[ ${#URLS[@]} -gt 0 ]]; then
|
||||||
|
for idx in "${!URLS[@]}"; do
|
||||||
|
local url="${URLS[$idx]:-}"
|
||||||
|
local dur="${DURS[$idx]:-0}"
|
||||||
|
local user="${USERS[$idx]:-}"
|
||||||
|
local pass="${PASSES[$idx]:-}"
|
||||||
|
local name="${NAMES[$idx]:-}"
|
||||||
|
|
||||||
|
jq --arg u "$url" \
|
||||||
|
--argjson d "$dur" \
|
||||||
|
--arg user "$user" \
|
||||||
|
--arg pass "$pass" \
|
||||||
|
--arg name "$name" \
|
||||||
|
'.tabs += [{"url":$u,"duration":$d,"username":$user,"password":$pass,"name":$name}]' \
|
||||||
|
"$tmp" > "${tmp}.new"
|
||||||
|
mv -f "${tmp}.new" "$tmp"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo -u "$KIOSK_USER" bash -c "cat > '$CONFIG_PATH'" < "$tmp"
|
||||||
|
sudo -u "$KIOSK_USER" chmod 644 "$CONFIG_PATH"
|
||||||
|
rm -f "$tmp"
|
||||||
|
|
||||||
|
log_success "Configuration saved"
|
||||||
|
|
||||||
|
if is_service_active lightdm; then
|
||||||
|
echo
|
||||||
|
if ask_yes_no "Reload kiosk now to apply changes?" "y"; then
|
||||||
|
echo "Reloading kiosk..."
|
||||||
|
sudo systemctl restart lightdm
|
||||||
|
sleep 2
|
||||||
|
log_success "Kiosk reloaded"
|
||||||
|
else
|
||||||
|
log_warning "Remember to reload: sudo systemctl restart lightdm"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
+240
@@ -0,0 +1,240 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# lib/menu.sh - Reusable numbered-menu framework + validated input helpers.
|
||||||
|
#
|
||||||
|
# Goal: menu *behavior* (numbering, "0 to exit/return", input validation)
|
||||||
|
# lives here once. Individual menus/*.sh files only supply their content
|
||||||
|
# (labels + handler functions) and never re-implement the loop/echo/case
|
||||||
|
# boilerplate that made the old single-file installer hard to change safely.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# my_menu_builder() {
|
||||||
|
# MENU_LABELS=("Do thing A" "Do thing B")
|
||||||
|
# MENU_HANDLERS=(action_a action_b)
|
||||||
|
# }
|
||||||
|
# run_menu "MY MENU TITLE" my_menu_builder [my_status_func]
|
||||||
|
#
|
||||||
|
# The builder runs fresh on every redraw, so labels/handlers can change
|
||||||
|
# based on current state (e.g. "no sites yet" vs "5 sites configured").
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Logging
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
echo "[INFO] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
echo "[ERROR] $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
echo "✓ $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warning() {
|
||||||
|
echo "⚠ $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
pause() {
|
||||||
|
read -r -p "Press Enter to continue..."
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Validated input helpers
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
validate_yes_no() {
|
||||||
|
local answer="$1"
|
||||||
|
case "${answer,,}" in
|
||||||
|
y|yes|yeah|yep|yup|sure|ok|okay) return 0 ;;
|
||||||
|
n|no|nope|nah) return 1 ;;
|
||||||
|
*) return 2 ;; # invalid
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ask_yes_no() {
|
||||||
|
local prompt="$1"
|
||||||
|
local default="${2:-n}"
|
||||||
|
local answer
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "$prompt (y/n) [$default]: " answer
|
||||||
|
answer="${answer:-$default}"
|
||||||
|
|
||||||
|
validate_yes_no "$answer"
|
||||||
|
local result=$?
|
||||||
|
|
||||||
|
if [[ $result -eq 0 ]]; then
|
||||||
|
return 0
|
||||||
|
elif [[ $result -eq 1 ]]; then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
echo "❌ Invalid input. Please enter 'y' for yes or 'n' for no"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_integer() {
|
||||||
|
local value="$1"
|
||||||
|
local min="${2:--2147483648}"
|
||||||
|
local max="${3:-2147483647}"
|
||||||
|
|
||||||
|
if [[ $value =~ ^-?[0-9]+$ ]]; then
|
||||||
|
if [[ $value -ge $min && $value -le $max ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ask_integer() {
|
||||||
|
local prompt="$1"
|
||||||
|
local default="$2"
|
||||||
|
local min="${3:--2147483648}"
|
||||||
|
local max="${4:-2147483647}"
|
||||||
|
local value
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "$prompt [$default]: " value
|
||||||
|
value="${value:-$default}"
|
||||||
|
|
||||||
|
if validate_integer "$value" "$min" "$max"; then
|
||||||
|
echo "$value"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "❌ Invalid number. Please enter an integer between $min and $max" >&2
|
||||||
|
echo >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_url() {
|
||||||
|
local url="$1"
|
||||||
|
if [[ $url =~ ^(https?|file|data)://.*$ ]] || [[ $url =~ ^about: ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ask_url() {
|
||||||
|
local prompt="$1"
|
||||||
|
local default="$2"
|
||||||
|
local url
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "$prompt [$default]: " url
|
||||||
|
url="${url:-$default}"
|
||||||
|
|
||||||
|
if validate_url "$url"; then
|
||||||
|
echo "$url"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "❌ Invalid URL. Must start with http://, https://, file://, data:, or about:" >&2
|
||||||
|
echo >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
ask_text() {
|
||||||
|
local prompt="$1"
|
||||||
|
local default="${2:-}"
|
||||||
|
local value
|
||||||
|
|
||||||
|
read -r -p "$prompt [$default]: " value
|
||||||
|
echo "${value:-$default}"
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_menu_choice() {
|
||||||
|
local choice="$1"
|
||||||
|
local max="$2"
|
||||||
|
validate_integer "$choice" 0 "$max"
|
||||||
|
}
|
||||||
|
|
||||||
|
ask_menu_choice() {
|
||||||
|
local max="$1"
|
||||||
|
local choice
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "Choose [0-$max]: " choice
|
||||||
|
|
||||||
|
if validate_menu_choice "$choice" "$max"; then
|
||||||
|
echo "$choice"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "❌ Invalid choice. Please enter a number between 0 and $max" >&2
|
||||||
|
echo >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Menu framework
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
print_menu_header() {
|
||||||
|
local title="$1"
|
||||||
|
echo "══════════════════════════════════════════════════════════"
|
||||||
|
printf " %s\n" "$title"
|
||||||
|
echo "══════════════════════════════════════════════════════════"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# run_menu TITLE BUILDER_FUNC [STATUS_FUNC] [EXIT_LABEL]
|
||||||
|
#
|
||||||
|
# BUILDER_FUNC must set the globals MENU_LABELS and MENU_HANDLERS (parallel
|
||||||
|
# indexed arrays). It is called once per redraw, so it can reflect current
|
||||||
|
# state. STATUS_FUNC, if given, is called right after the header to print
|
||||||
|
# read-only context (current settings, current list, etc).
|
||||||
|
#
|
||||||
|
# Entries are auto-numbered 1..N. "0" always returns from run_menu - no
|
||||||
|
# menu file needs to hand-roll its own exit case.
|
||||||
|
run_menu() {
|
||||||
|
local title="$1"
|
||||||
|
local builder="$2"
|
||||||
|
local status_func="${3:-}"
|
||||||
|
local exit_label="${4:-Return}"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
clear
|
||||||
|
print_menu_header "$title"
|
||||||
|
|
||||||
|
if [[ -n "$status_func" ]]; then
|
||||||
|
"$status_func"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
local -a MENU_LABELS=()
|
||||||
|
local -a MENU_HANDLERS=()
|
||||||
|
"$builder"
|
||||||
|
|
||||||
|
if [[ "${#MENU_LABELS[@]}" -eq 0 ]]; then
|
||||||
|
log_warning "Nothing to do here yet."
|
||||||
|
echo " 0. $exit_label"
|
||||||
|
echo
|
||||||
|
ask_menu_choice 0 >/dev/null
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local i=1
|
||||||
|
for label in "${MENU_LABELS[@]}"; do
|
||||||
|
printf " %2d. %s\n" "$i" "$label"
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
echo " 0. $exit_label"
|
||||||
|
echo
|
||||||
|
|
||||||
|
local choice
|
||||||
|
choice=$(ask_menu_choice "${#MENU_LABELS[@]}")
|
||||||
|
|
||||||
|
if [[ "$choice" == "0" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
"${MENU_HANDLERS[$((choice - 1))]}"
|
||||||
|
done
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/display.sh - "Display & Interaction" menu.
|
||||||
|
#
|
||||||
|
# Second menu migrated off the old single-file installer, folding together
|
||||||
|
# three small settings screens that used to be separate Core Settings
|
||||||
|
# entries (Touch controls, Navigation security, Optional Features). All
|
||||||
|
# three are simple scalar/boolean fields on config.json, so this is a
|
||||||
|
# deliberately different shape from menus/sites.sh's list CRUD - a toggle
|
||||||
|
# list where each entry shows its current value and flips/edits itself,
|
||||||
|
# saving immediately (same immediate-save pattern as Sites, so behavior
|
||||||
|
# stays consistent no matter which menu you're in).
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
display_status() {
|
||||||
|
echo "Touch gesture mode: $SWIPE_MODE"
|
||||||
|
echo "Link navigation: $ALLOW_NAVIGATION"
|
||||||
|
echo "Pause button: $(display_onoff "$ENABLE_PAUSE_BUTTON")"
|
||||||
|
echo "Keyboard button: $(display_onoff "$ENABLE_KEYBOARD_BUTTON")"
|
||||||
|
echo "Navigation button: $(display_onoff "$ENABLE_NAV_BUTTON")"
|
||||||
|
}
|
||||||
|
|
||||||
|
display_onoff() {
|
||||||
|
[[ "$1" == "true" ]] && echo "ON" || echo "OFF"
|
||||||
|
}
|
||||||
|
|
||||||
|
display_menu_builder() {
|
||||||
|
MENU_LABELS=(
|
||||||
|
"Touch gesture mode (currently: $SWIPE_MODE)"
|
||||||
|
"Link navigation security (currently: $ALLOW_NAVIGATION)"
|
||||||
|
"Toggle pause button (currently: $(display_onoff "$ENABLE_PAUSE_BUTTON"))"
|
||||||
|
"Toggle on-screen keyboard button (currently: $(display_onoff "$ENABLE_KEYBOARD_BUTTON"))"
|
||||||
|
"Toggle navigation/help button (currently: $(display_onoff "$ENABLE_NAV_BUTTON"))"
|
||||||
|
)
|
||||||
|
MENU_HANDLERS=(
|
||||||
|
action_set_touch_mode
|
||||||
|
action_set_navigation_security
|
||||||
|
action_toggle_pause_button
|
||||||
|
action_toggle_keyboard_button
|
||||||
|
action_toggle_nav_button
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
display_menu() {
|
||||||
|
load_existing_config
|
||||||
|
run_menu "DISPLAY & INTERACTION" display_menu_builder display_status
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Touch gesture mode
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_set_touch_mode() {
|
||||||
|
echo
|
||||||
|
echo "DUAL-DIRECTION (recommended for touchscreens):"
|
||||||
|
echo " 2-finger swipe = switch pages, 1-finger swipe = navigate within page"
|
||||||
|
echo "STANDARD (simpler):"
|
||||||
|
echo " 2-finger swipe = switch pages only, 1-finger swipes do nothing"
|
||||||
|
echo
|
||||||
|
|
||||||
|
local default
|
||||||
|
[[ "$SWIPE_MODE" == "dual" ]] && default="y" || default="n"
|
||||||
|
|
||||||
|
if ask_yes_no "Use dual-direction mode?" "$default"; then
|
||||||
|
SWIPE_MODE="dual"
|
||||||
|
else
|
||||||
|
SWIPE_MODE="standard"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Touch mode: $SWIPE_MODE"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Navigation security
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_set_navigation_security() {
|
||||||
|
echo
|
||||||
|
echo " r) restricted - only the loaded URL, no link clicking"
|
||||||
|
echo " s) same-origin - can click links within the same domain (recommended)"
|
||||||
|
echo " o) open - can click any link, browse anywhere"
|
||||||
|
echo
|
||||||
|
|
||||||
|
local choice
|
||||||
|
read -r -p "(r)estricted / (s)ame-origin / (o)pen [${ALLOW_NAVIGATION}]: " choice
|
||||||
|
|
||||||
|
case "${choice,,}" in
|
||||||
|
r) ALLOW_NAVIGATION="restricted" ;;
|
||||||
|
o) ALLOW_NAVIGATION="open" ;;
|
||||||
|
s) ALLOW_NAVIGATION="same-origin" ;;
|
||||||
|
"") ;; # keep current value
|
||||||
|
*) log_warning "Unrecognized choice, keeping '$ALLOW_NAVIGATION'" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
log_success "Link navigation: $ALLOW_NAVIGATION"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# On-screen button toggles
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_toggle_pause_button() {
|
||||||
|
if [[ "$ENABLE_PAUSE_BUTTON" == "true" ]]; then
|
||||||
|
ENABLE_PAUSE_BUTTON="false"
|
||||||
|
log_warning "Pause button disabled"
|
||||||
|
else
|
||||||
|
ENABLE_PAUSE_BUTTON="true"
|
||||||
|
log_success "Pause button enabled"
|
||||||
|
fi
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_toggle_keyboard_button() {
|
||||||
|
if [[ "$ENABLE_KEYBOARD_BUTTON" == "true" ]]; then
|
||||||
|
ENABLE_KEYBOARD_BUTTON="false"
|
||||||
|
log_warning "On-screen keyboard button disabled"
|
||||||
|
else
|
||||||
|
ENABLE_KEYBOARD_BUTTON="true"
|
||||||
|
log_success "On-screen keyboard button enabled"
|
||||||
|
fi
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_toggle_nav_button() {
|
||||||
|
if [[ "$ENABLE_NAV_BUTTON" == "true" ]]; then
|
||||||
|
ENABLE_NAV_BUTTON="false"
|
||||||
|
log_warning "Navigation/help button disabled"
|
||||||
|
else
|
||||||
|
ENABLE_NAV_BUTTON="true"
|
||||||
|
log_success "Navigation/help button enabled"
|
||||||
|
fi
|
||||||
|
save_config
|
||||||
|
}
|
||||||
+363
@@ -0,0 +1,363 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/sites.sh - "Sites & Page Timing" menu.
|
||||||
|
#
|
||||||
|
# First real menu built on lib/menu.sh + lib/config.sh, as a proof of
|
||||||
|
# concept for pulling menus out of the old 12k-line installer one at a
|
||||||
|
# time. Covers exactly what was asked for first: adding, deleting, and
|
||||||
|
# changing pages, and the timing (duration) of each.
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Index of the page currently being edited by the nested "edit page" menu.
|
||||||
|
SITE_EDIT_IDX=""
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Small helpers
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Normalize whatever the user typed into a URL, same rules the old
|
||||||
|
# installer used: bare host -> https://, bare IP -> http://.
|
||||||
|
sites_parse_url() {
|
||||||
|
local raw="$1"
|
||||||
|
if [[ "$raw" =~ ^https?:// ]]; then
|
||||||
|
echo "$raw"
|
||||||
|
elif [[ "$raw" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||||||
|
echo "http://${raw}"
|
||||||
|
else
|
||||||
|
echo "https://${raw}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Human label for a duration value: >0 seconds = auto-rotate, 0 = manual,
|
||||||
|
# -1 = hidden (PIN-gated).
|
||||||
|
sites_duration_label() {
|
||||||
|
local dur="$1"
|
||||||
|
if [[ "$dur" == "-1" ]]; then
|
||||||
|
echo "hidden"
|
||||||
|
elif [[ "$dur" == "0" ]]; then
|
||||||
|
echo "manual"
|
||||||
|
else
|
||||||
|
echo "auto-rotate ${dur}s"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
sites_display_label() {
|
||||||
|
local idx="$1"
|
||||||
|
local label="${URLS[$idx]}"
|
||||||
|
[[ -n "${NAMES[$idx]:-}" ]] && label="\"${NAMES[$idx]}\" - ${URLS[$idx]}"
|
||||||
|
echo "$label"
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Status line shown above the main Sites menu
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
sites_status() {
|
||||||
|
if [[ "${#URLS[@]}" -eq 0 ]]; then
|
||||||
|
echo "No pages configured yet."
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Current pages:"
|
||||||
|
local has_rotation=false
|
||||||
|
for idx in "${!URLS[@]}"; do
|
||||||
|
local dur="${DURS[$idx]}"
|
||||||
|
local flags=""
|
||||||
|
[[ "$dur" != "0" && "$dur" != "-1" ]] && has_rotation=true
|
||||||
|
[[ -n "${USERS[$idx]:-}" ]] && flags+=" [auth]"
|
||||||
|
[[ "$HOME_TAB_INDEX" == "$idx" ]] && flags+=" [HOME]"
|
||||||
|
|
||||||
|
printf " %2d. %s (%s)%s\n" "$((idx + 1))" "$(sites_display_label "$idx")" "$(sites_duration_label "$dur")" "$flags"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
if $has_rotation; then
|
||||||
|
echo "Auto-rotation: active (pages with duration > 0)"
|
||||||
|
else
|
||||||
|
echo "Auto-rotation: off (all pages manual/hidden)"
|
||||||
|
fi
|
||||||
|
if [[ "$HOME_TAB_INDEX" != "-1" ]]; then
|
||||||
|
echo "Home page: #$((HOME_TAB_INDEX + 1)), ${INACTIVITY_TIMEOUT}s inactivity timeout"
|
||||||
|
else
|
||||||
|
echo "Home page: disabled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Top-level Sites menu
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
sites_menu_builder() {
|
||||||
|
MENU_LABELS=("Add a page")
|
||||||
|
MENU_HANDLERS=(action_add_page)
|
||||||
|
|
||||||
|
if [[ "${#URLS[@]}" -gt 0 ]]; then
|
||||||
|
MENU_LABELS+=("Edit a page" "Delete a page")
|
||||||
|
MENU_HANDLERS+=(action_pick_and_edit_page action_delete_page)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${#URLS[@]}" -gt 1 ]]; then
|
||||||
|
MENU_LABELS+=("Reorder pages")
|
||||||
|
MENU_HANDLERS+=(action_reorder_page)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${#URLS[@]}" -gt 0 ]]; then
|
||||||
|
MENU_LABELS+=("Set/clear home page")
|
||||||
|
MENU_HANDLERS+=(action_set_home_page)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
sites_menu() {
|
||||||
|
load_existing_config
|
||||||
|
run_menu "SITES & PAGE TIMING" sites_menu_builder sites_status
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Add
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_add_page() {
|
||||||
|
echo
|
||||||
|
echo "Duration controls rotation:"
|
||||||
|
echo " > 0 = auto-rotates every X seconds"
|
||||||
|
echo " 0 = manual only (swipe/nav menu to reach it)"
|
||||||
|
echo " -1 = hidden (PIN-gated, F10 or 3-finger swipe)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
local raw_url url dur name needs_auth user pass
|
||||||
|
read -r -p "URL: " raw_url
|
||||||
|
if [[ -z "$raw_url" ]]; then
|
||||||
|
echo "Cancelled"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
url=$(sites_parse_url "$raw_url")
|
||||||
|
|
||||||
|
dur=$(ask_integer "Duration in seconds (-1=hidden, 0=manual)" "180" -1 86400)
|
||||||
|
name=$(ask_text "Page name (optional, blank = show URL)" "")
|
||||||
|
|
||||||
|
user=""
|
||||||
|
pass=""
|
||||||
|
if ask_yes_no "Does this page need HTTP Basic Auth?" "n"; then
|
||||||
|
read -r -p " Username: " user
|
||||||
|
read -r -s -p " Password: " pass
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
URLS+=("$url")
|
||||||
|
DURS+=("$dur")
|
||||||
|
USERS+=("$user")
|
||||||
|
PASSES+=("$pass")
|
||||||
|
NAMES+=("$name")
|
||||||
|
|
||||||
|
log_success "Added: $url ($(sites_duration_label "$dur"))"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Edit (nested menu on the selected page)
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_pick_and_edit_page() {
|
||||||
|
echo
|
||||||
|
for idx in "${!URLS[@]}"; do
|
||||||
|
echo " $((idx + 1)). $(sites_display_label "$idx")"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
local num
|
||||||
|
num=$(ask_integer "Edit which page? (0=cancel)" "0" 0 "${#URLS[@]}")
|
||||||
|
[[ "$num" == "0" ]] && return
|
||||||
|
|
||||||
|
SITE_EDIT_IDX=$((num - 1))
|
||||||
|
run_menu "EDIT PAGE #${num}" edit_page_menu_builder edit_page_status
|
||||||
|
}
|
||||||
|
|
||||||
|
edit_page_status() {
|
||||||
|
local idx="$SITE_EDIT_IDX"
|
||||||
|
echo "URL: ${URLS[$idx]}"
|
||||||
|
echo "Name: ${NAMES[$idx]:-(none)}"
|
||||||
|
echo "Timing: $(sites_duration_label "${DURS[$idx]}")"
|
||||||
|
if [[ -n "${USERS[$idx]:-}" ]]; then
|
||||||
|
echo "Basic auth: enabled (user: ${USERS[$idx]})"
|
||||||
|
else
|
||||||
|
echo "Basic auth: disabled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
edit_page_menu_builder() {
|
||||||
|
MENU_LABELS=("Change URL" "Change name" "Change timing (duration)" "Change Basic Auth")
|
||||||
|
MENU_HANDLERS=(action_edit_url action_edit_name action_edit_duration action_edit_auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
action_edit_url() {
|
||||||
|
local idx="$SITE_EDIT_IDX"
|
||||||
|
local raw_url
|
||||||
|
raw_url=$(ask_text "New URL" "${URLS[$idx]}")
|
||||||
|
URLS[$idx]=$(sites_parse_url "$raw_url")
|
||||||
|
log_success "URL updated"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_edit_name() {
|
||||||
|
local idx="$SITE_EDIT_IDX"
|
||||||
|
NAMES[$idx]=$(ask_text "New name (blank = show URL)" "${NAMES[$idx]:-}")
|
||||||
|
log_success "Name updated"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_edit_duration() {
|
||||||
|
local idx="$SITE_EDIT_IDX"
|
||||||
|
echo
|
||||||
|
echo " > 0 = auto-rotates every X seconds"
|
||||||
|
echo " 0 = manual only"
|
||||||
|
echo " -1 = hidden (PIN-gated)"
|
||||||
|
DURS[$idx]=$(ask_integer "Duration in seconds" "${DURS[$idx]}" -1 86400)
|
||||||
|
log_success "Timing updated: $(sites_duration_label "${DURS[$idx]}")"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_edit_auth() {
|
||||||
|
local idx="$SITE_EDIT_IDX"
|
||||||
|
if ask_yes_no "Enable HTTP Basic Auth for this page?" "$([[ -n "${USERS[$idx]:-}" ]] && echo y || echo n)"; then
|
||||||
|
read -r -p " Username: " USERS_new
|
||||||
|
read -r -s -p " Password: " PASSES_new
|
||||||
|
echo
|
||||||
|
USERS[$idx]="$USERS_new"
|
||||||
|
PASSES[$idx]="$PASSES_new"
|
||||||
|
log_success "Basic Auth updated"
|
||||||
|
else
|
||||||
|
USERS[$idx]=""
|
||||||
|
PASSES[$idx]=""
|
||||||
|
log_success "Basic Auth disabled"
|
||||||
|
fi
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Delete
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_delete_page() {
|
||||||
|
echo
|
||||||
|
for idx in "${!URLS[@]}"; do
|
||||||
|
echo " $((idx + 1)). $(sites_display_label "$idx")"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
local num
|
||||||
|
num=$(ask_integer "Delete which page? (0=cancel)" "0" 0 "${#URLS[@]}")
|
||||||
|
[[ "$num" == "0" ]] && { echo "Cancelled"; return; }
|
||||||
|
|
||||||
|
local del_idx=$((num - 1))
|
||||||
|
echo "Deleting: $(sites_display_label "$del_idx")"
|
||||||
|
|
||||||
|
unset 'URLS[del_idx]' 'DURS[del_idx]' 'USERS[del_idx]' 'PASSES[del_idx]' 'NAMES[del_idx]'
|
||||||
|
URLS=("${URLS[@]}")
|
||||||
|
DURS=("${DURS[@]}")
|
||||||
|
USERS=("${USERS[@]}")
|
||||||
|
PASSES=("${PASSES[@]}")
|
||||||
|
NAMES=("${NAMES[@]}")
|
||||||
|
|
||||||
|
if [[ "$HOME_TAB_INDEX" == "$del_idx" ]]; then
|
||||||
|
HOME_TAB_INDEX=-1
|
||||||
|
log_warning "Home page was deleted - home feature disabled"
|
||||||
|
elif [[ "$HOME_TAB_INDEX" -gt "$del_idx" ]]; then
|
||||||
|
HOME_TAB_INDEX=$((HOME_TAB_INDEX - 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Page deleted"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Reorder
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_reorder_page() {
|
||||||
|
echo
|
||||||
|
echo "Current order:"
|
||||||
|
for idx in "${!URLS[@]}"; do
|
||||||
|
echo " $((idx + 1)). $(sites_display_label "$idx")"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
local max="${#URLS[@]}"
|
||||||
|
local from_num to_num
|
||||||
|
from_num=$(ask_integer "Move which page? (0=cancel)" "0" 0 "$max")
|
||||||
|
[[ "$from_num" == "0" ]] && { echo "Cancelled"; return; }
|
||||||
|
to_num=$(ask_integer "Move to position?" "1" 1 "$max")
|
||||||
|
|
||||||
|
local from_idx=$((from_num - 1))
|
||||||
|
local to_idx=$((to_num - 1))
|
||||||
|
if [[ "$from_idx" == "$to_idx" ]]; then
|
||||||
|
echo "Same position - no change"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Work out where the home page (if any, and not the one being moved)
|
||||||
|
# will land, before we touch the arrays. Removing the moved item and
|
||||||
|
# then re-inserting it at $to_idx always places it at index $to_idx of
|
||||||
|
# the *final* array - no further adjustment needed there. Everything
|
||||||
|
# else only shifts by the remove and the insert individually.
|
||||||
|
local new_home_idx="$HOME_TAB_INDEX"
|
||||||
|
if [[ "$HOME_TAB_INDEX" == "$from_idx" ]]; then
|
||||||
|
new_home_idx="$to_idx"
|
||||||
|
elif [[ "$HOME_TAB_INDEX" != "-1" ]]; then
|
||||||
|
[[ "$from_idx" -lt "$HOME_TAB_INDEX" ]] && new_home_idx=$((new_home_idx - 1))
|
||||||
|
[[ "$to_idx" -le "$new_home_idx" ]] && new_home_idx=$((new_home_idx + 1))
|
||||||
|
fi
|
||||||
|
HOME_TAB_INDEX="$new_home_idx"
|
||||||
|
|
||||||
|
local move_url="${URLS[$from_idx]}" move_dur="${DURS[$from_idx]}" \
|
||||||
|
move_user="${USERS[$from_idx]}" move_pass="${PASSES[$from_idx]}" move_name="${NAMES[$from_idx]}"
|
||||||
|
|
||||||
|
unset 'URLS[from_idx]' 'DURS[from_idx]' 'USERS[from_idx]' 'PASSES[from_idx]' 'NAMES[from_idx]'
|
||||||
|
URLS=("${URLS[@]}")
|
||||||
|
DURS=("${DURS[@]}")
|
||||||
|
USERS=("${USERS[@]}")
|
||||||
|
PASSES=("${PASSES[@]}")
|
||||||
|
NAMES=("${NAMES[@]}")
|
||||||
|
|
||||||
|
URLS=("${URLS[@]:0:$to_idx}" "$move_url" "${URLS[@]:$to_idx}")
|
||||||
|
DURS=("${DURS[@]:0:$to_idx}" "$move_dur" "${DURS[@]:$to_idx}")
|
||||||
|
USERS=("${USERS[@]:0:$to_idx}" "$move_user" "${USERS[@]:$to_idx}")
|
||||||
|
PASSES=("${PASSES[@]:0:$to_idx}" "$move_pass" "${PASSES[@]:$to_idx}")
|
||||||
|
NAMES=("${NAMES[@]:0:$to_idx}" "$move_name" "${NAMES[@]:$to_idx}")
|
||||||
|
|
||||||
|
log_success "Pages reordered"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Home page
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_set_home_page() {
|
||||||
|
echo
|
||||||
|
if [[ "$HOME_TAB_INDEX" != "-1" ]]; then
|
||||||
|
echo "Current home page: #$((HOME_TAB_INDEX + 1)), ${INACTIVITY_TIMEOUT}s timeout"
|
||||||
|
else
|
||||||
|
echo "Home page currently disabled"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
for idx in "${!URLS[@]}"; do
|
||||||
|
echo " $((idx + 1)). $(sites_display_label "$idx")"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
local num
|
||||||
|
num=$(ask_integer "Set which page as home? (0=disable)" "0" 0 "${#URLS[@]}")
|
||||||
|
if [[ "$num" == "0" ]]; then
|
||||||
|
HOME_TAB_INDEX=-1
|
||||||
|
log_success "Home page disabled"
|
||||||
|
save_config
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
HOME_TAB_INDEX=$((num - 1))
|
||||||
|
local timeout_min
|
||||||
|
timeout_min=$(ask_integer "Inactivity timeout in minutes" "2" 1 240)
|
||||||
|
INACTIVITY_TIMEOUT=$((timeout_min * 60))
|
||||||
|
|
||||||
|
log_success "Home page: #${num} (${timeout_min}m inactivity timeout)"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
@@ -1,8 +1,25 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
################################################################################
|
################################################################################
|
||||||
### Ubuntu Based Kiosk v1.0.3 ###
|
### Ubuntu Based Kiosk v2.0.0 ###
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
|
# RELEASE v2.0.0 - Modular Management & Unversioned Filename
|
||||||
|
# - New git-clone-based management path: lib/menu.sh (reusable numbered-menu
|
||||||
|
# framework) + lib/config.sh (single config.json load/save) + menus/*.sh,
|
||||||
|
# run via ./install.sh against an already-installed kiosk. Sites & Page
|
||||||
|
# Timing and Display & Interaction are migrated; the rest of Core
|
||||||
|
# Settings/Addons/Advanced still live here and will move over the same
|
||||||
|
# way, one menu at a time. See Readme.md ("Modular Management").
|
||||||
|
# - Fixed: the old Sites menu could save config.json without first loading
|
||||||
|
# swipe/navigation/lockout settings, silently resetting them to defaults.
|
||||||
|
# - Fixed: reordering sites had an off-by-one that left the moved site one
|
||||||
|
# slot short of the requested position.
|
||||||
|
# - This script is now distributed as ubuntu-based-kiosk.sh (no version
|
||||||
|
# number in the filename) so it can be updated in place; released
|
||||||
|
# versions are tracked via git history and this changelog instead.
|
||||||
|
# Older ubuntu-based-kiosk-v*.sh / install_kiosk_*.sh files remain in the
|
||||||
|
# repo as archived releases.
|
||||||
|
#
|
||||||
# RELEASE v1.0.3 - Touch Screen Detection & Upgrade Reliability
|
# RELEASE v1.0.3 - Touch Screen Detection & Upgrade Reliability
|
||||||
# - Authelia auto-login addon (Addons menu → 5)
|
# - Authelia auto-login addon (Addons menu → 5)
|
||||||
# Password encrypted with AES-256-CBC keyed from /etc/machine-id
|
# Password encrypted with AES-256-CBC keyed from /etc/machine-id
|
||||||
@@ -68,7 +85,7 @@ set -euo pipefail
|
|||||||
### SECTION 1: CONSTANTS & GLOBALS
|
### SECTION 1: CONSTANTS & GLOBALS
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
SCRIPT_VERSION="1.0.3"
|
SCRIPT_VERSION="2.0.0"
|
||||||
|
|
||||||
# Resolve the real path to this script file.
|
# Resolve the real path to this script file.
|
||||||
# When piped (curl|bash or wget|bash), BASH_SOURCE[0] is a pipe descriptor,
|
# When piped (curl|bash or wget|bash), BASH_SOURCE[0] is a pipe descriptor,
|
||||||
Reference in New Issue
Block a user