mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 06:15:09 +00:00
Updates that move HEAD now install changed systemd units through a root-owned helper (/usr/local/sbin/ledmatrix-refresh-units, two literal sudo lines), with a backup restored on rollback; a refresh that fails part-way puts the old units back. Devices without the new sudo rule keep updating and are told to re-run the installer once. The one-shot installer now checks out the newest vX.Y.Z release (LEDMATRIX_CHANNEL=beta keeps main) and never moves an existing checkout backwards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
219 lines
7.8 KiB
Bash
Executable File
219 lines
7.8 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# LED Matrix Web Interface Sudo Configuration Script
|
|
# This script configures passwordless sudo access for the web interface user
|
|
|
|
set -e
|
|
|
|
echo "Configuring passwordless sudo access for LED Matrix Web Interface..."
|
|
|
|
# Get the current user (should be the user running the web interface)
|
|
WEB_USER=$(whoami)
|
|
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$PROJECT_DIR/../.." && pwd)"
|
|
|
|
echo "Detected web interface user: $WEB_USER"
|
|
echo "Project directory: $PROJECT_DIR"
|
|
echo "Project root: $PROJECT_ROOT"
|
|
|
|
# Check if running as root
|
|
if [ "$EUID" -eq 0 ]; then
|
|
echo "Error: This script should not be run as root."
|
|
echo "Run it as the user that will be running the web interface."
|
|
exit 1
|
|
fi
|
|
|
|
# Get the full paths to commands and validate each one
|
|
MISSING_CMDS=()
|
|
|
|
# Full path of a command, also looking in the sbin directories. This script runs
|
|
# as the web user, whose PATH usually lacks /usr/sbin and /sbin -- where reboot
|
|
# and poweroff live -- so `command -v` alone silently dropped their rules.
|
|
find_command() {
|
|
local found
|
|
found=$(command -v "$1" 2>/dev/null) && { printf '%s\n' "$found"; return 0; }
|
|
for dir in /usr/sbin /sbin /usr/bin /bin; do
|
|
if [ -x "$dir/$1" ]; then
|
|
printf '%s\n' "$dir/$1"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
SYSTEMCTL_PATH=$(find_command systemctl) || true
|
|
REBOOT_PATH=$(find_command reboot) || true
|
|
POWEROFF_PATH=$(find_command poweroff) || true
|
|
BASH_PATH=$(find_command bash) || true
|
|
JOURNALCTL_PATH=$(find_command journalctl) || true
|
|
SAFE_RM_PATH="$PROJECT_ROOT/scripts/fix_perms/safe_plugin_rm.sh"
|
|
SAFE_PIP_INSTALL_PATH="$PROJECT_ROOT/scripts/fix_perms/safe_pip_install.sh"
|
|
|
|
# Validate required commands (systemctl and bash are essential)
|
|
for CMD_NAME in SYSTEMCTL_PATH BASH_PATH; do
|
|
CMD_VAL="${!CMD_NAME}"
|
|
if [ -z "$CMD_VAL" ]; then
|
|
MISSING_CMDS+=("$CMD_NAME")
|
|
fi
|
|
done
|
|
|
|
if [ ${#MISSING_CMDS[@]} -gt 0 ]; then
|
|
echo "Error: Required commands not found: ${MISSING_CMDS[*]}" >&2
|
|
echo "Cannot generate valid sudoers configuration without these." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Validate helper scripts exist
|
|
if [ ! -f "$SAFE_RM_PATH" ]; then
|
|
echo "Error: Safe plugin removal helper not found: $SAFE_RM_PATH" >&2
|
|
exit 1
|
|
fi
|
|
if [ ! -f "$SAFE_PIP_INSTALL_PATH" ]; then
|
|
echo "Error: Safe pip install helper not found: $SAFE_PIP_INSTALL_PATH" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The rules are shared with first_time_install.sh (Step 10) so the two cannot
|
|
# drift apart; add or remove a grant in lib_sudoers.sh, not here.
|
|
SUDOERS_LIB="$PROJECT_DIR/lib_sudoers.sh"
|
|
if [ ! -f "$SUDOERS_LIB" ]; then
|
|
echo "Error: Sudoers rules library not found: $SUDOERS_LIB" >&2
|
|
exit 1
|
|
fi
|
|
# shellcheck source=scripts/install/lib_sudoers.sh
|
|
. "$SUDOERS_LIB"
|
|
|
|
echo "Command paths:"
|
|
echo " Systemctl: $SYSTEMCTL_PATH"
|
|
echo " Reboot: ${REBOOT_PATH:-(not found, skipping)}"
|
|
echo " Poweroff: ${POWEROFF_PATH:-(not found, skipping)}"
|
|
echo " Bash: $BASH_PATH"
|
|
echo " Journalctl: ${JOURNALCTL_PATH:-(not found, skipping)}"
|
|
echo " Safe plugin rm: $SAFE_RM_PATH"
|
|
echo " Safe pip install: $SAFE_PIP_INSTALL_PATH"
|
|
|
|
# Create a temporary sudoers file. A predictable name in a world-writable
|
|
# directory is a symlink target, and these rules end up in /etc/sudoers.d, so
|
|
# let mktemp pick the name; the trap removes it however the script ends.
|
|
TEMP_SUDOERS=$(mktemp "${TMPDIR:-/tmp}/ledmatrix_web_sudoers.XXXXXX") || {
|
|
echo "Error: could not create a temporary file" >&2
|
|
exit 1
|
|
}
|
|
trap 'rm -f "$TEMP_SUDOERS"' EXIT
|
|
|
|
web_sudoers_rules "$WEB_USER" "$PROJECT_ROOT" "$SYSTEMCTL_PATH" "$BASH_PATH" \
|
|
"$REBOOT_PATH" "$POWEROFF_PATH" "$JOURNALCTL_PATH" > "$TEMP_SUDOERS"
|
|
|
|
# Never offer to install rules we have not parsed. A malformed drop-in in
|
|
# /etc/sudoers.d makes sudo refuse every command for every user.
|
|
# visudo lives in /usr/sbin, which is not on every user's PATH.
|
|
if ! command -v visudo >/dev/null 2>&1 && [ -x /usr/sbin/visudo ]; then
|
|
PATH="$PATH:/usr/sbin"
|
|
fi
|
|
if command -v visudo >/dev/null 2>&1; then
|
|
if ! visudo -c -f "$TEMP_SUDOERS" >/dev/null 2>&1; then
|
|
echo ""
|
|
echo "✗ The generated sudoers rules did not parse:" >&2
|
|
visudo -c -f "$TEMP_SUDOERS" >&2 || true
|
|
echo "Nothing was changed." >&2
|
|
rm -f "$TEMP_SUDOERS"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "⚠ visudo not found; the rules below have not been validated"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Generated sudoers configuration:"
|
|
echo "--------------------------------"
|
|
cat "$TEMP_SUDOERS"
|
|
echo "--------------------------------"
|
|
|
|
echo ""
|
|
echo "This configuration will allow the web interface to:"
|
|
echo "- Start/stop/restart the ledmatrix service"
|
|
echo "- Enable/disable the ledmatrix service"
|
|
echo "- Check service status"
|
|
echo "- View system logs via journalctl"
|
|
echo "- Reboot and shutdown the system"
|
|
echo "- Remove plugin directories (for update/uninstall when root-owned files block deletion)"
|
|
echo "- Install plugin/base requirements.txt as root (so ledmatrix.service can see them)"
|
|
echo "- Install the LEDMatrix systemd units an update changed, and restore them on rollback"
|
|
echo " (/usr/local/sbin/ledmatrix-refresh-units, installed by install_service.sh)"
|
|
echo ""
|
|
|
|
# Ask for confirmation
|
|
read -p "Do you want to apply this configuration? (y/N): " -n 1 -r
|
|
echo
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
echo "Configuration cancelled."
|
|
rm -f "$TEMP_SUDOERS"
|
|
exit 0
|
|
fi
|
|
|
|
# Apply the configuration
|
|
echo "Applying sudoers configuration..."
|
|
# Harden the helper script: root-owned, not writable by web user
|
|
echo "Hardening safe_plugin_rm.sh ownership..."
|
|
if ! sudo chown root:root "$SAFE_RM_PATH"; then
|
|
echo "Warning: Could not set ownership on $SAFE_RM_PATH"
|
|
fi
|
|
if ! sudo chmod 755 "$SAFE_RM_PATH"; then
|
|
echo "Warning: Could not set permissions on $SAFE_RM_PATH"
|
|
fi
|
|
echo "Hardening safe_pip_install.sh ownership..."
|
|
if ! sudo chown root:root "$SAFE_PIP_INSTALL_PATH"; then
|
|
echo "Warning: Could not set ownership on $SAFE_PIP_INSTALL_PATH"
|
|
fi
|
|
if ! sudo chmod 755 "$SAFE_PIP_INSTALL_PATH"; then
|
|
echo "Warning: Could not set permissions on $SAFE_PIP_INSTALL_PATH"
|
|
fi
|
|
|
|
if sudo cp "$TEMP_SUDOERS" /etc/sudoers.d/ledmatrix_web; then
|
|
# sudo reads /etc/sudoers.d files that are root-owned and not writable by
|
|
# group or other; 440 is the mode visudo and first_time_install.sh use.
|
|
if ! sudo chmod 440 /etc/sudoers.d/ledmatrix_web; then
|
|
echo "Warning: could not set mode 440 on /etc/sudoers.d/ledmatrix_web"
|
|
fi
|
|
echo "Configuration applied successfully!"
|
|
echo ""
|
|
echo "Testing sudo access..."
|
|
|
|
# Ask sudo whether two of the new rules let this user in without a
|
|
# password. `sudo -l CMD` answers from the rules without running CMD, so
|
|
# this does not depend on whether ledmatrix.service is running, and it
|
|
# tests commands the rules actually grant.
|
|
if sudo -n -l "$SYSTEMCTL_PATH" status ledmatrix.service > /dev/null 2>&1; then
|
|
echo "✓ systemctl status ledmatrix.service - OK"
|
|
else
|
|
echo "✗ systemctl status ledmatrix.service - not allowed without a password"
|
|
fi
|
|
|
|
if sudo -n -l "$BASH_PATH" "$SAFE_RM_PATH" "$PROJECT_ROOT/plugin-repos/example" > /dev/null 2>&1; then
|
|
echo "✓ safe_plugin_rm.sh helper - OK"
|
|
else
|
|
echo "✗ safe_plugin_rm.sh helper - not allowed without a password"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Configuration complete! The web interface should now be able to:"
|
|
echo "- Execute system commands without password prompts"
|
|
echo "- Start and stop the LED matrix display"
|
|
echo "- Restart the system if needed"
|
|
echo ""
|
|
echo "You may need to restart the web interface service for changes to take effect:"
|
|
echo " sudo systemctl restart ledmatrix-web.service"
|
|
|
|
else
|
|
echo "Error: Failed to apply sudoers configuration."
|
|
echo "You may need to run this script with sudo privileges."
|
|
rm -f "$TEMP_SUDOERS"
|
|
exit 1
|
|
fi
|
|
|
|
# Clean up
|
|
rm -f "$TEMP_SUDOERS"
|
|
|
|
echo ""
|
|
echo "Configuration script completed successfully!"
|