Two more menus migrated onto lib/menu.sh + lib/config.sh, chosen specifically because neither touches config.json - a third and fourth shape for the framework (a system command via timedatectl, and a flat PIN file), on top of Sites' list CRUD and Display's JSON toggles. - menus/timezone.sh: also replaces the legacy script's hand-numbered 18-entry case statement with a plain data list (TIMEZONE_COMMON_ZONES) plus one handler that reads the number run_menu hands it - adding or removing a zone never touches numbering anywhere else. Required a small run_menu addition: handlers now receive the chosen 1-based number as $1, so one handler can serve a whole data-driven list instead of needing a wrapper function per entry. - menus/hidden_pin.sh: set/disable/reset the PIN gating hidden pages. Testing menus/timezone.sh surfaced a real bug before it ever shipped: this whole tool runs under `set -e`, and set_timezone() rejecting an invalid zone via a bare `return 1` as its last statement took down the *entire* install.sh session, not just that one action - a single typo would silently drop the user back to their shell. Fixed at the framework level in lib/menu.sh (run_menu now absorbs a failed handler's exit code) rather than patching set_timezone alone, since any future menu could hit the same trap. Verified against the real install.sh as a genuine non-root user: an invalid timezone now logs an error and redraws the Timezone menu instead of killing the session (confirmed exit code 0 at the end of the run). Note this specific hazard was introduced by this session's own return-1 idiom, not inherited from the legacy script, which never uses a bare return 1 in these functions. Also per the user: left the old configure_sites/configure_touch_controls/ configure_navigation_security/configure_optional_features functions in ubuntu-based-kiosk.sh untouched for now (still carrying the v2.0.0 settings-clobber and reorder bugs) rather than removing them - they'll be retired in one pass once enough of Core Settings/Addons/Advanced is migrated. Bumped SCRIPT_VERSION to 2.1.0 with matching changelog entries in the script header and Readme, and updated the Readme's "Modular Management" section to state plainly what is and isn't migrated yet. Verified: - Full regression: re-ran the Sites and Display scratch-config test suites against the updated run_menu signature - both still clean. - New scratch-config tests for hidden_pin.sh (set/mismatch/reject/ disable/reset, correct file permissions) and timezone.sh (builder entry count, common-zone pick by index, manual entry with legacy US/* alias normalization, region search + cancel, invalid-zone rejection) - all correct, with timedatectl/sudo stubbed only where needed to avoid mutating this sandbox's real system clock/timezone. - End-to-end: ran the real install.sh as a genuine non-root, non-"kiosk" user, navigating Timezone -> manual entry -> invalid zone -> confirmed no crash and a normal return to the menu, then Hidden Site PIN -> set a PIN -> confirmed the file on disk (mode 600, correct content) -> clean exit (code 0).
130 lines
4.7 KiB
Bash
130 lines
4.7 KiB
Bash
#!/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
|
|
}
|