diff --git a/HelperResources/CommonResources b/HelperResources/CommonResources index eddf74c..6674ef4 100755 --- a/HelperResources/CommonResources +++ b/HelperResources/CommonResources @@ -1030,11 +1030,13 @@ restartGuiV1Service () { # gui is the older service that runs GUI v1 only if [ -e "/service/gui" ]; then + logMessage "restarting GUI V1 (/service/gui)" svc -t "/service/gui" # restart GUI if NOT running v2 or can't determine if GUI v1 or v2 is selected elif [ -e "/service/start-gui" ]; then - guiVersion="$(dbus-send --system --print-reply --dest=com.victronenergy.settings /Settings/Gui/RunningVersion com.victronenergy.BusItem.GetValue | grep variant | awk '{print $3}')" + local guiVersion="$(dbus-send --system --print-reply --dest=com.victronenergy.settings /Settings/Gui/RunningVersion com.victronenergy.BusItem.GetValue | grep variant | awk '{print $3}')" if (( $guiVersion != 2 )); then + logMessage "restarting GUI V1 (/service/start-gui)" svc -t "/service/start-gui" fi fi @@ -1050,8 +1052,9 @@ restartGuiV2Service () { # restart GUI if NOT running v1 or can't determine if GUI v1 or v2 is selected if [ -e "/service/start-gui" ]; then - guiVersion="$(dbus-send --system --print-reply --dest=com.victronenergy.settings /Settings/Gui/RunningVersion com.victronenergy.BusItem.GetValue | grep variant | awk '{print $3}')" + local guiVersion="$(dbus-send --system --print-reply --dest=com.victronenergy.settings /Settings/Gui/RunningVersion com.victronenergy.BusItem.GetValue | grep variant | awk '{print $3}')" if (( $guiVersion != 1 )); then + logMessage "restarting GUI V2 (/service/start-gui)" svc -t "/service/start-gui" fi fi @@ -1167,83 +1170,90 @@ endScript () exit $EXIT_SUCCESS elif ! [ $scriptAction == 'INSTALL' ] && ! [ $scriptAction == 'UNINSTALL' ]; then setInstallFailed $EXIT_ERROR "unexpected script action $scriptAction - did not install or uninstall" + exit $EXIT_ERROR fi fi - # check for reboot or restarts + # check for reboot and service restarts if $rebootNeeded ; then if $userInteraction ; then if yesNoPrompt "Reboot system now (y) or do it manually later (n): " ; then - logMessage "rebooting ..." - reboot + deferReboot=false else logMessage "system must be rebooted to finish installation and activate components" - exit $EXIT_REBOOT fi - else - logMessage "completed - reboot needed" - exit $EXIT_REBOOT fi fi - if $restartGeneratorService ; then - logMessage "restarting generator service" - svc -t /service/dbus-generator-starter - fi - if $restartSystemCalc ; then - logMessage "restarting systemcalc service" - svc -t /service/dbus-systemcalc-py - fi - if $restartDigitalinputs ; then - logMessage "restarting digital inputs service" - svc -t /service/dbus-digitalinputs + + # restart services if a reboot won't happen below + if ! $rebootNeeded || $deferReboot ; then + if $restartGeneratorService ; then + logMessage "restarting generator service" + svc -t /service/dbus-generator-starter + fi + if $restartSystemCalc ; then + logMessage "restarting systemcalc service" + svc -t /service/dbus-systemcalc-py + fi + if $restartDigitalinputs ; then + logMessage "restarting digital inputs service" + svc -t /service/dbus-digitalinputs + fi fi #### TODO: add gui v2 if $restartGui ; then if $userInteraction ; then if yesNoPrompt "Restart the GUI now (y) or issue a do it manually later (n): " ; then - logMessage "completed - restarting GUI" restartGuiV1Service - if $installFailed || $uninstallFailed ; then - exit $installExitReason - else - exit $EXIT_SUCCESS - fi - else - echo "GUI must be restarted to activate changes" - exit $EXIT_RESTART_GUI + deferGuiRestart=false + restartGui=ralse fi else - if $deferGuiRestart ; then - logMessage "completed - GUI restart needed" - exit $EXIT_RESTART_GUI # GUI restart NOT deferred - do it now - else - logMessage "completed - restarting GUI" + if ! $deferGuiRestart ; then restartGuiV1Service - if $installFailed || $uninstallFailed ; then - exit $installExitReason - else - exit $EXIT_SUCCESS - fi + deferGuiRestart=false + restartGui=ralse fi fi fi - # if installation was attempted but failed, exit without checking anything else + local exitCode=$EXIT_SUCCESS if $installFailed ; then if [ $scriptAction == 'UNINSTALL' ]; then - logMessage "complete - package has been uninstalled" + if $uninstallFailed ; then + logMessage "CRITICAL: install failed with error $installExitReason" + logMessage "CRITICAL: uninstall also failed with error $uninstallExitReason - package state unknown" + exitCode=$uninstallExitReason + else + logMessage "install failed - package has been successfully uninstalled" + exitCode=$installExitReason + fi else - logMessage "complete - no changes were made" + logMessage "install failed during prechecks - no changes were made" + exitCode=$installExitReason fi - exit $installExitReason - # install/uninstall succeeded elif $uninstallFailed ; then - logMessage "complete - uninstall failed !!" + logMessage "CRITICAL: uninstall failed - package state unknown" + exitCode=$installExitReason + elif $rebootNeeded ; then + if $deferReboot ; then + logMessage "reboot needed to complete operaiton" + exitCode=$EXIT_REBOOT + else + logMessage "rebooting ..." + reboot + fi + elif $restartGui && $deferGuiRestart ; then + echo "GUI must be restarted to activate changes" + exitCode=$EXIT_RESTART_GUI + # install/uninstall succeeded else logMessage "complete - no errors" - exit $EXIT_SUCCESS + exitCode=$EXIT_SUCCESS fi + + exit $exitCode } # endScript () ######## this code is executed in-line when CommonResources is sourced diff --git a/HelperResources/CommonResources (original) b/HelperResources/CommonResources (original) new file mode 100755 index 0000000..6caca66 --- /dev/null +++ b/HelperResources/CommonResources (original) @@ -0,0 +1,1706 @@ +#!/bin/bash + + +# CommonResources for SetupHelper +# contains a functions and variables necessary for a setup script to interface with reinstallMods +# +# Refer to the SetupHelper ReadMe file for details on how to use these resources. + +# what action the script should take: +# NONE - do noting - signals script to prompt for user input on how to proceed +# INSTALL - install package components +# (decommissioned) PROMPT - prompt user for additional installation options +# UNINSTALL - remove package components +# EXIT - exit script without taking any action +# CHECK - runs file set checks only +# this will attempt to create a missing file set so PackageManager +# won't report it missing +# CommonResources may set the the action if initial checks +# indicate a clear direction +# otherwise, the action will be set based on user input (in the script) +# if failures occur during installation, +# scriptAction should be changed to UNINSTALL so the installation can be cleaned up +# and the setup script should test for UNINSTALL after it attempts installation +# A file set error indicates the file set for the current verion is not usable +# and installation should not occur +# checkFileSets EXITS locally + +scriptAction='NONE' + +# flags to control setup script behavior (in endScript) +rebootNeeded=false +runAgain=false +filesUpdated=false +restartGui=false +restartGenerator=false +restartSystemCalc=false +restartDigitalinputs=false + +# use local patch executable - BusyBox version has bugs +# include options that are used in all calls +patch="/data/SetupHelper/patch --force --silent --reject-file=/dev/null" + + +# file lists are populated by getFileLists called from_chckFileSets and autoinstall +# so these are global +fileList=() +fileListVersionIndependent=() +fileListAll=() + + +######## skip to bottom of file for remainder of code executed when script is sourced + + +# cleanup on any exit +exitCleanup () +{ + # remove temp directory + rm -rf "$tempFileDir" +} + +trap exitCleanup EXIT + +# checkPackageDependencies checks the packageDependencies file in the package directory +# +# all unmet dependencies are reported to the command line/log +# and to stderr if not running from the command line +# then the script exists + +function checkPackageDependencies () +{ + dependencyFile="$scriptDir/packageDependencies" + + #no dependencies specified for this package + if ! [ -f "$dependencyFile" ]; then return; fi + + errors="" + while IFS= read -r line; do + error="" + read package requirement <<< "$line" + if [ -f "$installedVersionPrefix"$package ]; then + packageInstalled=true + else + packageInstalled=false + fi + case $requirement in + installed) + if ! $packageInstalled ; then + error="$package must be installed" + fi + ;; + uninstalled) + if $packageInstalled ; then + error="$package must be uninstalled" + fi + ;; + esac + if ! [ -z "$error" ]; then + if ! [ -z "$errors" ]; then + errors+=", " + fi + errors+="$error" + fi + + done < "$dependencyFile" + if ! [ -z "$errors" ]; then + setInstallFailed $EXIT_PACKAGE_CONFLICT "$errors" + fi +} + +# getFileLists reads the file list from files in the FileSets directory +# +# 'fileList' file must only list version-dependent files +# 'fileListVersionIndependent' file must list only version-independent files +# prior to SetupHelper v6.0, this list is ignored +# 'fileListPatched' lists all files that should be patched before installation +# +# $1 specifies where the path to the fileList files +# +# three composite file lists are returned in global arrays: +# fileList contains only version-dependent files +# fileListVersionIndependent contains only version-independent files +# fileListPatched contains only files that need to be patched +# fileListAll contains both versioned and version-independent files + +function getFileLists () +{ + local verListFile="$1/fileList" + local indListFile="$1/fileListVersionIndependent" + local patchListFile="$1/fileListPatched" + local tempListVer=() + local tempListInd=() + local tempListPatched=() + + if [ -f "$verListFile" ]; then + while read -r line || [[ -n "$line" ]]; do + read -a params <<< $line + # parse line into space-separted parameters then discard any that don't begin with / + # this strips all comments beginning with # as well as any leading or trailing spaces + for param in ${params[@]} ; do + case $param in + /*) + tempListVer+=("$param") + ;; + esac + done + done < "$verListFile" + fi + if [ -f "$indListFile" ]; then + while read -r line || [[ -n "$line" ]]; do + read -a params <<< $line + for param in ${params[@]} ; do + case $param in + /*) + tempListInd+=("$param") + ;; + esac + done + done < "$indListFile" + fi + if [ -f "$patchListFile" ]; then + while read -r line || [[ -n "$line" ]]; do + read -a params <<< $line + for param in ${params[@]} ; do + case $param in + /*) + tempListPatched+=("$param") + ;; + esac + done + done < "$patchListFile" + fi + + # remove duplicate files from each list + fileList=($(printf "%s\n" "${tempListVer[@]}" | sort -u)) + fileListVersionIndependent=($(printf "%s\n" "${tempListInd[@]}" | sort -u)) + fileListPatched=($(printf "%s\n" "${tempListPatched[@]}" | sort -u)) + tempListAll=(${fileList[@]}) + tempListAll+=(${fileListVersionIndependent[@]}) + tempListAll+=(${fileListPatched[@]}) + fileListAll=($(printf "%s\n" "${tempListAll[@]}" | sort -u)) +} + + +# yesNoPrompt provides user prompting requesting a yes/no response +# +# $1 is the prompt displayed when pausing for user input +# +# $yesResponse is set to true if the response was yes +# +# returns 0 for yes, 1 for no + +yesNoPrompt () +{ + response='' + while true; do + /bin/echo -n "$*" + read response + case $response in + [yY]*) + yesResponse=true + return 0 + break + ;; + [nN]*) + yesResponse=false + return 1 + break + ;; + *) + esac + done +} + + +# standardActionPrompt provides the standard set of options for selecting script's action +# scriptAction is set by install/uninstall +# other actions are handled locally, including quitting from the script +# +# if nonstandard prompts are necessary, duplicate this code in the setup script +# and add the additional options and do not call standardActionPrompt +# +# the reinstall option is permitted only if setup options were previously set +# if the the reinstall action is choosen, the script action is set to INSTALL +# the setup script can then test this to skip further prompts +# +# $1 indicates if there are additional prompts needed during installaiton +# if this parameter is 'MORE_PROMPTS', installaiton does NOT change scriptAction +# if this parameter does not exist, installation WILL change scriptAction to INSTALL +# this provides backaward compatibility with scripts written prior to the reinstall logic +# +standardActionPrompt () +{ + if [ $# -gt 0 ] && [ $1 == 'MORE_PROMPTS' ]; then + updateScriptAction=false + else + updateScriptAction=true + fi + + echo + echo "Available actions:" + # don't allow install choice if incompatibilities have already been detected + if ! $installFailed ; then + echo " Install and activate (i)" + else + echo " can't install - errors reported above" + fi + if $optionsSet ; then + echo " Reinstall (r) based on options provided at last install" + fi + echo " Uninstall (u) and restores all files to stock" + echo " Quit (q) without further action" + echo " Display log (l) outputs the last 100 lines of the log" + echo + response='' + while true; do + /bin/echo -n "Choose an action from the list above: " + read response + case $response in + [iI]*) + if ! $installFailed ; then + if $updateScriptAction ; then + scriptAction='INSTALL' + fi + break + fi + ;; + [rR]*) + if $optionsSet ; then + scriptAction='INSTALL' + break + fi + ;; + [uU]*) + scriptAction='UNINSTALL' + break + ;; + [qQ]*) + exit $EXIT_SUCCESS + ;; + [lL]*) + tail -100 "$logFile" | tai64nlocal + ;; + *) + esac + done +} + + +# forcePackageUninstall insures a conflicting package is uninstalled before +# this package is installed +# the setup script must call this script BEFORE it begins installing anything +# $1 is the package name +# $2 contains an optional message + +forcePackageUninstall () +{ + if (( $# < 1 )); then + return + fi + if [ -f "$installedVersionPrefix""$1" ]; then + if (( $# >= 2 )); then + logMessage "${@:2}" + else + logMessage "uninstalling $1 - it conflicts with $packageName" + fi + if [ -e "/data/$1/setup" ]; then + "/data/$1/setup" "uninstall" "auto" "deferReboot" "deferGuiRestart" + else + logMessage "WARNING can't uninstall $1 - no package directory or no setup script" + fi + if [ -e "/data/settupOptions/$1" ]; then + touch "/data/settupOptions/$1/DO_NOT_AUTO_INSTALL" + fi + fi +} + + +# backupActiveFile makes a copy of the active file in file.orig +# if the original file does not exist the NO_ORIG flag is set +# to allow restoreAciveFile to remove the active file +# +# if the backup (.orig file) exists the backup is not updated +# +# $1 is the full path/file name to be backed up +# +# returns 0 if backup was made, 1 if not +# + +backupActiveFile () +{ + # don't do any work if install has already failed + if $installFailed ; then + return 1 + fi + local activeFile="$1" + local origFile="$activeFile.orig" + local noOrigFile="$activeFile.NO_ORIG" + + if [ -e "$activeFile" ]; then + if ! [ -e "$origFile" ]; then + cp "$activeFile" "$origFile" + rm -f "$noOrigFile" + return 0 + else + return 1 + fi + else + if ! [ -e "$noOrigFile" ]; then + touch "$noOrigFile" + return 0 + else + return 1 + fi + fi +} + + +# SetupHelper maintains a set of "restart flags" +# that control service restarts and system reboot +# after the package has been installed / uninstalled +# +# some restarts/reboots are based on the directory of the modified file +# others are based on the actual file itself +# +# not all services or specific files are flagged so some work in the setup script may be needed +# +# $1 is the full path and name to the modified file + +updateRestartFlags () +{ + # flag indicating any file update occurred + filesUpdated=true + + case $1 in + /opt/victronenergy/gui*) + restartGui=true + return;; + #### TODO: add gui-v2 + /opt/victronenergy/dbus-generator-starter/*) + restartGeneratorService=true + return;; + /opt/victronenergy/dbus-systemcalc-py/*) + restartSystemCalc=true + return;; + /opt/victronenergy/dbus-digitalinputs/*) + restartDigitalinputs=true + return;; + + /u-boot/overlay./*) # Raspberry PI DT overlay + rebootNeeded=true + return;; + /etc/udev/rules.d/*) # udev rules directory + rebootNeeded=true + return;; + esac + + # reboots based on specific file + case $( basename $1 ) in + gpio_list) + rebootNeeded=true + return;; + config.txt) + rebootNeeded=true + return;; + esac +} + + +# updateActiveFile first backs up the active file +# then copies the replacement (aka source) to the active file location (aka destination) +# +# two variations: +# +# updateActiveFile activeFile +# an attempt is made to locate the source (replacement) +# in the version directory or FileSets +# +# updateActiveFile sourceFile activeFile +# a separate source (replacement) file is specified +# +# both sourceFile and activeFile must be a full path to the file +# +# if the update fails, scriptAction is changed to UNINSTALL +# +# global thisFileUpdated is set to true if file was updated, false if not +# thisFileUpdated supports the old mechanism which may be used in some setup scripts +# returns 0 if file was updated, 1 if not + + +updateActiveFile () +{ + thisFileUpdated=false + + # don't do any work if install has already failed + if $installFailed ; then + return 1 + fi + + local sourceFile="" + local activeFile="" + + # separate source and replacement files specified + if [ $# == 2 ]; then + if [ -f "$1" ]; then + sourceFile="$1" + else + setInstallFailed $EXIT_FILE_SET_ERROR "specified soure file "$1" does not exist" + return 1 + fi + activeFile="$2" + # use active file for both source and destination + else + activeFile="$1" + fi + local baseName=$(basename "$activeFile") + + # replacement files are not needed for some versions + # if so marked, leave original untouched + if [ -e "$fileSet/$baseName.USE_ORIGINAL" ]; then + return 1 + fi + + # if the location of the active file must exist + if [ ! -e "$(dirname "$activeFile")" ]; then + setInstallFailed $EXIT_FILE_SET_ERROR "path to $activeFile does not exist" + return 1 + fi + + local usePatchedFile=false + local patchedReplacement="$tempFileDir/$baseName.patchedForInstall" + local currentPatchFile="$tempFileDir/$baseName.currentPatch" + + # source file not specified separately - look for it in expected places + if [ -z "$sourceFile" ]; then + # first in temp files - patched file + if [ -e "$patchedReplacement" ] && [ -e "$currentPatchFile" ]; then + sourceFile="$patchedReplacement" + usePatchedFile=true + # then in temp files - replacement + elif [ -e "$tempFileDir/$baseName" ]; then + sourceFile="$tempFileDir/$baseName" + # then in version-specific FileSet + elif [ -e "$fileSet/$baseName" ]; then + sourceFile="$fileSet/$baseName" + # then in version-independent file set + elif [ -e "$versionIndependentFileSet/$baseName" ]; then + sourceFile="$versionIndependentFileSet/$baseName" + # then in FileSets (previous location of version-indepencent files) + elif [ -e "$pkgFileSets/$baseName" ]; then + sourceFile="$pkgFileSets/$baseName" + # nothing found - can't continue + else + setInstallFailed $EXIT_FILE_SET_ERROR "no soure file for $baseName" + return 1 + fi + fi + + # can't continue if other packages modified this file and this is a replacement (not a patch) + local local packageList="$activeFile.package" + local previousPackage="" + local matchFound=false + if [ -e "$packageList" ]; then + previousPackages=$( cat "$packageList" ) + for previousPackage in ${previousPackages[@]}; do + if [ "$packageName" == "$previousPackage" ]; then + matchFound=true + elif ! $usePatchedFile ; then + setInstallFailed $EXIT_PACKAGE_CONFLICT "$baseName was already modfied by $previousPackage" + return 1 + fi + done + fi + + # add file to installed files list (used by uninstallAll) + if [ -e "$installedFilesList" ] \ + && (( $( grep -c "$activeFile" "$installedFilesList" ) == 0 )); then + echo "$activeFile" >> "$installedFilesList" + fi + + # save the current patch file for use during a future uninstall + local previousPatchFile="$previousPatchesDir/$baseName.patch" + if $usePatchedFile ; then + cp "$currentPatchFile" "$previousPatchFile" + # no patch file used for this update + else + rm -f "$previousPatchFile" + fi + + # if replacement, replace the .package file + if ! $usePatchedFile ; then + echo $packageName > "$packageList" + # if patch and this package not in list yet, add add it + elif ! $matchFound ; then + echo $packageName >> "$packageList" + fi + + # update the active file if needed + # patched files have already incorporated current active file content + # so nothing diffrerent here + doUpdate=false + if ! [ -f "$activeFile" ] || ! cmp -s "$sourceFile" "$activeFile" ; then + backupActiveFile "$activeFile" + cp "$sourceFile" "$activeFile" + if [[ -x "$activeFile.orig" ]]; then + chmod +x "$activeFile" + fi + updateRestartFlags "$activeFile" + thisFileUpdated=true + return 0 + else + return 1 + fi +} # end updateActiveFile () + + +# restoreActiveFile +# restores the active file to the content before this package modified it +# for replacements, the backup copy is moved to the active location +# if the backup copy doesn't exist BUT the NO_ORIG flag is set +# the active copy is deleted to restore the system to stock +# +# for patches, this package's changes are removed from the active file +# by reverse patching it +# +# if the reverse patch fails, the original is restored +# modifications from all other packages are also removed !! +# this is drastic but has best chance to leave the system without errors +# +# $1 is the active name, the one to be backed up +# +# returns 0 if active file was restored, 1 if not +# also sets thisFileUpdated for backwards compatibility with existing setup scripts +# + +restoreActiveFile () +{ + thisFileUpdated=false + local activeFile="$1" + local packageList="$activeFile.package" + # look for this package's name in .package list + # and remove it if found + matchFound=false + remainingPackages="" + if [ -f "$packageList" ]; then + previousPackages=($( cat "$packageList" )) + for previousPackage in ${previousPackages[@]}; do + if [ "$packageName" == "$previousPackage" ]; then + matchFound=true + else + remainingPackages+="$previousPackage " + fi + done + # no .package file - so proceed with removal anyway + else + matchFound=true + fi + + # if this package not found - nothing to do + if ! $matchFound ; then + return 1 + fi + + local baseName=$( basename "$activeFile" ) + local previousPatchFile="$previousPatchesDir/$baseName.patch" + + reversePatchError=false + restoreOriginal=false + # no other packages so restore the original + if [ -z "$remainingPackages" ] ; then + restoreOriginal=true + # other packages have also modified the active file + # attempt to reverse patch the active file + # if success, move result into the active position + # DO NOT restore original + else + if [ -e "$previousPatchFile" ]; then + tempFile="$tempFileDir/$baseName" + cp "$activeFile" "$tempFile" + if $patch --reverse -o "$activeFile.tmp" "$tempFile" "$previousPatchFile" &> /dev/null ; then + mv -f "$activeFile.tmp" "$activeFile" + thisFileUpdated=true + else + logMessage "CRITICAL: $packageName $baseName - reverse patch failed" + rm -f "$activeFile.tmp" + reversePatchError=true + fi + else + logMessage "CRITICAL: $packageName $baseName - no prevoius patch file" + reversePatchError=true + fi + # if the reverse patch failed, remove mods from ALL packages + #### TODO: DRASTIC but can't think of a way around this + if $reversePatchError ; then + message1="CRITICAL: $package $baseName could not be uninstalled cleanly" + message2=" $remainingPackages must be uninstalled and reinstalled" + logMessage "$message1" + logMessage "$message2" + echo "$message1" >> "$scriptDir/patchErrors" + echo "$message2" >> "$scriptDir/patchErrors" + + # report errors to other packages as well + for otherPackage in $remainingPackages ; do + otherPkgPatchErrors="$packageRoot/$otherPackage/patchErrors" + echo "$message1" >> "$otherPkgPatchErrors" + echo "$otherPackage must be uninstalled and reinstalled" >> "$otherPkgPatchErrors" + # remove previous patch in other package(s) since it no longer applies + rm -f "$previousPatchesRoot/$otherPackage/$baseName.patch" + done + setInstallFailed $EXIT_PATCH_ERROR "patch error details were saved in $packageName/patchErrors" + fi + fi + + # always remove previous patch file + rm -f "$previousPatchFile" + + # restore original if no other packages have modified this active file + # or if reverse patch failed + if $restoreOriginal || $reversePatchError ; then + if [ -e "$activeFile.orig" ]; then + mv -f "$activeFile.orig" "$activeFile" + thisFileUpdated=true + elif [ -f "$activeFile.NO_ORIG" ]; then + rm -f "$activeFile" + thisFileUpdated=true + fi + + rm -f "$packageList" + + # there are other packages, remove only this package from list + else + grep -v "$packageName" "$packageList" | tee "$packageList" > /dev/null + fi + + # remove file from installed file list + if [ -f "$installedFilesList" ]; then + grep -v "$activeFile" "$installedFilesList" | tee "$installedFilesList" > /dev/null + fi + if $thisFileUpdated; then + updateRestartFlags "$activeFile" + return 0 + else + return 1 + fi +} # end restoreActiveFile () + + +# checkFileSets validates the file sets used to install package modifications +# +# If a file set for the current Venus OS version exists, the replacement files in that file set +# are usable as is and no further checks are needed. +# The COMPLETE flag file indicates that the file set was validated on the computer creating +# the file sets and all replacement files (or symlinks to other file sets) exist. +# No checks are needed for a COMPLETE file set. +# If not, an attempt is made to create a file set for the current Venus OS version +# If the new active files for the new version all match another version +# the new file set is populated automatically with replacement files from the other version +# and may be used with no further action +# If not, new file set is marked INCOMPLETE and installation failure information is set +# The package can not be installed on this Venus OS version +# +# Replacement files that have no original specify an "alternate original" that is used +# for version comparisons that locate an appropriate replacement + +checkFileSets () +{ + # no file sets - nothing to check + if ! [ -e "$pkgFileSets" ]; then return; fi + + # no checks needed if all replacement files exist in the selected file set + if [ -f "$fileSet/COMPLETE" ]; then return; fi + + # sort versionList in reverse version order to make searches faster + # since newer versions will most likely contain the desired files to create a new file set + local rawVersionList=($(ls -d "$pkgFileSets"/v* 2> /dev/null)) + local tempList=() + local fs + local baseName + local version + local versionNumber + for fs in ${rawVersionList[@]} ; do + version=$(basename $fs) + versionStringToNumber $version + tempList+=("$version:$versionNumber") + done + local versionList=( $(echo ${tempList[@]} | tr ' ' '\n' | sort -t ':' -r -n -k 2 | uniq ) ) + + # versioned file sets exist but empty file list + if [ ! -z "$versionList" ] && [ -z "$fileList" ]; then + setInstallFailed $EXIT_FILE_SET_ERROR "empty file list" + touch "$fileSet/INCOMPLETE" + rm -f "$fileSet/COMPLETE" + return + # no versioned file sets - nothing to validate - allow install + elif [ -z "$versionList" ]; then return; fi + + # attempt to create a new file set or validate an existing one not marked as COMPLETE + + rm -f "$fileSet/INCOMPLETE" + + # attempt to create file set if it doesn't exist + if [ ! -d "$fileSet" ]; then + logMessage "creating a new file set for $venusVersion" + mkdir "$fileSet" + fi + + for file in ${fileList[@]} ; do + baseName=$(basename "$file") + + # version-independent file exists + if [ -e "$versionIndependentFileSet/$baseName" ] || [ -e "$pkgFileSets/$baseName" ]; then + # no versioned files (only version-independent found) - skip version checks + if [ -z $( find "$fileSet"/v* -name $baseName ) ]; then + continue + # continue with tests if any versioned files exist + else + logMessage "WARNING $baseName versioned file exists - version-independent file will be ignored" + fi + fi + + # skip checks if replacement file already exists + # or if there is no replacement file needed + if [ -f "$fileSet/$baseName" ] || [ -f "$fileSet/$baseName.USE_ORIGINAL" ]; then + rm -f "$fileSet/$baseName.NO_REPLACEMENT" + continue + fi + + local activeFile + if [ -f "$altOrigFileDir/$baseName.ALT_ORIG" ]; then + activeFile=$(cat "$altOrigFileDir/$baseName.ALT_ORIG") + elif [ -f "$pkgFileSets/$baseName.ALT_ORIG" ]; then + activeFile=$(cat "$pkgFileSets/$baseName.ALT_ORIG") + else + activeFile=$file + fi + # package already installed, use .orig file for comparisons + if [ -f "$activeFile.orig" ]; then + activeFile="$activeFile.orig" + fi + + # can't process if no original (aka active) file exists in the file set + if [ ! -f "$activeFile" ]; then + logMessage "ERROR $venusVersion $baseName no active file" + touch "$fileSet/$baseName.NO_ACTIVE_FILE" + touch "$fileSet/INCOMPLETE" + continue + fi + + # if an active file exists look for a match in another file set + if [ ! -z "$activeFile" ]; then + matchFound=false + for entry in ${versionList[@]}; do + otherVersion=$(echo $entry | awk -F ':' '{print $1}') + + # skip this version + if [ "$venusVersion" = "$otherVersion" ]; then + continue + fi + + otherFile="$pkgFileSets/$otherVersion/$baseName" + + # skip symbolic links and nonexistent originals + if [ ! -f "$otherFile.orig" ] || [ -L "$otherFile.orig" ] ; then + continue + fi + + # files match + if cmp -s "$activeFile" "$otherFile.orig" > /dev/null ; then + matchFound=true + break + fi + done + + if $matchFound ;then + rm -f "$fileSet/$baseName.orig" + rm -f "$fileSet/$baseName.NO_ORIG" + # if other file set contains a replacement file, link to it + if [ -f "$otherFile" ]; then + rm -f "$fileSet/$baseName" + ln -s "../$otherVersion/$baseName" "$fileSet/$baseName" + rm -f "$fileSet/$baseName.NO_REPLACEMENT" + rm -f "$fileSet/$baseName.USE_ORIGINAL" + # if other file set does not contain a replacement, this one will not either + # this IS permitted and handled in the updateActiveFile and restoreActiveFile functions + elif [ -f "$otherFile.USE_ORIGINAL" ]; then + touch "$fileSet/$baseName.USE_ORIGINAL" + rm -f "$fileSet/$baseName.NO_REPLACEMENT" + fi + # no match to a previous verison - can't create file set automatically + # but copy original file to aid manual editing + else + logMessage "ERROR $venusVersion $baseName no replacement file" + cp "$activeFile" "$fileSet/$baseName.orig" + touch "$fileSet/$baseName.NO_REPLACEMENT" + touch "$fileSet/INCOMPLETE" + fi + fi + done + + if [ -f "$fileSet/INCOMPLETE" ]; then + setInstallFailed $EXIT_FILE_SET_ERROR "incomplete file set for $venusVersion" + # if we get this far and fs is not marked INCOMPLETE, then the file set does not need to be checked again next pass + else + touch "$fileSet/COMPLETE" + fi +} + +# builds the installedFilesList and installedServices lists from the package's setup script +# this is needed for installs with a prior version of SetupHelper that did not +# create the installed... lists +# +# uninstall... functions then use these lists to uninstall + +buildUninstallListsfromSetupScript () +{ + # prevent this from running a second time + if [ "$uninstallListsAlreadyBuilt" == 'yes' ]; then + return + fi + uninstallListsAlreadyBuilt='yes' + local param + + scriptUninstallFilesList=() + scriptUninstallServicesList=() + while read -r line || [[ -n "$line" ]]; do + commandFound=false + read -a params <<< $line + numberOfParams=${#params} + for (( i=0; i < $numberOfParams; i++ ));do + case "${params[i]}" in + updateActiveFile) + # parameter of intrest is the second one if it exists + # otherwise, it should be the first one + param=${params[i+2]} + if [ -z "$param" ] || [[ $param == \#* ]]; then + param=$( echo ${params[i+1]} | sed s?'$qmlDir'?$qmlDir? ) + fi + if ! [ -z "$param" ] && ! [[ $param == \#* ]]; then + # remove any quotes around parameters + param=$( echo $param | sed -e 's?"?? g' -e "s?'?? g" ) + scriptUninstallFilesList+=("$param") + fi + ;; + installService) + # parameter of intrest is the first one if it exists + # otherwise, is the packageName + param=${params[i+1]} + if ! [ -z "$param" ] && ! [[ $param == \#* ]]; then + scriptUninstallServicesList+=("$param") + else + # remove any quotes around parameters + param=$( echo $param | sed -e 's?"?? g' -e "s?'?? g" ) + scriptUninstallServicesList+=($packageName) + fi + ;; + esac + done + done < "$scriptDir/setup" +} + +# install / uninstall all files / services functions +# +# the install... functions are called from endScript if the related flags are set +# but may also be called in the setup script +# if processing needs to be done after the files are installed +# +# NOTE: uninstall functions are called in endScrpit during an uninstall +# there are no flags to control this ! +# +# NOTE: only services in the package's serice directory are installed +# any services in the package's root diretory are not installed + + +installAllFiles () +{ + if [ -z "$fileListAll" ]; then + getFileLists "$pkgFileSets" + fi + + if [ ! -z "$fileListAll" ]; then + logMessage "installing files" + local file + for file in ${fileListAll[@]}; do + if $installFailed ; then break; fi + updateActiveFile "$file" + done + fi +} + +# uninstall files from +# installed files list if present +# and from file lists in the package +# and from updateActiveFile calls found in the setup script +# this insures complete uninstall even for packages installed +# with SetupHelper prior to v6.0 + +uninstallAllFiles () +{ + local restoreFilesList=() + # add installdeFilesList if present (might be empty but that's fine) + if [ -f "$installedFilesList" ]; then + restoreFilesList=( $( cat "$installedFilesList" ) ) + fi + # add file lists & calls to installActiveFile in setup script + restoreFilesList+=( ${fileListAll[@]} ) + buildUninstallListsfromSetupScript + restoreFilesList+=( ${scriptUninstallFilesList[@]} ) + + # remove duplicates + if (( ${#restoreFilesList[@]} > 1 )); then + restoreFilesList=( $(printf "%s\n" "${restoreFilesList[@]}" | sort -u ) ) + fi + + # uninstall the files + if [ ! -z "$restoreFilesList" ]; then + logMessage "uninstalling files" + local file + for file in ${restoreFilesList[@]}; do + restoreActiveFile $file + done + fi +} + + +installAllServices () +{ + local serviceList + local service + # get list of services in the package's service directory + if [ -d "$servicesDir" ]; then + servicesList=( $( cd "$servicesDir"; ls -d * 2> /dev/null ) ) + if ! [ -z "$servicesList" ]; then + logMessage "installing services" + for service in ${servicesList[@]} ; do + if $installFailed; then break; fi + installService $service + done + fi + fi +} + + +# uninstal services found in the services directory +# and restoreServices call in the package's setup script + +uninstallAllServices () +{ + local tempList=() + local servicesList=() + local service + + if [ -f "$installedServicesList" ]; then + servicesList=( $( cat "$installedServicesList" ) ) + fi + # add list from the setup script itself + buildUninstallListsfromSetupScript + servicesList+=( ${scriptUninstallServicesList[@]} ) + + # remove duplicates + if (( ${#servicesList[@]} > 1 )); then + servicesList=( $(printf "%s\n" "${servicesList[@]}" | sort -u ) ) + fi + + # uninstall services + if [ ! -z "$servicesList" ]; then + logMessage "uninstalling services" + for service in ${servicesList[@]} ; do + if [ -z "$service" ]; then + removeService $packageName + else + removeService $service + fi + done + fi +} + + +# restart the GUI V1 service +# begining at about v3.20~18, changes were made to accommodate the gui-v2 +# and these changes require different handling of a GUI service restart + + +restartGuiV1Service () +{ + # gui is the older service that runs GUI v1 only + if [ -e "/service/gui" ]; then + svc -t "/service/gui" + # restart GUI if NOT running v2 or can't determine if GUI v1 or v2 is selected + elif [ -e "/service/start-gui" ]; then + guiVersion="$(dbus-send --system --print-reply --dest=com.victronenergy.settings /Settings/Gui/RunningVersion com.victronenergy.BusItem.GetValue | grep variant | awk '{print $3}')" + if (( $guiVersion != 2 )); then + svc -t "/service/start-gui" + fi + fi +} + +# for backward compatibility (oler setup scripts) +restartGuiService () +{ + restartGuiV1Service +} + +restartGuiV2Service () +{ + # restart GUI if NOT running v1 or can't determine if GUI v1 or v2 is selected + if [ -e "/service/start-gui" ]; then + guiVersion="$(dbus-send --system --print-reply --dest=com.victronenergy.settings /Settings/Gui/RunningVersion com.victronenergy.BusItem.GetValue | grep variant | awk '{print $3}')" + if (( $guiVersion != 1 )); then + svc -t "/service/start-gui" + fi + fi +} + +# determine how the setup script should exit based on $scriptAction and other flags +# services may be restarted here also +# +# endScript accepts these optional parameters which determines if files and/or services are installed +# +# 'INSTALL_FILES' causes files from the file lists to be installed +# 'INSTALL_SERVICES' causes services in the services directory to be installed +# all services must be in the package's services directory +# 'ADD_DBUS_SETTINGS' will add/update dBus settings from the DbusSettingsList +# any or may be included +# do NOT include these if related processing is needed in the setup script ! +# instead, call installAllFiles and/or installAllServices in line with the other processing +# or call updateActiveFile or installService directly +# +# this function completes package installation +# and sets up conditions for reinstallation following a Venus Os firmware update +# +# may EXIT or REBOOT within the function - DOES NOT RETURN TO CALLER + + +endScript () +{ + if [ $scriptAction == 'INSTALL' ] && ! $installFailed ; then + # do installs as indicated from caller + while (( $# > 0 )); do + case "$1" in + 'INSTALL_FILES') + installAllFiles + ;; + 'INSTALL_SERVICES') + installAllServices + ;; + 'ADD_DBUS_SETTINGS') + addAllDbusSettings + ;; + esac + shift + done + + # assume that if we get this far, any command line opitons have already been set + touch "$setupOptionsDir/optionsSet" + + # clear flag preventing auto installs in PackageManager + rm -f "$setupOptionsDir/DO_NOT_AUTO_INSTALL" + + # if script needs to run again, installedVersionFile flag file is removed + # script should run again at boot time via reinstallMods + if $runAgain ; then + logMessage "script will run again at startup" + rm -f "$installedVersionFile" + # otherwise, installation is complete - update installedVersion + else + cp "$scriptDir/version" "$installedVersionFile" + fi + + # update rc.local to include call to reinstallMods + # do only for SetupHelper since other packages are now installed by PackageManager + if [ "$packageName" == "SetupHelper" ]; then + if [ ! -f "$rcLocal" ]; then + logMessage "creating $rcLocal" + cp "$scriptDir/rcS.local" "$rcLocal" + chmod +x "$rcLocal" + elif [ $(grep -c "blind install" "$rcLocal") -gt 0 ]; then + logMessage "REPLACING blind install $rcLocal with the standard one" + rm -f "$rcLocal" + cp "$scriptDir/rcS.local" "$rcLocal" + chmod +x "$rcLocal" + elif [ $(grep -c "SetupHelper" "$rcLocal") == 0 ]; then + logMessage "adding SetupHelper reinstall script to $rcLocal" + sed -e '2d' "$scriptDir/rcS.local" >> $rcLocal + fi + fi + fi + + + if [ $scriptAction == 'UNINSTALL' ] ; then + if $installFailed ; then + logMessage "INSTALL failed - attempting UNINSTALL in endScript" + # package was actually uninstalled (not an install failure) - set flag preventing auto installs + else + touch "$setupOptionsDir/DO_NOT_AUTO_INSTALL" + fi + + # ALWAYS attempt to uninstall files and services + uninstallAllFiles + uninstallAllServices + + # flag package not installed since package is being removed + rm -f "$installedVersionFile" + + # when uninstalling SetupHelper, remove EMPTY installed...Lists in all packages + # to prevent a future install with an older SetupHelper + # confusing a future future uninstall with a new SetupHelper + if [ "$packageName" == "SetupHelper" ] && [ -e "$installedFilesDir" ]; then + for file in $(ls "$installedFilesDir") ; do + if ! [ -s "$installedFilesDir/$file" ]; then + rm "$installedFilesDir/$file" + fi + done + # remove lines from rcS.local + sed -i -e "/# SetupHelper reinstall/,/fi/d" "$rcLocal" + fi + fi + + # setup script signals nothing to do - exit without further action without errors + if ! $installFailed && ! $uninstallFailed; then + if [ $scriptAction == 'EXIT' ]; then + exit $EXIT_SUCCESS + elif ! [ $scriptAction == 'INSTALL' ] && ! [ $scriptAction == 'UNINSTALL' ]; then + setInstallFailed $EXIT_ERROR "unexpected script action $scriptAction - did not install or uninstall" + fi + fi + + # check for reboot or restarts + if $rebootNeeded ; then + if $userInteraction ; then + if yesNoPrompt "Reboot system now (y) or do it manually later (n): " ; then + logMessage "rebooting ..." + reboot + else + logMessage "system must be rebooted to finish installation and activate components" + exit $EXIT_REBOOT + fi + else + logMessage "completed - reboot needed" + exit $EXIT_REBOOT + fi + fi + if $restartGeneratorService ; then + logMessage "restarting generator service" + svc -t /service/dbus-generator-starter + fi + if $restartSystemCalc ; then + logMessage "restarting systemcalc service" + svc -t /service/dbus-systemcalc-py + fi + if $restartDigitalinputs ; then + logMessage "restarting digital inputs service" + svc -t /service/dbus-digitalinputs + fi + #### TODO: add gui v2 + if $restartGui ; then + if $userInteraction ; then + if yesNoPrompt "Restart the GUI now (y) or issue a do it manually later (n): " ; then + logMessage "completed - restarting GUI" + restartGuiV1Service + if $installFailed || $uninstallFailed ; then + exit $installExitReason + else + exit $EXIT_SUCCESS + fi + else + echo "GUI must be restarted to activate changes" + exit $EXIT_RESTART_GUI + fi + else + if $deferGuiRestart ; then + logMessage "completed - GUI restart needed" + exit $EXIT_RESTART_GUI + # GUI restart NOT deferred - do it now + else + logMessage "completed - restarting GUI" + restartGuiV1Service + if $installFailed || $uninstallFailed ; then + exit $installExitReason + else + exit $EXIT_SUCCESS + fi + fi + fi + fi + + # if installation was attempted but failed, exit without checking anything else + if $installFailed ; then + if [ $scriptAction == 'UNINSTALL' ]; then + logMessage "complete - package has been uninstalled" + else + logMessage "complete - no changes were made" + fi + exit $installExitReason + # install/uninstall succeeded + elif $uninstallFailed ; then + logMessage "complete - uninstall failed !!" + else + logMessage "complete - no errors" + exit $EXIT_SUCCESS + fi +} # endScript () + +######## this code is executed in-line when CommonResources is sourced + +# check for reinstall parameter +# set $scriptAction to control work following the source command +# if "force" is also provided on the command line, then the installedVersionFile is not checked +# installedVersionFile contains the installed version (if any) +# it is compared to the version file in the package directory +# if installedVersionFile is missing or contents are different, the installation will proceed +# if the two versions match, there is no need to reinstall the package +# we assume a reinstall is always run without benefit of a console (runningAtBoot will be true) +# so there will be no prompts and all actions will be automatic +# +# "deferReboot" signals that endScript should not reboot the system, but return EXIT_REBOOT +# assuming the caller will evenutally reboot the system +# +# "deferGuiRestart" is similar for restarting the GUI +# +# "install" causes the package to be installed silently +# "uninstall" causes the package to be uninstalled silently +# +# command line parameters may appear in any order +# +# +# logToConsole is set to true in the LogHandler script +# It is set to false here the 'auto' parameter is passed on the command line +# which indicates this script is NOT being run from the command line + +# cleanup from previous versions - reinstallScriptsList no loner used +rm -f "/data/reinstallScriptsList" + +# initialize version strings and numbers for future checks +if [ -f "$installedVersionFile" ]; then + installedVersion=$(cat "$installedVersionFile") + versionStringToNumber $installedVersion + installedVersionNumber=$versionNumber +else + installedVersion="" + installedVersionNumber=0 +fi + +packageVersionFile="$scriptDir/version" +if [ -f "$packageVersionFile" ]; then + packageVersion=$(cat "$packageVersionFile") + + versionStringToNumber $packageVersion + packageVersionNumber=$versionNumber +else + packageVersion="" + packageVersionNumber=0 +fi + +# collect command line options +reinstall=false +force=false +deferReboot=false +deferGuiRestart=false +userInteraction=true +runFromPm=false +while [ $# -gt 0 ]; do + case $1 in + "reinstall") + reinstall=true + ;; + "force") + force=true + ;; + "deferReboot") + deferReboot=true + ;; + "deferGuiRestart") + deferGuiRestart=true + ;; + "install") + scriptAction='INSTALL' + ;; + "uninstall") + scriptAction='UNINSTALL' + ;; + "auto") + logToConsole=false + userInteraction=false + ;; + "runFromPm") + runFromPm=true + logToConsole=false + userInteraction=false + deferReboot=true + deferGuiRestart=true + ;; + "check") + # if no other actions were set, set it here + # but allow other actions to override this one + if [ $scriptAction == 'NONE' ]; then + scriptAction='CHECK' + fi + ;; + *) + esac + shift +done + +# do after logToConsole is enabled/disabled abvove +logMessage "--- starting setup script $packageVersion action: $scriptAction" + +# packages that require options to proceed unattended +# must include the optionsRequried flag file in their package directory +# if the flag is present and options haven't been previously set, +# SD/USB media will be checked for the package options directory +# and copy them into position + +opitonsRequiredFile="$scriptDir/optionsRequired" +optionsSet=false +if [ -f $opitonsRequiredFile ]; then + if [ -f "$setupOptionsDir/optionsSet" ]; then + optionsSet=true + # options not set - check media for options if doing a blind install + elif [ $scriptAction == 'INSTALL' ]; then + mediaList=($(ls /media)) + for dir in ${mediaList[@]} ; do + altSetupDir="/media/$dir/"$(basename $setupOptionsRoot)"/$packageName" + if [ -f "$altSetupDir/optionsSet" ]; then + cp -r "$altSetupDir" "$setupOptionsRoot" + if [ -f "$setupOptionsDir/optionsSet" ]; then + logMessage "options retrieved from SD/USB media" + optionsSet=true + fi + break + fi + done + fi + +# no command line options are needed - ok to reinstall even if +# setup was not run from the command line +else + optionsSet=true +fi + +# called from reinstallMods at boot time +if $reinstall ; then + runningAtBoot=true + if $force ; then + scriptAction='INSTALL' + # not installed, do it now + elif (( installedVersionNumber == 0 )); then + scriptAction='INSTALL' + # check versions and install only if package version is newer than installed version + else + # trigger install if version numbers differ + if (( installedVersionNumber != packageVersionNumber )); then + scriptAction='INSTALL' + else + exit $EXIT_SUCCESS + fi + fi + +# not running from reinstallMods +else + runningAtBoot=false +fi + +if [ ! -d "$setupOptionsDir" ]; then + logMessage "creating package options directory $setupOptionsDir" + mkdir -p $setupOptionsDir +fi + +# initialze integer version number for venus version +# used below and in checkFileSets +versionStringToNumber $venusVersion +venusVersionNumber=$versionNumber + +getFileLists "$pkgFileSets" + +# create temporary directory for temporary install/uninstall files (unique temp directory in volatile storage) +# updateActiveFile always checks this location first for a replacement before checking file sets +# tempFileDir is removed in the exit trap above but it is in volatile storage so will be removed on boot anyway +tempFileDir=$( mktemp -d ) + +# patch files previously used to patch a file are stored in /etc/venus +# so they track the selected root fs and are erased when Venus OS is updated +previousPatchesRoot="/etc/venus/previousPatches" +previousPatchesDir="$previousPatchesRoot/$packageName" +if ! [ -e "$previousPatchesDir" ]; then + mkdir -p "$previousPatchesDir" +fi +# relocate previous patch files +oldPreviousPatchesDir="$setupOptionsDir/previousPatches" +if [ -e "$oldPreviousPatchesDir" ]; then + logMessage "relocating previous patches" + mv "$oldPreviousPatchesDir"/* "$previousPatchesDir" + rm -rf "$oldPreviousPatchesDir" +fi +unset oldPreviousPatchesDir + +# do install pre-checks - skip if uninstalling +if [ $scriptAction != 'UNINSTALL' ]; then + + # prevent installing Raspberry Pi packages on other platforms + if [ -f "$scriptDir/raspberryPiOnly" ]; then + if [[ $machine != *"raspberrypi"* ]]; then + setInstallFailed $EXIT_INCOMPATIBLE_PLATFORM "$packageName not compatible with $machine" + fi + fi + # check to see if package is compatible with this Venus version + if [ -f "$scriptDir/firstCompatibleVersion" ]; then + firstCompatibleVersion=$(cat "$scriptDir/firstCompatibleVersion") + # no first compatible version specified - use the default + else + firstCompatibleVersion='v2.71' + fi + versionStringToNumber $firstCompatibleVersion + firstCompatibleVersionNumber=$versionNumber + if (( $venusVersionNumber < $firstCompatibleVersionNumber )); then + setInstallFailed $EXIT_INCOMPATIBLE_VERSION "$venusVersion before first compatible $firstCompatibleVersion" + elif [ -f "$scriptDir/obsoleteVersion" ]; then + obsoleteVersion=$(cat "$scriptDir/obsoleteVersion") + versionStringToNumber $obsoleteVersion + obsoleteVersionNumber=$versionNumber + if (( $venusVersionNumber >= $obsoleteVersionNumber )); then + setInstallFailed $EXIT_INCOMPATIBLE_VERSION "$venusVersion after last compatible $obsoleteVersion" + fi + fi + + # determine if GUI v1 is installed + # Note, it may NOT be running or selected to run!!!! + if [ ! -d "/opt/victronenergy/gui" ]; then + guiV1present=false + else + guiV1present=true + fi + # block installs if any GUI files would be modified and GUI v1 is not present + # packages can bypass GUI v1 checks and allow installs even if package contains them + if [ -f "$scriptDir/GUI_V1_NOT_REQUIRED" ]; then + guiV1required=false + # files in the GUI v1 directory are considered mandatory + elif (( $( cat "$pkgFileSets/fileList"* 2>/dev/null | grep -c '/gui/' ) > 0 )); then + guiV1required=true + # look also in setup script + elif (( $( grep 'updateActiveFile' "$scriptDir/setup" | grep -c '$qmlDir\|/gui/') > 0 )); then + guiV1required=true + else + guiV1required=false + fi + if ! $guiV1present && $guiV1required ; then + setInstallFailed $EXIT_NO_GUI_V1 "$packageName requires GUI v1" + fi + + # attempting an install without the comand line prompting + # and needed options have not been set yet - can't continue + if ! $installFailed && [ $scriptAction == 'INSTALL' ]; then + if ! $optionsSet ; then + setInstallFailed $EXIT_OPTIONS_NOT_SET "required options have not been set" + fi + fi + + checkFileSets + + # checkFileSets created a missing file set and set the INCOMPLETE flag if needed + # that is all CHECK needed to do so EXIT HERE !!!! + if [ $scriptAction == 'CHECK' ]; then + if [ -f "$fileSet/INCOMPLETE" ]; then + exit $EXIT_FILE_SET_ERROR + fi + fi + + checkPackageDependencies + + # create patched files for files with VisibleItemModel for older Venus OS versions + versionStringToNumber "v3.00~14" + if (( $venusVersionNumber < $versionNumber )); then + logMessage "patching VisibleItemModel to VisualItemModel in all .qml replacements" + for file in ${fileListVersionIndependent[@]}; do + baseName=$( basename "$file" ) + if ! [[ "$baseName" == *.qml ]]; then continue; fi + sourceFile="$versionIndependentFileSet/$baseName" + if ! [ -f "$sourceFile" ]; then continue; fi + if (( $(grep -c "VisibleItemModel" "$sourceFile") == 0 )); then continue; fi + sed -e 's/VisibleItemModel/VisualItemModel/' "$sourceFile" > "$tempFileDir/$baseName" + done + fi + + # create the forward and reverse patched files + # used during the actual install and to test if the patch/reverse patch will succeed + # done here so PackageManager knows if this will be possible before starting the install + # + # if this and other packages have both modified the active file, + # the patch from this package is first removed + # by reverse patching the active file with the PREVIOUS patch file + # the new patch is then applied + # a test reverse patch insures the patch can be removed in the future + # the patch file used for this patch is then saved so it can be used + # for the reverse patch on next install/uninstall + # + # if no other packages have modified the active file, + # the patch is applied to .orig file if exists + # rather than reverse patching the active file + # this maintains compatibility with packages installed with older versions of SetupHelper + + if ! $installFailed && ! [ -z "$fileListPatched" ];then + patchErrors=() + for activeFile in ${fileListPatched[@]}; do + baseName=$( basename $activeFile ) + tempActiveFile="$tempFileDir/$baseName" + currentPatchFile="$tempFileDir/$baseName.currentPatch" + previousPatchFile="$previousPatchesDir/$baseName.patch" + + rm -f "$currentPatchFile" + + if ! [ -e "$activeFile" ] ; then + patchErrors+=( "$baseName no active file for patch" ) + continue + fi + + # check for this and other packages in .package list + packageList="$activeFile.package" + thisPackageInList=false + otherPackagesInList=false + if [ -f "$packageList" ]; then + previousPackages=$( cat "$packageList" ) + for previousPackage in ${previousPackages[@]}; do + if [ $packageName == $previousPackage ]; then + thisPackageInList=true + else + otherPackagesInList=true + fi + done + fi + + patchOk=false + + if $thisPackageInList; then + # only this package modified active file + # ignore any previous patch and patch .orig file + if ! $otherPackagesInList ; then + if [ -e "$activeFile.orig" ]; then + cp "$activeFile.orig" "$tempActiveFile" + patchOk=true + else + patchErrors+=( "$baseName no .orig file for patch" ) + fi + # this and others have modified the active file + # attempt to remove the previous patch for this package + # then patch the result + elif [ -e "$previousPatchFile" ]; then + if $patch --reverse -o "$tempActiveFile" "$activeFile" "$previousPatchFile" &> /dev/null ; then + patchOk=true + # reverse patch failed + else + patchErrors+=( "$baseName unable to remove previous patch" ) + fi + else + patchErrors+=( "$baseName no previous patch file" ) + fi + # this package has not previously modified the active file + # patch the active file + else + cp "$activeFile" "$tempActiveFile" + patchOk=true + fi + patchSuccess=false + # a suitable source for the patch was located above + if $patchOk; then + # attempt to patch the active file with any file ending in .patch + # the first one that successfully creates a forward AND reverse patch is used + # .patchedForInstall provides the patched file for updateActiveFile + patchFiles=( $( ls "$patchSourceDir/$baseName"*.patch ) ) + forwardPatched="$tempActiveFile.patchedForInstall" + for patchFile in ${patchFiles[@]};do + if $patch --forward -o "$forwardPatched" "$tempActiveFile" "$patchFile" &> /dev/null ; then + # forward patch succeeded - test reverse patch (both must succeed) + if $patch --reverse -o /dev/null "$forwardPatched" "$patchFile" &> /dev/null ; then + patchSuccess=true + break + fi + fi + done + if $patchSuccess ; then + # save this so forwardPatched (created above) is used for install + # when file is installed, currentPatchFile will be copied to previousPatchFile + # for a future uninstall or reinstall + cp "$patchFile" "$currentPatchFile" + else + patchErrors+=( "$baseName patch unsuccesful" ) + rm -f "$forwardPatched" + fi + else + patchErrors+=( "$baseName no patch source" ) + fi + done # for activeFile + + # save errors in patchErrors file + if ! [ -z "$patchErrors" ] ; then + rm -f "$scriptDir/patchErrors" + for patchError in "${patchErrors[@]}"; do + logMessage "$patchError" + echo "$patchError" >> "$scriptDir/patchErrors" + done + setInstallFailed $EXIT_PATCH_ERROR "patch error details were saved in $packageName/patchErrors" + + # pre-checks only - direct exit + if [ $scriptAction == 'CHECK' ]; then + exit $EXIT_PATCH_ERROR + # script will exit since we are still in pre-checks !! + else + endScript + fi + # no errors - remove the error history + else + rm -f "$scriptDir/patchErrors" + fi + fi # if fileListPatched +fi # if [ $scriptAction != 'UNINSTALL' ] + +# go no further if just checking +if [ $scriptAction == 'CHECK' ]; then + exit $EXIT_SUCCESS +fi + +# in the Victron images, the root FS is read-only and is a minimum size +# in order to install modificaitons, the root partition needs to be +# remounted read-write and resized to allow mods to be added +# updateRootToReadWrite calls remount-rw.sh or resize2fs.sh +# then check to make sure there is sufficient space before allowing installs + +updateRootToReadWrite + +# done with pre checks +# prior to this no system mofications have been made +# after this system modifications may occur + +if $installFailed ; then + if ! $userInteraction ; then + logMessage "ERROR: errors occured during pre-checks - can't continue" + # EXIT HERE if errors occured during pre-checks while running unattended !!!! + # the LAST install failure reported to setInstallFailed is used as the exit code + exit $installExitReason # EXIT HERE !!!! + # command line install request failed - reset scriptAction to NONE + # so prompts will be shown - eg + # to show the log or + # to trigger a manual uninstall + elif [ $scriptAction == 'INSTALL' ]; then + logMessage "ERROR: install failed during pre-checks - select another action" + scriptAction='NONE' + fi +fi + +# create installed files (and services) directory +if [ ! -d "$installedFilesDir" ]; then + mkdir "$installedFilesDir" +fi + +installPreChecks=false + + +#### do standard prompting, automatic install/uninstall then exit +if [ "$standardPromptAndActions" == 'yes' ]; then + # prompt only if action hasn't been set yet (from command line) + if [ "$scriptAction" == 'NONE' ]; then + standardActionPrompt + fi + endScript 'INSTALL_FILES' 'INSTALL_SERVICES' 'ADD_DBUS_SETTINGS' +fi + +# otherwise continue with the setup script + +#### continue executing the setup script which sourced this file diff --git a/HelperResources/EssentialResources b/HelperResources/EssentialResources index 53970f6..464e1f9 100755 --- a/HelperResources/EssentialResources +++ b/HelperResources/EssentialResources @@ -138,6 +138,7 @@ qmlDir=/opt/victronenergy/gui/qml installFailed=false installExitReason=$EXIT_ERROR +uninstallExitReason=$EXIT_ERROR installFailMessage="" installPreChecks=true installFailCount=0 @@ -145,12 +146,14 @@ uninstallFailed=false setInstallFailed () { + local reason + (( installFailCount += 1 )) if [ ! -z "$1" ]; then - installExitReason=$1 + reason=$1 # no reson specified - use the generaic error exit code else - installExitReason=EXIT_ERROR + reason=EXIT_ERROR fi message="${@:2}" if [ ! -z "$message" ]; then @@ -165,8 +168,10 @@ setInstallFailed () fi if [ $scriptAction == 'UNINSTALL' ]; then + uninstallExitReason=$reason uninstallFailed=true else + installExitReason=$reason installFailed=true fi if ! $userInteraction && [ $scriptAction == 'INSTALL' ]; then diff --git a/changes b/changes index f5f631d..e2fbd23 100644 --- a/changes +++ b/changes @@ -1,3 +1,7 @@ +v8.11: + improved install/uninstall error handling in endScript () + fixed: GUI and other service restarts not always happening + v8.10: moved velib_python in SetupHelper to a single version dropping support for firmware earlier than v3.10 diff --git a/venus-data-UninstallPackages.tgz b/venus-data-UninstallPackages.tgz deleted file mode 100644 index 3d3fae2..0000000 Binary files a/venus-data-UninstallPackages.tgz and /dev/null differ diff --git a/venus-data.tgz b/venus-data.tgz deleted file mode 100644 index ef0954a..0000000 Binary files a/venus-data.tgz and /dev/null differ diff --git a/version b/version index 5eb381b..c0c8af0 100644 --- a/version +++ b/version @@ -1 +1 @@ -v8.10 +v8.11