Add Web UI addon: browser-based config editor (v2.16.0)
New Addons -> Web UI: a small Node/Express app (webui/) installed as a systemd service running as $KIOSK_USER, giving a browser-based editor for Sites & Page Timing, Display & Interaction, and Password Protection & Lockout - the three Core Settings menus that are pure config.json read/write with no privileged system mutation involved. Runs with no sudo at all, since config.json is already owned by $KIOSK_USER. webui/lib/config.js re-implements lib/config.sh's exact schema and merge-on-save contract in JS (kiosk-app/main.js already reads the same config.json directly in JS, so this isn't a new pattern), so it can never silently clobber fields it doesn't track - the same bug previously fixed in lib/config.sh's own history. No login of its own by design: Authelia runs elsewhere, and the expectation is a reverse proxy (e.g. Caddy) with Authelia forward-auth in front of it, the same way other self-hosted apps get protected - Authelia integration is explicitly out of scope for this repo. Deliberately narrow scope for this first pass: WiFi, Timezone, Power/Display/Quiet Hours, Complete Uninstall, every other addon, and everything in Advanced remain terminal-only, since a network-facing process shouldn't be handed sudo-level system mutation without a lot more thought than this pass gives it. Wired into Complete Uninstall (webui_do_uninstall) and Clone Settings (addon-presence detection) the same way every other addon is. This is the single-kiosk piece of the web-based GUI this repo's "Modular Management" notes have mentioned for a while - a central multi-kiosk fleet dashboard is an intentional follow-up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VfsFSoRqfbRG7XAg5RoE7e
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
# menus/addon_webui.sh - "Web UI" addon: installs webui/ (a small Node/
|
||||
# Express app) as a systemd service running as $KIOSK_USER, giving a
|
||||
# browser-based editor for Sites & Page Timing, Display & Interaction,
|
||||
# and Password Protection & Lockout - the three Core Settings menus that
|
||||
# are pure config.json read/write with no privileged system mutation
|
||||
# involved. The remaining Core Settings/Addons/Advanced menus (WiFi,
|
||||
# Timezone, Power/Display/Quiet Hours, Complete Uninstall, every other
|
||||
# addon, everything in Advanced) stay terminal-only for now - a
|
||||
# network-facing process shouldn't be handed sudo-level system mutation
|
||||
# without a lot more thought than this first pass gives it.
|
||||
#
|
||||
# No login of its own, by design: this addon assumes it'll be put behind
|
||||
# a reverse proxy (e.g. Caddy) with Authelia forward-auth in front, the
|
||||
# same way other self-hosted apps get protected - Authelia integration
|
||||
# is explicitly out of scope for this repo (Authelia runs elsewhere).
|
||||
# Direct LAN access with no proxy in front has no authentication at all -
|
||||
# treat it the same as SSH access to this kiosk.
|
||||
#
|
||||
# webui/'s own app-level logic (config.json schema/merge, API validation)
|
||||
# lives and is tested entirely under webui/ - this file only wires it up
|
||||
# as a system service and never touches config.json itself.
|
||||
#
|
||||
# Depends on: lib/menu.sh, lib/config.sh being sourced first.
|
||||
################################################################################
|
||||
|
||||
webui_is_installed() {
|
||||
[[ -f "$SYSTEMD_DIR/kiosk-webui.service" ]]
|
||||
}
|
||||
|
||||
webui_is_active() {
|
||||
systemctl is-active --quiet kiosk-webui
|
||||
}
|
||||
|
||||
webui_port() {
|
||||
if sudo test -f "$WEBUI_ENV_DIR/webui.env" 2>/dev/null; then
|
||||
sudo grep -oP '^PORT=\K.*' "$WEBUI_ENV_DIR/webui.env" 2>/dev/null || echo "8090"
|
||||
else
|
||||
echo "8090"
|
||||
fi
|
||||
}
|
||||
|
||||
addon_webui_status() {
|
||||
if webui_is_installed && webui_is_active; then
|
||||
echo "Web UI: running at http://$(get_ip_address):$(webui_port)"
|
||||
elif webui_is_installed; then
|
||||
echo "Web UI: installed but not running"
|
||||
else
|
||||
echo "Web UI: not installed"
|
||||
fi
|
||||
}
|
||||
|
||||
addon_webui_menu_builder() {
|
||||
if webui_is_installed; then
|
||||
MENU_LABELS=("Change port" "Restart service" "Uninstall")
|
||||
MENU_HANDLERS=(action_webui_reconfigure action_webui_restart action_webui_uninstall)
|
||||
else
|
||||
MENU_LABELS=("Install Web UI")
|
||||
MENU_HANDLERS=(action_webui_install)
|
||||
fi
|
||||
}
|
||||
|
||||
addon_webui_menu() {
|
||||
run_menu "WEB UI" addon_webui_menu_builder addon_webui_status
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Shared install helpers
|
||||
################################################################################
|
||||
|
||||
# Copies webui/ from this checkout onto the kiosk and installs its npm
|
||||
# dependencies as $KIOSK_USER - same shape as provision_install_app in
|
||||
# lib/provision.sh, but addon-scoped (opt-in) rather than core. Every
|
||||
# critical step is individually guarded (matches menus/addon_cups.sh's
|
||||
# action_install_cups) rather than relying on set -e to stop a bare
|
||||
# sequence - menu actions are always invoked via run_menu's
|
||||
# `"${MENU_HANDLERS[...]}" "$choice" || true` dispatch, which already
|
||||
# exempts everything they do from set -e for the whole call, so a bare
|
||||
# unguarded sequence here would silently continue past a real failure
|
||||
# (e.g. attempting npm install into a directory `cp` never populated).
|
||||
webui_install_app_files() {
|
||||
if ! sudo mkdir -p "$WEBUI_DIR"; then
|
||||
log_error "Could not create $WEBUI_DIR"
|
||||
return 1
|
||||
fi
|
||||
if ! sudo cp -r "$SCRIPT_DIR/webui/." "$WEBUI_DIR/"; then
|
||||
log_error "Could not copy Web UI app files"
|
||||
return 1
|
||||
fi
|
||||
sudo chown -R "$KIOSK_USER:$KIOSK_USER" "$WEBUI_DIR"
|
||||
if ! sudo -u "$KIOSK_USER" bash -lc "cd '$WEBUI_DIR' && npm install --omit=dev --unsafe-perm"; then
|
||||
log_error "npm install failed"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
webui_write_env_file() {
|
||||
local port="$1"
|
||||
sudo mkdir -p "$WEBUI_ENV_DIR"
|
||||
sudo tee "$WEBUI_ENV_DIR/webui.env" > /dev/null <<EOF
|
||||
PORT=${port}
|
||||
BIND_ADDR=0.0.0.0
|
||||
CONFIG_PATH=${CONFIG_PATH}
|
||||
EOF
|
||||
}
|
||||
|
||||
webui_write_unit_file() {
|
||||
sudo tee "$SYSTEMD_DIR/kiosk-webui.service" > /dev/null <<EOF
|
||||
[Unit]
|
||||
Description=Kiosk Web UI
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${KIOSK_USER}
|
||||
WorkingDirectory=${WEBUI_DIR}
|
||||
EnvironmentFile=${WEBUI_ENV_DIR}/webui.env
|
||||
ExecStart=/usr/bin/node ${WEBUI_DIR}/server.js
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Actions
|
||||
################################################################################
|
||||
|
||||
action_webui_install() {
|
||||
echo
|
||||
echo "This installs a small web app for editing Sites & Page Timing,"
|
||||
echo "Display & Interaction, and Password Protection & Lockout from a"
|
||||
echo "browser. It has no login of its own - put it behind your own"
|
||||
echo "reverse proxy (e.g. Caddy + Authelia) if it needs to be reachable"
|
||||
echo "beyond a trusted LAN."
|
||||
echo
|
||||
ask_yes_no "Install the Web UI?" "n" || { echo "Cancelled"; return; }
|
||||
|
||||
local port
|
||||
port=$(ask_integer "Port to listen on" "8090" 1024 65535)
|
||||
|
||||
echo "Installing app files and npm dependencies..."
|
||||
if ! webui_install_app_files; then
|
||||
log_error "Web UI install failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
webui_write_env_file "$port"
|
||||
webui_write_unit_file
|
||||
|
||||
if enable_and_start_units kiosk-webui; then
|
||||
sudo ufw allow "${port}/tcp" comment 'Kiosk Web UI' 2>/dev/null || true
|
||||
log_success "Web UI installed: http://$(get_ip_address):${port}"
|
||||
else
|
||||
log_error "Web UI installed but failed to start - check: sudo journalctl -u kiosk-webui -n 50"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
action_webui_reconfigure() {
|
||||
echo
|
||||
local current_port
|
||||
current_port=$(webui_port)
|
||||
local port
|
||||
port=$(ask_integer "Port to listen on" "$current_port" 1024 65535)
|
||||
|
||||
if [[ "$port" == "$current_port" ]]; then
|
||||
echo "No change"
|
||||
return
|
||||
fi
|
||||
|
||||
webui_write_env_file "$port"
|
||||
sudo systemctl restart kiosk-webui
|
||||
sudo ufw allow "${port}/tcp" comment 'Kiosk Web UI' 2>/dev/null || true
|
||||
log_success "Web UI now listening on port ${port}"
|
||||
}
|
||||
|
||||
action_webui_restart() {
|
||||
sudo systemctl restart kiosk-webui
|
||||
sleep 1
|
||||
if webui_is_active; then
|
||||
log_success "Web UI restarted"
|
||||
else
|
||||
log_error "Web UI failed to restart - check: sudo journalctl -u kiosk-webui -n 50"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
action_webui_uninstall() {
|
||||
echo
|
||||
ask_yes_no "Uninstall the Web UI?" "n" || { echo "Cancelled"; return; }
|
||||
webui_do_uninstall
|
||||
}
|
||||
|
||||
# The actual removal, no prompt - shared with Complete Uninstall so that
|
||||
# operation doesn't need to re-implement Web UI teardown a second time.
|
||||
# No `ufw delete` - matches every other addon's uninstall in this
|
||||
# codebase, which never removes its own firewall rule either.
|
||||
webui_do_uninstall() {
|
||||
sudo systemctl stop kiosk-webui 2>/dev/null || true
|
||||
sudo systemctl disable kiosk-webui 2>/dev/null || true
|
||||
sudo rm -f "$SYSTEMD_DIR/kiosk-webui.service"
|
||||
sudo systemctl daemon-reload 2>/dev/null || true
|
||||
sudo rm -rf "$WEBUI_DIR" "$WEBUI_ENV_DIR"
|
||||
log_success "Web UI removed"
|
||||
}
|
||||
Reference in New Issue
Block a user