Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8672c15469 | ||
|
|
074b2ec2e3 |
@@ -1,6 +1,6 @@
|
|||||||
# Ubuntu Based Kiosk
|
# Ubuntu Based Kiosk
|
||||||
|
|
||||||
**Current Version:** 2.0.0 (check script header for latest version)
|
**Current Version:** 2.2.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/
|
||||||
@@ -1182,8 +1182,18 @@ terminal menu and the web UI, so they can't drift apart).
|
|||||||
concept for this approach.
|
concept for this approach.
|
||||||
- `menus/display.sh` — **Display & Interaction**: touch gesture mode,
|
- `menus/display.sh` — **Display & Interaction**: touch gesture mode,
|
||||||
link navigation security, and the on-screen pause/keyboard/navigation
|
link navigation security, and the on-screen pause/keyboard/navigation
|
||||||
button toggles. A second proof of concept covering a different menu
|
button toggles. A different menu shape from Sites (settings toggles
|
||||||
shape (settings toggles vs. the list CRUD in Sites).
|
vs. list CRUD).
|
||||||
|
- `menus/timezone.sh` — **Timezone**: also replaces the legacy script's
|
||||||
|
hand-numbered 18-entry `case` statement with a plain data list plus one
|
||||||
|
handler — the numbering is just `run_menu`'s job now.
|
||||||
|
- `menus/hidden_pin.sh` — **Hidden Site PIN**: the PIN gating hidden
|
||||||
|
pages (`duration: -1` in Sites). A fourth shape again — a flat file,
|
||||||
|
not `config.json`.
|
||||||
|
- `menus/lockout.sh` — **Password Protection & Lockout**: enable/disable,
|
||||||
|
change password, inactivity timeout, daily lock time, boot password.
|
||||||
|
The password is SHA-256 hashed before it's ever written to disk, same
|
||||||
|
as the legacy menu — never stored as plaintext.
|
||||||
- `install.sh` — entry point for the modular tool. Run it against an
|
- `install.sh` — entry point for the modular tool. Run it against an
|
||||||
*already-installed* kiosk:
|
*already-installed* kiosk:
|
||||||
```bash
|
```bash
|
||||||
@@ -1192,19 +1202,35 @@ terminal menu and the web UI, so they can't drift apart).
|
|||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
This does **not** yet replace first-time installation — that's still the
|
**Honest status:** this does not yet replace first-time installation, or
|
||||||
single-file script above (`Quick Install`). The rest of Core
|
most of the old installer. `ubuntu-based-kiosk.sh` is still ~12,000
|
||||||
Settings/Addons/Advanced will move into `menus/*.sh` the same way, one
|
lines and still contains its own unremoved, unmodified copies of every
|
||||||
menu at a time, and `install.sh` will eventually take over the whole
|
menu above (plus WiFi, Power/Display/Quiet Hours, Upgrade, Reinstall,
|
||||||
`show_main_menu` from the legacy script.
|
Uninstall, all Addons, and all of Advanced — none of that has moved
|
||||||
|
yet). Both copies coexist deliberately: the old ones stay until enough
|
||||||
|
of Core Settings/Addons/Advanced is migrated to
|
||||||
|
retire them in one pass, rather than leaving the legacy menu half-wired.
|
||||||
|
Migration continues one `menus/*.sh` file at a time; first-time
|
||||||
|
installation itself is the last and largest piece to move, if it moves
|
||||||
|
at all.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Project Status & Future Plans
|
## Project Status & Future Plans
|
||||||
|
|
||||||
**Current Version:** 2.0.0
|
**Current Version:** 2.2.0
|
||||||
|
|
||||||
**Recent Updates (v2.0.0):**
|
**Recent Updates (v2.2.0):**
|
||||||
|
- **Fifth menu migrated:** Password Protection & Lockout (`menus/lockout.sh`) — enable/disable, change password, inactivity timeout, daily lock time, boot password. The password is SHA-256 hashed before it's ever written to `config.json` (matching the Electron app's own comparison logic) — verified never stored as plaintext.
|
||||||
|
- **Bug fix:** `lib/menu.sh` was missing `ask_time`/`validate_time` entirely — caught by testing this menu before it shipped; "set a daily lock time" would otherwise have failed for every user. Ported from the legacy script.
|
||||||
|
- **Refactor:** promoted the ON/OFF toggle-label helper out of `menus/display.sh` into a shared `onoff()` in `lib/menu.sh`, so `menus/lockout.sh` doesn't need to depend on another menu file — menus only ever depend on `lib/`.
|
||||||
|
|
||||||
|
**Previous (v2.1.0):**
|
||||||
|
- **Two more menus migrated:** Timezone (`menus/timezone.sh`) and Hidden Site PIN (`menus/hidden_pin.sh`), joining Sites & Page Timing and Display & Interaction in `./install.sh`. Timezone also replaces the old hand-numbered 18-entry list with a data-driven one built on the generic menu framework.
|
||||||
|
- **Bug fix (framework-level):** `install.sh` runs under `set -e`; a menu action that legitimately fails (e.g. rejecting an invalid timezone) and returns non-zero as its last statement could take down the *entire* session instead of just that action. Caught by testing before this ever shipped broadly; `run_menu()` now absorbs a failed handler's exit code, protecting every menu — present and future.
|
||||||
|
- The old, unmigrated `configure_sites`/`configure_touch_controls`/`configure_navigation_security`/`configure_optional_features` in `ubuntu-based-kiosk.sh` are staying in place for now (still carrying the v2.0.0 bugs below) until enough of Core Settings/Addons/Advanced is migrated to retire them in one pass — see "Modular Management" below for exactly what's covered so far.
|
||||||
|
|
||||||
|
**Previous (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.
|
- **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:** 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.
|
- **Bug fix:** reordering sites had an off-by-one that left the moved site one slot short of the requested position.
|
||||||
|
|||||||
+26
-11
@@ -3,16 +3,19 @@
|
|||||||
# install.sh - Modular management entry point for Ubuntu Based Kiosk.
|
# install.sh - Modular management entry point for Ubuntu Based Kiosk.
|
||||||
#
|
#
|
||||||
# This is NOT yet the full system installer - that is still the big
|
# 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
|
# single-file script (ubuntu-based-kiosk.sh) documented in Readme.md, and
|
||||||
# Readme.md, and first-time provisioning of a new kiosk still goes through
|
# first-time provisioning of a new kiosk still goes through it. That file
|
||||||
# it. This entry point is the start of pulling the *menu system* out of
|
# still also contains its own (unmigrated, unmodified) copies of every
|
||||||
# that 12k-line file into small, independently editable modules under
|
# menu below - both copies coexist deliberately until enough of Core
|
||||||
# lib/ and menus/, so a change to (say) the Sites menu can't accidentally
|
# Settings/Addons/Advanced has moved over to retire the old ones in one
|
||||||
# break WiFi setup or the uninstaller three thousand lines away.
|
# pass. This entry point is the modular replacement, one menus/*.sh file
|
||||||
|
# at a time, 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 &
|
# Migrated so far: Sites & Page Timing (menus/sites.sh), Display &
|
||||||
# Interaction (menus/display.sh). The rest of Core Settings/Addons/Advanced
|
# Interaction (menus/display.sh), Timezone (menus/timezone.sh), Hidden
|
||||||
# will move over the same way, one menus/*.sh file at a time.
|
# Site PIN (menus/hidden_pin.sh), Password Protection & Lockout
|
||||||
|
# (menus/lockout.sh).
|
||||||
#
|
#
|
||||||
# Usage (once the kiosk has already been installed):
|
# Usage (once the kiosk has already been installed):
|
||||||
# git clone <repo>
|
# git clone <repo>
|
||||||
@@ -32,6 +35,12 @@ source "$SCRIPT_DIR/lib/config.sh"
|
|||||||
source "$SCRIPT_DIR/menus/sites.sh"
|
source "$SCRIPT_DIR/menus/sites.sh"
|
||||||
# shellcheck source=menus/display.sh
|
# shellcheck source=menus/display.sh
|
||||||
source "$SCRIPT_DIR/menus/display.sh"
|
source "$SCRIPT_DIR/menus/display.sh"
|
||||||
|
# shellcheck source=menus/timezone.sh
|
||||||
|
source "$SCRIPT_DIR/menus/timezone.sh"
|
||||||
|
# shellcheck source=menus/hidden_pin.sh
|
||||||
|
source "$SCRIPT_DIR/menus/hidden_pin.sh"
|
||||||
|
# shellcheck source=menus/lockout.sh
|
||||||
|
source "$SCRIPT_DIR/menus/lockout.sh"
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Preflight
|
# Preflight
|
||||||
@@ -68,8 +77,14 @@ fi
|
|||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
main_menu_builder() {
|
main_menu_builder() {
|
||||||
MENU_LABELS=("Sites & Page Timing" "Display & Interaction")
|
MENU_LABELS=(
|
||||||
MENU_HANDLERS=(sites_menu display_menu)
|
"Sites & Page Timing"
|
||||||
|
"Display & Interaction"
|
||||||
|
"Timezone"
|
||||||
|
"Hidden Site PIN"
|
||||||
|
"Password Protection & Lockout"
|
||||||
|
)
|
||||||
|
MENU_HANDLERS=(sites_menu display_menu timezone_menu hidden_pin_menu lockout_menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
main_menu_status() {
|
main_menu_status() {
|
||||||
|
|||||||
+42
-1
@@ -38,6 +38,12 @@ log_warning() {
|
|||||||
echo "⚠ $*"
|
echo "⚠ $*"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Shared "true"/"false" -> "ON"/"OFF" label for status lines and menu
|
||||||
|
# entries showing a boolean setting's current value.
|
||||||
|
onoff() {
|
||||||
|
[[ "$1" == "true" ]] && echo "ON" || echo "OFF"
|
||||||
|
}
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
read -r -p "Press Enter to continue..."
|
read -r -p "Press Enter to continue..."
|
||||||
}
|
}
|
||||||
@@ -112,6 +118,30 @@ ask_integer() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate_time() {
|
||||||
|
local time="$1"
|
||||||
|
[[ $time =~ ^([0-1][0-9]|2[0-3]):([0-5][0-9])$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
ask_time() {
|
||||||
|
local prompt="$1"
|
||||||
|
local default="$2"
|
||||||
|
local time
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "$prompt [$default]: " time
|
||||||
|
time="${time:-$default}"
|
||||||
|
|
||||||
|
if validate_time "$time"; then
|
||||||
|
echo "$time"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "❌ Invalid time format. Please use HH:MM (00:00 to 23:59)" >&2
|
||||||
|
echo >&2
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
validate_url() {
|
validate_url() {
|
||||||
local url="$1"
|
local url="$1"
|
||||||
if [[ $url =~ ^(https?|file|data)://.*$ ]] || [[ $url =~ ^about: ]]; then
|
if [[ $url =~ ^(https?|file|data)://.*$ ]] || [[ $url =~ ^about: ]]; then
|
||||||
@@ -193,6 +223,11 @@ print_menu_header() {
|
|||||||
#
|
#
|
||||||
# Entries are auto-numbered 1..N. "0" always returns from run_menu - no
|
# Entries are auto-numbered 1..N. "0" always returns from run_menu - no
|
||||||
# menu file needs to hand-roll its own exit case.
|
# menu file needs to hand-roll its own exit case.
|
||||||
|
#
|
||||||
|
# The handler is called as `handler "$choice"` (the 1-based number picked),
|
||||||
|
# so a data-driven list (e.g. a set of timezones) can share one handler
|
||||||
|
# instead of needing a distinct wrapper function per entry. Handlers that
|
||||||
|
# don't care can just ignore the argument.
|
||||||
run_menu() {
|
run_menu() {
|
||||||
local title="$1"
|
local title="$1"
|
||||||
local builder="$2"
|
local builder="$2"
|
||||||
@@ -235,6 +270,12 @@ run_menu() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
"${MENU_HANDLERS[$((choice - 1))]}"
|
# `|| true`: this whole tool runs under `set -e`. A handler that
|
||||||
|
# legitimately fails (invalid input, a guard clause, etc) and
|
||||||
|
# returns non-zero as its last statement must not be allowed to
|
||||||
|
# take the entire session down - it should just redraw the menu.
|
||||||
|
# Absorbing that here means no menus/*.sh file has to think about
|
||||||
|
# set -e at all.
|
||||||
|
"${MENU_HANDLERS[$((choice - 1))]}" "$choice" || true
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-10
@@ -17,22 +17,18 @@
|
|||||||
display_status() {
|
display_status() {
|
||||||
echo "Touch gesture mode: $SWIPE_MODE"
|
echo "Touch gesture mode: $SWIPE_MODE"
|
||||||
echo "Link navigation: $ALLOW_NAVIGATION"
|
echo "Link navigation: $ALLOW_NAVIGATION"
|
||||||
echo "Pause button: $(display_onoff "$ENABLE_PAUSE_BUTTON")"
|
echo "Pause button: $(onoff "$ENABLE_PAUSE_BUTTON")"
|
||||||
echo "Keyboard button: $(display_onoff "$ENABLE_KEYBOARD_BUTTON")"
|
echo "Keyboard button: $(onoff "$ENABLE_KEYBOARD_BUTTON")"
|
||||||
echo "Navigation button: $(display_onoff "$ENABLE_NAV_BUTTON")"
|
echo "Navigation button: $(onoff "$ENABLE_NAV_BUTTON")"
|
||||||
}
|
|
||||||
|
|
||||||
display_onoff() {
|
|
||||||
[[ "$1" == "true" ]] && echo "ON" || echo "OFF"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display_menu_builder() {
|
display_menu_builder() {
|
||||||
MENU_LABELS=(
|
MENU_LABELS=(
|
||||||
"Touch gesture mode (currently: $SWIPE_MODE)"
|
"Touch gesture mode (currently: $SWIPE_MODE)"
|
||||||
"Link navigation security (currently: $ALLOW_NAVIGATION)"
|
"Link navigation security (currently: $ALLOW_NAVIGATION)"
|
||||||
"Toggle pause button (currently: $(display_onoff "$ENABLE_PAUSE_BUTTON"))"
|
"Toggle pause button (currently: $(onoff "$ENABLE_PAUSE_BUTTON"))"
|
||||||
"Toggle on-screen keyboard button (currently: $(display_onoff "$ENABLE_KEYBOARD_BUTTON"))"
|
"Toggle on-screen keyboard button (currently: $(onoff "$ENABLE_KEYBOARD_BUTTON"))"
|
||||||
"Toggle navigation/help button (currently: $(display_onoff "$ENABLE_NAV_BUTTON"))"
|
"Toggle navigation/help button (currently: $(onoff "$ENABLE_NAV_BUTTON"))"
|
||||||
)
|
)
|
||||||
MENU_HANDLERS=(
|
MENU_HANDLERS=(
|
||||||
action_set_touch_mode
|
action_set_touch_mode
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/hidden_pin.sh - "Hidden Site PIN" menu.
|
||||||
|
#
|
||||||
|
# Guards access to hidden pages (duration = -1, see menus/sites.sh) via a
|
||||||
|
# flat PIN file rather than config.json - a fourth shape for the framework
|
||||||
|
# to prove out (plain file, not JSON at all).
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
hidden_pin_file() {
|
||||||
|
echo "$KIOSK_DIR/.jitsi-pin"
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden_pin_status() {
|
||||||
|
local pin_file
|
||||||
|
pin_file=$(hidden_pin_file)
|
||||||
|
|
||||||
|
if sudo -u "$KIOSK_USER" test -f "$pin_file" 2>/dev/null; then
|
||||||
|
local current_pin
|
||||||
|
current_pin=$(sudo -u "$KIOSK_USER" cat "$pin_file" 2>/dev/null)
|
||||||
|
if [[ "$current_pin" == "NOPIN" ]]; then
|
||||||
|
echo "Current: no PIN (hidden pages open to anyone)"
|
||||||
|
else
|
||||||
|
echo "Current: PIN set (${#current_pin} digits)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Current: not configured (default: 1234)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden_pin_menu_builder() {
|
||||||
|
MENU_LABELS=("Set new PIN (4-8 digits)" "Disable PIN (open access)" "Reset to default (1234)")
|
||||||
|
MENU_HANDLERS=(action_set_pin action_disable_pin action_reset_pin)
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden_pin_menu() {
|
||||||
|
run_menu "HIDDEN SITE PIN" hidden_pin_menu_builder hidden_pin_status
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Actions
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
write_pin() {
|
||||||
|
local value="$1"
|
||||||
|
local pin_file
|
||||||
|
pin_file=$(hidden_pin_file)
|
||||||
|
|
||||||
|
sudo mkdir -p "$KIOSK_DIR"
|
||||||
|
echo "$value" | sudo -u "$KIOSK_USER" tee "$pin_file" > /dev/null
|
||||||
|
sudo -u "$KIOSK_USER" chmod 600 "$pin_file"
|
||||||
|
log_warning "Restart the kiosk display for this to take effect"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_set_pin() {
|
||||||
|
echo
|
||||||
|
local new_pin confirm_pin
|
||||||
|
while true; do
|
||||||
|
read -r -p "Enter new PIN (4-8 digits): " new_pin
|
||||||
|
|
||||||
|
if [[ ! "$new_pin" =~ ^[0-9]{4,8}$ ]]; then
|
||||||
|
echo "❌ PIN must be 4-8 digits"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -r -p "Confirm PIN: " confirm_pin
|
||||||
|
|
||||||
|
if [[ "$new_pin" == "$confirm_pin" ]]; then
|
||||||
|
write_pin "$new_pin"
|
||||||
|
log_success "PIN updated"
|
||||||
|
break
|
||||||
|
else
|
||||||
|
echo "❌ PINs don't match, try again"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
action_disable_pin() {
|
||||||
|
write_pin "NOPIN"
|
||||||
|
log_success "PIN disabled - hidden pages accessible without a PIN"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_reset_pin() {
|
||||||
|
write_pin "1234"
|
||||||
|
log_success "PIN reset to default (1234)"
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/lockout.sh - "Password Protection & Lockout" menu.
|
||||||
|
#
|
||||||
|
# Fifth menu migrated. Back to config.json (like Display), but with a
|
||||||
|
# sensitive field: the lockout password is SHA-256 hashed before it's
|
||||||
|
# ever written to disk (matching the Electron app's comparison logic in
|
||||||
|
# main.js) - LOCKOUT_PASSWORD must never hold plaintext.
|
||||||
|
#
|
||||||
|
# Unlike the legacy configure_password_protection wizard (walk through
|
||||||
|
# every question once, then one final "save these changes? y/n"), this
|
||||||
|
# follows the same immediate-save pattern as every other migrated menu:
|
||||||
|
# each action is a complete, standalone change. Re-running "Enable" to
|
||||||
|
# change your mind is just as easy as the old "discard changes" path,
|
||||||
|
# and there's no separate confirm-at-the-end step to forget.
|
||||||
|
#
|
||||||
|
# LOCKOUT_ACTIVE_START/LOCKOUT_ACTIVE_END are intentionally never touched
|
||||||
|
# here - the app doesn't act on them (see Readme "Configuration Files"),
|
||||||
|
# so lib/config.sh just carries whatever is already in config.json
|
||||||
|
# through unchanged.
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
lockout_status() {
|
||||||
|
if [[ "$ENABLE_PASSWORD_PROTECTION" == "true" ]]; then
|
||||||
|
echo "Password protection: ENABLED"
|
||||||
|
echo "Inactivity lockout: ${LOCKOUT_TIMEOUT} minutes$( [[ "$LOCKOUT_TIMEOUT" == "0" ]] && echo " (disabled - boot/wake only)")"
|
||||||
|
if [[ -n "$LOCKOUT_AT_TIME" ]]; then
|
||||||
|
echo "Daily lock time: $LOCKOUT_AT_TIME"
|
||||||
|
else
|
||||||
|
echo "Daily lock time: not set"
|
||||||
|
fi
|
||||||
|
echo "Password on boot: $(onoff "$REQUIRE_PASSWORD_ON_BOOT")"
|
||||||
|
else
|
||||||
|
echo "Password protection: disabled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
lockout_menu_builder() {
|
||||||
|
if [[ "$ENABLE_PASSWORD_PROTECTION" == "true" ]]; then
|
||||||
|
MENU_LABELS=(
|
||||||
|
"Change lockout password"
|
||||||
|
"Change inactivity lockout timeout (currently: ${LOCKOUT_TIMEOUT}m)"
|
||||||
|
"Set/clear daily lock time (currently: ${LOCKOUT_AT_TIME:-not set})"
|
||||||
|
"Toggle require password on boot (currently: $(onoff "$REQUIRE_PASSWORD_ON_BOOT"))"
|
||||||
|
"Disable password protection"
|
||||||
|
)
|
||||||
|
MENU_HANDLERS=(
|
||||||
|
action_change_password
|
||||||
|
action_change_timeout
|
||||||
|
action_change_daily_lock
|
||||||
|
action_toggle_boot_password
|
||||||
|
action_disable_protection
|
||||||
|
)
|
||||||
|
else
|
||||||
|
MENU_LABELS=("Enable password protection")
|
||||||
|
MENU_HANDLERS=(action_enable_protection)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
lockout_menu() {
|
||||||
|
load_existing_config
|
||||||
|
run_menu "PASSWORD PROTECTION & LOCKOUT" lockout_menu_builder lockout_status
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Shared helpers
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Prompts for a new password twice, hashes it, and assigns to
|
||||||
|
# LOCKOUT_PASSWORD. Returns 1 (without saving) if the user gives up.
|
||||||
|
prompt_and_hash_password() {
|
||||||
|
local pass1 pass2
|
||||||
|
while true; do
|
||||||
|
read -r -s -p "Enter password: " pass1
|
||||||
|
echo
|
||||||
|
read -r -s -p "Confirm password: " pass2
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ -z "$pass1" ]]; then
|
||||||
|
echo "❌ Password cannot be empty"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$pass1" != "$pass2" ]]; then
|
||||||
|
echo "❌ Passwords don't match, try again"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
LOCKOUT_PASSWORD=$(echo -n "$pass1" | sha256sum | cut -d' ' -f1)
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Actions
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_enable_protection() {
|
||||||
|
echo
|
||||||
|
echo "Add password protection with automatic lockout:"
|
||||||
|
echo " • Blank screen after an inactivity period"
|
||||||
|
echo " • Password required to unlock"
|
||||||
|
echo " • Password required after display schedule wake-up"
|
||||||
|
echo " • Optional: lock at a specific time daily"
|
||||||
|
echo " • Optional: require password on system boot"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Set lockout password:"
|
||||||
|
prompt_and_hash_password
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Session lockout time (minutes of inactivity)."
|
||||||
|
echo "Enter 0 to only require a password after display wake or boot."
|
||||||
|
LOCKOUT_TIMEOUT=$(ask_integer "Lockout timeout in minutes" "30" 0 1440)
|
||||||
|
|
||||||
|
echo
|
||||||
|
if ask_yes_no "Lock automatically at a specific time each day?" "n"; then
|
||||||
|
LOCKOUT_AT_TIME=$(ask_time "Time to lock (24-hour HH:MM)" "17:00")
|
||||||
|
else
|
||||||
|
LOCKOUT_AT_TIME=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
if ask_yes_no "Require password on system boot/power on?" "y"; then
|
||||||
|
REQUIRE_PASSWORD_ON_BOOT="true"
|
||||||
|
else
|
||||||
|
REQUIRE_PASSWORD_ON_BOOT="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENABLE_PASSWORD_PROTECTION="true"
|
||||||
|
log_success "Password protection enabled (lockout: ${LOCKOUT_TIMEOUT}m)"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_disable_protection() {
|
||||||
|
ENABLE_PASSWORD_PROTECTION="false"
|
||||||
|
LOCKOUT_PASSWORD=""
|
||||||
|
LOCKOUT_TIMEOUT=0
|
||||||
|
LOCKOUT_AT_TIME=""
|
||||||
|
REQUIRE_PASSWORD_ON_BOOT="false"
|
||||||
|
log_success "Password protection disabled"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_change_password() {
|
||||||
|
echo
|
||||||
|
prompt_and_hash_password
|
||||||
|
log_success "Password updated"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_change_timeout() {
|
||||||
|
echo
|
||||||
|
echo "Session lockout time (minutes of inactivity)."
|
||||||
|
echo "Enter 0 to only require a password after display wake or boot."
|
||||||
|
LOCKOUT_TIMEOUT=$(ask_integer "Lockout timeout in minutes" "$LOCKOUT_TIMEOUT" 0 1440)
|
||||||
|
log_success "Lockout timeout: ${LOCKOUT_TIMEOUT}m"
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_change_daily_lock() {
|
||||||
|
echo
|
||||||
|
local default_prompt
|
||||||
|
[[ -n "$LOCKOUT_AT_TIME" ]] && default_prompt="y" || default_prompt="n"
|
||||||
|
|
||||||
|
if ask_yes_no "Lock automatically at a specific time each day?" "$default_prompt"; then
|
||||||
|
LOCKOUT_AT_TIME=$(ask_time "Time to lock (24-hour HH:MM)" "${LOCKOUT_AT_TIME:-17:00}")
|
||||||
|
log_success "Will lock at ${LOCKOUT_AT_TIME} daily"
|
||||||
|
else
|
||||||
|
LOCKOUT_AT_TIME=""
|
||||||
|
log_success "Daily lock time cleared"
|
||||||
|
fi
|
||||||
|
save_config
|
||||||
|
}
|
||||||
|
|
||||||
|
action_toggle_boot_password() {
|
||||||
|
if [[ "$REQUIRE_PASSWORD_ON_BOOT" == "true" ]]; then
|
||||||
|
REQUIRE_PASSWORD_ON_BOOT="false"
|
||||||
|
log_warning "Password on boot disabled"
|
||||||
|
else
|
||||||
|
REQUIRE_PASSWORD_ON_BOOT="true"
|
||||||
|
log_success "Password on boot enabled"
|
||||||
|
fi
|
||||||
|
save_config
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/timezone.sh - "Timezone" menu.
|
||||||
|
#
|
||||||
|
# Third menu migrated off the old single-file installer, and a different
|
||||||
|
# shape again: not config.json at all - talks to timedatectl/system state
|
||||||
|
# directly. Also the clearest demonstration of the framework's value: the
|
||||||
|
# original hand-numbered an 18-entry list ("1) America/New_York ... 18)
|
||||||
|
# Enter manually") in a single case statement. Here the common-zone list
|
||||||
|
# is just data, one handler (action_pick_common_timezone) handles all of
|
||||||
|
# them using the number run_menu hands it, and adding/removing a zone
|
||||||
|
# from the list never touches numbering anywhere else.
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
TIMEZONE_COMMON_ZONES=(
|
||||||
|
"America/New_York" "America/Chicago" "America/Denver" "America/Los_Angeles"
|
||||||
|
"America/Phoenix" "America/Anchorage" "Pacific/Honolulu" "Europe/London"
|
||||||
|
"Europe/Paris" "Europe/Berlin" "Europe/Rome" "Asia/Tokyo" "Asia/Shanghai"
|
||||||
|
"Asia/Dubai" "Australia/Sydney" "Pacific/Auckland"
|
||||||
|
)
|
||||||
|
TIMEZONE_COMMON_LABELS=(
|
||||||
|
"US Eastern" "US Central" "US Mountain" "US Pacific" "US Arizona" "US Alaska"
|
||||||
|
"US Hawaii" "UK" "Central Europe" "Germany" "Italy" "Japan" "China" "UAE"
|
||||||
|
"Australia East" "New Zealand"
|
||||||
|
)
|
||||||
|
|
||||||
|
timezone_status() {
|
||||||
|
echo "Current timezone: $(timedatectl show -p Timezone --value)"
|
||||||
|
}
|
||||||
|
|
||||||
|
timezone_menu_builder() {
|
||||||
|
MENU_LABELS=()
|
||||||
|
MENU_HANDLERS=()
|
||||||
|
for i in "${!TIMEZONE_COMMON_ZONES[@]}"; do
|
||||||
|
MENU_LABELS+=("${TIMEZONE_COMMON_ZONES[$i]} (${TIMEZONE_COMMON_LABELS[$i]})")
|
||||||
|
MENU_HANDLERS+=(action_pick_common_timezone)
|
||||||
|
done
|
||||||
|
MENU_LABELS+=("Search for timezone by region" "Enter timezone manually")
|
||||||
|
MENU_HANDLERS+=(action_search_timezone action_manual_timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
timezone_menu() {
|
||||||
|
run_menu "TIMEZONE" timezone_menu_builder timezone_status
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Actions
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Called by run_menu as `action_pick_common_timezone "$choice"` - $choice is
|
||||||
|
# the 1-based menu number, which lines up directly with TIMEZONE_COMMON_ZONES.
|
||||||
|
action_pick_common_timezone() {
|
||||||
|
local choice="$1"
|
||||||
|
set_timezone "${TIMEZONE_COMMON_ZONES[$((choice - 1))]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_search_timezone() {
|
||||||
|
echo
|
||||||
|
echo "Available regions:"
|
||||||
|
local regions
|
||||||
|
regions=($(timedatectl list-timezones | cut -d'/' -f1 | sort -u))
|
||||||
|
for i in "${!regions[@]}"; do
|
||||||
|
printf " %2d) %s\n" "$((i + 1))" "${regions[$i]}"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
local region_num
|
||||||
|
region_num=$(ask_integer "Select region number (0=cancel)" "0" 0 "${#regions[@]}")
|
||||||
|
[[ "$region_num" == "0" ]] && { echo "Cancelled"; return; }
|
||||||
|
local selected_region="${regions[$((region_num - 1))]}"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Timezones in $selected_region:"
|
||||||
|
local timezones
|
||||||
|
timezones=($(timedatectl list-timezones | grep "^${selected_region}/"))
|
||||||
|
for i in "${!timezones[@]}"; do
|
||||||
|
printf " %3d) %s\n" "$((i + 1))" "${timezones[$i]}"
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
|
||||||
|
local tz_num
|
||||||
|
tz_num=$(ask_integer "Select timezone number (0=cancel)" "0" 0 "${#timezones[@]}")
|
||||||
|
[[ "$tz_num" == "0" ]] && { echo "Cancelled"; return; }
|
||||||
|
set_timezone "${timezones[$((tz_num - 1))]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_manual_timezone() {
|
||||||
|
echo
|
||||||
|
local new_tz
|
||||||
|
read -r -p "Enter timezone (e.g., America/New_York): " new_tz
|
||||||
|
[[ -z "$new_tz" ]] && { echo "Cancelled"; return; }
|
||||||
|
set_timezone "$new_tz"
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Shared apply logic
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
set_timezone() {
|
||||||
|
local new_tz="$1"
|
||||||
|
|
||||||
|
# A few legacy US/* aliases users might type manually - normalize before
|
||||||
|
# validating against the canonical IANA list.
|
||||||
|
case "$new_tz" in
|
||||||
|
"US/Eastern") new_tz="America/New_York" ;;
|
||||||
|
"US/Central") new_tz="America/Chicago" ;;
|
||||||
|
"US/Mountain") new_tz="America/Denver" ;;
|
||||||
|
"US/Pacific") new_tz="America/Los_Angeles" ;;
|
||||||
|
"US/Alaska") new_tz="America/Anchorage" ;;
|
||||||
|
"US/Hawaii") new_tz="Pacific/Honolulu" ;;
|
||||||
|
"US/Arizona") new_tz="America/Phoenix" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if ! timedatectl list-timezones | grep -qx "$new_tz"; then
|
||||||
|
log_error "Invalid timezone: $new_tz"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if sudo timedatectl set-timezone "$new_tz"; then
|
||||||
|
log_success "Timezone updated to $new_tz"
|
||||||
|
else
|
||||||
|
# Fallback: set timezone directly without D-Bus
|
||||||
|
sudo ln -sf "/usr/share/zoneinfo/$new_tz" /etc/localtime
|
||||||
|
echo "$new_tz" | sudo tee /etc/timezone > /dev/null
|
||||||
|
log_success "Timezone updated to $new_tz (direct)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
+39
-2
@@ -1,8 +1,45 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
################################################################################
|
################################################################################
|
||||||
### Ubuntu Based Kiosk v2.0.0 ###
|
### Ubuntu Based Kiosk v2.2.0 ###
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
|
# RELEASE v2.2.0 - Password Protection & Lockout Migrated
|
||||||
|
# - New in ./install.sh: Password Protection & Lockout (menus/lockout.sh) -
|
||||||
|
# enable/disable, change password (SHA-256 hashed before it's ever
|
||||||
|
# written to disk, matching main.js's comparison logic - never
|
||||||
|
# plaintext), inactivity timeout, daily lock time, boot password.
|
||||||
|
# - Fixed: lib/menu.sh was missing ask_time/validate_time entirely (only
|
||||||
|
# caught by testing this menu, before it shipped - "Set daily lock
|
||||||
|
# time" would have failed with "ask_time: command not found" for every
|
||||||
|
# user). Ported from the legacy script; also promoted the ON/OFF
|
||||||
|
# toggle-label helper (previously private to menus/display.sh) to a
|
||||||
|
# shared `onoff()` in lib/menu.sh so menus/lockout.sh doesn't have to
|
||||||
|
# depend on menus/display.sh - menus should only ever depend on lib/.
|
||||||
|
#
|
||||||
|
# RELEASE v2.1.0 - Two More Menus Migrated, Menu Framework Hardened
|
||||||
|
# - New in ./install.sh: Timezone (menus/timezone.sh) and Hidden Site PIN
|
||||||
|
# (menus/hidden_pin.sh) menus, alongside Sites & Page Timing and Display
|
||||||
|
# & Interaction from v2.0.0. Timezone doubles as a demonstration of the
|
||||||
|
# framework: the old hand-numbered 18-entry case statement is now just
|
||||||
|
# a data list plus one handler.
|
||||||
|
# - Hardened lib/menu.sh: since this whole tool runs under `set -e`, a menu
|
||||||
|
# action that legitimately fails (e.g. rejecting an invalid timezone) and
|
||||||
|
# returns non-zero as its last statement could take down the *entire*
|
||||||
|
# session, not just that one action - one typo would silently drop the
|
||||||
|
# user back to their shell. Caught by testing menus/timezone.sh (its
|
||||||
|
# set_timezone() does `return 1` on an invalid zone) before this ever
|
||||||
|
# shipped; run_menu() now absorbs a failed handler's exit code so it
|
||||||
|
# only redraws the menu, protecting every menu, present and future.
|
||||||
|
# - The old (unmigrated) configure_sites/configure_touch_controls/
|
||||||
|
# configure_navigation_security/configure_optional_features functions
|
||||||
|
# still live in this script, unchanged, and still have both v2.0.0 bugs
|
||||||
|
# above - left in place deliberately until enough of Core Settings/
|
||||||
|
# Addons/Advanced is migrated to retire them in one pass. configure_
|
||||||
|
# timezone/configure_hidden_site_pin don't share the set -e hazard
|
||||||
|
# (they never use a bare `return 1`), but are otherwise also still
|
||||||
|
# here unchanged pending the same cleanup. See Readme.md ("Modular
|
||||||
|
# Management") for current migration status.
|
||||||
|
#
|
||||||
# RELEASE v2.0.0 - Modular Management & Unversioned Filename
|
# RELEASE v2.0.0 - Modular Management & Unversioned Filename
|
||||||
# - New git-clone-based management path: lib/menu.sh (reusable numbered-menu
|
# - New git-clone-based management path: lib/menu.sh (reusable numbered-menu
|
||||||
# framework) + lib/config.sh (single config.json load/save) + menus/*.sh,
|
# framework) + lib/config.sh (single config.json load/save) + menus/*.sh,
|
||||||
@@ -85,7 +122,7 @@ set -euo pipefail
|
|||||||
### SECTION 1: CONSTANTS & GLOBALS
|
### SECTION 1: CONSTANTS & GLOBALS
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
SCRIPT_VERSION="2.0.0"
|
SCRIPT_VERSION="2.2.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