Migrate Password Protection & Lockout menu; add missing ask_time helper; bump to v2.2.0
Fifth menu migrated onto lib/menu.sh + lib/config.sh: menus/lockout.sh covers enable/disable, changing the password, inactivity timeout, daily lock time, and boot password. The password is SHA-256 hashed before it's ever assigned to LOCKOUT_PASSWORD (matching main.js's comparison logic) - verified by test that the stored value is the correct hash and never plaintext. Rewrote the legacy configure_password_protection's linear "ask everything, confirm save at the end" wizard as the same immediate-save pattern used by every other migrated menu: each action (change password, change timeout, toggle boot password, ...) is a complete, standalone change, consistent with Sites/Display/Timezone/Hidden PIN. LOCKOUT_ACTIVE_START/END are deliberately left untouched - per the Readme they're inert leftover fields the app ignores, so lib/config.sh just carries whatever is already in config.json through unchanged. Testing this menu surfaced a real gap before it ever shipped: lib/menu.sh never had ask_time/validate_time at all (only validate_integer/ask_integer, ask_url, etc were ported when the framework was first built) - "set a daily lock time" would have failed for every single user with "ask_time: command not found". Ported both from the legacy script. Also promoted the ON/OFF toggle-label helper (previously private to menus/display.sh as display_onoff) to a shared onoff() in lib/menu.sh, since menus/lockout.sh needed the same thing and menu files should only ever depend on lib/, never on each other. Bumped SCRIPT_VERSION to 2.2.0 with matching changelog entries in the script header and Readme, and updated "Modular Management" to list the new menu and drop Password Protection & Lockout from the "not yet migrated" list. Verified: - Full regression: re-ran the Sites, Display, Timezone/PIN scratch-config suites after every change in this round (the onoff refactor, and again after adding ask_time) - all still clean. - New scratch-config test for lockout.sh: enable (password+timeout+daily lock+boot toggle), independently recomputed the expected SHA-256 hash and confirmed it matches config.json exactly, change password, change timeout, clear daily lock, toggle boot password, disable (confirmed every field clears), and that the menu builder's options correctly differ between the enabled and disabled states. - End-to-end: ran the real install.sh as a genuine non-root, non-"kiosk" user - Lockout menu -> enable protection with a real password entered via the masked prompt -> set 20m timeout, 23:00 daily lock, boot password on -> confirmed the menu redraws with the new state -> clean exit (code 0). Checked the resulting config.json and file permissions on disk.
This commit is contained in:
@@ -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.
|
||||
|
||||
+12
-3
@@ -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 <repo>
|
||||
@@ -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() {
|
||||
|
||||
+30
@@ -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
|
||||
|
||||
+6
-10
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+15
-2
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user