Migrate Remote Access addon; fix status-function crash gap in run_menu; bump to v2.8.0

Third and biggest Addon migrated: menus/addon_remote_access.sh - VNC
(x11vnc), WireGuard, Tailscale, and Netbird, each with its own install/
connect/status/uninstall flow. Same risk class as CUPS (real apt
packages, real system state) but broader in scope: Tailscale and
Netbird install via the vendors' own documented `curl -fsSL <url> | sh`
method, preserved exactly as-is rather than redesigned.

- lib/config.sh: new $WIREGUARD_DIR, same pattern as $SYSTEMD_DIR/
  $BIN_DIR/etc - nothing in this file hardcodes /etc/wireguard.
- lib/menu.sh: promoted power_schedule.sh's enable_and_start_timers()
  to a shared enable_and_start_units() (works for services now too, not
  just timers) - Remote Access needed the identical enable+start-with-
  graceful-failure-reporting pattern for x11vnc and wg-quick@, so this
  is fixed once and reused rather than duplicated a second time.
  power_schedule.sh's four call sites renamed to match.

Found and fixed a real framework-level bug while building this file:
run_menu()'s *handler* call has been `|| true`-guarded since v2.1.0,
but the *status function* call (`"$status_func"` on its own line) was
still completely bare. A status function's entire job is read-only
display, but if it contains so much as a pipeline whose grep matches
nothing - which pipefail turns into a pipeline failure even though the
actual last command in it (e.g. sed) succeeds - that bare call would
crash the *entire session*, not just fail to show status text. Found
while writing wireguard_status()'s `sudo wg show | grep ... | sed ...`
and deliberately verifying its exact failure mode rather than assuming
run_menu already covered it. Fixed once in run_menu() itself
(lib/menu.sh), protecting every status function across every menu -
present and future - the same "fix once at the framework level"
pattern as the v2.1.0 handler fix.

Given the framework fix meant this class of bug had been silently
possible since v2.1.0, audited every existing status function across
every already-migrated menu for the same specific shape (a bare
`var=$(...)` assignment from a grep-based pipeline, not embedded in an
echo and not already guarded - embedded substitutions and if-condition
contexts are both already safe on their own). Found and fixed one real
instance in power_schedule_status(). menus/addon_remote_access.sh's own
two equivalent pipelines (wireguard_status, netbird_status) were
written with `|| true` from the start once the pattern was identified.

Verified:
- New scratch/stub test for addon_remote_access.sh, with curl stubbed
  separately from sudo (Tailscale/Netbird's install scripts must never
  reach the real network regardless of what sudo intercepts) and a
  belt-and-suspenders `sh` stub in case anything got past curl: full
  status/menu-builder coverage for all four sub-areas in their real,
  unstubbed "not installed" state (none of the four tools exist in this
  sandbox); VNC install/change-password/uninstall with systemd unit
  content verified (correct $KIOSK_USER/$KIOSK_HOME substitution);
  WireGuard install, paste-config (content written correctly to scratch
  $WIREGUARD_DIR), and uninstall - including documenting a genuine
  cat-until-EOF test-harness limitation (a redirected pipe's EOF is
  permanent for the whole stream, unlike a real terminal's per-read
  Ctrl+D, so only the config's *default* name is testable through
  simple stdin redirection - inherent to the design, matches the legacy
  script's identical `cat`-based approach, not a bug); Tailscale and
  Netbird install/connect-interactive/connect-with-key/uninstall; and
  all four cancel paths confirmed to make zero sudo calls.
- Full regression: re-ran all 11 prior scratch/stub suites after the
  lib/menu.sh and power_schedule.sh changes - all still clean.
- End-to-end: ran the real install.sh as a genuine non-root, non-
  "kiosk" user through Addons -> Remote Access -> all four sub-menus in
  turn, each showing accurate real (unstubbed) "not installed" status,
  selecting Install, declining the confirmation, and returning cleanly
  - zero invalid-choice errors, clean exit code 0.
This commit is contained in:
Claude
2026-08-19 01:35:15 +00:00
parent cdd6f5adcf
commit 0454a259fa
7 changed files with 495 additions and 26 deletions
+11 -4
View File
@@ -1,6 +1,6 @@
# Ubuntu Based Kiosk
**Current Version:** 2.7.0 (check script header for latest version)
**Current Version:** 2.8.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/
@@ -1212,6 +1212,8 @@ terminal menu and the web UI, so they can't drift apart).
- `menus/addon_authelia.sh`**Authelia Auto-Login** (Addons):
encrypted SSO credentials plus the server-side setup instructions.
Prompted the `save_config` merge fix above.
- `menus/addon_remote_access.sh`**Remote Access** (Addons): VNC,
WireGuard, Tailscale, Netbird. The biggest Addon so far.
- `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:
@@ -1224,7 +1226,7 @@ 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 Upgrade, Reinstall, Uninstall, 3 more Addons, and the
menu above (plus Upgrade, Reinstall, Uninstall, 2 more Addons, and the
other 8 Advanced items — none of that has moved yet). Both copies
coexist deliberately: the old ones stay until enough
of Core Settings/Addons/Advanced is migrated to
@@ -1246,9 +1248,14 @@ full migration pass.
## Project Status & Future Plans
**Current Version:** 2.7.0
**Current Version:** 2.8.0
**Recent Updates (v2.7.0):**
**Recent Updates (v2.8.0):**
- **Remote Access migrated** — VNC, WireGuard, Tailscale, and Netbird, each with its own install/connect/status/uninstall flow. The biggest Addon so far. Tailscale/Netbird install via the vendors' own `curl | sh` method, preserved as-is.
- **Important framework-level bug found and fixed:** `run_menu()`'s *handler* call has been crash-guarded since v2.1.0, but its *status function* call was still completely bare. A status function is meant to be read-only display, but a pipeline whose `grep` matches nothing (which `pipefail` turns into a failure even though the actual last command succeeds) would crash the **entire session**, not just fail to show status. Found while building `wireguard_status()` and verifying its exact failure mode rather than assuming it was covered. Fixed once, in the framework, protecting every status function across every menu — present and future. Also audited every existing status function for the same shape and fixed one real instance in `power_schedule_status()`.
- Deduplicated: promoted `power_schedule.sh`'s `enable_and_start_timers()` to a shared `enable_and_start_units()` in `lib/menu.sh` (works for services now, not just timers) rather than writing the same helper a second time for VNC/WireGuard.
**Previous (v2.7.0):**
- **Backported fix:** `ubuntu-based-kiosk.sh`'s own `save_config()` had the identical config-clobbering bug fixed in `lib/config.sh` under v2.6.0 — it silently deleted Authelia credentials (or any field it doesn't explicitly know about) the next time Sites, Touch Controls, Navigation, or Password Protection saved. This was a real, currently-shipping credential-loss bug, so it's fixed directly in the legacy script now rather than waiting for those menus to be migrated. Verified in isolation against the exact extracted function before touching the shipping copy. Nothing else about those menus changed.
**Previous (v2.6.0):**
+6 -3
View File
@@ -17,7 +17,8 @@
# Hidden Site PIN, Password Protection & Lockout, WiFi,
# Power/Display/Quiet Hours.
# Addons: CUPS Printing (menus/addon_cups.sh), Authelia Auto-Login
# (menus/addon_authelia.sh).
# (menus/addon_authelia.sh), Remote Access - VNC/WireGuard/
# Tailscale/Netbird (menus/addon_remote_access.sh).
# Advanced: Diagnostics (menus/diagnostics.sh - system status/logs/
# audio/network).
#
@@ -55,6 +56,8 @@ source "$SCRIPT_DIR/menus/diagnostics.sh"
source "$SCRIPT_DIR/menus/addon_cups.sh"
# shellcheck source=menus/addon_authelia.sh
source "$SCRIPT_DIR/menus/addon_authelia.sh"
# shellcheck source=menus/addon_remote_access.sh
source "$SCRIPT_DIR/menus/addon_remote_access.sh"
################################################################################
# Preflight
@@ -118,8 +121,8 @@ core_settings_menu() {
}
addons_menu_builder() {
MENU_LABELS=("CUPS Printing" "Authelia Auto-Login")
MENU_HANDLERS=(addon_cups_menu addon_authelia_menu)
MENU_LABELS=("CUPS Printing" "Authelia Auto-Login" "Remote Access")
MENU_HANDLERS=(addon_cups_menu addon_authelia_menu remote_access_menu)
}
addons_menu() {
+1
View File
@@ -30,6 +30,7 @@
: "${BIN_DIR:=/usr/local/bin}"
: "${NETPLAN_DIR:=/etc/netplan}"
: "${POLKIT_DIR:=/etc/polkit-1/localauthority/50-local.d}"
: "${WIREGUARD_DIR:=/etc/wireguard}"
# 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
+20 -1
View File
@@ -84,6 +84,18 @@ get_vpn_ips() {
[[ -n "$vpn_info" ]] && echo "$vpn_info" || echo "None"
}
# enable_and_start_units UNIT [UNIT...]
# Reloads systemd and enables+starts the given unit(s) - services or
# timers - returning non-zero if enable or start fails (e.g. systemd/
# D-Bus unreachable, or a real failure on real hardware). 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_units() {
sudo systemctl daemon-reload 2>/dev/null || true
sudo systemctl enable "$@" 2>/dev/null && sudo systemctl start "$@" 2>/dev/null
}
pause() {
read -r -p "Press Enter to continue..."
}
@@ -279,7 +291,14 @@ run_menu() {
print_menu_header "$title"
if [[ -n "$status_func" ]]; then
"$status_func"
# `|| true`: same reasoning as the handler call below - a
# status function's job is read-only display, and a
# legitimately failing command inside it (e.g. a pipeline
# whose grep matches nothing, which pipefail turns into a
# pipeline failure even though the actual last command
# succeeded) must not be allowed to kill the whole session
# over what should be, at worst, incomplete status text.
"$status_func" || true
echo
fi
+418
View File
@@ -0,0 +1,418 @@
#!/bin/bash
################################################################################
# menus/addon_remote_access.sh - "Remote Access" addon (VNC, WireGuard,
# Tailscale, Netbird).
#
# Third Addon migrated, and the biggest so far in scope (4 sub-areas).
# All four genuinely mutate real system state at fixed paths this project
# doesn't own the layout of (apt packages, /etc/wireguard, real VPN
# client CLIs) - same risk class as CUPS. Only $WIREGUARD_DIR and
# $SYSTEMD_DIR (lib/config.sh) are parameterized, since those are the
# only paths this file itself writes to; every command (apt, systemctl,
# wg, tailscale, netbird, x11vnc) gets full stubbing in every test.
#
# Tailscale and Netbird install themselves via `curl -fsSL <vendor
# url> | sh` - the vendors' own documented install method, preserved as-
# is rather than redesigned. This is NEVER allowed to run for real in
# any test: curl itself is stubbed, not just sudo, so there is no path
# by which a test could reach the network.
#
# None of x11vnc/wg/tailscale/netbird are installed in a fresh
# environment, so their "not installed" detection is real/unstubbed and
# safe to exercise end-to-end - only the "install" actions need stubs.
#
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
################################################################################
remote_access_status() {
echo "VNC: $(is_service_active x11vnc && echo "running" || echo "not installed")"
echo "WireGuard: $(wireguard_connected && echo "connected" || (command -v wg &>/dev/null && echo "installed, not connected" || echo "not installed"))"
echo "Tailscale: $(command -v tailscale &>/dev/null && echo "installed" || echo "not installed")"
echo "Netbird: $(command -v netbird &>/dev/null && echo "installed" || echo "not installed")"
}
remote_access_menu_builder() {
MENU_LABELS=("VNC Remote Desktop" "WireGuard VPN" "Tailscale VPN" "Netbird VPN")
MENU_HANDLERS=(vnc_menu wireguard_menu tailscale_menu netbird_menu)
}
remote_access_menu() {
run_menu "REMOTE ACCESS" remote_access_menu_builder remote_access_status
}
################################################################################
# VNC (x11vnc)
################################################################################
vnc_status() {
if is_service_active x11vnc; then
echo "VNC: running - connect to $(get_ip_address):5900"
else
echo "VNC: not installed"
fi
}
vnc_menu_builder() {
if is_service_active x11vnc; then
MENU_LABELS=("Reconfigure password" "Uninstall")
MENU_HANDLERS=(action_vnc_change_password action_vnc_uninstall)
else
MENU_LABELS=("Install x11vnc")
MENU_HANDLERS=(action_vnc_install)
fi
}
vnc_menu() {
run_menu "VNC REMOTE DESKTOP" vnc_menu_builder vnc_status
}
action_vnc_install() {
echo
ask_yes_no "Install x11vnc?" "n" || { echo "Cancelled"; return; }
if ! sudo apt install -y x11vnc; then
log_error "x11vnc installation failed"
return 1
fi
local vnc_pass
read -r -s -p "VNC password: " vnc_pass
echo
if [[ -z "$vnc_pass" ]]; then
log_error "No password provided - cancelled"
return 1
fi
sudo -u "$KIOSK_USER" mkdir -p "$KIOSK_HOME/.vnc"
sudo -u "$KIOSK_USER" x11vnc -storepasswd "$vnc_pass" "$KIOSK_HOME/.vnc/passwd"
sudo tee "$SYSTEMD_DIR/x11vnc.service" > /dev/null <<EOF
[Unit]
Description=x11vnc Remote Desktop
After=lightdm.service
[Service]
Type=simple
User=${KIOSK_USER}
ExecStart=/usr/bin/x11vnc -display :0 -auth guess -rfbauth ${KIOSK_HOME}/.vnc/passwd -forever -loop -noxdamage -repeat -shared
Restart=always
[Install]
WantedBy=multi-user.target
EOF
if enable_and_start_units x11vnc; then
sudo ufw allow 5900/tcp comment 'VNC' 2>/dev/null || true
log_success "VNC installed - connect to $(get_ip_address):5900"
else
log_warning "x11vnc installed but systemctl enable/start failed - check 'systemctl status x11vnc'"
fi
}
action_vnc_change_password() {
echo
local vnc_pass
read -r -s -p "New VNC password: " vnc_pass
echo
if [[ -z "$vnc_pass" ]]; then
log_error "No password provided - cancelled"
return 1
fi
sudo -u "$KIOSK_USER" x11vnc -storepasswd "$vnc_pass" "$KIOSK_HOME/.vnc/passwd"
if sudo systemctl restart x11vnc 2>/dev/null; then
log_success "VNC password updated"
else
log_warning "Password file updated, but restarting x11vnc failed - check 'systemctl status x11vnc'"
fi
}
action_vnc_uninstall() {
echo
ask_yes_no "Remove VNC?" "n" || { echo "Cancelled"; return; }
sudo systemctl stop x11vnc 2>/dev/null || true
sudo systemctl disable x11vnc 2>/dev/null || true
sudo rm -f "$SYSTEMD_DIR/x11vnc.service"
sudo apt remove -y x11vnc
log_success "VNC removed"
}
################################################################################
# WireGuard
################################################################################
wireguard_connected() {
command -v wg &>/dev/null && sudo wg show 2>/dev/null | grep -q interface
}
wireguard_status() {
if wireguard_connected; then
echo "WireGuard: connected"
sudo wg show 2>/dev/null | grep -E "interface:|endpoint:|allowed ips:" | sed 's/^/ /' || true
elif command -v wg &>/dev/null; then
echo "WireGuard: installed, not connected"
else
echo "WireGuard: not installed"
fi
}
wireguard_menu_builder() {
if wireguard_connected; then
MENU_LABELS=("Show full config" "Paste new config" "Uninstall")
MENU_HANDLERS=(action_wireguard_show_config action_wireguard_paste_config action_wireguard_uninstall)
elif command -v wg &>/dev/null; then
MENU_LABELS=("Paste config" "Uninstall")
MENU_HANDLERS=(action_wireguard_paste_config action_wireguard_uninstall)
else
MENU_LABELS=("Install WireGuard")
MENU_HANDLERS=(action_wireguard_install)
fi
}
wireguard_menu() {
run_menu "WIREGUARD VPN" wireguard_menu_builder wireguard_status
}
action_wireguard_install() {
echo
ask_yes_no "Install WireGuard?" "n" || { echo "Cancelled"; return; }
if ! sudo apt install -y wireguard wireguard-tools; then
log_error "WireGuard installation failed"
return 1
fi
log_success "WireGuard installed"
echo
if ask_yes_no "Paste a config now?" "n"; then
action_wireguard_paste_config
fi
}
action_wireguard_show_config() {
echo
sudo wg show all
}
# Reads a WireGuard config from stdin until EOF (Ctrl+D on a real
# terminal) - same as the legacy addon. Writes to $WIREGUARD_DIR rather
# than a hardcoded /etc/wireguard, so tests can point it at scratch space
# and verify the written content without touching the real directory.
action_wireguard_paste_config() {
echo
echo "Paste your WireGuard config (Ctrl+D when done):"
local config
config=$(cat)
if [[ -z "$config" ]]; then
log_error "No config provided"
return 1
fi
local wg_name
wg_name=$(ask_text "Config name" "wg0")
sudo mkdir -p "$WIREGUARD_DIR"
echo "$config" | sudo tee "$WIREGUARD_DIR/${wg_name}.conf" > /dev/null
sudo chmod 600 "$WIREGUARD_DIR/${wg_name}.conf"
if enable_and_start_units "wg-quick@${wg_name}"; then
log_success "WireGuard configured: $wg_name"
else
log_warning "Config written, but systemctl enable/start failed - check 'systemctl status wg-quick@${wg_name}'"
fi
}
action_wireguard_uninstall() {
echo
ask_yes_no "Remove WireGuard?" "n" || { echo "Cancelled"; return; }
sudo systemctl stop 'wg-quick@*' 2>/dev/null || true
sudo systemctl disable 'wg-quick@*' 2>/dev/null || true
sudo apt remove -y wireguard wireguard-tools
log_success "WireGuard removed"
}
################################################################################
# Tailscale
################################################################################
tailscale_backend_state() {
tailscale status --json 2>/dev/null | jq -r '.BackendState // "unknown"' 2>/dev/null || echo "unknown"
}
tailscale_status() {
if ! command -v tailscale &>/dev/null; then
echo "Tailscale: not installed"
return
fi
if [[ "$(tailscale_backend_state)" == "Running" ]]; then
echo "Tailscale: connected"
echo " Hostname: $(tailscale status --json 2>/dev/null | jq -r '.Self.HostName // "unknown"')"
echo " IP: $(tailscale ip -4 2>/dev/null)"
else
echo "Tailscale: installed, not connected"
fi
}
tailscale_menu_builder() {
if command -v tailscale &>/dev/null; then
MENU_LABELS=("Connect (interactive)" "Connect with auth key" "Show status" "Uninstall")
MENU_HANDLERS=(action_tailscale_connect_interactive action_tailscale_connect_authkey action_tailscale_show_status action_tailscale_uninstall)
else
MENU_LABELS=("Install Tailscale")
MENU_HANDLERS=(action_tailscale_install)
fi
}
tailscale_menu() {
run_menu "TAILSCALE VPN" tailscale_menu_builder tailscale_status
}
action_tailscale_install() {
echo
ask_yes_no "Install Tailscale?" "n" || { echo "Cancelled"; return; }
if ! curl -fsSL https://tailscale.com/install.sh | sh; then
log_error "Tailscale installation failed"
return 1
fi
log_success "Tailscale installed"
echo
echo "Options:"
echo " 1. Connect now (interactive)"
echo " 2. Connect with auth key"
echo " 3. Connect later"
local choice
choice=$(ask_integer "Choose" "3" 1 3)
case "$choice" in
1) action_tailscale_connect_interactive ;;
2) action_tailscale_connect_authkey ;;
esac
}
action_tailscale_connect_interactive() {
echo
if sudo tailscale up; then
log_success "Tailscale connected"
else
log_error "Tailscale connection failed"
fi
}
action_tailscale_connect_authkey() {
echo
echo "Get an auth key from: https://login.tailscale.com/admin/settings/keys"
local authkey
read -r -p "Enter auth key: " authkey
if [[ -z "$authkey" ]]; then
echo "Cancelled"
return
fi
if sudo tailscale up --authkey="$authkey"; then
log_success "Tailscale connected"
else
log_error "Tailscale connection failed"
fi
}
action_tailscale_show_status() {
echo
tailscale status
}
action_tailscale_uninstall() {
echo
ask_yes_no "Remove Tailscale?" "n" || { echo "Cancelled"; return; }
sudo tailscale down 2>/dev/null || true
sudo apt remove -y tailscale
log_success "Tailscale removed"
}
################################################################################
# Netbird
################################################################################
netbird_connected() {
[[ "$(netbird status 2>/dev/null | grep "Status:" | awk '{print $2}')" == "Connected" ]]
}
netbird_status() {
if ! command -v netbird &>/dev/null; then
echo "Netbird: not installed"
return
fi
if netbird_connected; then
echo "Netbird: connected"
netbird status 2>/dev/null | grep -E "NetBird IP:|Public key:" | sed 's/^/ /' || true
else
echo "Netbird: installed, not connected"
fi
}
netbird_menu_builder() {
if command -v netbird &>/dev/null; then
MENU_LABELS=("Connect with setup key" "Show status" "Uninstall")
MENU_HANDLERS=(action_netbird_connect action_netbird_show_status action_netbird_uninstall)
else
MENU_LABELS=("Install Netbird")
MENU_HANDLERS=(action_netbird_install)
fi
}
netbird_menu() {
run_menu "NETBIRD VPN" netbird_menu_builder netbird_status
}
action_netbird_install() {
echo
ask_yes_no "Install Netbird?" "n" || { echo "Cancelled"; return; }
if ! curl -fsSL https://pkgs.netbird.io/install.sh | sh; then
log_error "Netbird installation failed"
return 1
fi
log_success "Netbird installed"
echo
if ask_yes_no "Connect with a setup key now?" "n"; then
action_netbird_connect
fi
}
action_netbird_connect() {
echo
echo "Get a setup key from the Netbird dashboard"
local setup_key
read -r -p "Enter setup key: " setup_key
if [[ -z "$setup_key" ]]; then
echo "Cancelled"
return
fi
if sudo netbird up --setup-key "$setup_key"; then
log_success "Netbird connected"
else
log_error "Netbird connection failed"
fi
}
action_netbird_show_status() {
echo
netbird status
}
action_netbird_uninstall() {
echo
ask_yes_no "Remove Netbird?" "n" || { echo "Cancelled"; return; }
sudo netbird down 2>/dev/null || true
sudo apt remove -y netbird
log_success "Netbird removed"
}
+5 -16
View File
@@ -46,17 +46,6 @@ 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
################################################################################
@@ -66,7 +55,7 @@ power_schedule_status() {
if timer_exists kiosk-shutdown.timer; then
any=true
local t; t=$(timer_oncalendar kiosk-shutdown.timer)
local t; t=$(timer_oncalendar kiosk-shutdown.timer) || true
echo "Power: shutdown daily at ${t:-an unknown time}"
fi
if timer_exists kiosk-display-off.timer; then
@@ -205,7 +194,7 @@ EOF
log_info "RTC wake cron job created"
fi
if enable_and_start_timers kiosk-shutdown.timer; then
if enable_and_start_units 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'"
@@ -342,7 +331,7 @@ Persistent=true
WantedBy=timers.target
EOF
if enable_and_start_timers kiosk-display-off.timer kiosk-display-on.timer; then
if enable_and_start_units 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'"
@@ -476,7 +465,7 @@ 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
if enable_and_start_units 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'"
@@ -561,7 +550,7 @@ Persistent=true
WantedBy=timers.target
EOF
if enable_and_start_timers kiosk-electron-reload.timer; then
if enable_and_start_units 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'"
+34 -2
View File
@@ -1,8 +1,40 @@
#!/bin/bash
################################################################################
### Ubuntu Based Kiosk v2.7.0 ###
### Ubuntu Based Kiosk v2.8.0 ###
################################################################################
#
# RELEASE v2.8.0 - Remote Access Migrated (VNC/WireGuard/Tailscale/
# Netbird); Framework-Level Status-Function Crash Fixed
# - New in ./install.sh: Remote Access (menus/addon_remote_access.sh) -
# VNC (x11vnc), WireGuard, Tailscale, and Netbird, each with its own
# install/connect/status/uninstall flow. The biggest Addon migrated so
# far (4 sub-areas). Tailscale and Netbird install via the vendors'
# own documented `curl -fsSL <url> | sh` method, preserved as-is.
# - New $WIREGUARD_DIR (lib/config.sh), same pattern as $SYSTEMD_DIR
# etc - nothing here hardcodes /etc/wireguard.
# - Promoted power_schedule.sh's enable_and_start_timers() to a shared
# enable_and_start_units() in lib/menu.sh (works for services now too,
# not just timers) - Remote Access needed the identical pattern for
# x11vnc and wg-quick@, so this is now fixed and reusable everywhere
# instead of being duplicated a second time.
# - IMPORTANT framework-level bug found and fixed in lib/menu.sh's
# run_menu(): the *handler* call has been `|| true`-guarded since
# v2.1.0, but the *status function* call was still bare and completely
# unprotected. A status function's job is read-only display, but if
# one contains so much as a pipeline whose grep matches nothing (which
# pipefail turns into a pipeline failure even though the actual last
# command in it succeeds), that bare call would crash the *entire
# session* - not just fail to show status. Found while writing
# wireguard_status()'s `sudo wg show | grep ... | sed ...` and
# confirming its exact failure mode before assuming it was already
# covered. Fixed once in run_menu() itself, protecting every status
# function across every menu, present and future - same "fix once at
# the framework level" pattern as the v2.1.0 handler fix. Also audited
# every existing status function across all menus for the same
# specific shape (a bare `var=$(...)` assignment from a grep-based
# pipeline, not embedded in an echo and not already guarded) and found
# one real instance in power_schedule_status(), now fixed too.
#
# RELEASE v2.7.0 - Backported Fix: save_config() No Longer Deletes
# Authelia Credentials (or Any Other Untracked Field)
# - This script's own save_config() had the exact bug described under
@@ -288,7 +320,7 @@ set -euo pipefail
### SECTION 1: CONSTANTS & GLOBALS
################################################################################
SCRIPT_VERSION="2.7.0"
SCRIPT_VERSION="2.8.0"
# Resolve the real path to this script file.
# When piped (curl|bash or wget|bash), BASH_SOURCE[0] is a pipe descriptor,