Compare commits
3
Commits
8672c15469
...
1b16bcf3ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b16bcf3ee | ||
|
|
459da53182 | ||
|
|
2375bf5eab |
@@ -1,6 +1,6 @@
|
|||||||
# Ubuntu Based Kiosk
|
# Ubuntu Based Kiosk
|
||||||
|
|
||||||
**Current Version:** 2.2.0 (check script header for latest version)
|
**Current Version:** 2.5.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/
|
||||||
@@ -1194,7 +1194,23 @@ terminal menu and the web UI, so they can't drift apart).
|
|||||||
change password, inactivity timeout, daily lock time, boot password.
|
change password, inactivity timeout, daily lock time, boot password.
|
||||||
The password is SHA-256 hashed before it's ever written to disk, same
|
The password is SHA-256 hashed before it's ever written to disk, same
|
||||||
as the legacy menu — never stored as plaintext.
|
as the legacy menu — never stored as plaintext.
|
||||||
- `install.sh` — entry point for the modular tool. Run it against an
|
- `menus/wifi.sh` — **WiFi**: the riskiest menu so far — rewrites live
|
||||||
|
netplan config and, over SSH, can disconnect the session configuring
|
||||||
|
it. Preserves the legacy menu's netplan backup, 60-second SSH
|
||||||
|
watchdog, and restore-on-failure exactly.
|
||||||
|
- `menus/power_schedule.sh` — **Power/Display/Quiet Hours**: scheduled
|
||||||
|
shutdown (+ RTC wake where available), display on/off, quiet-hours
|
||||||
|
audio muting, and an Electron reload timer, each as systemd timers.
|
||||||
|
Can power the physical machine off and on a schedule.
|
||||||
|
- `menus/diagnostics.sh` — **Diagnostics**: system status, log viewing,
|
||||||
|
audio diagnostics, network test — 4 of the legacy Advanced menu's 12
|
||||||
|
items, all read-only.
|
||||||
|
- `menus/addon_cups.sh` — **CUPS Printing** (Addons): install,
|
||||||
|
reconfigure for network access, complete uninstall (purge). The first
|
||||||
|
Addon migrated — genuinely mutates real system state (apt packages,
|
||||||
|
`/etc/cups`, ufw) rather than this project's own files.
|
||||||
|
- `install.sh` — entry point for the modular tool, now grouped **Core
|
||||||
|
Settings / Addons / Advanced** like the legacy menu. Run it against an
|
||||||
*already-installed* kiosk:
|
*already-installed* kiosk:
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/outis1one/ubuntu-based-kiosk/
|
git clone https://github.com/outis1one/ubuntu-based-kiosk/
|
||||||
@@ -1205,9 +1221,9 @@ terminal menu and the web UI, so they can't drift apart).
|
|||||||
**Honest status:** this does not yet replace first-time installation, or
|
**Honest status:** this does not yet replace first-time installation, or
|
||||||
most of the old installer. `ubuntu-based-kiosk.sh` is still ~12,000
|
most of the old installer. `ubuntu-based-kiosk.sh` is still ~12,000
|
||||||
lines and still contains its own unremoved, unmodified copies of every
|
lines and still contains its own unremoved, unmodified copies of every
|
||||||
menu above (plus WiFi, Power/Display/Quiet Hours, Upgrade, Reinstall,
|
menu above (plus Upgrade, Reinstall, Uninstall, 4 more Addons, and the
|
||||||
Uninstall, all Addons, and all of Advanced — none of that has moved
|
other 8 Advanced items — none of that has moved yet). Both copies
|
||||||
yet). Both copies coexist deliberately: the old ones stay until enough
|
coexist deliberately: the old ones stay until enough
|
||||||
of Core Settings/Addons/Advanced is migrated to
|
of Core Settings/Addons/Advanced is migrated to
|
||||||
retire them in one pass, rather than leaving the legacy menu half-wired.
|
retire them in one pass, rather than leaving the legacy menu half-wired.
|
||||||
Migration continues one `menus/*.sh` file at a time; first-time
|
Migration continues one `menus/*.sh` file at a time; first-time
|
||||||
@@ -1218,9 +1234,27 @@ at all.
|
|||||||
|
|
||||||
## Project Status & Future Plans
|
## Project Status & Future Plans
|
||||||
|
|
||||||
**Current Version:** 2.2.0
|
**Current Version:** 2.5.0
|
||||||
|
|
||||||
**Recent Updates (v2.2.0):**
|
**Recent Updates (v2.5.0):**
|
||||||
|
- **First Addon migrated:** CUPS Printing — install/reconfigure/complete uninstall, in `./install.sh`. Genuinely mutates real system state (`apt install`/`remove --purge`, `/etc/cups`, `ufw`) at fixed paths CUPS itself doesn't let us relocate, so every test uses full command-level `sudo` stubbing rather than the scratch-directory approach used for this project's own files.
|
||||||
|
- **Menu restructured:** `install.sh`'s top level is now grouped Core Settings / Addons / Advanced, matching the legacy tool, instead of one flat list — done now while it's cheap, ahead of the list getting unwieldy.
|
||||||
|
- **Bug fix:** a "wait for service to start" retry loop used a bare `cmd1 && cmd2 && break` as its body — that's not made safe by being inside a loop; a bare `&&`/`||` list used as a standalone statement is fully subject to `set -e`, and the first command failing on an early iteration (near-certain right after a fresh install) would have killed the whole session. Restored the `if cmd1 && cmd2; then break; fi` form.
|
||||||
|
- **Resolved:** real uncertainty about how far `run_menu`'s `handler || true` guard (added in v2.1.0) actually reaches — confirmed with an isolated test that it protects against a failing command no matter how many function calls deep, so the session-crash risk chased since v2.1.0 is already covered end-to-end by that one fix. Per-statement guards still matter for a different reason: without them, a deep failure bubbles past the menu actually responsible for it to wherever the nearest `|| true` happens to catch it.
|
||||||
|
|
||||||
|
**Previous (v2.4.0):**
|
||||||
|
- **Diagnostics migrated** — system status, log viewing (Electron/LightDM/journal), an 8-step audio diagnostic, and a ping+DNS network test, from the legacy Advanced menu. A change of pace: everything here is read-only, no destructive-action risk to manage.
|
||||||
|
- **Bug fix (set -e safety):** every diagnostic whose failure is the expected case — no lightdm running, no audio hardware, no network, missing logs, `ping`/`nslookup` not even installed — was a bare unguarded statement that would have crashed the whole session instead of reporting "not found" and moving on. Fixed throughout; a diagnostics tool has to survive exactly the broken states it exists to diagnose.
|
||||||
|
- Manual Electron Update, Factory Reset, Export/Import Settings, Emergency Hotspot, and Fix Blank Screen are staying in the legacy script for now — destructive/mutating, and some share Upgrade's coupling to the legacy script's self-extraction mechanism (see v2.3.0 notes).
|
||||||
|
|
||||||
|
**Previous (v2.3.0):**
|
||||||
|
- **WiFi and Power/Display/Quiet Hours migrated** — by far the riskiest menus tackled so far. WiFi rewrites live netplan config and, over SSH, can disconnect the session configuring it; power scheduling can shut the physical machine down and wake it via RTC. Every legacy safety mechanism is preserved exactly: netplan backup, 60-second SSH watchdog, restore-on-failure for WiFi; RTC availability detection for power scheduling.
|
||||||
|
- **Bug fix:** the legacy menu refused to open "Configure power schedule" at all without RTC hardware, even though shutdown-only scheduling never needed it.
|
||||||
|
- **Bug fix:** none of the six HH:MM time prompts across these menus were format-validated before — a typo silently produced a broken schedule. All now go through the same `ask_time` validator as everywhere else.
|
||||||
|
- **Bug fix (set -e safety):** several more bare statements whose failure would have killed the entire session — `ls *.yaml` with no netplan file present, the backup-restore reapply after a failed `netplan apply`, and `systemctl enable`/`start` after writing each timer pair. The last was only caught by testing without a live systemd; a real failure on actual hardware would have hit the same crash. All now report a warning and return to the menu.
|
||||||
|
- Deliberately **not** migrated: the legacy "Test schedules & system" option, which leads into a shared diagnostics submenu (audio/network/keyboard tests) unrelated to scheduling — that belongs with a future Advanced/Diagnostics pass.
|
||||||
|
|
||||||
|
**Previous (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.
|
- **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.
|
- **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/`.
|
- **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/`.
|
||||||
|
|||||||
+57
-7
@@ -12,10 +12,13 @@
|
|||||||
# at a time, so a change to (say) the Sites menu can't accidentally break
|
# at a time, so a change to (say) the Sites menu can't accidentally break
|
||||||
# WiFi setup or the uninstaller three thousand lines away.
|
# WiFi setup or the uninstaller three thousand lines away.
|
||||||
#
|
#
|
||||||
# Migrated so far: Sites & Page Timing (menus/sites.sh), Display &
|
# Migrated so far, grouped the same way the legacy menu groups them:
|
||||||
# Interaction (menus/display.sh), Timezone (menus/timezone.sh), Hidden
|
# Core Settings: Sites & Page Timing, Display & Interaction, Timezone,
|
||||||
# Site PIN (menus/hidden_pin.sh), Password Protection & Lockout
|
# Hidden Site PIN, Password Protection & Lockout, WiFi,
|
||||||
# (menus/lockout.sh).
|
# Power/Display/Quiet Hours.
|
||||||
|
# Addons: CUPS Printing (menus/addon_cups.sh).
|
||||||
|
# Advanced: Diagnostics (menus/diagnostics.sh - system status/logs/
|
||||||
|
# audio/network).
|
||||||
#
|
#
|
||||||
# Usage (once the kiosk has already been installed):
|
# Usage (once the kiosk has already been installed):
|
||||||
# git clone <repo>
|
# git clone <repo>
|
||||||
@@ -41,6 +44,14 @@ source "$SCRIPT_DIR/menus/timezone.sh"
|
|||||||
source "$SCRIPT_DIR/menus/hidden_pin.sh"
|
source "$SCRIPT_DIR/menus/hidden_pin.sh"
|
||||||
# shellcheck source=menus/lockout.sh
|
# shellcheck source=menus/lockout.sh
|
||||||
source "$SCRIPT_DIR/menus/lockout.sh"
|
source "$SCRIPT_DIR/menus/lockout.sh"
|
||||||
|
# shellcheck source=menus/wifi.sh
|
||||||
|
source "$SCRIPT_DIR/menus/wifi.sh"
|
||||||
|
# shellcheck source=menus/power_schedule.sh
|
||||||
|
source "$SCRIPT_DIR/menus/power_schedule.sh"
|
||||||
|
# shellcheck source=menus/diagnostics.sh
|
||||||
|
source "$SCRIPT_DIR/menus/diagnostics.sh"
|
||||||
|
# shellcheck source=menus/addon_cups.sh
|
||||||
|
source "$SCRIPT_DIR/menus/addon_cups.sh"
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Preflight
|
# Preflight
|
||||||
@@ -73,18 +84,57 @@ if ! is_kiosk_installed; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Top-level menu
|
# Top-level menu - grouped the same way the legacy menu groups them
|
||||||
|
# (Core Settings / Addons / Advanced), so the structure stays familiar
|
||||||
|
# and the flat list doesn't grow unwieldy as more menus migrate in.
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
main_menu_builder() {
|
core_settings_menu_builder() {
|
||||||
MENU_LABELS=(
|
MENU_LABELS=(
|
||||||
"Sites & Page Timing"
|
"Sites & Page Timing"
|
||||||
"Display & Interaction"
|
"Display & Interaction"
|
||||||
"Timezone"
|
"Timezone"
|
||||||
"Hidden Site PIN"
|
"Hidden Site PIN"
|
||||||
"Password Protection & Lockout"
|
"Password Protection & Lockout"
|
||||||
|
"WiFi"
|
||||||
|
"Power/Display/Quiet Hours"
|
||||||
)
|
)
|
||||||
MENU_HANDLERS=(sites_menu display_menu timezone_menu hidden_pin_menu lockout_menu)
|
MENU_HANDLERS=(
|
||||||
|
sites_menu
|
||||||
|
display_menu
|
||||||
|
timezone_menu
|
||||||
|
hidden_pin_menu
|
||||||
|
lockout_menu
|
||||||
|
wifi_menu
|
||||||
|
power_schedule_menu
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
core_settings_menu() {
|
||||||
|
run_menu "CORE SETTINGS" core_settings_menu_builder
|
||||||
|
}
|
||||||
|
|
||||||
|
addons_menu_builder() {
|
||||||
|
MENU_LABELS=("CUPS Printing")
|
||||||
|
MENU_HANDLERS=(addon_cups_menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
addons_menu() {
|
||||||
|
run_menu "ADDONS" addons_menu_builder
|
||||||
|
}
|
||||||
|
|
||||||
|
advanced_menu_builder() {
|
||||||
|
MENU_LABELS=("Diagnostics")
|
||||||
|
MENU_HANDLERS=(diagnostics_menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
advanced_menu() {
|
||||||
|
run_menu "ADVANCED" advanced_menu_builder
|
||||||
|
}
|
||||||
|
|
||||||
|
main_menu_builder() {
|
||||||
|
MENU_LABELS=("Core Settings" "Addons" "Advanced")
|
||||||
|
MENU_HANDLERS=(core_settings_menu addons_menu advanced_menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
main_menu_status() {
|
main_menu_status() {
|
||||||
|
|||||||
@@ -20,6 +20,22 @@
|
|||||||
: "${KIOSK_DIR:=${KIOSK_HOME}/kiosk-app}"
|
: "${KIOSK_DIR:=${KIOSK_HOME}/kiosk-app}"
|
||||||
: "${CONFIG_PATH:=${KIOSK_DIR}/config.json}"
|
: "${CONFIG_PATH:=${KIOSK_DIR}/config.json}"
|
||||||
|
|
||||||
|
# System paths that menus (e.g. power/display/quiet-hours scheduling)
|
||||||
|
# write units, scripts, and cron entries into. Overridable so tests can
|
||||||
|
# point them at a scratch directory instead of the real system - nothing
|
||||||
|
# under menus/ should ever hardcode /etc/systemd/system, /etc/cron.d, or
|
||||||
|
# /usr/local/bin directly.
|
||||||
|
: "${SYSTEMD_DIR:=/etc/systemd/system}"
|
||||||
|
: "${CRON_D_DIR:=/etc/cron.d}"
|
||||||
|
: "${BIN_DIR:=/usr/local/bin}"
|
||||||
|
: "${NETPLAN_DIR:=/etc/netplan}"
|
||||||
|
: "${POLKIT_DIR:=/etc/polkit-1/localauthority/50-local.d}"
|
||||||
|
|
||||||
|
# The admin account actually running this tool (as opposed to $KIOSK_USER,
|
||||||
|
# the kiosk's own restricted account) - used where an addon needs to grant
|
||||||
|
# *this* user a group membership (e.g. lpadmin for CUPS).
|
||||||
|
: "${BUILD_USER:=${SUDO_USER:-$(whoami)}}"
|
||||||
|
|
||||||
# Site/tab arrays
|
# Site/tab arrays
|
||||||
declare -a URLS=()
|
declare -a URLS=()
|
||||||
declare -a DURS=()
|
declare -a DURS=()
|
||||||
|
|||||||
+40
@@ -44,6 +44,46 @@ onoff() {
|
|||||||
[[ "$1" == "true" ]] && echo "ON" || echo "OFF"
|
[[ "$1" == "true" ]] && echo "ON" || echo "OFF"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Current primary IP, or the literal "No IP" if there isn't one (e.g. no
|
||||||
|
# network yet). Callers that only care whether there's an address should
|
||||||
|
# still check for -n on top of this, since "No IP" is itself non-empty.
|
||||||
|
get_ip_address() {
|
||||||
|
local ip
|
||||||
|
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||||
|
if [[ -n "$ip" ]]; then
|
||||||
|
echo "$ip"
|
||||||
|
else
|
||||||
|
echo "No IP"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# "WireGuard: 10.x.x.x | Tailscale: 100.x.x.x" for whichever VPN clients
|
||||||
|
# are installed and connected, or "None" if none are.
|
||||||
|
get_vpn_ips() {
|
||||||
|
local vpn_info=""
|
||||||
|
|
||||||
|
if command -v wg &>/dev/null && sudo wg show 2>/dev/null | grep -q interface; then
|
||||||
|
local wg_ip
|
||||||
|
wg_ip=$(sudo wg show all | grep "allowed ips" | head -1 | awk '{print $3}' | cut -d'/' -f1)
|
||||||
|
[[ -n "$wg_ip" ]] && vpn_info="${vpn_info}WireGuard: $wg_ip | "
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v tailscale &>/dev/null; then
|
||||||
|
local ts_ip
|
||||||
|
ts_ip=$(tailscale ip -4 2>/dev/null)
|
||||||
|
[[ -n "$ts_ip" ]] && vpn_info="${vpn_info}Tailscale: $ts_ip | "
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v netbird &>/dev/null; then
|
||||||
|
local nb_ip
|
||||||
|
nb_ip=$(netbird status 2>/dev/null | grep "NetBird IP:" | awk '{print $3}')
|
||||||
|
[[ -n "$nb_ip" ]] && vpn_info="${vpn_info}Netbird: $nb_ip | "
|
||||||
|
fi
|
||||||
|
|
||||||
|
vpn_info="${vpn_info% | }"
|
||||||
|
[[ -n "$vpn_info" ]] && echo "$vpn_info" || echo "None"
|
||||||
|
}
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
read -r -p "Press Enter to continue..."
|
read -r -p "Press Enter to continue..."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/addon_cups.sh - "CUPS Printing" addon (from the legacy Addons menu).
|
||||||
|
#
|
||||||
|
# First Addon migrated. Genuinely mutates real system state - installs/
|
||||||
|
# purges apt packages, writes /etc/cups/cupsd.conf and a polkit rule,
|
||||||
|
# touches ufw - at fixed paths CUPS itself doesn't let us relocate the
|
||||||
|
# way $SYSTEMD_DIR/$CRON_D_DIR/etc let us relocate our own files. Only
|
||||||
|
# the polkit rule's directory is parameterized ($POLKIT_DIR, since that's
|
||||||
|
# ours to place); everything else (cupsd.conf, apt, systemctl, ufw) gets
|
||||||
|
# full command-level `sudo` stubbing in every test - there is no scratch
|
||||||
|
# equivalent for a real apt-managed subsystem's own file layout.
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
cups_is_installed() {
|
||||||
|
dpkg -l 2>/dev/null | grep -q "^ii\s\+cups\s"
|
||||||
|
}
|
||||||
|
|
||||||
|
cups_is_active() {
|
||||||
|
systemctl is-active --quiet cups
|
||||||
|
}
|
||||||
|
|
||||||
|
addon_cups_status() {
|
||||||
|
if cups_is_installed && cups_is_active; then
|
||||||
|
echo "CUPS: installed and running (http://$(get_ip_address):631)"
|
||||||
|
elif cups_is_installed; then
|
||||||
|
echo "CUPS: installed but not running"
|
||||||
|
else
|
||||||
|
echo "CUPS: not installed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
addon_cups_menu_builder() {
|
||||||
|
if cups_is_installed && cups_is_active; then
|
||||||
|
MENU_LABELS=("Reconfigure for network access" "Complete uninstall (purge)")
|
||||||
|
MENU_HANDLERS=(action_reconfigure_cups action_cups_uninstall)
|
||||||
|
elif cups_is_installed; then
|
||||||
|
MENU_LABELS=("Start CUPS" "Complete uninstall (purge)")
|
||||||
|
MENU_HANDLERS=(action_start_cups action_cups_uninstall)
|
||||||
|
else
|
||||||
|
MENU_LABELS=("Install CUPS printing")
|
||||||
|
MENU_HANDLERS=(action_install_cups)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
addon_cups_menu() {
|
||||||
|
run_menu "CUPS PRINTING SUPPORT" addon_cups_menu_builder addon_cups_status
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Actions
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_install_cups() {
|
||||||
|
echo
|
||||||
|
ask_yes_no "Install CUPS printing?" "n" || { echo "Cancelled"; return; }
|
||||||
|
|
||||||
|
echo "Installing CUPS from scratch..."
|
||||||
|
if ! sudo apt update; then
|
||||||
|
log_error "apt update failed - check network/package sources and try again"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! sudo apt install -y cups cups-client cups-filters printer-driver-all \
|
||||||
|
printer-driver-cups-pdf hplip printer-driver-gutenprint \
|
||||||
|
foomatic-db-compressed-ppds openprinting-ppds; then
|
||||||
|
log_error "CUPS package installation failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo systemctl enable cups 2>/dev/null || true
|
||||||
|
sudo systemctl start cups 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Waiting for CUPS to start..."
|
||||||
|
for _ in {1..30}; do
|
||||||
|
# Must stay in an `if` - a bare `cmd1 && cmd2` statement is
|
||||||
|
# subject to set -e itself when cmd1 fails, which is virtually
|
||||||
|
# guaranteed on early iterations right after install.
|
||||||
|
if cups_is_active && lpstat -r &>/dev/null 2>&1; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# $BUILD_USER already resolves to $SUDO_USER when the tool was run via
|
||||||
|
# sudo, so a single usermod covers it - the legacy code ran this twice
|
||||||
|
# (once for a hardcoded computed user, once again for $SUDO_USER
|
||||||
|
# directly), which was harmless but genuinely redundant.
|
||||||
|
sudo usermod -aG lpadmin "$BUILD_USER"
|
||||||
|
|
||||||
|
action_reconfigure_cups
|
||||||
|
|
||||||
|
log_success "CUPS installed"
|
||||||
|
echo " Web interface: http://$(get_ip_address):631"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_start_cups() {
|
||||||
|
sudo systemctl enable cups
|
||||||
|
sudo systemctl start cups
|
||||||
|
log_success "CUPS started"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_reconfigure_cups() {
|
||||||
|
if [[ -f /etc/cups/cupsd.conf ]]; then
|
||||||
|
sudo cp /etc/cups/cupsd.conf "/etc/cups/cupsd.conf.backup-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v cupsctl &>/dev/null; then
|
||||||
|
sudo cupsctl --remote-admin --remote-any --share-printers 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo sed -i 's/^Listen localhost:631/Port 631/' /etc/cups/cupsd.conf 2>/dev/null || true
|
||||||
|
sudo sed -i 's/^Listen 127.0.0.1:631/Port 631/' /etc/cups/cupsd.conf 2>/dev/null || true
|
||||||
|
|
||||||
|
sudo mkdir -p "$POLKIT_DIR"
|
||||||
|
sudo tee "$POLKIT_DIR/kiosk-printing.pkla" > /dev/null <<EOF
|
||||||
|
[Allow kiosk printing]
|
||||||
|
Identity=unix-user:${KIOSK_USER}
|
||||||
|
Action=org.opensuse.cupspkhelper.mechanism.*
|
||||||
|
ResultAny=yes
|
||||||
|
ResultInactive=yes
|
||||||
|
ResultActive=yes
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo ufw allow 631/tcp comment 'CUPS' 2>/dev/null || true
|
||||||
|
sudo systemctl restart cups 2>/dev/null || true
|
||||||
|
|
||||||
|
log_success "CUPS configured for network access"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_cups_uninstall() {
|
||||||
|
echo
|
||||||
|
ask_yes_no "Completely remove CUPS, including all queues and settings (purge)?" "n" || { echo "Cancelled"; return; }
|
||||||
|
|
||||||
|
echo "Performing complete CUPS uninstall..."
|
||||||
|
|
||||||
|
sudo systemctl stop cups cups-browsed 2>/dev/null || true
|
||||||
|
sudo systemctl disable cups cups-browsed 2>/dev/null || true
|
||||||
|
|
||||||
|
sudo apt remove --purge -y cups cups-daemon cups-client cups-filters \
|
||||||
|
cups-common cups-core-drivers cups-server-common cups-browsed \
|
||||||
|
cups-ppdc cups-bsd libcups2 libcupsimage2 2>/dev/null || true
|
||||||
|
|
||||||
|
sudo apt remove --purge -y printer-driver-all printer-driver-cups-pdf \
|
||||||
|
hplip printer-driver-gutenprint foomatic-db-compressed-ppds \
|
||||||
|
openprinting-ppds 2>/dev/null || true
|
||||||
|
|
||||||
|
sudo rm -rf /etc/cups /var/cache/cups /var/spool/cups /var/log/cups /usr/share/cups
|
||||||
|
sudo rm -f "$POLKIT_DIR/kiosk-printing.pkla"
|
||||||
|
|
||||||
|
sudo apt autoremove -y
|
||||||
|
sudo apt clean
|
||||||
|
|
||||||
|
log_success "CUPS completely removed"
|
||||||
|
}
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/diagnostics.sh - "Diagnostics" menu (from the legacy Advanced menu).
|
||||||
|
#
|
||||||
|
# A deliberate change of pace after Sites/WiFi/Power: everything here is
|
||||||
|
# read-only (system/audio status, log tailing, ping+DNS) except one
|
||||||
|
# optional "play a test sound?" prompt, so there's no destructive-action
|
||||||
|
# risk profile to design around. Straight port, using $KIOSK_USER/
|
||||||
|
# $KIOSK_HOME instead of the legacy code's mix of the variable and a
|
||||||
|
# hardcoded "kiosk" literal.
|
||||||
|
#
|
||||||
|
# Only 4 of the legacy Advanced menu's 12 items are here (System
|
||||||
|
# Diagnostics, View Logs, Audio Diagnostics, Network Test) - Manual
|
||||||
|
# Electron Update, Factory Reset, Export/Import Settings, Emergency
|
||||||
|
# Hotspot, and Fix Blank Screen are mutating/destructive and belong with
|
||||||
|
# a later, more careful pass (some, like Manual Electron Update, share
|
||||||
|
# Upgrade's issue of being coupled to the legacy script's own
|
||||||
|
# self-extraction mechanism - see ubuntu-based-kiosk.sh's changelog for
|
||||||
|
# why Upgrade/Reinstall/Uninstall aren't migrated yet either).
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
diagnostics_menu_builder() {
|
||||||
|
MENU_LABELS=("System status" "View logs" "Audio diagnostics" "Network test")
|
||||||
|
MENU_HANDLERS=(action_system_diagnostics view_logs_menu action_audio_diagnostics action_network_test)
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnostics_menu() {
|
||||||
|
run_menu "DIAGNOSTICS" diagnostics_menu_builder
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# System status
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_system_diagnostics() {
|
||||||
|
clear
|
||||||
|
echo " ═══ SYSTEM DIAGNOSTICS ═══"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "=== Kiosk Status ==="
|
||||||
|
systemctl status lightdm --no-pager -l 2>&1 | head -20 || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Audio Status ==="
|
||||||
|
sudo -u "$KIOSK_USER" pactl info 2>/dev/null | grep -E "Server|User" || echo "Not running"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Network ==="
|
||||||
|
echo "IP: $(get_ip_address)"
|
||||||
|
echo "VPN: $(get_vpn_ips)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Logs
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
view_logs_menu_builder() {
|
||||||
|
MENU_LABELS=("Electron log (last 50 lines)" "LightDM log (last 50 lines)" "System journal (last 100 lines)")
|
||||||
|
MENU_HANDLERS=(action_view_electron_log action_view_lightdm_log action_view_journal)
|
||||||
|
}
|
||||||
|
|
||||||
|
view_logs_menu() {
|
||||||
|
run_menu "VIEW LOGS" view_logs_menu_builder
|
||||||
|
}
|
||||||
|
|
||||||
|
action_view_electron_log() {
|
||||||
|
echo
|
||||||
|
if sudo test -f "$KIOSK_HOME/electron.log"; then
|
||||||
|
sudo tail -50 "$KIOSK_HOME/electron.log" || true
|
||||||
|
else
|
||||||
|
echo "No electron log found yet"
|
||||||
|
fi
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
action_view_lightdm_log() {
|
||||||
|
echo
|
||||||
|
sudo tail -50 /var/log/lightdm/lightdm.log 2>&1 || echo "No lightdm log found"
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
action_view_journal() {
|
||||||
|
echo
|
||||||
|
sudo journalctl -n 100 || log_error "Could not read the system journal"
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Audio diagnostics
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
audio_diagnostics_pactl() {
|
||||||
|
sudo -u "$KIOSK_USER" XDG_RUNTIME_DIR="/run/user/$(id -u "$KIOSK_USER")" pactl "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_audio_diagnostics() {
|
||||||
|
clear
|
||||||
|
echo "═══ AUDIO DIAGNOSTICS ═══"
|
||||||
|
echo
|
||||||
|
|
||||||
|
local issue_found=false
|
||||||
|
|
||||||
|
echo "[1/8] Checking audio hardware..."
|
||||||
|
if lspci 2>/dev/null | grep -i audio || lsusb 2>/dev/null | grep -i audio; then
|
||||||
|
log_success "Audio hardware detected"
|
||||||
|
lspci 2>/dev/null | grep -i audio || true
|
||||||
|
lsusb 2>/dev/null | grep -i audio | head -3 || true
|
||||||
|
else
|
||||||
|
log_error "No audio hardware detected"
|
||||||
|
issue_found=true
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "[2/8] Checking ALSA devices..."
|
||||||
|
if aplay -l &>/dev/null; then
|
||||||
|
log_success "ALSA devices found"
|
||||||
|
aplay -l 2>/dev/null | grep -E "^card|device" || true
|
||||||
|
else
|
||||||
|
log_error "No ALSA devices"
|
||||||
|
issue_found=true
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "[3/8] Checking PipeWire status..."
|
||||||
|
local pipewire_running=false
|
||||||
|
if audio_diagnostics_pactl info &>/dev/null; then
|
||||||
|
log_success "PipeWire accessible"
|
||||||
|
pipewire_running=true
|
||||||
|
audio_diagnostics_pactl info 2>/dev/null | grep -E "Server|User|Host" || true
|
||||||
|
else
|
||||||
|
log_error "PipeWire not accessible to kiosk user"
|
||||||
|
issue_found=true
|
||||||
|
echo " Try: sudo -u ${KIOSK_USER} systemctl --user start pipewire pipewire-pulse"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
if $pipewire_running; then
|
||||||
|
echo "[4/8] Checking audio sinks..."
|
||||||
|
local sinks
|
||||||
|
sinks=$(audio_diagnostics_pactl list sinks short 2>/dev/null) || true
|
||||||
|
if [[ -n "$sinks" ]]; then
|
||||||
|
echo "$sinks"
|
||||||
|
local default_sink
|
||||||
|
default_sink=$(audio_diagnostics_pactl get-default-sink 2>/dev/null || echo "none")
|
||||||
|
echo "Default: $default_sink"
|
||||||
|
else
|
||||||
|
log_error "No audio sinks found"
|
||||||
|
issue_found=true
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "[5/8] Checking active streams..."
|
||||||
|
local sink_inputs
|
||||||
|
sink_inputs=$(audio_diagnostics_pactl list sink-inputs short 2>/dev/null) || true
|
||||||
|
if [[ -n "$sink_inputs" ]]; then
|
||||||
|
echo "Active streams:"
|
||||||
|
echo "$sink_inputs"
|
||||||
|
else
|
||||||
|
echo "No active streams"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo "[4/8] Skipped - PipeWire not running"
|
||||||
|
echo "[5/8] Skipped - PipeWire not running"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[6/8] Checking Squeezelite..."
|
||||||
|
if systemctl is-active --quiet squeezelite; then
|
||||||
|
log_success "Squeezelite running"
|
||||||
|
|
||||||
|
if $pipewire_running; then
|
||||||
|
local sq_pid
|
||||||
|
sq_pid=$(pgrep -f squeezelite | head -1) || true
|
||||||
|
if [[ -n "$sq_pid" ]]; then
|
||||||
|
if audio_diagnostics_pactl list sink-inputs 2>/dev/null | grep -q "application.process.id = \"$sq_pid\""; then
|
||||||
|
log_success "Squeezelite connected to audio"
|
||||||
|
else
|
||||||
|
log_warning "Squeezelite NOT connected to audio sink"
|
||||||
|
issue_found=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Squeezelite not running"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
if $pipewire_running; then
|
||||||
|
echo "[7/8] Checking volume..."
|
||||||
|
local volume muted
|
||||||
|
volume=$(audio_diagnostics_pactl get-sink-volume @DEFAULT_SINK@ 2>/dev/null | grep -oE '[0-9]+%' | head -1 || echo "unknown")
|
||||||
|
muted=$(audio_diagnostics_pactl get-sink-mute @DEFAULT_SINK@ 2>/dev/null || echo "unknown")
|
||||||
|
echo "Volume: $volume"
|
||||||
|
echo "Muted: $muted"
|
||||||
|
else
|
||||||
|
echo "[7/8] Skipped - PipeWire not running"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "[8/8] Audio test..."
|
||||||
|
if ask_yes_no "Play test sound?" "n" && $pipewire_running; then
|
||||||
|
echo "Playing beep..."
|
||||||
|
audio_diagnostics_pactl_play_test
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "═══════════════════════════════"
|
||||||
|
if $issue_found; then
|
||||||
|
echo "⚠️ ISSUES DETECTED - See above"
|
||||||
|
else
|
||||||
|
echo "✓ All checks passed"
|
||||||
|
fi
|
||||||
|
echo "═══════════════════════════════"
|
||||||
|
|
||||||
|
pause
|
||||||
|
}
|
||||||
|
|
||||||
|
audio_diagnostics_pactl_play_test() {
|
||||||
|
sudo -u "$KIOSK_USER" XDG_RUNTIME_DIR="/run/user/$(id -u "$KIOSK_USER")" paplay /usr/share/sounds/alsa/Front_Center.wav 2>/dev/null || \
|
||||||
|
sudo -u "$KIOSK_USER" XDG_RUNTIME_DIR="/run/user/$(id -u "$KIOSK_USER")" speaker-test -t sine -f 1000 -l 1 2>/dev/null || \
|
||||||
|
echo "No test available"
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Network test
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_network_test() {
|
||||||
|
echo
|
||||||
|
echo " ═══ NETWORK TEST ═══"
|
||||||
|
echo
|
||||||
|
echo "Ping test..."
|
||||||
|
ping -c 4 8.8.8.8 || log_error "Ping failed"
|
||||||
|
echo
|
||||||
|
echo "DNS test..."
|
||||||
|
nslookup google.com || log_error "DNS lookup failed"
|
||||||
|
pause
|
||||||
|
}
|
||||||
@@ -0,0 +1,660 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/power_schedule.sh - "Power / Display / Quiet Hours" menu.
|
||||||
|
#
|
||||||
|
# Sixth menu migrated, and the biggest and riskiest so far: it writes
|
||||||
|
# systemd timers/services, a cron entry, and shell scripts that can power
|
||||||
|
# off the physical machine, blank the display, mute audio, and (via RTC)
|
||||||
|
# wake the machine back up on a schedule. Every write goes through
|
||||||
|
# $SYSTEMD_DIR / $CRON_D_DIR / $BIN_DIR (lib/config.sh) rather than
|
||||||
|
# hardcoded /etc/systemd/system, /etc/cron.d, /usr/local/bin, so tests can
|
||||||
|
# point them at a scratch directory - this file must never assume it's
|
||||||
|
# safe to actually mutate the real system just because it's running.
|
||||||
|
#
|
||||||
|
# Deliberately out of scope: the legacy dispatcher's "6. Test schedules &
|
||||||
|
# system" led into a shared diagnostics submenu (audio test, network
|
||||||
|
# test, keyboard test, ...) that isn't specific to scheduling and belongs
|
||||||
|
# with a future Advanced/Diagnostics migration instead. What *is* in
|
||||||
|
# scope - testing the schedule you just configured - stays here as the
|
||||||
|
# same inline "test now?" prompts the legacy menu already had.
|
||||||
|
#
|
||||||
|
# Bug fixed vs. the legacy configure_power_display_quiet: it refused to
|
||||||
|
# even open "Configure power schedule" when no RTC wake was detected,
|
||||||
|
# even though shutdown-only scheduling (configure_power_schedule's own
|
||||||
|
# fallback) never needed RTC in the first place. Also: none of shutdown
|
||||||
|
# time/wake time/display off/on/quiet start/end/custom Electron reload
|
||||||
|
# time were validated as HH:MM in the legacy menu (plain `read`, no
|
||||||
|
# format check) - a typo would silently produce a broken OnCalendar=
|
||||||
|
# value. All of those now go through ask_time.
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Shared helpers
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
rtc_wake_available() {
|
||||||
|
[[ -w /sys/class/rtc/rtc0/wakealarm ]] || sudo test -w /sys/class/rtc/rtc0/wakealarm 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
timer_exists() {
|
||||||
|
[[ -f "$SYSTEMD_DIR/$1" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
timer_oncalendar() {
|
||||||
|
grep "^OnCalendar=" "$SYSTEMD_DIR/$1" 2>/dev/null | cut -d'=' -f2 | sed 's/\*-\*-\* //' | sed 's/:00$//'
|
||||||
|
}
|
||||||
|
|
||||||
|
# enable_and_start_timers TIMER [TIMER...]
|
||||||
|
# Reloads systemd and enables+starts the given timer units, returning
|
||||||
|
# non-zero if enable or start fails (e.g. systemd/D-Bus unreachable).
|
||||||
|
# Always call this from an `if`/`&&`/`||` context: this whole tool runs
|
||||||
|
# under set -e, so a bare, unguarded call whose last command fails would
|
||||||
|
# take down the entire session instead of just this one action.
|
||||||
|
enable_and_start_timers() {
|
||||||
|
sudo systemctl daemon-reload 2>/dev/null || true
|
||||||
|
sudo systemctl enable "$@" 2>/dev/null && sudo systemctl start "$@" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Top-level menu
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
power_schedule_status() {
|
||||||
|
local any=false
|
||||||
|
|
||||||
|
if timer_exists kiosk-shutdown.timer; then
|
||||||
|
any=true
|
||||||
|
local t; t=$(timer_oncalendar kiosk-shutdown.timer)
|
||||||
|
echo "Power: shutdown daily at ${t:-an unknown time}"
|
||||||
|
fi
|
||||||
|
if timer_exists kiosk-display-off.timer; then
|
||||||
|
any=true
|
||||||
|
echo "Display: off at $(timer_oncalendar kiosk-display-off.timer), on at $(timer_oncalendar kiosk-display-on.timer)"
|
||||||
|
fi
|
||||||
|
if timer_exists kiosk-quiet-start.timer; then
|
||||||
|
any=true
|
||||||
|
echo "Quiet: $(timer_oncalendar kiosk-quiet-start.timer) to $(timer_oncalendar kiosk-quiet-end.timer)"
|
||||||
|
fi
|
||||||
|
if timer_exists kiosk-electron-reload.timer; then
|
||||||
|
any=true
|
||||||
|
echo "Reload: enabled ($(timer_oncalendar kiosk-electron-reload.timer))"
|
||||||
|
fi
|
||||||
|
$any || echo "No schedules configured"
|
||||||
|
|
||||||
|
echo
|
||||||
|
if rtc_wake_available; then
|
||||||
|
echo "RTC wake: available (can schedule power on/off)"
|
||||||
|
else
|
||||||
|
echo "RTC wake: not available (display/quiet/reload scheduling still works)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
power_schedule_menu_builder() {
|
||||||
|
MENU_LABELS=(
|
||||||
|
"Configure power schedule$(rtc_wake_available || echo ' (shutdown only - no RTC wake)')"
|
||||||
|
"Configure display schedule"
|
||||||
|
"Configure quiet hours"
|
||||||
|
"Configure Electron reload schedule"
|
||||||
|
"Remove all schedules"
|
||||||
|
)
|
||||||
|
MENU_HANDLERS=(
|
||||||
|
action_configure_power_schedule
|
||||||
|
action_configure_display_schedule
|
||||||
|
action_configure_quiet_hours
|
||||||
|
electron_reload_menu
|
||||||
|
action_remove_all_schedules
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
power_schedule_menu() {
|
||||||
|
run_menu "POWER / DISPLAY / QUIET HOURS" power_schedule_menu_builder power_schedule_status
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Power schedule
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_configure_power_schedule() {
|
||||||
|
echo
|
||||||
|
local rtc_ok=false
|
||||||
|
rtc_wake_available && rtc_ok=true
|
||||||
|
|
||||||
|
if $rtc_ok; then
|
||||||
|
echo "RTC wake capability detected - can schedule shutdown and wake."
|
||||||
|
else
|
||||||
|
echo "RTC wake not available - shutdown only, no auto-wake."
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
local shutdown_time wake_time=""
|
||||||
|
shutdown_time=$(ask_time "Shutdown time (24-hour HH:MM)" "22:00")
|
||||||
|
$rtc_ok && wake_time=$(ask_time "Wake time (24-hour HH:MM)" "06:00")
|
||||||
|
|
||||||
|
sudo systemctl stop kiosk-shutdown.timer 2>/dev/null || true
|
||||||
|
sudo systemctl disable kiosk-shutdown.timer 2>/dev/null || true
|
||||||
|
sudo rm -f "$SYSTEMD_DIR/kiosk-shutdown.service" "$SYSTEMD_DIR/kiosk-shutdown.timer"
|
||||||
|
sudo rm -f "$BIN_DIR/kiosk-power-off.sh" "$BIN_DIR/rtc-wake.sh"
|
||||||
|
sudo rm -f "$CRON_D_DIR/kiosk-rtc-wake"
|
||||||
|
|
||||||
|
sudo tee "$BIN_DIR/kiosk-power-off.sh" > /dev/null <<'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
logger "KIOSK: Scheduled shutdown initiated"
|
||||||
|
systemctl poweroff
|
||||||
|
EOF
|
||||||
|
sudo chmod +x "$BIN_DIR/kiosk-power-off.sh"
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-shutdown.service" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Scheduled Shutdown
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=${BIN_DIR}/kiosk-power-off.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-shutdown.timer" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Shutdown Timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* ${shutdown_time}:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if $rtc_ok && [[ -n "$wake_time" ]]; then
|
||||||
|
sudo tee "$BIN_DIR/rtc-wake.sh" > /dev/null <<'RTCSCRIPT'
|
||||||
|
#!/bin/bash
|
||||||
|
WAKE_TIME="$1"
|
||||||
|
CURRENT=$(date +%s)
|
||||||
|
WAKE=$(date -d "$WAKE_TIME" +%s)
|
||||||
|
|
||||||
|
# If wake time is earlier than current time, schedule for tomorrow
|
||||||
|
[[ $WAKE -le $CURRENT ]] && WAKE=$(date -d "tomorrow $WAKE_TIME" +%s)
|
||||||
|
|
||||||
|
# Clear existing alarm
|
||||||
|
echo 0 > /sys/class/rtc/rtc0/wakealarm 2>/dev/null || true
|
||||||
|
|
||||||
|
# Set new alarm
|
||||||
|
if echo $WAKE > /sys/class/rtc/rtc0/wakealarm 2>/dev/null; then
|
||||||
|
logger "KIOSK: RTC wake set for $(date -d @$WAKE '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo "RTC wake set for $(date -d @$WAKE '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
else
|
||||||
|
logger "KIOSK: ERROR - Failed to set RTC wake"
|
||||||
|
echo "ERROR: Failed to set RTC wake"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
RTCSCRIPT
|
||||||
|
sudo chmod +x "$BIN_DIR/rtc-wake.sh"
|
||||||
|
|
||||||
|
local shutdown_hour="${shutdown_time%%:*}"
|
||||||
|
local shutdown_min="${shutdown_time##*:}"
|
||||||
|
local wake_min=$((10#$shutdown_min - 5))
|
||||||
|
local wake_hour=$((10#$shutdown_hour))
|
||||||
|
[[ $wake_min -lt 0 ]] && { wake_min=$((wake_min + 60)); wake_hour=$((wake_hour - 1)); }
|
||||||
|
[[ $wake_hour -lt 0 ]] && wake_hour=$((wake_hour + 24))
|
||||||
|
|
||||||
|
sudo tee "$CRON_D_DIR/kiosk-rtc-wake" > /dev/null <<EOF
|
||||||
|
# Set RTC wake alarm 5 minutes before shutdown
|
||||||
|
$wake_min $wake_hour * * * root ${BIN_DIR}/rtc-wake.sh "$wake_time" >> /var/log/kiosk-rtc.log 2>&1
|
||||||
|
EOF
|
||||||
|
log_info "RTC wake cron job created"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if enable_and_start_timers kiosk-shutdown.timer; then
|
||||||
|
log_success "Power schedule configured: shutdown at ${shutdown_time}$( [[ -n "$wake_time" ]] && echo ", wake at ${wake_time}")"
|
||||||
|
else
|
||||||
|
log_warning "Schedule files written, but systemctl enable/start failed - check 'systemctl status kiosk-shutdown.timer'"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Display schedule
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_configure_display_schedule() {
|
||||||
|
echo
|
||||||
|
if timer_exists kiosk-shutdown.timer; then
|
||||||
|
log_warning "Power shutdown configured at $(timer_oncalendar kiosk-shutdown.timer) - display will already be off by then"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
local doff don
|
||||||
|
doff=$(ask_time "Display OFF time (24-hour HH:MM)" "22:00")
|
||||||
|
don=$(ask_time "Display ON time (24-hour HH:MM)" "06:00")
|
||||||
|
|
||||||
|
sudo systemctl stop kiosk-display-off.timer kiosk-display-on.timer 2>/dev/null || true
|
||||||
|
sudo systemctl disable kiosk-display-off.timer kiosk-display-on.timer 2>/dev/null || true
|
||||||
|
sudo rm -f "$SYSTEMD_DIR"/kiosk-display-off.{service,timer} "$SYSTEMD_DIR"/kiosk-display-on.{service,timer}
|
||||||
|
sudo rm -f "$BIN_DIR/kiosk-display-off.sh" "$BIN_DIR/kiosk-display-on.sh"
|
||||||
|
|
||||||
|
sudo tee "$BIN_DIR/kiosk-display-off.sh" > /dev/null <<EOF
|
||||||
|
#!/bin/bash
|
||||||
|
# Turn off display using multiple methods for reliability
|
||||||
|
export DISPLAY=:0
|
||||||
|
export XAUTHORITY=${KIOSK_HOME}/.Xauthority
|
||||||
|
|
||||||
|
logger "KIOSK: Display OFF script starting"
|
||||||
|
|
||||||
|
# Method 1: xset via kiosk user
|
||||||
|
sudo -u ${KIOSK_USER} DISPLAY=:0 XAUTHORITY=${KIOSK_HOME}/.Xauthority xset dpms force off 2>/dev/null && logger "KIOSK: xset dpms off success" || logger "KIOSK: xset dpms off failed"
|
||||||
|
|
||||||
|
# Method 2: vbetool (if available)
|
||||||
|
if command -v vbetool &>/dev/null; then
|
||||||
|
vbetool dpms off 2>/dev/null && echo "✓ vbetool off" || echo "✗ vbetool failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Method 3: Backlight control (laptops)
|
||||||
|
if [[ -d /sys/class/backlight ]]; then
|
||||||
|
for bl in /sys/class/backlight/*/brightness; do
|
||||||
|
if [[ -w "\$bl" ]]; then
|
||||||
|
echo 0 > "\$bl" 2>/dev/null && echo "✓ backlight off: \$bl" || echo "✗ backlight failed"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
logger "KIOSK: Display turned OFF (scheduled)"
|
||||||
|
EOF
|
||||||
|
sudo chmod +x "$BIN_DIR/kiosk-display-off.sh"
|
||||||
|
|
||||||
|
sudo tee "$BIN_DIR/kiosk-display-on.sh" > /dev/null <<EOF
|
||||||
|
#!/bin/bash
|
||||||
|
# Turn on display using multiple methods for reliability
|
||||||
|
export DISPLAY=:0
|
||||||
|
export XAUTHORITY=${KIOSK_HOME}/.Xauthority
|
||||||
|
|
||||||
|
logger "KIOSK: Display ON script starting"
|
||||||
|
|
||||||
|
# Method 1: xset via kiosk user
|
||||||
|
sudo -u ${KIOSK_USER} DISPLAY=:0 XAUTHORITY=${KIOSK_HOME}/.Xauthority xset dpms force on 2>/dev/null && logger "KIOSK: xset dpms on success" || logger "KIOSK: xset dpms on failed"
|
||||||
|
|
||||||
|
# Method 2: vbetool (if available)
|
||||||
|
if command -v vbetool &>/dev/null; then
|
||||||
|
vbetool dpms on 2>/dev/null && echo "✓ vbetool on" || echo "✗ vbetool failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Method 3: Backlight control (laptops)
|
||||||
|
if [[ -d /sys/class/backlight ]]; then
|
||||||
|
for bl in /sys/class/backlight/*/brightness; do
|
||||||
|
if [[ -w "\$bl" ]]; then
|
||||||
|
cat "\${bl%/*}/max_brightness" > "\$bl" 2>/dev/null && echo "✓ backlight on: \$bl" || echo "✗ backlight failed"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Method 4: Wake up input (move mouse)
|
||||||
|
sudo -u ${KIOSK_USER} DISPLAY=:0 XAUTHORITY=${KIOSK_HOME}/.Xauthority xdotool mousemove 1 1 2>/dev/null && logger "KIOSK: mouse wiggle success" || logger "KIOSK: mouse wiggle failed"
|
||||||
|
|
||||||
|
# Method 5: Signal Electron app to require password if enabled
|
||||||
|
sudo -u ${KIOSK_USER} touch ${KIOSK_DIR}/.display-wake 2>/dev/null && logger "KIOSK: password flag set" || logger "KIOSK: password flag failed"
|
||||||
|
|
||||||
|
logger "KIOSK: Display turned ON (scheduled)"
|
||||||
|
EOF
|
||||||
|
sudo chmod +x "$BIN_DIR/kiosk-display-on.sh"
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-display-off.service" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Display Off
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=${BIN_DIR}/kiosk-display-off.sh
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-display-on.service" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Display On
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=${BIN_DIR}/kiosk-display-on.sh
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-display-off.timer" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Display Off Timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* ${doff}:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-display-on.timer" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Display On Timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* ${don}:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if enable_and_start_timers kiosk-display-off.timer kiosk-display-on.timer; then
|
||||||
|
log_success "Display schedule configured: off at ${doff}, on at ${don}"
|
||||||
|
else
|
||||||
|
log_warning "Schedule files written, but systemctl enable/start failed - check 'systemctl status kiosk-display-off.timer'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
if ask_yes_no "Test display control now?" "n"; then
|
||||||
|
echo "Testing display OFF in 3 seconds..."
|
||||||
|
sleep 3
|
||||||
|
sudo "$BIN_DIR/kiosk-display-off.sh"
|
||||||
|
echo "Waiting 5 seconds..."
|
||||||
|
sleep 5
|
||||||
|
echo "Testing display ON..."
|
||||||
|
sudo "$BIN_DIR/kiosk-display-on.sh"
|
||||||
|
log_success "Display test complete"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Quiet hours
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_configure_quiet_hours() {
|
||||||
|
echo
|
||||||
|
timer_exists kiosk-shutdown.timer && echo "Power shutdown: $(timer_oncalendar kiosk-shutdown.timer)"
|
||||||
|
if timer_exists kiosk-display-off.timer; then
|
||||||
|
echo "Display: off at $(timer_oncalendar kiosk-display-off.timer), on at $(timer_oncalendar kiosk-display-on.timer)"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
local qstart qend qmode
|
||||||
|
qstart=$(ask_time "Quiet hours start (24-hour HH:MM)" "22:00")
|
||||||
|
qend=$(ask_time "Quiet hours end (24-hour HH:MM)" "07:00")
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "What should be muted during quiet hours?"
|
||||||
|
echo " 1. All audio (mute system)"
|
||||||
|
echo " 2. Squeezelite only (stop music player)"
|
||||||
|
read -r -p "Choice [1]: " qmode
|
||||||
|
qmode="${qmode:-1}"
|
||||||
|
|
||||||
|
sudo systemctl stop kiosk-quiet-start.timer kiosk-quiet-end.timer 2>/dev/null || true
|
||||||
|
sudo systemctl disable kiosk-quiet-start.timer kiosk-quiet-end.timer 2>/dev/null || true
|
||||||
|
sudo rm -f "$SYSTEMD_DIR"/kiosk-quiet-start.{service,timer} "$SYSTEMD_DIR"/kiosk-quiet-end.{service,timer}
|
||||||
|
sudo rm -f "$BIN_DIR/kiosk-quiet-start.sh" "$BIN_DIR/kiosk-quiet-end.sh"
|
||||||
|
|
||||||
|
case "$qmode" in
|
||||||
|
2)
|
||||||
|
sudo tee "$BIN_DIR/kiosk-quiet-start.sh" > /dev/null <<'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
systemctl stop squeezelite 2>/dev/null
|
||||||
|
logger "KIOSK: Quiet hours started - Squeezelite stopped"
|
||||||
|
echo "✓ Quiet hours: Squeezelite stopped"
|
||||||
|
EOF
|
||||||
|
sudo tee "$BIN_DIR/kiosk-quiet-end.sh" > /dev/null <<'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
systemctl start squeezelite 2>/dev/null
|
||||||
|
logger "KIOSK: Quiet hours ended - Squeezelite started"
|
||||||
|
echo "✓ Quiet hours ended: Squeezelite started"
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
qmode=1
|
||||||
|
sudo tee "$BIN_DIR/kiosk-quiet-start.sh" > /dev/null <<'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
# Save current volume before muting
|
||||||
|
pactl get-sink-volume @DEFAULT_SINK@ | grep -oE '[0-9]+%' | head -1 | tr -d '%' > /tmp/kiosk-vol-backup 2>/dev/null || echo "100" > /tmp/kiosk-vol-backup
|
||||||
|
pactl set-sink-mute @DEFAULT_SINK@ 1 2>/dev/null
|
||||||
|
logger "KIOSK: Quiet hours started - all audio muted"
|
||||||
|
echo "✓ Quiet hours: All audio muted"
|
||||||
|
EOF
|
||||||
|
sudo tee "$BIN_DIR/kiosk-quiet-end.sh" > /dev/null <<'EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
# Restore previous volume
|
||||||
|
VOL=$(cat /tmp/kiosk-vol-backup 2>/dev/null || echo "100")
|
||||||
|
pactl set-sink-mute @DEFAULT_SINK@ 0 2>/dev/null
|
||||||
|
pactl set-sink-volume @DEFAULT_SINK@ ${VOL}% 2>/dev/null
|
||||||
|
logger "KIOSK: Quiet hours ended - audio restored to ${VOL}%"
|
||||||
|
echo "✓ Quiet hours ended: Audio restored to ${VOL}%"
|
||||||
|
EOF
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
sudo chmod +x "$BIN_DIR/kiosk-quiet-start.sh" "$BIN_DIR/kiosk-quiet-end.sh"
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-quiet-start.service" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Quiet Hours Start
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=${BIN_DIR}/kiosk-quiet-start.sh
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-quiet-end.service" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Quiet Hours End
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=${BIN_DIR}/kiosk-quiet-end.sh
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-quiet-start.timer" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Quiet Hours Start Timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* ${qstart}:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-quiet-end.timer" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Kiosk Quiet Hours End Timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=*-*-* ${qend}:00
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
local mode_label="All audio muted"
|
||||||
|
[[ "$qmode" == "2" ]] && mode_label="Squeezelite stopped"
|
||||||
|
|
||||||
|
if enable_and_start_timers kiosk-quiet-start.timer kiosk-quiet-end.timer; then
|
||||||
|
log_success "Quiet hours configured: ${qstart} to ${qend} (${mode_label})"
|
||||||
|
else
|
||||||
|
log_warning "Schedule files written, but systemctl enable/start failed - check 'systemctl status kiosk-quiet-start.timer'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
if ask_yes_no "Test quiet hours now?" "n"; then
|
||||||
|
echo "Testing quiet START..."
|
||||||
|
sudo "$BIN_DIR/kiosk-quiet-start.sh"
|
||||||
|
echo "Waiting 5 seconds..."
|
||||||
|
sleep 5
|
||||||
|
echo "Testing quiet END..."
|
||||||
|
sudo "$BIN_DIR/kiosk-quiet-end.sh"
|
||||||
|
log_success "Quiet hours test complete"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Electron reload schedule (its own small nested menu, mirroring the
|
||||||
|
# legacy configure_electron_reload's "configured vs not" dispatch)
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
electron_reload_menu_builder() {
|
||||||
|
if timer_exists kiosk-electron-reload.timer; then
|
||||||
|
MENU_LABELS=("Change schedule" "Disable automatic reload")
|
||||||
|
MENU_HANDLERS=(electron_reload_custom action_disable_electron_reload)
|
||||||
|
else
|
||||||
|
MENU_LABELS=("Daily at 3am" "Every 3 days at 3am" "Custom schedule")
|
||||||
|
MENU_HANDLERS=(action_electron_reload_daily action_electron_reload_every_3_days electron_reload_custom)
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
electron_reload_status() {
|
||||||
|
if timer_exists kiosk-electron-reload.timer; then
|
||||||
|
echo "Automatic reload: enabled ($(timer_oncalendar kiosk-electron-reload.timer))"
|
||||||
|
else
|
||||||
|
echo "Automatic reload: not configured"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
electron_reload_menu() {
|
||||||
|
run_menu "ELECTRON RELOAD SCHEDULE" electron_reload_menu_builder electron_reload_status
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup_electron_reload_timer SCHEDULE DESCRIPTION
|
||||||
|
# SCHEDULE is a systemd OnCalendar= expression, not just a time - unlike
|
||||||
|
# the shutdown/display/quiet timers above, so it isn't run through ask_time.
|
||||||
|
setup_electron_reload_timer() {
|
||||||
|
local schedule="$1"
|
||||||
|
local description="$2"
|
||||||
|
|
||||||
|
sudo systemctl stop kiosk-electron-reload.timer 2>/dev/null || true
|
||||||
|
sudo systemctl disable kiosk-electron-reload.timer 2>/dev/null || true
|
||||||
|
sudo rm -f "$SYSTEMD_DIR"/kiosk-electron-reload.{service,timer}
|
||||||
|
sudo rm -f "$BIN_DIR/kiosk-reload-electron"
|
||||||
|
|
||||||
|
sudo tee "$BIN_DIR/kiosk-reload-electron" > /dev/null <<'RELOADSCRIPT'
|
||||||
|
#!/bin/bash
|
||||||
|
logger "KIOSK: Scheduled Electron reload"
|
||||||
|
systemctl restart lightdm
|
||||||
|
RELOADSCRIPT
|
||||||
|
sudo chmod +x "$BIN_DIR/kiosk-reload-electron"
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-electron-reload.service" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Reload Electron App
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=${BIN_DIR}/kiosk-reload-electron
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo tee "$SYSTEMD_DIR/kiosk-electron-reload.timer" > /dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Electron Reload Timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnCalendar=$schedule
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if enable_and_start_timers kiosk-electron-reload.timer; then
|
||||||
|
log_success "Electron reload configured: $description"
|
||||||
|
else
|
||||||
|
log_warning "Schedule files written, but systemctl enable/start failed - check 'systemctl status kiosk-electron-reload.timer'"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
action_electron_reload_daily() {
|
||||||
|
setup_electron_reload_timer "*-*-* 03:00:00" "daily at 3am"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_electron_reload_every_3_days() {
|
||||||
|
setup_electron_reload_timer "*-*-1,4,7,10,13,16,19,22,25,28,31 03:00:00" "every 3 days at 3am"
|
||||||
|
}
|
||||||
|
|
||||||
|
electron_reload_custom() {
|
||||||
|
echo
|
||||||
|
echo "Custom schedule options:"
|
||||||
|
echo " 1. Every X days at a specific time"
|
||||||
|
echo " 2. Daily at a custom time"
|
||||||
|
echo " 3. Specific weekday"
|
||||||
|
echo " 0. Cancel"
|
||||||
|
echo
|
||||||
|
|
||||||
|
local choice
|
||||||
|
choice=$(ask_integer "Choose" "0" 0 3)
|
||||||
|
[[ "$choice" == "0" ]] && { echo "Cancelled"; return; }
|
||||||
|
|
||||||
|
local schedule="" description=""
|
||||||
|
case "$choice" in
|
||||||
|
1)
|
||||||
|
local days time day_list=""
|
||||||
|
days=$(ask_integer "Reload every X days" "3" 1 31)
|
||||||
|
time=$(ask_time "Time (24-hour HH:MM)" "03:00")
|
||||||
|
for ((d = 1; d <= 31; d += days)); do
|
||||||
|
day_list="${day_list}${d},"
|
||||||
|
done
|
||||||
|
day_list="${day_list%,}"
|
||||||
|
schedule="*-*-${day_list} ${time}:00"
|
||||||
|
description="every ${days} days at ${time}"
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
local time
|
||||||
|
time=$(ask_time "Time (24-hour HH:MM)" "03:00")
|
||||||
|
schedule="*-*-* ${time}:00"
|
||||||
|
description="daily at ${time}"
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
local day time
|
||||||
|
echo "Days: Mon Tue Wed Thu Fri Sat Sun"
|
||||||
|
read -r -p "Enter day: " day
|
||||||
|
time=$(ask_time "Time (24-hour HH:MM)" "03:00")
|
||||||
|
schedule="${day} *-*-* ${time}:00"
|
||||||
|
description="every ${day} at ${time}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
setup_electron_reload_timer "$schedule" "$description"
|
||||||
|
}
|
||||||
|
|
||||||
|
action_disable_electron_reload() {
|
||||||
|
if ask_yes_no "Disable automatic Electron reload?" "n"; then
|
||||||
|
sudo systemctl stop kiosk-electron-reload.timer 2>/dev/null || true
|
||||||
|
sudo systemctl disable kiosk-electron-reload.timer 2>/dev/null || true
|
||||||
|
sudo rm -f "$SYSTEMD_DIR"/kiosk-electron-reload.{service,timer}
|
||||||
|
sudo rm -f "$BIN_DIR/kiosk-reload-electron"
|
||||||
|
sudo systemctl daemon-reload 2>/dev/null || true
|
||||||
|
log_success "Automatic Electron reload disabled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Remove all
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
action_remove_all_schedules() {
|
||||||
|
echo
|
||||||
|
ask_yes_no "Remove ALL power/display/quiet/reload schedules?" "n" || { echo "Cancelled"; return; }
|
||||||
|
|
||||||
|
for timer in kiosk-shutdown kiosk-display-off kiosk-display-on kiosk-quiet-start kiosk-quiet-end kiosk-electron-reload; do
|
||||||
|
sudo systemctl stop "${timer}.timer" 2>/dev/null || true
|
||||||
|
sudo systemctl disable "${timer}.timer" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
sudo rm -f "$SYSTEMD_DIR"/kiosk-shutdown.{service,timer}
|
||||||
|
sudo rm -f "$SYSTEMD_DIR"/kiosk-display-off.{service,timer} "$SYSTEMD_DIR"/kiosk-display-on.{service,timer}
|
||||||
|
sudo rm -f "$SYSTEMD_DIR"/kiosk-quiet-start.{service,timer} "$SYSTEMD_DIR"/kiosk-quiet-end.{service,timer}
|
||||||
|
sudo rm -f "$SYSTEMD_DIR"/kiosk-electron-reload.{service,timer}
|
||||||
|
sudo rm -f "$BIN_DIR/kiosk-power-off.sh" "$BIN_DIR/kiosk-display-off.sh" "$BIN_DIR/kiosk-display-on.sh"
|
||||||
|
sudo rm -f "$BIN_DIR/kiosk-quiet-start.sh" "$BIN_DIR/kiosk-quiet-end.sh"
|
||||||
|
sudo rm -f "$BIN_DIR/rtc-wake.sh" "$BIN_DIR/kiosk-reload-electron"
|
||||||
|
sudo rm -f "$CRON_D_DIR/kiosk-rtc-wake"
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload 2>/dev/null || true
|
||||||
|
|
||||||
|
log_success "All schedules removed"
|
||||||
|
}
|
||||||
+241
@@ -0,0 +1,241 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# menus/wifi.sh - "WiFi" configuration.
|
||||||
|
#
|
||||||
|
# HIGH RISK, unlike anything migrated so far: this changes live network
|
||||||
|
# configuration and, if run over SSH, can disconnect the very session
|
||||||
|
# configuring it. Every safety mechanism from the legacy configure_wifi
|
||||||
|
# is preserved exactly: a netplan backup before writing, a 60-second
|
||||||
|
# watchdog (armed only when $SSH_CONNECTION is set) that reverts to the
|
||||||
|
# backup if the new config never comes up, and an explicit "restore
|
||||||
|
# backup?" prompt if `netplan apply` itself fails outright.
|
||||||
|
#
|
||||||
|
# Netplan's directory is $NETPLAN_DIR (lib/config.sh) rather than a
|
||||||
|
# hardcoded /etc/netplan, so a test can point it at scratch space. But
|
||||||
|
# unlike every other migrated menu, there is deliberately no automated
|
||||||
|
# test - not even a stubbed one - that calls the real `netplan apply`,
|
||||||
|
# `nmcli`, `iw`, `wpa_cli`, or `sudo ip link set ... up`. Only the pure
|
||||||
|
# logic (SSID/password handling, YAML generation, backup naming) is
|
||||||
|
# covered by tests with those commands stubbed; the actual apply step
|
||||||
|
# is exercised by hand against real hardware only.
|
||||||
|
#
|
||||||
|
# Unlike the other migrated menus, this one has no sub-options - it's a
|
||||||
|
# single linear wizard, same as the legacy configure_wifi - so wifi_menu
|
||||||
|
# IS the action, not a run_menu wrapper.
|
||||||
|
#
|
||||||
|
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
wifi_menu() {
|
||||||
|
echo
|
||||||
|
echo " ═══ WIFI CONFIGURATION ═══"
|
||||||
|
echo
|
||||||
|
|
||||||
|
local has_tools=false
|
||||||
|
if command -v nmcli &>/dev/null || command -v iw &>/dev/null || command -v wpa_cli &>/dev/null; then
|
||||||
|
has_tools=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! $has_tools; then
|
||||||
|
log_error "No WiFi tools found (nmcli, iw, or wpa_cli)"
|
||||||
|
echo "Install: sudo apt install network-manager wireless-tools wpasupplicant"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local wifi_iface
|
||||||
|
wifi_iface=$(ls /sys/class/net 2>/dev/null | grep -E "^wl" | head -1)
|
||||||
|
|
||||||
|
if [[ -z "$wifi_iface" ]]; then
|
||||||
|
log_warning "No WiFi hardware detected"
|
||||||
|
echo "If you have a USB WiFi adapter, ensure it's plugged in."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Interface: $wifi_iface"
|
||||||
|
echo "Current IP: $(get_ip_address)"
|
||||||
|
echo
|
||||||
|
|
||||||
|
if [[ -n "${SSH_CONNECTION:-}" ]]; then
|
||||||
|
log_warning "SSH detected - changes auto-revert after 60s if the connection fails"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
ask_yes_no "Configure WiFi?" "n" || return 0
|
||||||
|
|
||||||
|
echo "Bringing up interface..."
|
||||||
|
if ! sudo ip link set "$wifi_iface" up 2>/dev/null; then
|
||||||
|
log_error "Failed to bring up interface"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
echo "Scanning for networks (this takes 5-10 seconds)..."
|
||||||
|
local scan_results=""
|
||||||
|
|
||||||
|
if command -v nmcli &>/dev/null; then
|
||||||
|
if sudo nmcli device wifi rescan 2>/dev/null; then
|
||||||
|
sleep 5
|
||||||
|
scan_results=$(nmcli -t -f SSID,SIGNAL device wifi list 2>/dev/null | sort -t: -k2 -rn | cut -d: -f1 | grep -v "^$" | uniq)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$scan_results" ]] && command -v iw &>/dev/null; then
|
||||||
|
local scan_tmp
|
||||||
|
scan_tmp=$(mktemp)
|
||||||
|
if sudo iw dev "$wifi_iface" scan 2>/dev/null | grep -E "^BSS|SSID:" > "$scan_tmp"; then
|
||||||
|
scan_results=$(grep "SSID:" "$scan_tmp" | sed 's/.*SSID: //' | grep -v "^$" | sort -u)
|
||||||
|
fi
|
||||||
|
rm -f "$scan_tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$scan_results" ]]; then
|
||||||
|
sudo wpa_cli -i "$wifi_iface" scan >/dev/null 2>&1 || true
|
||||||
|
sleep 5
|
||||||
|
scan_results=$(sudo wpa_cli -i "$wifi_iface" scan_results 2>/dev/null | awk -F'\t' 'NR>1 && $5!="" {print $5}' | sort -u)
|
||||||
|
fi
|
||||||
|
|
||||||
|
local ssid=""
|
||||||
|
if [[ -z "$scan_results" ]]; then
|
||||||
|
log_warning "No networks found in scan"
|
||||||
|
echo "This could mean:"
|
||||||
|
echo " • WiFi is disabled in BIOS/UEFI"
|
||||||
|
echo " • Hardware WiFi switch is off"
|
||||||
|
echo " • Driver not loaded"
|
||||||
|
echo " • Networks out of range"
|
||||||
|
echo
|
||||||
|
if ask_yes_no "Enter SSID manually anyway?" "n"; then
|
||||||
|
read -r -p "SSID: " ssid
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo "Available networks (strongest first):"
|
||||||
|
echo "$scan_results" | nl -w2 -s'. '
|
||||||
|
echo " 0. Manual entry"
|
||||||
|
echo
|
||||||
|
local choice
|
||||||
|
read -r -p "Select network number or enter SSID: " choice
|
||||||
|
if [[ "$choice" == "0" ]]; then
|
||||||
|
read -r -p "SSID: " ssid
|
||||||
|
elif [[ "$choice" =~ ^[0-9]+$ ]]; then
|
||||||
|
ssid=$(echo "$scan_results" | sed -n "${choice}p")
|
||||||
|
else
|
||||||
|
ssid="$choice"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$ssid" ]]; then
|
||||||
|
log_error "No SSID provided"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local password
|
||||||
|
read -r -s -p "Password for '$ssid': " password
|
||||||
|
echo
|
||||||
|
if [[ -z "$password" ]]; then
|
||||||
|
log_error "No password provided"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
apply_wifi_config "$wifi_iface" "$ssid" "$password"
|
||||||
|
}
|
||||||
|
|
||||||
|
# apply_wifi_config IFACE SSID PASSWORD
|
||||||
|
# Split out from wifi_menu so a test can drive it directly without going
|
||||||
|
# through interface detection/scanning, which don't exist in a container.
|
||||||
|
apply_wifi_config() {
|
||||||
|
local wifi_iface="$1"
|
||||||
|
local ssid="$2"
|
||||||
|
local password="$3"
|
||||||
|
|
||||||
|
# `|| true`: under set -e + pipefail (this whole tool runs under both),
|
||||||
|
# `ls` matching nothing exits non-zero even with stderr silenced, which
|
||||||
|
# would abort this function outright instead of falling through to the
|
||||||
|
# default filename below. Same class of bug as the run_menu fix in
|
||||||
|
# lib/menu.sh - masked here in practice because cloud-init almost
|
||||||
|
# always leaves a *.yaml file behind, but not guaranteed.
|
||||||
|
local netplan_file
|
||||||
|
netplan_file=$(ls "$NETPLAN_DIR"/*.yaml 2>/dev/null | head -1) || true
|
||||||
|
[[ -z "$netplan_file" ]] && netplan_file="$NETPLAN_DIR/50-cloud-init.yaml"
|
||||||
|
|
||||||
|
local backup=""
|
||||||
|
if [[ -f "$netplan_file" ]]; then
|
||||||
|
backup="${netplan_file}.backup-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
sudo cp "$netplan_file" "$backup"
|
||||||
|
log_success "Backup: $backup"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local temp_plan
|
||||||
|
temp_plan=$(mktemp --suffix=.yaml)
|
||||||
|
cat > "$temp_plan" <<EOF
|
||||||
|
network:
|
||||||
|
version: 2
|
||||||
|
renderer: networkd
|
||||||
|
wifis:
|
||||||
|
$wifi_iface:
|
||||||
|
dhcp4: true
|
||||||
|
dhcp6: false
|
||||||
|
optional: true
|
||||||
|
access-points:
|
||||||
|
"$ssid":
|
||||||
|
password: "$password"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [[ -n "${SSH_CONNECTION:-}" ]] && [[ -n "$backup" ]]; then
|
||||||
|
local watchdog
|
||||||
|
watchdog=$(mktemp --suffix=.sh)
|
||||||
|
cat > "$watchdog" <<'WATCHEOF'
|
||||||
|
#!/bin/bash
|
||||||
|
sleep 60
|
||||||
|
if [[ -f "$1" && -f "$2" ]]; then
|
||||||
|
ip=$(hostname -I | awk '{print $1}')
|
||||||
|
if [[ -z "$ip" ]] || ! ping -c 2 8.8.8.8 >/dev/null 2>&1; then
|
||||||
|
cp "$1" "$2"
|
||||||
|
netplan apply 2>/dev/null
|
||||||
|
echo "WiFi config reverted - connection failed" | wall
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -f "$0"
|
||||||
|
WATCHEOF
|
||||||
|
chmod +x "$watchdog"
|
||||||
|
nohup sudo bash "$watchdog" "$backup" "$netplan_file" >/dev/null 2>&1 &
|
||||||
|
echo "Watchdog started - will revert in 60s if the connection fails"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo cp "$temp_plan" "$netplan_file"
|
||||||
|
sudo chmod 0600 "$netplan_file"
|
||||||
|
rm -f "$temp_plan"
|
||||||
|
|
||||||
|
echo "Applying configuration..."
|
||||||
|
local netplan_log
|
||||||
|
netplan_log=$(mktemp)
|
||||||
|
if sudo netplan apply > "$netplan_log" 2>&1; then
|
||||||
|
cat "$netplan_log"
|
||||||
|
sleep 10
|
||||||
|
local new_ip
|
||||||
|
new_ip=$(get_ip_address)
|
||||||
|
if [[ -n "$new_ip" && "$new_ip" != "No IP" ]]; then
|
||||||
|
log_success "Connected: $ssid ($new_ip)"
|
||||||
|
[[ -n "${SSH_CONNECTION:-}" ]] && echo "Connection successful - watchdog will not revert"
|
||||||
|
else
|
||||||
|
log_warning "Config applied but no IP yet"
|
||||||
|
echo "Check: sudo journalctl -u systemd-networkd -f"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_error "netplan apply failed"
|
||||||
|
echo "Error log:"
|
||||||
|
cat "$netplan_log"
|
||||||
|
if [[ -n "$backup" ]] && ask_yes_no "Restore backup?" "y"; then
|
||||||
|
sudo cp "$backup" "$netplan_file"
|
||||||
|
# Last resort after everything else failed: report, don't crash
|
||||||
|
# the session if even the restore-and-reapply doesn't work.
|
||||||
|
if sudo netplan apply; then
|
||||||
|
log_success "Backup restored and applied"
|
||||||
|
else
|
||||||
|
log_error "Failed to reapply the restored backup - manual intervention needed"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rm -f "$netplan_log"
|
||||||
|
}
|
||||||
+106
-2
@@ -1,8 +1,112 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
################################################################################
|
################################################################################
|
||||||
### Ubuntu Based Kiosk v2.2.0 ###
|
### Ubuntu Based Kiosk v2.5.0 ###
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
|
# RELEASE v2.5.0 - First Addon Migrated (CUPS), Menu Restructured
|
||||||
|
# - New in ./install.sh: CUPS Printing (menus/addon_cups.sh) - the first
|
||||||
|
# Addon migrated. Install/reconfigure/complete uninstall (purge),
|
||||||
|
# genuinely mutating real system state (apt install/remove --purge,
|
||||||
|
# /etc/cups, ufw) at fixed paths CUPS itself doesn't let us relocate -
|
||||||
|
# unlike the systemd/cron/bin paths this project controls, there is no
|
||||||
|
# scratch equivalent for a real apt-managed subsystem's own file
|
||||||
|
# layout, so every test uses full command-level `sudo` stubbing
|
||||||
|
# instead. Only the polkit rule's directory is parameterized
|
||||||
|
# ($POLKIT_DIR, since that one is ours to place).
|
||||||
|
# - install.sh's top-level menu is now grouped the same way the legacy
|
||||||
|
# menu groups things - Core Settings / Addons / Advanced - instead of
|
||||||
|
# one flat list, ahead of that list getting unwieldy as more Addons
|
||||||
|
# and Advanced items migrate in.
|
||||||
|
# - Two bugs caught and fixed before they ever shipped, both instructive
|
||||||
|
# beyond this one file:
|
||||||
|
# - A "wait for service to start" retry loop used a bare `cmd1 &&
|
||||||
|
# cmd2 && break` as its body. That's not safe merely because it's
|
||||||
|
# inside a loop - a bare &&/|| list used as a standalone statement
|
||||||
|
# (not the condition of if/while/until) is fully subject to set -e,
|
||||||
|
# and cmd1 failing on an early iteration (near-certain right after
|
||||||
|
# a fresh install) would have killed the whole session. Restored
|
||||||
|
# the `if cmd1 && cmd2; then break; fi` form the legacy script
|
||||||
|
# already used correctly, rather than "simplifying" it away.
|
||||||
|
# - Resolved real uncertainty about how far run_menu's `handler ||
|
||||||
|
# true` guard (added in v2.1.0) actually reaches: verified with a
|
||||||
|
# minimal isolated test that it protects against a bare failing
|
||||||
|
# command no matter how many function calls deep it occurs - bash's
|
||||||
|
# errexit exemption for the left side of `||` covers the entire
|
||||||
|
# evaluation, not just the immediately-called function. So the
|
||||||
|
# session-crash risk this project has been chasing since v2.1.0 is
|
||||||
|
# already covered end-to-end by that one fix. Per-statement guards
|
||||||
|
# (`|| true`, explicit `if`) still matter for a different reason:
|
||||||
|
# without them a deep failure silently bubbles up past the menu
|
||||||
|
# that's actually responsible for it to wherever the nearest `||
|
||||||
|
# true` happens to catch it, which may be several menu levels
|
||||||
|
# higher than where the user actually was.
|
||||||
|
#
|
||||||
|
# RELEASE v2.4.0 - Diagnostics Migrated
|
||||||
|
# - New in ./install.sh: Diagnostics (menus/diagnostics.sh) - system
|
||||||
|
# status, log viewing (Electron/LightDM/journal), an 8-step audio
|
||||||
|
# diagnostic, and a ping+DNS network test, pulled from the legacy
|
||||||
|
# Advanced menu. Everything here is read-only except one optional
|
||||||
|
# "play a test sound?" prompt - a deliberate change of pace after
|
||||||
|
# Sites/WiFi/Power, with no destructive-action risk to design around.
|
||||||
|
# Manual Electron Update, Factory Reset, Export/Import Settings,
|
||||||
|
# Emergency Hotspot, and Fix Blank Screen are staying in the legacy
|
||||||
|
# script for now - they're mutating/destructive, and some share
|
||||||
|
# Upgrade's coupling to the legacy script's own self-extraction
|
||||||
|
# mechanism (see v2.3.0 below for why Upgrade/Reinstall/Uninstall
|
||||||
|
# aren't migrated either).
|
||||||
|
# - Fixed (set -e safety, same class as v2.1.0/v2.3.0): every diagnostic
|
||||||
|
# command whose failure is actually the expected, common case - no
|
||||||
|
# lightdm running, no audio hardware, no network, missing log files,
|
||||||
|
# `ping`/`nslookup` not even installed - was a bare unguarded
|
||||||
|
# statement that would have crashed the whole session instead of
|
||||||
|
# reporting "not found" and moving on. A diagnostics tool has to be
|
||||||
|
# the most crash-proof code in the project, since it exists to run
|
||||||
|
# *when something is already broken*; every one of these now reports
|
||||||
|
# and continues instead. Also worth noting for future menus: writing
|
||||||
|
# `local var;` and `var=$(cmd)` as separate statements (good practice,
|
||||||
|
# and how earlier real bugs in this migration were caught) removes an
|
||||||
|
# accidental safety net bash's `local x=$(cmd)` has on one line - that
|
||||||
|
# form masks the substitution's exit code with `local`'s own
|
||||||
|
# always-success status. Splitting them is correct, but each split
|
||||||
|
# assignment needs its own explicit `|| true` (or real fallback) where
|
||||||
|
# a failure is expected and non-fatal, rather than relying on that
|
||||||
|
# quirk by accident.
|
||||||
|
#
|
||||||
|
# RELEASE v2.3.0 - WiFi and Power/Display/Quiet Hours Migrated
|
||||||
|
# - New in ./install.sh: WiFi (menus/wifi.sh) and Power/Display/Quiet
|
||||||
|
# Hours (menus/power_schedule.sh) - by far the biggest and riskiest
|
||||||
|
# menus migrated so far. WiFi can rewrite live netplan config and, if
|
||||||
|
# run over SSH, disconnect the very session configuring it; Power
|
||||||
|
# schedule can shut the physical machine down and wake it via RTC.
|
||||||
|
# Every safety mechanism from the legacy menus is preserved exactly:
|
||||||
|
# netplan backup + 60s SSH watchdog + restore-on-apply-failure for
|
||||||
|
# WiFi; RTC availability detection for power scheduling. New
|
||||||
|
# $SYSTEMD_DIR/$CRON_D_DIR/$BIN_DIR/$NETPLAN_DIR variables (lib/config.sh)
|
||||||
|
# mean nothing under menus/ hardcodes /etc/systemd/system, /etc/cron.d,
|
||||||
|
# /usr/local/bin, or /etc/netplan - tests point them at scratch space.
|
||||||
|
# - Fixed: the legacy dispatcher refused to open "Configure power
|
||||||
|
# schedule" at all when no RTC wake was detected, even though
|
||||||
|
# shutdown-only scheduling never needed RTC in the first place.
|
||||||
|
# - Fixed: none of shutdown/wake/display-off/display-on/quiet-start/
|
||||||
|
# quiet-end/custom-Electron-reload times were validated as HH:MM in
|
||||||
|
# the legacy menus (plain `read`, no format check) - now all go
|
||||||
|
# through ask_time.
|
||||||
|
# - Fixed (set -e safety, same class as v2.1.0's run_menu fix): several
|
||||||
|
# bare, unguarded statements whose failure would have taken down the
|
||||||
|
# entire session instead of just that action - `ls *.yaml` when no
|
||||||
|
# netplan file exists (masked in practice by cloud-init usually
|
||||||
|
# leaving one behind), the restore-and-reapply `netplan apply` after
|
||||||
|
# an initial apply failure, and `systemctl enable`/`start` after
|
||||||
|
# writing each of the four timer pairs. The last of these was caught
|
||||||
|
# only by testing in an environment without a live systemd - a real
|
||||||
|
# `enable`/`start` failure on actual hardware (bad unit, daemon-reload
|
||||||
|
# skipped, ...) would have hit the same bug. All now report a clear
|
||||||
|
# warning and return to the menu instead.
|
||||||
|
# - Deliberately NOT migrated: the legacy dispatcher's "Test schedules &
|
||||||
|
# system" led into a shared diagnostics submenu (audio/network/
|
||||||
|
# keyboard tests) that isn't specific to scheduling and belongs with a
|
||||||
|
# future Advanced/Diagnostics migration instead.
|
||||||
|
#
|
||||||
# RELEASE v2.2.0 - Password Protection & Lockout Migrated
|
# RELEASE v2.2.0 - Password Protection & Lockout Migrated
|
||||||
# - New in ./install.sh: Password Protection & Lockout (menus/lockout.sh) -
|
# - New in ./install.sh: Password Protection & Lockout (menus/lockout.sh) -
|
||||||
# enable/disable, change password (SHA-256 hashed before it's ever
|
# enable/disable, change password (SHA-256 hashed before it's ever
|
||||||
@@ -122,7 +226,7 @@ set -euo pipefail
|
|||||||
### SECTION 1: CONSTANTS & GLOBALS
|
### SECTION 1: CONSTANTS & GLOBALS
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
SCRIPT_VERSION="2.2.0"
|
SCRIPT_VERSION="2.5.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