#!/bin/bash export LC_ALL=C export LANG=C # Initial Variables LOGFILE="/var/log/photobooth_install.log" SILENT=false UPDATE=false SKIP_AUTO_UPDATE=false SKIP_WEBSERVER=false SKIP_PHP=false SKIP_NODE=false SKIP_PYTHON=false PHOTOBOOTH_FOUND=false INSTALLFOLDERPATH="" PHOTOBOOTH_SUBFOLDER="" # Webbrowser WEBBROWSER="unknown" # GitHub GIT_INSTALLED=false BRANCH="dev" REMOTE_BRANCH_API="https://api.github.com/repos/PhotoboothProject/photobooth/branches/${BRANCH}" REMOTE_BRANCH_SHA="" # OS environment FORCE_RASPBERRY_PI=false RUNNING_ON_PI=false HAS_SYSTEMD=$([[ -x "$(command -v systemctl)" && "$(ps -p 1 -o comm=)" == "systemd" ]] && echo true || echo false) LOCAL_ARCH=$(uname -m) OS_CODENAME="unknown" # PHP PHP_VERSION="8.4" DEBIAN=( "bullseye" "bookworm" "trixie" ) # Node.js NODEJS_MAJOR="20" NODEJS_MINOR="15" # Packages COMMON_PACKAGES=( "gphoto2" "libimage-exiftool-perl" "nodejs" "python3" "rsync" "udisks2" ) APACHE_PACKAGES=( "apache2" "libapache2-mod-php${PHP_VERSION}" ) PHP_PACKAGES=( "php${PHP_VERSION}" "php${PHP_VERSION}-cli" "php${PHP_VERSION}-gd" "php${PHP_VERSION}-xml" "php${PHP_VERSION}-zip" "php${PHP_VERSION}-mbstring" ) EXTRA_PACKAGES=( "git" "jq" "curl" "gcc" "g++" "make" "apt-transport-https" "lsb-release" "ca-certificates" "software-properties-common" ) # go2rtc DEFAULT_GO2RTC_VERSION="1.9.13" GO2RTC_VERSIONS=("1.9.13" "1.9.12" "1.9.11" "1.9.10" "1.9.9" "1.9.8" "1.9.7" "1.9.6" "1.9.4" "1.9.2") GO2RTC_UPDATE_ONLY=false GO2RTC_EXTRA_PACKAGES=( "ffmpeg" "fswebcam" ) # gphoto2 webcam GPHOTO2_WEBCAM_EXTRA_PACKAGES=( "v4l2loopback-dkms" "v4l-utils" "python3" "python3-gphoto2" "python3-psutil" "python3-zmq" ) # rembg REMBG_PACKAGES=( "python3" "python3-pip" "python3-venv" "php${PHP_VERSION}-curl" ) REMBG_PIP_PACKAGES=( "rembg[cpu,cli]" "pillow" "filetype" "watchdog" "aiohttp" ) # ================================================== # Logging / helper functions # ================================================== function log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" >>"$LOGFILE" } function confirm() { local title=$1 local message=$2 local height=${3:-10} local width=${4:-60} echo "$(date '+%Y-%m-%d %H:%M:%S') - $1: $2" >>"$LOGFILE" if [ "$SILENT" = true ]; then echo "$title: $message" sleep 2 else whiptail --title "$title" --msgbox "$message" "$height" "$width" fi } function info() { local title=$1 local message=$2 local height=${3:-10} local width=${4:-60} echo "$(date '+%Y-%m-%d %H:%M:%S') - $1: $2" >>"$LOGFILE" if [ "$SILENT" = true ]; then echo "$title: $message" else whiptail --title "$title" --infobox "$message" "$height" "$width" < /dev/tty > /dev/tty 2>&1 fi sleep 1 } function warn() { local title="Warning" local message=$1 local height=${2:-10} local width=${3:-60} info "$title" "$message" "$height" "$width" } function error() { local title="Error" local message=$1 local height=${2:-10} local width=${3:-60} info "$title" "$message" "$height" "$width" } function print_logo() { local logo=" %@@@@. @@ @@* @@@@@@@@@@@@@@@@@@@@@@ @@%%%%%%%%%%%%%%%%%%%%%@ @@ @@@@@@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@ @@@@@@ @@ @@%%%%%%%%%%%%%%%%%%%%%@ P H O T O B O O T H @@%%%%%%%%%%%%%%%%%%%%%@ " if [ "$SILENT" = true ]; then echo "$logo" else local height=22 local width=50 whiptail --title "Welcome!" --infobox "$logo" "$height" "$width" fi sleep 2 } function show_help() { echo "Photobooth Setup Wizard" echo "" echo "Adjust your setup for Photobooth. Available options:" echo "" echo " --branch= Specify the Git branch to use for installation or updates." echo " --php= Set the PHP version for the setup (e.g., --php=8.3)." echo " --silent Run the Photobooth Setup Wizard in silent mode" echo " for automated installation or updates." echo " --username=\"\" Required if --silent is used." echo " Provide a username for installation or updates." echo " --raspberry Skip automatic Raspberry Pi detection and enable Raspberry Pi specific configuration." echo " --wayland Skip automatic Wayland detection and enable Wayland configuration." echo " --update Requires --silent to update Photobooth if installed already." echo " --skip-webserver Skip web server setup" echo " (if already configured or e.g. Nginx is used as Webserver)." echo " --skip-php Skip PHP installation" echo " (if already configured for used Webserver)." echo " --skip-node Skip Node.js and npm installation" echo " (if already installed as required)." echo " --skip-python Skip python3 installation" echo " (if already installed as required)." echo " --skip-auto-update Skip automatic updates for Photobooth Setup Wizard." echo "" echo "Examples:" echo " $0 --silent --branch=dev --php=8.3 --username=\"photobooth\" --update" echo " $0 --branch=stable4 --skip-webserver" echo "" echo "For more information, refer to the documentation at" echo "https://photoboothproject.github.io" exit 0 } function photobooth_installed() { local search_paths=("/var/www/html/photobooth" "/var/www/html") for full_path in "${search_paths[@]}"; do if [[ -d "$full_path" && -f "$full_path/lib/configsetup.inc.php" ]]; then PHOTOBOOTH_FOUND=true INSTALLFOLDERPATH="$full_path" PHOTOBOOTH_SUBFOLDER="${INSTALLFOLDERPATH#/var/www/html}" return 0 fi done return 1 } function check_installfolderpath() { if [[ -z "$INSTALLFOLDERPATH" ]]; then if ! photobooth_installed; then error "INSTALLFOLDERPATH is not defined or empty!" return 1 fi fi return 0 } function is_wayland_env() { if [ "${WAYLAND_ENV:-}" = "true" ]; then return 0 fi local conf="/etc/lightdm/lightdm.conf" if [ -f "$conf" ]; then session=$(grep -E "^user-session=" "$conf" | cut -d= -f2) case "$session" in rpd-labwc|LXDE-pi-labwc|rpd-wayfire|LXDE-pi-wayfire) return 0 ;; esac fi # Fallback: check if wayfire or labwc is running if pgrep wayfire >/dev/null || pgrep labwc >/dev/null; then return 0 else return 1 fi } function install_system_icon() { local icon_dir="/usr/share/icons/hicolor/scalable/apps" local icon_file="$icon_dir/photobooth.svg" local icon_url="https://github.com/PhotoboothProject/photobooth/raw/refs/heads/dev/resources/img/favicon.svg" local local_file="" # Return if icon already exists if [[ -f "$icon_file" ]]; then info "System Icon" "Photobooth icon exists already." return 0 fi mkdir -p "$icon_dir" # Only set local_file if INSTALLFOLDERPATH is valid if check_installfolderpath; then local_file="$INSTALLFOLDERPATH/resources/img/favicon.svg" fi # Prefer local file over download if [[ -n "$local_file" && -f "$local_file" ]]; then cp "$local_file" "$icon_file" info "System Icon" "Copied Photobooth icon." elif command -v wget >/dev/null 2>&1; then if wget -qO "$icon_file" "$icon_url"; then info "System Icon" "Downloaded Photobooth icon." else error "Failed to download icon!" return 1 fi else error "wget not available and no local icon found!" return 1 fi chmod 644 "$icon_file" if command -v update-icon-caches >/dev/null 2>&1; then update-icon-caches /usr/share/icons/hicolor > /dev/null 2>&1 fi info "System Icon" "Photobooth system icon installed successfully" return 0 } function install_package() { local package=$1 if dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q "ok installed"; then info "Package installation" "${package} is already installed." return 0 else info "[Package]" "Installing missing package: ${package}" # Handle PHP versioned packages with fallback if [[ "$package" =~ ^php[0-9]+\.[0-9]+- ]] || [[ "$package" =~ ^libapache2-mod-php[0-9]+\.[0-9]+$ ]]; then local pkg_generic pkg_generic=$(echo "$package" | sed -E "s/[0-9]+\.[0-9]+-/-/; s/[0-9]+\.[0-9]+$//") if apt-get -qq install -y "$package" >/dev/null 2>&1; then info "Package installation" "Successfully installed ${package}." return 0 else warn "Package ${package} not available, falling back to ${pkg_generic}..." if apt-get -qq install -y "$pkg_generic" >/dev/null 2>&1; then info "Package installation" "Successfully installed ${pkg_generic}." return 0 else error "Failed to install ${package} and fallback ${pkg_generic}." return 1 fi fi else # Regular package install if apt-get -qq install -y "$package" >/dev/null 2>&1; then info "Package installation" "Successfully installed ${package}." return 0 else # Special case: ignore failure on software-properties-common, unavailable on Debian Trixie if [[ "$package" == "software-properties-common" ]]; then warn "Ignoring failed install of ${package}." return 0 fi warn "Failed to install ${package}." return 1 fi fi fi } function install_packages() { local packages=("$@") for package in "${packages[@]}"; do if ! install_package "$package"; then error "Aborting package installation due to failure: ${package}." return 1 fi done return 0 } function remove_package() { local package=$1 if dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q "ok installed"; then info "Package uninstall" "Removing package: ${package}" if apt-get -qq remove -y "$package" >/dev/null 2>&1; then info "Package uninstall" "Successfully removed ${package}." return 0 else error "Failed to remove ${package}." return 1 fi else info "Package uninstall" "${package} is not installed." return 0 fi } function remove_packages() { local packages=("$@") for package in "${packages[@]}"; do if ! remove_package "$package"; then error "Aborting package removal due to failure: ${package}." return 1 fi done return 0 } function test_command() { local cmd="$1" eval "$cmd" &>/dev/null local status=$? if [[ -f "test.mjpeg" ]]; then info "go2rtc installation" "Deleting existing test.mjpeg file." rm test.mjpeg fi return $status } function add_source_list() { local source_entry="$1" local source_file="$2" if grep -Fxq "$source_entry" "$source_file" 2>/dev/null; then info "Source list" "Source list entry already exists: $source_entry" else echo "$source_entry" >>"$source_file" info "Source list" "Added source list entry: $source_entry" fi } function ensure_add_apt_repository() { if ! command -v add-apt-repository >/dev/null 2>&1; then info "Setup" "add-apt-repository not found. Installing software-properties-common..." if ! apt-get update -y >/dev/null 2>&1; then error "Failed to update package lists (needed for software-properties-common)." return 1 fi if ! apt-get install -y --no-install-recommends software-properties-common >/dev/null 2>&1; then error "Failed to install software-properties-common." return 1 fi info "Setup" "Installed software-properties-common successfully." fi return 0 } function add_apt_repository_once() { local repo="$1" local clean_repo="${repo#ppa:}" # Sanity check if [[ -z "$repo" ]]; then error "No repository provided to add_apt_repository_once" return 1 fi # Check if repository already exists if grep -q "$clean_repo" /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null; then info "Add apt repository" "Repository '$repo' is already added." return 0 fi if grep -q "^deb .*$repo" /etc/apt/sources.list /etc/apt/sources.list.d/*.list 2>/dev/null; then info "Add apt repository" "Repository '$repo' is already added." return 0 fi # Ensure add-apt-repository is available if ! ensure_add_apt_repository; then return 1 fi # Add repository info "Add apt repository" "Adding repository: $repo" if add-apt-repository -y "$repo" >/dev/null 2>&1; then info "Add apt repository" "Successfully added repository: $repo" return 0 else error "Failed to add repository: $repo" return 1 fi } # ================================================== # Environment detection # ================================================== # Photobooth Setup Wizard update function self_update() { local all_args=("$@") local curr_date curr_date="$(date +%Y%m%d%H%M%S)" local script_name="install-photobooth.sh" local script_remote_url="https://raw.githubusercontent.com/PhotoboothProject/photobooth/refs/heads/dev/$script_name" local script_temp_file="/tmp/$script_name" local script_backup_file="/tmp/${script_name}.bak_${curr_date}" local script_abs_path script_abs_path="$(realpath "$0")" info "Photobooth Setup Wizard" "Checking for Photobooth Setup Wizard updates..." if ! wget -q -O "$script_temp_file" "$script_remote_url"; then confirm "Error" "Unable to download the latest Photobooth Setup Wizard." return 1 fi if ! cmp -s "$script_temp_file" "$script_abs_path"; then confirm "Photobooth Setup Wizard" "Updated Photobooth Setup Wizard found!" if ! whiptail --title "Photobooth Setup Wizard" \ --yesno "Update Photobooth Setup Wizard to latest version?" \ 12 60; then info "Photobooth Setup Wizard" "Skipping Photobooth Setup Wizard update." sleep 2 return 0 fi info "Photobooth Setup Wizard" "Updating the Photobooth Setup Wizard..." if ! cp "$script_abs_path" "$script_backup_file"; then confirm "Photobooth Setup Wizard" "Failed to create a backup of $script_abs_path. Update aborted." return 1 fi info "Photobooth Setup Wizard" "Backup created: $script_backup_file" if mv -f "$script_temp_file" "$script_abs_path"; then if ! chmod +x "$script_abs_path"; then confirm "Photobooth Setup Wizard" "Failed to add execution permission to $script_abs_path. Update aborted." return 1 fi confirm "Photobooth Setup Wizard" "Photobooth Setup Wizard updated successfully." info "Photobooth Setup Wizard" "Restarting Photobooth Setup Wizard..." sleep 2 exec "$script_abs_path" "${all_args[@]}" else confirm "Photobooth Setup Wizard" "Failed to update Photobooth Setup Wizard!" return 1 fi else info "Photobooth Setup Wizard" "No updates available." rm -f "$script_temp_file" fi } function detect_os_codename() { local os="" if command -v lsb_release >/dev/null 2>&1; then os=$(lsb_release -sc 2>/dev/null) elif [[ -r /etc/os-release ]]; then # Try VERSION_CODENAME first os=$(grep -E '^VERSION_CODENAME=' /etc/os-release | cut -d= -f2) if [[ -z "$os" ]]; then # Extract from VERSION string as fallback os=$(grep -E '^VERSION=' /etc/os-release | sed -E 's/.*\((.*)\).*/\1/') fi fi if [[ -n "$os" ]]; then echo "$os" fi } # Check if running on Raspberry Pi function detect_pi() { if [ "$FORCE_RASPBERRY_PI" = false ]; then local pi_model if [ ! -f /proc/device-tree/model ]; then no_raspberry 2 else pi_model=$(tr -d '\0' &1 1>&2 2>&3); then if whiptail --title "Photobooth Setup Wizard" \ --yesno "Are you sure you want to exit?" \ 8 50; then exit 0 else continue fi fi fi # Validate username if [ -n "$USERNAME" ]; then if id "$USERNAME" &>/dev/null; then break else if [ "$SILENT" = true ]; then confirm "Invalid Username" "Error: The username '$USERNAME' does not exist. Continuing without a defined user." USERNAME="" break else confirm "Invalid Username" "The username '$USERNAME' does not exist. Please try again." USERNAME="" fi fi else if [ "$SILENT" = true ]; then confirm "Setup Wizard" "Username not defined. Ignoring..." break else confirm "Setup Wizard" "Username cannot be empty. Please try again." fi fi done } function check_webserver() { local servers=("apache2" "nginx" "lighttpd") local installed_but_not_running=false for server in "${servers[@]}"; do # Check if package is installed if dpkg-query -W -f='${Status}' "$server" 2>/dev/null | grep -q "ok installed"; then # Check if systemctl exists and service is active if [[ "$HAS_SYSTEMD" == true ]] && systemctl is-active --quiet "$server"; then info "Webserver Check" "$server is installed and running." case $server in apache2) return 1 ;; nginx) return 2 ;; lighttpd) return 3 ;; esac else info "Webserver Check" "$server is installed but not running (or systemctl unavailable)." installed_but_not_running=true fi fi done if [[ "$installed_but_not_running" == true ]]; then info "Webserver Check" "One or more webservers are installed but not running." return 4 fi info "Webserver Check" "No webserver is installed and running." return 0 } function prepare_php_environment() { if detected_os=$(detect_os_codename) && [[ $detected_os ]]; then OS_CODENAME="$detected_os" info "OS Detection" "Detected distribution codename: $OS_CODENAME" else confirm "Warning" "Could not detect OS codename." fi info "PHP preparation" "Detected OS: $OS_CODENAME" # Add PHP repository based on OS if [[ "${DEBIAN[*]}" =~ $OS_CODENAME ]]; then info "PHP preparation" "Adding Sury PHP repository for Debian." wget -qO /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg >/dev/null 2>&1 echo "deb https://packages.sury.org/php/ $OS_CODENAME main" \ | tee /etc/apt/sources.list.d/php.list >/dev/null 2>&1 elif [[ "$OS_CODENAME" == "mantic" ]]; then info "PHP preparation" "No source lists available for 'mantic'." else if [[ "$OS_CODENAME" == "jammy" ]]; then info "PHP preparation" "Checking for 'jammy-updates' in sources list." add_source_list "deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted" /etc/apt/sources.list fi if ! ensure_add_apt_repository; then error "Failed to install software-properties-common (needed for add-apt-repository)." return 2 fi info "PHP preparation" "Adding Ondrej PHP PPA." if ! add_apt_repository_once "ppa:ondrej/php"; then error "Failed to add Ondrej PHP PPA." return 1 fi fi if apt-get -qq update 2>&1 | grep -q "does not have a Release file"; then info "PPA ondrej/php is not valid for $OS_CODENAME, removing..." add-apt-repository --remove ppa:ondrej/php rm -f /etc/apt/sources.list.d/ondrej-ubuntu-php*.list fi if ! apt-get -qq update >/dev/null 2>&1; then error "Update after adding repositories failed." return 1 fi info "PHP preparation" "PHP preparation completed successfully." return 0 } function set_php_version_cli() { local version="$1" if [[ -z "$version" ]]; then error "No PHP version provided to set_php_version_cli" >&2 return 1 fi local php_bin="/usr/bin/php${version}" if [[ ! -x "$php_bin" ]]; then error "$php_bin not found (is php${version} installed?)" >&2 return 1 fi local priority priority=$(echo "$version" | tr -d '.') update-alternatives --install /usr/bin/php php "$php_bin" "$priority" >/dev/null 2>&1 || warn "Failed to install PHP via update-alternatives." update-alternatives --set php "$php_bin" >/dev/null 2>&1 || error "Failed to set default PHP CLI version." info "PHP CLI" "CLI php now points to: $(php -v | head -n1)" return 0 } function set_php_version_apache() { local version="$1" if [[ -z "$version" ]]; then error "No PHP version provided." >&2 return 1 fi a2dismod -f php* >/dev/null 2>&1 || true if a2enmod "php${version}" >/dev/null 2>&1; then confirm "Apache Webserver" "Apache is now configured to use PHP ${version} " if [[ "$HAS_SYSTEMD" == true ]]; then if systemctl is-active --quiet apache2; then # Restart if already running if ! systemctl restart apache2 &>/dev/null; then confirm "Apache Webserver" "Failed to restart Apache Webserver. Please reboot to apply." fi fi fi return 0 else error "Could not enable php${version} for Apache" >&2 return 1 fi } # ================================================== # Change defaults for install / update # ================================================== function set_branch() { local new_branch if new_branch=$(whiptail --title "Set Git Branch" \ --inputbox "Enter the branch you want to use (e.g., dev):" 10 50 "$BRANCH" 3>&1 1>&2 2>&3); then BRANCH="$new_branch" info "Git Branch" "Branch set to $BRANCH" else info "Git Branch" "No changes made to branch." fi } function set_php_version() { local new_php_version if new_php_version=$(whiptail --title "Set PHP Version" \ --inputbox "Enter the PHP version you want to use (e.g., 8.3):" 10 50 "$PHP_VERSION" 3>&1 1>&2 2>&3); then PHP_VERSION="$new_php_version" info "PHP Version" "PHP version set to $PHP_VERSION" else info "PHP Version" "No changes made to PHP version." fi } function toggle_skip_webserver() { if whiptail --title "Web Server Setup" \ --yesno "Current value: Skip web server setup = $SKIP_WEBSERVER\n\nToggle this option?" 10 50; then SKIP_WEBSERVER=$([ "$SKIP_WEBSERVER" = true ] && echo false || echo true) info "Web Server Setup" "Skip web server setup toggled to $SKIP_WEBSERVER" else info "Web Server Setup" "No changes made." fi } function toggle_skip_php() { if whiptail --title "PHP Setup" \ --yesno "Current value: Skip PHP setup = $SKIP_PHP\n\nToggle this option?" 10 50; then SKIP_PHP=$([ "$SKIP_PHP" = true ] && echo false || echo true) info "PHP Setup" "Skip PHP setup toggled to $SKIP_PHP" else info "PHP Setup" "No changes made." fi } function toggle_skip_node() { if whiptail --title "Node.js Setup" \ --yesno "Current value: Skip Node.js and npm setup = $SKIP_NODE\n\nToggle this option?" 10 50; then SKIP_NODE=$([ "$SKIP_NODE" = true ] && echo false || echo true) info "Node.js Setup" "Skip Node.js and npm setup toggled to $SKIP_NODE" else info "Node.js Setup" "No changes made." fi } function toggle_skip_python() { if whiptail --title "Python3 Setup" \ --yesno "Current value: Skip Python3 setup = $SKIP_PYTHON\n\nToggle this option?" 10 50; then SKIP_PYTHON=$([ "$SKIP_PYTHON" = true ] && echo false || echo true) info "Python3 Setup" "Skip Python3 setup toggled to $SKIP_PYTHON" else info "Python3 Setup" "No changes made." fi } # ================================================== # # ================================================== function detect_browser() { local browser="" if update-alternatives --query x-www-browser &>/dev/null; then browser=$(update-alternatives --display x-www-browser \ | grep 'currently' | awk -F/ '{print $4}') fi case "$browser" in chromium-browser|chromium|google-chrome|google-chrome-stable|google-chrome-beta) WEBBROWSER="$browser" CHROME_FLAGS=true ;; firefox|firefox-esr) WEBBROWSER="$browser" CHROME_FLAGS=false ;; *) for b in chromium chromium-browser google-chrome google-chrome-stable google-chrome-beta firefox firefox-esr; do if command -v "$b" >/dev/null; then WEBBROWSER="$b" [[ "$b" =~ chrome|chromium ]] && CHROME_FLAGS=true || CHROME_FLAGS=false return fi done WEBBROWSER="unknown" CHROME_FLAGS=false ;; esac } # Returns the kiosk flag to be used function setup_kiosk_browser() { local kiosk_flag="${1:---kiosk http://localhost}" echo "$kiosk_flag" } # Returns the full Chrome command flags including kiosk # Usage: setup_chrome_flags [kiosk_flag] [chrome_default_flags] function setup_chrome_flags() { local local_env="${1:-default}" local kiosk_flag="${2:-$(setup_kiosk_browser "--kiosk http://localhost")}" local chrome_default_flags="${3:---noerrdialogs --disable-infobars --disable-features=Translate --no-first-run --check-for-update-interval=31536000 --touch-events=enabled --password-store=basic}" local flags="" case "$local_env" in pi-wayland) flags="$chrome_default_flags --ozone-platform=wayland --start-maximized" ;; pi) flags="$chrome_default_flags --use-gl=egl" ;; *) flags="$chrome_default_flags" ;; esac echo "$flags $kiosk_flag" } function browser_shortcut() { local flags="" local shortcut="$1" local local_env="default" if [[ -z "$shortcut" ]]; then confirm "Browser Shortcut" "Error: Shortcut path is required! Cannot create shortcut!" return 1 fi if ! photobooth_installed; then confirm "Browser Shortcut" "Error: Photobooth not installed!" return 1 fi # Ensure parent directory exists mkdir -p "$(dirname "$shortcut")" || { confirm "Browser Shortcut" "Error: Could not create $(dirname "$shortcut")" return 1 } detect_browser if [ "$WEBBROWSER" = "unknown" ]; then confirm "Browser Shortcut" "No browser detected. Browser shortcut cannot proceed." return 2 fi if [ "$CHROME_FLAGS" = true ]; then if [ "$RUNNING_ON_PI" = true ] && is_wayland_env; then local_env="pi-wayland" elif [ "$RUNNING_ON_PI" = true ]; then local_env="pi" fi flags="$(setup_chrome_flags "$local_env")" else flags="$(setup_kiosk_browser)" fi cat >"$shortcut" </dev/null 2>&1 || \ warn "Failed to set default ACL for owner on $folder" setfacl -d -m g::rwx "$folder" >/dev/null 2>&1 || \ warn "Failed to set default ACL for group on $folder" setfacl -d -m o::r "$folder" >/dev/null 2>&1 || \ warn "Failed to set default ACL for others on $folder" # Apply ACLs recursively to existing files/folders setfacl -R -m u::rwx "$folder" >/dev/null 2>&1 || \ warn "Failed to apply ACL for owner recursively on $folder" setfacl -R -m g::rwx "$folder" >/dev/null 2>&1 || \ warn "Failed to apply ACL for group recursively on $folder" setfacl -R -m o::r "$folder" >/dev/null 2>&1 || \ warn "Failed to apply ACL for others recursively on $folder" info "ACL Setup" "Default and recursive ACLs applied to $folder" return 0 } function general_permissions() { info "Permissions" "Setting general permissions." if ! check_installfolderpath; then return 1 fi # Change ownership of the installation folder chown -R www-data:www-data "$INSTALLFOLDERPATH"/ >/dev/null 2>&1 || warn "Failed to set ownership for $INSTALLFOLDERPATH" # Set permissions on private folder chmod 2775 "$INSTALLFOLDERPATH/private" >/dev/null 2>&1 || warn "Failed to set permissions and setgid bit on $INSTALLFOLDERPATH/private" if set_private_acl; then info "Permissions" "ACLs successfully applied" else warn "Failed to apply ACLs" fi # Add `www-data` to necessary groups gpasswd -a www-data plugdev >/dev/null 2>&1 || warn "Failed to add www-data to plugdev group" gpasswd -a www-data video >/dev/null 2>&1 || warn "Failed to add www-data to video group" if [ -n "$USERNAME" ]; then gpasswd -a "$USERNAME" www-data >/dev/null 2>&1 || warn "Failed to add $USERNAME to www-data group!" else warn "No username defined! Can not add user to www-data group!" fi # Fix permissions on cache folder info "Permissions" "Fixing permissions on cache folder." mkdir -p "/var/www/.cache" >/dev/null 2>&1 || warn "Failed to create /var/www/.cache directory" chown -R www-data:www-data "/var/www/.cache" >/dev/null 2>&1 || warn "Failed to set ownership for /var/www/.cache" # Fix permissions on npm folder info "Permissions" "Fixing permissions on npm folder." mkdir -p "/var/www/.npm" >/dev/null 2>&1 || warn "Failed to create /var/www/.npm directory" chown -R www-data:www-data "/var/www/.npm" >/dev/null 2>&1 || warn "Failed to set ownership for /var/www/.npm" # Disable camera automount info "Permissions" "Disabling camera automount." chmod -x /usr/lib/gvfs/gvfs-gphoto2-volume-monitor >/dev/null 2>&1 || warn "Failed to disable camera automount" return 0 } function gpio_permission() { if [ "$RUNNING_ON_PI" = false ]; then return fi local boot_config info "Remote Buzzer GPIO Configuration" "Removing deprecated GPIO settings and configuration" # Determine the correct boot configuration file if [ -f '/boot/firmware/config.txt' ]; then boot_config="/boot/firmware/config.txt" else boot_config="/boot/config.txt" fi # Add the www-data user to the GPIO group usermod -a -G gpio www-data >/dev/null 2>&1 || warn "Failed to add www-data to gpio group" sed -i '/# Photobooth/,/# Photobooth End/d' "$boot_config" >/dev/null 2>&1 || warn "Failed to remove old Photobooth GPIO configuration" # Remove old artifacts from the node-rpio library, if present if [ -f '/etc/udev/rules.d/20-photobooth-gpiomem.rules' ]; then info "Remote Buzzer Update" "Old artifacts from the node-rpio library detected. Removing obsolete configuration." rm -f /etc/udev/rules.d/20-photobooth-gpiomem.rules >/dev/null 2>&1 || warn "Failed to remove old udev rules" sed -i '/dtoverlay=gpio-no-irq/d' "$boot_config" >/dev/null 2>&1 || warn "Failed to remove dtoverlay from $boot_config" fi # Update artifacts in the user configuration for the new implementation if [ -f "$INSTALLFOLDERPATH/config/my.config.inc.php" ]; then sed -i '/remotebuzzer/{n;n;s/enabled/usebuttons/}' "$INSTALLFOLDERPATH/config/my.config.inc.php" >/dev/null 2>&1 || warn "Failed to update remotebuzzer configuration in my.config.inc.php" fi info "Remote Buzzer GPIO Configuration" "Setup complete. Reboot your Raspberry Pi to apply the changes." return 0 } function remove_gpio_permission() { if [ "$RUNNING_ON_PI" = false ]; then return fi local boot_config info "Remote Buzzer GPIO Feature" "Removing GPIO access for www-data user." # Determine the correct boot configuration file if [ -f "/boot/firmware/config.txt" ]; then boot_config="/boot/firmware/config.txt" elif [ -f "/boot/config.txt" ]; then boot_config="/boot/config.txt" else error "Could not find a valid Raspberry Pi boot config file." return 1 fi # Check if www-data is part of the gpio group if groups www-data | grep -q "\bgpio\b"; then if gpasswd -d www-data gpio >/dev/null 2>&1; then info "Remote Buzzer GPIO Feature" "Successfully removed www-data user from the gpio group." else warn "Failed to remove www-data user from the gpio group." fi else info "Remote Buzzer GPIO Feature" "www-data user is not a member of the gpio group. No action needed." fi # Remove old artifacts from the node-rpio library, if present if [ -f '/etc/udev/rules.d/20-photobooth-gpiomem.rules' ]; then info "Remote Buzzer Update" "Old artifacts from the node-rpio library detected. Removing obsolete configuration." rm -f /etc/udev/rules.d/20-photobooth-gpiomem.rules >/dev/null 2>&1 || warn "Failed to remove old udev rules" sed -i '/dtoverlay=gpio-no-irq/d' "$boot_config" >/dev/null 2>&1 || warn "Failed to remove dtoverlay from $boot_config" fi # Remove configuration required for the onoff library info "Remote Buzzer GPIO Configuration" "GPIO settings removed in $boot_config" sed -i '/# Photobooth/,/# Photobooth End/d' "$boot_config" >/dev/null 2>&1 || warn "Failed to clean old Photobooth GPIO configuration." return 0 } function setup_printer_groups() { # Add www-data to lp group if gpasswd -a www-data lp >/dev/null 2>&1; then info "Printer Group Setup" "Added www-data to lp group." else warn "Failed to add www-data to lp group." return 1 fi # Add www-data to lpadmin group if gpasswd -a www-data lpadmin >/dev/null 2>&1; then info "Printer Group Setup" "Added www-data to lpadmin group." else warn "Failed to add www-data to lpadmin group." return 2 fi return 0 } function remove_printer_groups() { # Remove www-data from lp group if groups www-data | grep -q "\blp\b"; then if gpasswd -d www-data lp >/dev/null 2>&1; then info "Printer Group Removal" "Removed www-data from lp group." else warn "Failed to remove www-data from lp group." return 1 fi else info "Printer Group Removal" "www-data is not a member of lp group. No action needed." fi # Remove www-data from lpadmin group if groups www-data | grep -q "\blpadmin\b"; then if gpasswd -d www-data lpadmin >/dev/null 2>&1; then info "Printer Group Removal" "Removed www-data from lpadmin group." else warn "Printer Group Removal" "Failed to remove www-data from lpadmin group." return 2 fi else info "www-data is not a member of lpadmin group. No action needed." fi return 0 } function install_wwwdata_sudoers() { local sudoers_file="/etc/sudoers.d/020_www-data-shutdown" cat >"$sudoers_file" <<'EOF' # Photobooth buttons for www-data to shutdown or reboot the system www-data ALL=(ALL) NOPASSWD: /sbin/shutdown www-data ALL=(ALL) NOPASSWD: /sbin/reboot EOF chmod 440 "$sudoers_file" # Validate syntax (safe check, optional but recommended) if visudo -cf "$sudoers_file" >/dev/null 2>&1; then info "Setup" "Installed sudoers rule for www-data at $sudoers_file" return 0 else error "Invalid sudoers file created at $sudoers_file. Please check manually." return 1 fi } function create_polkit_usb_rule() { local PKLA_DIR="/etc/polkit-1/localauthority/50-local.d" local RULES_DIR="/etc/polkit-1/rules.d" if [[ -d "$PKLA_DIR" ]]; then cat >"$PKLA_DIR/photobooth.pkla" <"$RULES_DIR/photobooth.rules" <<'EOF' polkit.addRule(function(action, subject) { if (subject.isUser && subject.user == "www-data" && (action.id.indexOf("org.freedesktop.udisks2.filesystem-mount") == 0 || action.id.indexOf("org.freedesktop.udisks2.filesystem-unmount") == 0)) { return polkit.Result.YES; } }); EOF return 0 else return 1 fi } function remove_polkit_usb_rule() { local PKLA_FILE="/etc/polkit-1/localauthority/50-local.d/photobooth.pkla" local RULES_FILE="/etc/polkit-1/rules.d/photobooth.rules" local REMOVED=false [[ -f "$PKLA_FILE" ]] && rm -f "$PKLA_FILE" && REMOVED=true [[ -f "$RULES_FILE" ]] && rm -f "$RULES_FILE" && REMOVED=true $REMOVED && return 0 || return 1 } function disable_automount() { local configured=false local pcmanfm_conf="" if [[ -f "/etc/xdg/pcmanfm/default/pcmanfm.conf" ]]; then pcmanfm_conf="/etc/xdg/pcmanfm/default/pcmanfm.conf" if ! grep -q "^\[volume\]" "$pcmanfm_conf"; then echo "[volume]" >>"$pcmanfm_conf" fi for key in mount_on_startup mount_removable autorun; do if grep -q "^$key=" "$pcmanfm_conf"; then sed -i "s/^$key=.*/$key=0/" "$pcmanfm_conf" else echo "$key=0" >>"$pcmanfm_conf" fi done configured=true info "Auto mount Disable" "System default adjusted." fi if [ -z "$USERNAME" ]; then local detected_user detected_user=$(detect_single_home_user) if [[ -n "$detected_user" ]]; then USERNAME="$detected_user" info "Auto mount Disable" "Automatically detected username: $USERNAME" else warn "Auto mount Disable" "No username detected." fi fi if [ -n "$USERNAME" ]; then local xdg_config="${XDG_CONFIG_HOME:-/home/$USERNAME/.config}" local config_base="$xdg_config/pcmanfm" local profile_folder="" if [[ -d "$config_base" ]]; then profile_folder=$(find "$config_base" -mindepth 1 -maxdepth 1 -type d | head -n 1) fi if [[ -n "$profile_folder" ]]; then pcmanfm_conf="$profile_folder/pcmanfm.conf" if [[ -f "$pcmanfm_conf" ]]; then if ! grep -q "^\[volume\]" "$pcmanfm_conf"; then echo "[volume]" >>"$pcmanfm_conf" fi for key in mount_on_startup mount_removable autorun; do if grep -q "^$key=" "$pcmanfm_conf"; then sed -i "s/^$key=.*/$key=0/" "$pcmanfm_conf" else echo "$key=0" >>"$pcmanfm_conf" fi done chown "$USERNAME:$USERNAME" "$pcmanfm_conf" 2>/dev/null configured=true info "Auto mount Disable" "User config adjusted." fi fi fi if $configured; then return 0 else return 1 fi } function set_usb_sync() { if whiptail --title "USB Sync" \ --yesno "Setup USB Sync policy?\n\nThis is needed to use the USB Sync feature of Photobooth.\nUSB Sync can be activated via Adminpanel." \ 12 60; then if create_polkit_usb_rule; then if disable_automount; then confirm "USB Sync" "USB Sync policy created and auto mount settings updated successfully." else confirm "USB Sync" "USB Sync policy created, but no configuration file was found to adjust auto mount." fi else confirm "USB Sync" "Failed to create USB Sync policy!" fi else if remove_polkit_usb_rule; then confirm "USB Sync" "USB Sync policy removed." else confirm "USB Sync" "No USB Sync policy file found to remove." fi fi } # ================================================== # UI # ================================================== function hide_mouse() { if is_wayland_env; then if [ -f "/usr/share/icons/PiXflat/cursors/left_ptr" ]; then mv /usr/share/icons/PiXflat/cursors/left_ptr /usr/share/icons/PiXflat/cursors/left_ptr.bak confirm "Hide mouse" "Mouse cursor hidden for Wayland by backing up 'left_ptr' icon." elif [ -f "/usr/share/icons/PiXtrix/cursors/left_ptr" ]; then mv /usr/share/icons/PiXtrix/cursors/left_ptr /usr/share/icons/PiXtrix/cursors/left_ptr.bak confirm "Hide mouse" "Mouse cursor hidden for Wayland by backing up 'left_ptr' icon." else confirm "Hide mouse" "Cursor already hidden or 'left_ptr' icon not found." fi else local lxde_autostart_file="/etc/xdg/lxsession/LXDE-pi/autostart" if [ ! -f "$lxde_autostart_file" ]; then confirm "Hide mouse" "Aborting. LXDE-pi autostart not found." return 1 fi if ! install_package "unclutter"; then confirm "Hide mouse" "Aborting. Can not install unclutter." return 2 fi # Remove existing Photobooth-related configurations to avoid duplicates sed -i '/# Photobooth/,/# Photobooth End/d' "$lxde_autostart_file" # Append new settings to autostart cat >>"$lxde_autostart_file" <