diff --git a/Readme.md b/Readme.md index 25adc0a..d4499d4 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,6 @@ # Ubuntu Based Kiosk -**Current Version:** 2.1.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** **License:** GPL v3 - Keep derivatives open source **Repository:** https://github.com/outis1one/ubuntu-based-kiosk/ @@ -1190,6 +1190,10 @@ terminal menu and the web UI, so they can't drift apart). - `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 *already-installed* kiosk: ```bash @@ -1201,10 +1205,10 @@ terminal menu and the web UI, so they can't drift apart). **Honest status:** this does not yet replace first-time installation, or most of the old installer. `ubuntu-based-kiosk.sh` is still ~12,000 lines and still contains its own unremoved, unmodified copies of every -menu above (plus WiFi, Power/Display/Quiet Hours, Password Protection & -Lockout, Upgrade, Reinstall, 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 +menu above (plus WiFi, Power/Display/Quiet Hours, Upgrade, Reinstall, +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 @@ -1214,9 +1218,14 @@ at all. ## Project Status & Future Plans -**Current Version:** 2.1.0 +**Current Version:** 2.2.0 -**Recent Updates (v2.1.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. diff --git a/install.sh b/install.sh index 23ab743..6e9bdcb 100755 --- a/install.sh +++ b/install.sh @@ -14,7 +14,8 @@ # # Migrated so far: Sites & Page Timing (menus/sites.sh), Display & # Interaction (menus/display.sh), Timezone (menus/timezone.sh), Hidden -# Site PIN (menus/hidden_pin.sh). +# Site PIN (menus/hidden_pin.sh), Password Protection & Lockout +# (menus/lockout.sh). # # Usage (once the kiosk has already been installed): # git clone @@ -38,6 +39,8 @@ source "$SCRIPT_DIR/menus/display.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 @@ -74,8 +77,14 @@ fi ################################################################################ main_menu_builder() { - MENU_LABELS=("Sites & Page Timing" "Display & Interaction" "Timezone" "Hidden Site PIN") - MENU_HANDLERS=(sites_menu display_menu timezone_menu hidden_pin_menu) + MENU_LABELS=( + "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() { diff --git a/lib/menu.sh b/lib/menu.sh index 6fa6d3f..542f43a 100644 --- a/lib/menu.sh +++ b/lib/menu.sh @@ -38,6 +38,12 @@ log_warning() { 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() { read -r -p "Press Enter to continue..." } @@ -112,6 +118,30 @@ ask_integer() { 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() { local url="$1" if [[ $url =~ ^(https?|file|data)://.*$ ]] || [[ $url =~ ^about: ]]; then diff --git a/menus/display.sh b/menus/display.sh index 64442a7..56172ec 100644 --- a/menus/display.sh +++ b/menus/display.sh @@ -17,22 +17,18 @@ 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" + echo "Pause button: $(onoff "$ENABLE_PAUSE_BUTTON")" + echo "Keyboard button: $(onoff "$ENABLE_KEYBOARD_BUTTON")" + echo "Navigation button: $(onoff "$ENABLE_NAV_BUTTON")" } 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"))" + "Toggle pause button (currently: $(onoff "$ENABLE_PAUSE_BUTTON"))" + "Toggle on-screen keyboard button (currently: $(onoff "$ENABLE_KEYBOARD_BUTTON"))" + "Toggle navigation/help button (currently: $(onoff "$ENABLE_NAV_BUTTON"))" ) MENU_HANDLERS=( action_set_touch_mode diff --git a/menus/lockout.sh b/menus/lockout.sh new file mode 100644 index 0000000..9810c1a --- /dev/null +++ b/menus/lockout.sh @@ -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 +} diff --git a/ubuntu-based-kiosk.sh b/ubuntu-based-kiosk.sh index d15874f..4574bfd 100644 --- a/ubuntu-based-kiosk.sh +++ b/ubuntu-based-kiosk.sh @@ -1,8 +1,21 @@ #!/bin/bash ################################################################################ -### Ubuntu Based Kiosk v2.1.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 @@ -109,7 +122,7 @@ set -euo pipefail ### SECTION 1: CONSTANTS & GLOBALS ################################################################################ -SCRIPT_VERSION="2.1.0" +SCRIPT_VERSION="2.2.0" # Resolve the real path to this script file. # When piped (curl|bash or wget|bash), BASH_SOURCE[0] is a pipe descriptor,