#!/usr/bin/env bash # This file is part of The RetroPie Project # # The RetroPie Project is the legal property of its developers, whose names are # too numerous to list here. Please refer to the COPYRIGHT.md file distributed with this source. # # See the LICENSE.md file at the top-level directory of this distribution and # at https://raw.githubusercontent.com/RetroPie/RetroPie-Setup/master/LICENSE.md # rp_module_id="setup" rp_module_desc="GUI based setup for RetroPie" rp_module_section="" function _setup_gzip_log() { setsid tee >(setsid gzip --stdout >"$1") } function rps_logInit() { if [[ ! -d "$__logdir" ]]; then if mkdir -p "$__logdir"; then chown "$__user":"$__group" "$__logdir" else fatalError "Couldn't make directory $__logdir" fi fi # remove all but the last 20 logs find "$__logdir" -type f | sort | head -n -20 | xargs -d '\n' --no-run-if-empty rm local now=$(date +'%Y-%m-%d_%H%M%S') logfilename="$__logdir/rps_$now.log.gz" touch "$logfilename" chown "$__user":"$__group" "$logfilename" time_start=$(date +"%s") } function rps_logStart() { echo -e "Log started at: $(date -d @$time_start)\n" echo "RetroPie-Setup version: $__version ($(sudo -u "$__user" git -C "$scriptdir" log -1 --pretty=format:%h))" echo "System: $__platform ($__platform_arch) - $__os_desc - $(uname -a)" } function rps_logEnd() { time_end=$(date +"%s") echo echo "Log ended at: $(date -d @$time_end)" date_total=$((time_end-time_start)) local hours=$((date_total / 60 / 60 % 24)) local mins=$((date_total / 60 % 60)) local secs=$((date_total % 60)) echo "Total running time: $hours hours, $mins mins, $secs secs" } function rps_printInfo() { local log="$1" reset if [[ ${#__ERRMSGS[@]} -gt 0 ]]; then printMsgs "dialog" "${__ERRMSGS[@]}" [[ -n "$log" ]] && printMsgs "dialog" "Please see $log for more in depth information regarding the errors." fi if [[ ${#__INFMSGS[@]} -gt 0 ]]; then printMsgs "dialog" "${__INFMSGS[@]}" fi __ERRMSGS=() __INFMSGS=() } function depends_setup() { # check for VERSION file - if it doesn't exist we will run the post_update script as it won't be triggered # on first upgrade to 4.x if [[ ! -f "$rootdir/VERSION" ]]; then joy2keyStop exec "$scriptdir/retropie_packages.sh" setup post_update gui_setup fi if [[ "$__os_debian_ver" -le 10 ]] && isPlatform "rpi" && isPlatform "mesa" && ! isPlatform "rpi4"; then printMsgs "dialog" "WARNING: You have the experimental desktop GL driver enabled. This is NOT supported by RetroPie, and Emulation Station as well as emulators may fail to launch.\n\nPlease disable the experimental desktop GL driver from the raspi-config 'Advanced Options' menu." fi if isPlatform "rpi" && isPlatform "64bit"; then printMsgs "dialog" "WARNING: 64bit support on the Raspberry Pi is not yet officially supported, although the main emulator package selection should work ok." fi if [[ "$__os_debian_ver" -eq 8 ]]; then printMsgs "dialog" "Raspbian/Debian Jessie and versions of Ubuntu below 18.04 are no longer supported.\n\nPlease install RetroPie from a fresh image (or if running Ubuntu, upgrade your OS)." fi if [[ "$__os_debian_ver" -eq 9 ]] && [[ "$__os_id" == "Raspbian" ]]; then printMsgs "dialog" "Your version of RetroPie which is based on Raspbian Stretch is no longer supported.\n\nWe recommend you install the latest RetroPie image.\n\nPre-built binaries are now disabled for your system. You can still update packages from source, but due to the age of Raspbian Stretch we can't guarantee all software included will work." fi # make sure user has the correct group permissions if ! isPlatform "x11"; then local group for group in input video; do if ! hasFlag "$(groups $__user)" "$group"; then dialog --yesno "Your user '$__user' is not a member of the system group '$group'.\n\nThis is needed for RetroPie to function correctly. May I add '$__user' to group '$group'?\n\nYou will need to restart for these changes to take effect." 22 76 2>&1 >/dev/tty && usermod -a -G "$group" "$__user" fi done fi # set a global __setup to 1 which is used to adjust package function behaviour if called from the setup gui __setup=1 # print any pending msgs - eg during module scanning which wouldn't be seen otherwise rps_printInfo } function updatescript_setup() { clear chown -R "$__user":"$__group" "$scriptdir" printHeading "Fetching latest version of the RetroPie Setup Script." pushd "$scriptdir" >/dev/null if [[ ! -d ".git" ]]; then printMsgs "dialog" "Cannot find directory '.git'. Please clone the RetroPie Setup script via 'git clone https://github.com/RetroPie/RetroPie-Setup.git'" popd >/dev/null return 1 fi local error if ! error=$(sudo -u "$__user" git pull --ff-only 2>&1 >/dev/null); then printMsgs "dialog" "Update failed:\n\n$error" popd >/dev/null return 1 fi popd >/dev/null printMsgs "dialog" "Fetched the latest version of the RetroPie Setup script." return 0 } function post_update_setup() { local return_func=("$@") joy2keyStart echo "$__version" >"$rootdir/VERSION" clear local logfilename rps_logInit { rps_logStart # run _update_hook_id functions - eg to fix up modules for retropie-setup 4.x install detection printHeading "Running post update hooks" rp_updateHooks rps_logEnd } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" printMsgs "dialog" "NOTICE: The RetroPie-Setup script and pre-made RetroPie SD card images are available to download for free from https://retropie.org.uk.\n\nThe pre-built RetroPie image includes software that has non commercial licences. Selling RetroPie images or including RetroPie with your commercial product is not allowed.\n\nNo copyrighted games are included with RetroPie.\n\nIf you have been sold this software, you can let us know about it by emailing retropieproject@gmail.com." # return to set return function "${return_func[@]}" } function package_setup() { local id="$1" local default="" if ! rp_isEnabled "$id"; then printMsgs "dialog" "Sorry but package '$id' is not available for your system ($__platform)\n\nPackage flags: ${__mod_info[$id/flags]}\n\nYour $__platform flags: ${__platform_flags[*]}" return 1 fi # associative array so we can pull out the messages later for the confirmation requester declare -A option_msgs=( ["U"]="" ["B"]="Install from pre-compiled binary" ["S"]="Install from source" ) while true; do local options=() local status local has_binary=0 local has_net=0 isConnected && has_net=1 # for modules with nonet flag that don't need to download data, we force has_net to 1, so we get install options hasFlag "${__mod_info[$id/flags]}" "nonet" && has_net=1 if [[ "$has_net" -eq 1 ]]; then dialog --backtitle "$__backtitle" --infobox "Checking for updates for $id ..." 3 60 >/dev/tty rp_hasBinary "$id" local ret="$?" [[ "$ret" -eq 0 ]] && has_binary=1 [[ "$ret" -eq 2 ]] && has_net=0 fi local is_installed=0 local pkg_origin="" local pkg_date="" if ! rp_isInstalled "$id"; then status="Not installed" else is_installed=1 rp_loadPackageInfo "$id" pkg_origin="${__mod_info[$id/pkg_origin]}" pkg_date="${__mod_info[$id/pkg_date]}" [[ -n "$pkg_date" ]] && pkg_date="$(date -u -d "$pkg_date" 2>/dev/null)" status="Installed - via $pkg_origin" [[ -n "$pkg_date" ]] && status+=" (built: $pkg_date)" if [[ "$has_net" -eq 1 ]]; then rp_hasNewerModule "$id" "$pkg_origin" local has_newer="$?" case "$has_newer" in 0) status+="\nUpdate is available." option_msgs["U"]="Update (from $pkg_origin)" ;; 1) status+="\nYou are running the latest $pkg_origin." option_msgs["U"]="Re-install (from $pkg_origin)" ;; 2) if [[ "$pkg_origin" == "unknown" ]]; then if [[ "$has_binary" -eq 1 ]]; then pkg_origin="binary" else pkg_origin="source" fi fi option_msgs["U"]="Update (from $pkg_origin)" status+="\nUpdate may be available (Unable to check for this package)." ;; 3) has_net=0 ;; esac fi fi if [[ "$has_net" -eq 1 ]]; then if [[ "$is_installed" -eq 1 ]]; then options+=(U "${option_msgs["U"]}") fi if [[ "$pkg_origin" != "binary" && "$has_binary" -eq 1 ]]; then options+=(B "${option_msgs["B"]}") fi if [[ "$pkg_origin" != "source" ]] && fnExists "sources_${id}"; then options+=(S "${option_msgs[S]}") fi else status+="\nInstall options disabled:\n$__NET_ERRMSG" fi if [[ "$is_installed" -eq 1 ]]; then if fnExists "gui_${id}"; then options+=(C "Configuration / Options") fi options+=(X "Remove") fi if [[ -d "$__builddir/$id" ]]; then options+=(Z "Clean source folder") fi local help="${__mod_info[$id/desc]}\n\n${__mod_info[$id/help]}" if [[ -n "$help" ]]; then options+=(H "Package help") fi if [[ "$is_installed" -eq 1 ]]; then options+=(V "Package version information") fi cmd=(dialog --backtitle "$__backtitle" --cancel-label "Back" --default-item "$default" --menu "Choose an option for $id\n$status" 22 76 16) choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) default="$choice" local logfilename case "$choice" in U|B|S) dialog --defaultno --yesno "Are you sure you want to ${option_msgs[$choice]}?" 22 76 2>&1 >/dev/tty || continue local mode case "$choice" in U) mode="_auto_" ;; B) mode="_binary_" ;; S) mode="_source_" ;; esac clear rps_logInit { rps_logStart rp_installModule "$id" "$mode" rps_logEnd } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" ;; C) rps_logInit { rps_logStart rp_callModule "$id" gui rps_logEnd } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" ;; X) local text="Are you sure you want to remove $id?" case "${__mod_info[$id/section]}" in core) text+="\n\nWARNING - core packages are needed for RetroPie to function!" ;; depends) text+="\n\nWARNING - this package is required by other RetroPie packages - removing may cause other packages to fail." text+="\n\nNOTE: This will be reinstalled if missing when updating packages that require it." ;; esac dialog --defaultno --yesno "$text" 22 76 2>&1 >/dev/tty || continue rps_logInit { rps_logStart clear rp_callModule "$id" remove rps_logEnd } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" ;; H) printMsgs "dialog" "$help" ;; V) local info rp_loadPackageInfo "$id" read -r -d '' info << _EOF_ Package Origin: ${__mod_info[$id/pkg_origin]} Build Date: ${__mod_info[$id/pkg_date]} Built from source via: Type: ${__mod_info[$id/pkg_repo_type]} URL: ${__mod_info[$id/pkg_repo_url]} Branch: ${__mod_info[$id/pkg_repo_branch]} Commit: ${__mod_info[$id/pkg_repo_commit]} Date: ${__mod_info[$id/pkg_repo_date]} _EOF_ printMsgs "dialog" "$info" ;; Z) rp_callModule "$id" clean printMsgs "dialog" "$__builddir/$id has been removed." ;; *) break ;; esac done } function section_gui_setup() { local section="$1" local ids=() case "$section" in all|inst) name="packages" local id for id in "${__mod_id[@]}"; do # if we are showing installed packaged, skip those that are not installed [[ "$section" == "inst" ]] && ! rp_isInstalled "$id" && continue # don't show packages from depends or modules with no section (admin) ! [[ "${__mod_info[$id/section]}" =~ ^(depends|config|)$ ]] && ids+=("$id") done ;; *) name="${__sections[$section]} packages" ids=($(rp_getSectionIds $section)) ;; esac local default="" local status="" local has_net=1 while true; do local options=() local pkgs=() status="Please choose a package from below" if ! isConnected; then status+="\nInstall options disabled ($__NET_ERRMSG)" has_net=0 fi local id local num_pkgs=0 local info local type local last_type="" for id in "${ids[@]}"; do local type="${__mod_info[$id/vendor]} - ${__mod_info[$id/type]}" # do a heading for each origin and module type if [[ "$last_type" != "$type" ]]; then info="$type" pkgs+=("----" "\Z4$info ----" "Packages from $info") last_type="$type" fi if ! rp_isEnabled "$id"; then info="\Z1$id\Zn" else if rp_isInstalled "$id"; then rp_loadPackageInfo "$id" "pkg_origin" local pkg_origin="${__mod_info[$id/pkg_origin]}" info="$id (Installed - via $pkg_origin)" ((num_pkgs++)) else info="$id" fi fi pkgs+=("${__mod_idx[$id]}" "$info" "$id - ${__mod_info[$id/desc]}"$'\n\n'"${__mod_info[$id/help]}") done if [[ "$has_net" -eq 1 && "$num_pkgs" -gt 0 ]]; then options+=( U "Update all installed $name" "This will update any installed $name. The packages will be updated by the method used previously." ) fi # allow installing an entire section except for drivers and dependencies - as it's probably a bad idea if [[ "$has_net" -eq 1 && "$section" != "driver" && "$section" != "depends" ]]; then # don't show "Install all packages" when we are showing only installed packages if [[ "$section" != "inst" ]]; then options+=(I "Install all $name" "This will install all $name. If a package is not installed, and a pre-compiled binary is available it will be used. If a package is already installed, it will be updated by the method used previously.") fi options+=(X "Remove all installed $name" "X This will remove all installed $name.") fi options+=("${pkgs[@]}") local cmd=(dialog --colors --backtitle "$__backtitle" --cancel-label "Back" --item-help --help-button --default-item "$default" --menu "$status" 22 76 16) local choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) [[ -z "$choice" ]] && break if [[ "${choice[@]:0:4}" == "HELP" ]]; then # remove HELP choice="${choice[@]:5}" # get id of menu item default="${choice/%\ */}" # remove id choice="${choice#* }" printMsgs "dialog" "$choice" continue fi default="$choice" local logfilename case "$choice" in U|I) local mode="update" [[ "$choice" == "I" ]] && mode="install" dialog --defaultno --yesno "Are you sure you want to $mode all installed $name?" 22 76 2>&1 >/dev/tty || continue rps_logInit { rps_logStart for id in "${ids[@]}"; do ! rp_isEnabled "$id" && continue # if we are updating, skip packages that are not installed if [[ "$mode" == "update" ]]; then if rp_isInstalled "$id"; then rp_installModule "$id" "_update_" fi else rp_installModule "$id" "_auto_" fi done rps_logEnd } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" ;; X) local text="Are you sure you want to remove all installed $name?" [[ "$section" == "core" ]] && text+="\n\nWARNING - core packages are needed for RetroPie to function!" dialog --defaultno --yesno "$text" 22 76 2>&1 >/dev/tty || continue rps_logInit { rps_logStart for id in "${ids[@]}"; do rp_isInstalled "$id" && rp_callModule "$id" remove done rps_logEnd } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" ;; ----) ;; *) package_setup "${__mod_id[$choice]}" ;; esac done } function config_gui_setup() { local default while true; do local options=() local id for id in "${__mod_id[@]}"; do # show all configuration modules and any installed packages with a gui function if [[ "${__mod_info[$id/section]}" == "config" ]] || rp_isInstalled "$id" && rp_isEnabled "$id" && fnExists "gui_$id"; then options+=("${__mod_idx[$id]}" "$id - ${__mod_info[$id/desc]}" "${__mod_idx[$id]} ${__mod_info[$id/desc]}") fi done local cmd=(dialog --backtitle "$__backtitle" --cancel-label "Back" --item-help --help-button --default-item "$default" --menu "Choose an option" 22 76 16) local choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) [[ -z "$choice" ]] && break if [[ "${choice[@]:0:4}" == "HELP" ]]; then choice="${choice[@]:5}" default="${choice/%\ */}" choice="${choice#* }" printMsgs "dialog" "$choice" continue fi [[ -z "$choice" ]] && break default="$choice" id="${__mod_id[$choice]}" local logfilename rps_logInit { rps_logStart if fnExists "gui_$id"; then rp_callModule "$id" depends rp_callModule "$id" gui else rp_callModule "$id" clean rp_callModule "$id" fi rps_logEnd } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" done } function update_packages_setup() { clear local id for id in ${__mod_id[@]}; do if rp_isInstalled "$id" && [[ "${__mod_info[$id/section]}" != "depends" ]]; then rp_installModule "$id" "_update_" fi done } function check_connection_gui_setup() { local ip="$(getIPAddress)" if [[ -z "$ip" ]]; then printMsgs "dialog" "Sorry, you don't seem to be connected to the internet, so installing/updating is not available." return 1 fi return 0 } function update_packages_gui_setup() { local update="$1" if [[ "$update" != "update" ]]; then ! check_connection_gui_setup && return 1 dialog --defaultno --yesno "Are you sure you want to update installed packages?" 22 76 2>&1 >/dev/tty || return 1 updatescript_setup || return 1 # restart at post_update and then call "update_packages_gui_setup update" afterwards joy2keyStop exec "$scriptdir/retropie_packages.sh" setup post_update update_packages_gui_setup update fi local update_os=0 dialog --yesno "Would you like to update the underlying OS packages (eg kernel etc) ?" 22 76 2>&1 >/dev/tty && update_os=1 clear local logfilename rps_logInit { rps_logStart if [[ "$update_os" -eq 1 ]]; then if rp_isEnabled "raspbiantools"; then rp_callModule raspbiantools apt_upgrade else aptUpdate apt-get -y dist-upgrade fi fi update_packages_setup rps_logEnd } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" printMsgs "dialog" "Installed packages have been updated." gui_setup } function basic_install_setup() { local id for id in $(rp_getSectionIds core) $(rp_getSectionIds main); do rp_installModule "$id" done return 0 } function packages_gui_setup() { local section local default local options=() for section in core main opt driver exp depends; do options+=("$section" "Manage ${__sections[$section]} packages" "$section Choose to install/update/configure packages from the ${__sections[$section]}") done options+=("----" "" "") options+=("inst" "Manage all installed packages" "Install/update/remove currently installed packages") options+=("all" "Manage all packages" "Install/update/remove packages from full list") local cmd while true; do cmd=(dialog --backtitle "$__backtitle" --cancel-label "Back" --item-help --help-button --default-item "$default" --menu "Choose an option" 22 76 16) local choice choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) [[ -z "$choice" ]] && break if [[ "${choice[@]:0:4}" == "HELP" ]]; then choice="${choice[@]:5}" default="${choice/%\ */}" choice="${choice#* }" printMsgs "dialog" "$choice" continue fi [[ "$choice" != "----" ]] && section_gui_setup "$choice" default="$choice" done } function uninstall_setup() { dialog --defaultno --yesno "Are you sure you want to uninstall RetroPie?" 22 76 2>&1 >/dev/tty || return 0 dialog --defaultno --colors --yesno "Are you \ZbREALLY\Zn sure you want to uninstall RetroPie?\n\nThe \Z4\Zb$rootdir\Zn folder will be removed - this includes configuration files for all RetroPie components. If you wish to keep any config files of the emulators/ports that RetroPie used, then choose \ZrNo\Zn now and save it first." 22 76 2>&1 >/dev/tty || return 0 clear printHeading "Uninstalling RetroPie" for id in "${__mod_id[@]}"; do rp_isInstalled "$id" && rp_callModule $id remove done rm -rfv "$rootdir" dialog --defaultno --yesno "Do you want to remove all the files from $datadir - this includes all your installed ROMs, BIOS files and custom splashscreens." 22 76 2>&1 >/dev/tty && rm -rfv "$datadir" if dialog --defaultno --yesno "Do you want to remove all the system packages that RetroPie depends on? \n\nWARNING: this will remove packages like SDL even if they were installed before you installed RetroPie - it will also remove any package configurations - such as those in /etc/samba for Samba.\n\nIf unsure choose No (selected by default)." 22 76 2>&1 >/dev/tty; then clear # remove all dependencies for id in "${__mod_id[@]}"; do rp_isInstalled "$id" && rp_callModule "$id" depends remove done fi printMsgs "dialog" "RetroPie has been uninstalled." } function reboot_setup() { clear reboot } # retropie-setup main menu function gui_setup() { joy2keyStart depends_setup local default while true; do local commit=$(sudo -u "$__user" git -C "$scriptdir" log -1 --pretty=format:"%cr (%h)") cmd=(dialog --backtitle "$__backtitle" --title "RetroPie-Setup Script" --cancel-label "Exit" --item-help --help-button --default-item "$default" --menu "Version: $__version - Last Commit: $commit\nSystem: $__platform ($__platform_arch) - running on $__os_desc" 22 76 16) options=( I "Basic install" "I This will install all packages from Core and Main which gives a basic RetroPie install. Further packages can then be installed later from the Optional and Experimental sections. If binaries are available they will be used, alternatively packages will be built from source - which will take longer." U "Update" "U Updates RetroPie-Setup and all currently installed packages. Will also allow to update OS packages. If binaries are available they will be used, otherwise packages will be built from source." P "Manage packages" "P Install/Remove and Configure the various components of RetroPie, including emulators, ports, and controller drivers." C "Configuration / tools" "C Configuration and Tools. Any packages you have installed that have additional configuration options will also appear here." S "Update RetroPie-Setup script" "S Update this RetroPie-Setup script. This will update this main management script only, but will not update any software packages. To update packages use the 'Update' option from the main menu, which will also update the RetroPie-Setup script." X "Uninstall RetroPie" "X Uninstall RetroPie completely." R "Perform reboot" "R Reboot your machine." ) choice=$("${cmd[@]}" "${options[@]}" 2>&1 >/dev/tty) [[ -z "$choice" ]] && break if [[ "${choice[@]:0:4}" == "HELP" ]]; then choice="${choice[@]:5}" default="${choice/%\ */}" choice="${choice#* }" printMsgs "dialog" "$choice" continue fi default="$choice" case "$choice" in I) ! check_connection_gui_setup && continue dialog --defaultno --yesno "Are you sure you want to do a basic install?\n\nThis will install all packages from the 'Core' and 'Main' package sections." 22 76 2>&1 >/dev/tty || continue clear local logfilename rps_logInit { rps_logStart basic_install_setup rps_logEnd } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" ;; U) update_packages_gui_setup ;; P) packages_gui_setup ;; C) config_gui_setup ;; S) ! check_connection_gui_setup && continue dialog --defaultno --yesno "Are you sure you want to update the RetroPie-Setup script ?" 22 76 2>&1 >/dev/tty || continue if updatescript_setup; then joy2keyStop exec "$scriptdir/retropie_packages.sh" setup post_update gui_setup fi ;; X) local logfilename rps_logInit { uninstall_setup } &> >(_setup_gzip_log "$logfilename") rps_printInfo "$logfilename" ;; R) dialog --defaultno --yesno "Are you sure you want to reboot?\n\nNote that if you reboot when Emulation Station is running, you will lose any metadata changes." 22 76 2>&1 >/dev/tty || continue reboot_setup ;; esac done joy2keyStop clear }