diff --git a/.DS_Store b/.DS_Store index 3bd0de9..10a3966 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/CommonResources b/CommonResources index 35d89af..93d155b 100755 --- a/CommonResources +++ b/CommonResources @@ -5,12 +5,9 @@ ######## skip to bottom of file for code executed when script is called -setupHelperDir="/data/SetupHelper" -source "$setupHelperDir/LogHandler" -reinstallParam="reinstall" - # get the full, unambiguous path to this script scriptDir="$( cd "$(dirname $0)" >/dev/null 2>&1 ; /bin/pwd -P )" +packageName=$(basename "$scriptDir") shortScriptName=$(basename "$scriptDir")/$(basename "$0") fullScriptName="$scriptDir/$(basename "$0")" @@ -22,6 +19,15 @@ installedFlag=/etc/venus/isInstalled-$(basename "$scriptDir") pkgFileSets="$scriptDir/FileSets" fileSet="$pkgFileSets/$venusVersion" +# GitHub account +gitHubUser="kwindrem" + +setupHelperDir="/data/SetupHelper" +source "$setupHelperDir/LogHandler" +source "$setupHelperDir/ServiceResources" +reinstallParam="reinstall" + + reinstallScriptsList="/data/reinstallScriptsList" configFile="/u-boot/config.txt" @@ -64,6 +70,32 @@ runAgain=false filesUpdated=false restartGui=false +# 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 + +yesNoPrompt () +{ + response='' + while true; do + /bin/echo -n "$*" + read response + case $response in + [yY]*) + yesResponse=true + break + ;; + [nN]*) + yesResponse=false + break + ;; + *) + esac + done +} + # standardActionPrompt provides the standard set of options for selecting script's action # scriptAction is set by install/uninstall @@ -141,36 +173,8 @@ standardActionPrompt () *) esac done - } -# 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 - -yesNoPrompt () -{ - response='' - while true; do - /bin/echo -n "$*" - read response - case $response in - [yY]*) - yesResponse=true - break - ;; - [nN]*) - yesResponse=false - break - ;; - *) - esac - done -} - - # backupActiveFile makes a copy of the active file in file.orig # if the original file does not exist, no backup is made # BUT sets a flag file that will cause restoreFile to delete the active copy @@ -240,9 +244,17 @@ updateActiveFile () fi if ! $sourceFound; then - logMessage "ERROR: no replacement file for $sourceFile" - thisFileUpdated=false - scriptAction='UNINSTALL' + # replacement files are not be needed for some versions + # if so marked, leave original untouched + if [ -f "$fileSet/$(basename $sourceFile).NO_REPLACEMENT" ]; then +echo "#### no replacement flaged for $sourceFile" + return + # if not flagged, this is a fatal error + else + logMessage "ERROR: no replacement file for $sourceFile" + thisFileUpdated=false + scriptAction='UNINSTALL' + fi return fi backupActiveFile "$destinationFile" @@ -333,14 +345,9 @@ checkFileSets () _checkFileSets () { - # nothing to do if there is no fileList (version-dependent files) - if [ ! -f "$pkgFileSets/fileList" ]; then - return - fi - # check to see if package is compatible with this Venus version - if [ ! -z $obsoleteVersion ]; then - versionStringToNumber $obsoleteVersion + if [ -f "$scriptDir/obsoleteVersion" ]; then + versionStringToNumber $(cat "$scriptDir/obsoleteVersion") obsoleteVersion=$versionNumber versionStringToNumber $venusVersion @@ -361,73 +368,92 @@ _checkFileSets () return fi + # nothing to do if there is no fileList (version-dependent files) + if [ ! -f "$pkgFileSets/fileList" ]; then + return + fi + # attempt to create file set if it doesn't exist if [ ! -d "$fileSet" ]; then logMessage "creating file set for $venusVersion" mkdir "$fileSet" + fi - local fileList=$(cat "$pkgFileSets/fileList") - local versionList=($(ls -d "$pkgFileSets"/v*)) - local activeFile="" - local baseName="" - local file="" - - for file in $fileList ; do - baseName=$(basename "$file") - activeFile=$file - if [ -f "$pkgFileSets/$baseName.ALT_ORIG" ]; then - activeFile=$(cat "$pkgFileSets/$baseName.ALT_ORIG") - fi - # package already installed, use .orig file for comparisons - if [ -f "$activeFile.orig" ]; then - activeFile="$activeFile.orig" - fi + local fileList=$(cat "$pkgFileSets/fileList") + local versionList=($(ls -d "$pkgFileSets"/v*)) + local activeFile="" + local baseName="" + local file="" + + for file in $fileList ; do + baseName=$(basename "$file") + activeFile=$file + if [ -f "$pkgFileSets/$baseName.ALT_ORIG" ]; then + activeFile=$(cat "$pkgFileSets/$baseName.ALT_ORIG") + fi + # package already installed, use .orig file for comparisons + if [ -f "$activeFile.orig" ]; then + activeFile="$activeFile.orig" + fi - # can't process if no Venus file - if [ ! -f "$activeFile" ]; then - logMessage "ERROR $venusVersion $baseName no original file" - touch "$fileSet/$baseName.NO_ORIG" - touch "$fileSet/$baseName.NO_REPLACEMENT" - touch "$fileSet/INCOMPLETE" + # can't process if no Venus file + if [ ! -f "$activeFile" ]; then + logMessage "ERROR $venusVersion $baseName no original file" + touch "$fileSet/$baseName.NO_ORIG" + touch "$fileSet/$baseName.NO_REPLACEMENT" + touch "$fileSet/INCOMPLETE" + continue + fi + + # skip checks if replacement file already exists in file set + if [ -f "$fileSet/$baseName" ]; then + rm -f "$fileSet/$baseName.NO_REPLACEMENT" + continue + fi + + # look for a match in another version + matchFound=false + for v2 in ${versionList[@]} ; do + otherVersion=$(basename $v2) + otherVersionDir="$pkgFileSets/$otherVersion" + otherFile="$pkgFileSets/$otherVersion/$baseName" + + if [ "$otherVersion" == "$venusVersion" ]; then continue fi - # look for a match in another version - matchFound=false - for v2 in ${versionList[@]} ; do - otherVersion=$(basename $v2) - otherVersionDir="$pkgFileSets/$otherVersion" - otherFile="$pkgFileSets/$otherVersion/$baseName" - - if [ "$otherVersion" == "$venusVersion" ]; then - continue - fi - # skip symbolic links and nonexistent originals - if [ ! -f "$otherFile.orig" ] || [ -L "$otherFile.orig" ] ; then - continue - fi - cmp -s "$activeFile" "$otherFile.orig" > /dev/null - # files match - if [ $? -eq 0 ]; then - matchFound=true - break - fi - done - - if $matchFound ;then - ln -s "../$otherVersion/$baseName.orig" "$fileSet/$baseName.orig" + # skip symbolic links and nonexistent originals + if [ ! -f "$otherFile.orig" ] || [ -L "$otherFile.orig" ] ; then + continue + fi + cmp -s "$activeFile" "$otherFile.orig" > /dev/null + # files match + if [ $? -eq 0 ]; then + matchFound=true + break + fi + done + + if $matchFound ;then + ln -s "../$otherVersion/$baseName.orig" "$fileSet/$baseName.orig" + rm -f "$fileSet/$baseName.NO_ORIG" + # if other file set contains a replacement file, link to it + if [ -f "$otherFile" ]; then ln -s "../$otherVersion/$baseName" "$fileSet/$baseName" - rm -f "$fileSet/$baseName.NO_ORIG" rm -f "$fileSet/$baseName.NO_REPLACEMENT" - # 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" + # 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.NO_REPLACEMENT" ]; then touch "$fileSet/$baseName.NO_REPLACEMENT" - touch "$fileSet/INCOMPLETE" fi - done - 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 + done if [ -f "$fileSet/INCOMPLETE" ]; then logMessage "ERROR: incomplete file set for $venusVersion - can't continue" @@ -449,6 +475,7 @@ endScript () if [ $scriptAction == 'INSTALL' ] ; then # indicate installation was successful, so reinstalls can use the existing options in the future touch "$setupOptionsDir/optionsSet" + # set up reinstallMods to run this script again after a VenusOS update if [ ! -f "$reinstallScriptsList" ] || [ $(grep -c "$fullScriptName" "$reinstallScriptsList") == 0 ]; then logMessage "adding $shortScriptName" to $(basename "$reinstallScriptsList") @@ -500,57 +527,84 @@ endScript () else # if reboot needed ask user if it should be done now if $rebootNeeded ; then - - yesNoPrompt "Reboot system now (y) or issue a reboot manually later (n): " - if $yesResponse ; then - echo "rebooting ..." - reboot + if $deferReboot ; then + exit $exitReboot else - echo "system must be rebooted to finish installation and activate components" + yesNoPrompt "Reboot system now (y) or issue a reboot manually later (n): " + if $yesResponse ; then + echo "rebooting ..." + reboot + else + echo "system must be rebooted to finish installation and activate components" + fi fi elif $runAgain ; then echo "$shortScriptName NOT completed" echo " run it again manually to finish" echo " or reboot the system to finish automatically" + exit $exitSuccess else logMessage "completed" + exit $exitSuccess fi fi } ######## this code is executed in-line when CommonResources is sourced -# check for reinstall (first parameter to setup script command line) +# check for reinstall parameter # set $scriptAction to control work following the source command -# if second parameter is "force" then the installedFlag is not checked -if [ $# -gt 0 ] && [ $1 == $reinstallParam ] ; then - if [ $# -ge 2 ] && [ $2 == "force" ]; then - scriptAction='INSTALL' +# if "force" is also provided on the command line, then the installedFlag is not checked +# 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 prompt for a user reboot, but return exitReboot +# This is used by the manual package install called from SetupHelper setup script +# command line parameters may appear in any order + +reinstall=false +force=false +deferReboot=false +while [ $# -gt 0 ]; do + case $1 in + $reinstallParam) + reinstall=true + ;; + "force") + force=true + ;; + "deferReboot") + deferReboot=true + ;; + *) + esac + shift +done + +if $reinstall ; then + if $force ; then + scriptAction='INSTALL' elif [ ! -f "$installedFlag" ]; then scriptAction='INSTALL' else exit fi runningAtBoot=true -# not a reinstall, ignore the installed flag and continue with MANUAL installation + logToConsole=false + +# not a reinstall, continue with MANUAL installation else runningAtBoot=false - pruneSetupLogFile fi -_checkFileSets - -if [ scriptAction != 'UNINSTALL' ];then - if [ ! -d "$setupOptionsRoot" ]; then - logMessage "creating root setup options directory $setupOptionsRoot" - mkdir $setupOptionsRoot - fi - - if [ ! -d "$setupOptionsDir" ]; then - logMessage "creating package options directory to $setupOptionsDir" - mkdir $setupOptionsDir - fi +if [ ! -d "$setupOptionsRoot" ]; then + logMessage "creating root setup options directory $setupOptionsRoot" + mkdir $setupOptionsRoot fi +if [ ! -d "$setupOptionsDir" ]; then + logMessage "creating package options directory to $setupOptionsDir" + mkdir $setupOptionsDir +fi +_checkFileSets diff --git a/LogHandler b/LogHandler index 244dfd1..639c194 100755 --- a/LogHandler +++ b/LogHandler @@ -11,6 +11,11 @@ setupLogFile="/var/log/SetupHelper" +# enable logging to console +# scripts can disable logging by setting +# logToConsole to false AFTER sourcing LogHandler +logToConsole=true + # output the last 100 lines of the log file to the console # the full path to the log file should be passed in $1 # converts the tai64 time stamp to human readable form @@ -38,7 +43,7 @@ displayLog () logMessage () { # to console - if ! $runningAtBoot ; then + if $logToConsole ; then echo "$*" fi @@ -57,17 +62,3 @@ logMessage () echo "$shortScriptName: $*" | tai64n >> $packageLogFile fi } - - -# truncates the log file if it's larger than 2000 lines -# it is called from the initialization code in CommonResources -# if NOT running at boot time - -pruneSetupLogFile () -{ - if [ $(wc -l $setupLogFile | awk '{print $1}') -gt 2000 ]; then - tail -1000 $setupLogFile > $setupLogFile.tmp - mv $setupLogFile.tmp $setupLogFile - logMessage "log file truncated to last 1000 lines" - fi -} diff --git a/ReadMe b/ReadMe index 9155910..a02cba4 100644 --- a/ReadMe +++ b/ReadMe @@ -1,14 +1,23 @@ -The SetupHelper package provides a set of utilities to simplfy - installing modifications to Victron Venus Os - and includes a mechanism to automatically reinstall them following a Venus OS update +The SetupHelper package provides: + a set of utilities to simplfy installing modifications to Victron Venus Os + a mechanism to automatically reinstall them following a Venus OS update + an automatic update mechanism to keep packages up to date + from GitHub archives or USB stick + a manual package installation mechanism from GitHub archives or USB stick + +Supported pacages are: + SetupHelper GeneratorConnector GuiMods RpiDisplaySetup RpiGpioSetup TankRepeater + +More information is provided below. Setup: -The easiest way to install this code IF the Venus device has internet access is to run the following command: +There are two methods to fetch a package archive. + +IF the Venus device has internet access is to run the following command: wget -qO - https://github.com/kwindrem/SetupHelper/archive/current.tar.gz | tar -xzf - -C /data -mv /data/SetupHelper-current /data/SetupHelper -If not, do the following a computer that does have internet access +If the Venus device does not have internet access, you will need to fetch the archive using a computer that does have internet access: click on this link in a web browser: https://github.com/kwindrem/SetupHelper/archive/current.tar.gz @@ -18,41 +27,132 @@ copy the venus-data.tar.gz to a USB stick, put the stick in the Venus device and reboot. When Venus boots, it will unarchive the file to /data/SetupHelper-current +Move the directory into it's active locaiton: +rm -rf /data/SetupHelper mv /data/SetupHelper-current /data/SetupHelper -Then move rcS.local to /data, or edit that file if it already exists and add: +Finally, run the setup script to complete the installation +/data/SetupHelper/setup + +Use the manual or automatic update mechnism for future updates + +Description: + +There are two parts to SetupHelper: +1) Install and update utilities help a user manage packages. + +2) CommonResources helps in writing scripts to perform the installation. + +The latter is of concern only to those writing new Venus modificaitons +or modifying an existing setup script. +These are described in detail later in the document. + +SetupHelper package management: + +The intent of this part of SetupHelper is to minimize the effort needed +to install and update packages. + +packageAutoUpdater handles automatic package updates. + +There are two methods and they should NOT be used at the same time: USB and GitHub. + +GitHub updates are enabled from the setup menu (g) option. + +If enabled, automatic GitHub update checks will download and install the GitHub version +if it is newer than the current version. + +One update check is made every 10 minutes, so it takes up to an hour check all packages. + +If you are experimenting with modificaitons and wish to avoid GitHub updates overriding your work, +disable automatic updates. + + +USB updates are handy when the Venus device is not connected to the internet. - /data/SetupHelper/reinstallMods +To use the USB update process: + Navigate to the GitHub, repo, click on tags and select "current". + Choose the .tar.gz download link. + (Do not download from the Code tab or download the .zip file. These won't work.) + Copy the archive files to a USB memory stick. + Insert the stick in the Venus device. -You can alternatively use rc.local as explained below +Regardless the update source (USB or GitHub), +packageAutoUpdater replaces the current copy on the Venus device with the archive contents. +Then the package's setup script is run to reinstall the modificaitons. +No user interaction is needed in this process. -The following lines should be inclueded in the setup script to use the functions and variables provided here +A USB package update disables automatic GitHub updates to avoid conflicts. +(GitHub and files on a USB stick could have different timestamps.) + +packageAutoUpdater runs in the background (as a service). + + +packageInstaller provides the opportunity to install packages for the first time, or reinstall them. +For each package, packageInstaller prompts the user to install that package. +Reinstallation is not allowed if automatic updates are enabled. +Those packages are skipped. +The user is prompted to disable automaitc updates while running packageInstaller +before checking for packages. + +Packages can be installed from eithera USB memory stick or from GitHub. +After the package is copied to the Venus device it's setup script is run +and the user must answer the prompts to complete the instalation. + +The method of putting an archive on a USB stick for manaul install is the same as the one for automatic updates. + +As with package updates, GitHub is the desired source if your Venus device has an internet connection. + +packageInstaller is called from the SetupHelper setup script by choosing the package (p) option at the main prompt. +The packageInstaller is a shell script and can also be run manually: +/data/SetupHelper/manualUpdate + + + +Setup script aids: + +SetupHelper provides a number of functions and variables that simplfy scripts written to +install Venus OS mods. The remainder of this ReadMe describes the resources available and +how to incorporate them into a setup script. + +Read on if you are intersting in writing a package setup script (or modifying an existing one) +or understanding how the mechanism works. + +To use SetupHelper, the script must be written to include CommonResources, +then use the functions included in CommonResources to carry out the modiciations. #### following lines incorporate SetupHelper utilities into this script # Refer to the SetupHelper ReadMe file for details. -source "/data/SetupHelper/CommonResources" +source "/data/SetupHelper/CommonResources" #### end of lines to include SetupHelper A Venus software update overwrites any modifications to the root partition. It is therefore necessary to reinstall the modifications following the Venus update. -For automatic reinstallation of any modificaitons using SetupHelper following a Venus update, - add the following line in /data/rcS.local or /data/rc.local to call the reinstaller: - - /data/SetupHelper/reinstallMods +Running setup for any package using SetupHelper will install the necessary code to reinstall packages +during system boot Typically, rcS.local is used because it runs prior to starting services so conflicts with running services can be avoided (services don't need to be restarted after they've been modified) and if a reboot is required, it happens faster -If your setup script needs resources launched later in boot, use /data/rc.local +If your setup script needs resources launched later in boot, change the following line in CommonResources + +rcLocal="/data/rcS.local" + +to + +rcLocal="/data/rc.local" + +If you have already run any setup scripts and want to make this change, make sure to edit /data/rcS.local +to remove the call to reinstallMods or it will be called twice during boot! reinstallScriptsList is a list of setup scripts, one per line that will be called from reinstallMods. Scripts in this list are called EVERY time the system boots. The script must avoid repeating work if it can be avoided. +SetupHelper tools (CommonResources) handles this for the script write. reinstallScriptsList is hard-coded to reside in the /data directory. The location must match in CommonResources, and in reinstallMods @@ -219,8 +319,9 @@ fileList contains a list of Venus files to be managed by this package. Packages may also contain files that do not exist in the stock Venus image. These are NOT included in fileList! -The special file NOT_COMPATIBLE in a file set indicates the package is not compatible with this Venus version - CommonResources will prevent the package installation or force an uninstall if it already has been +obsoleteVersion is a file optionally contained in the package directory. +It indicates a Venus OS version at which the package is no longer compatible. +CommonResources prevents the package from being installed for the specified version and all those following it. Use full paths/name for all files to avoid problems when running the script from other locations such as the boot code, and quote them in case the names contain spaces. @@ -302,4 +403,3 @@ LogHandler is a logging and log display mechanism. It is sourced by CommonResour The latter must be initialized in setup script code If no package log file exists, $packageLogFile shoudl remain null "" - diff --git a/ServiceResources b/ServiceResources new file mode 100755 index 0000000..f39be20 --- /dev/null +++ b/ServiceResources @@ -0,0 +1,161 @@ +# ServiceManager for SetupHelper +# contains a functions to install, remove, start and stop a package's service + +# managing a normal package's service is straight forward +# +# normally, services are connected via a symbolic link, but to avoid issues with +# updating package files, the service directory is COPIED to the /service directory instead. +# +# If the active copy of the service already exists, the run and log/run files are updated +# ONLY if there are changes. This leaves other files managed by supervise untouched. +# +# For all functions, $1 specifies the package name + + +# startService and stopService start and stop the service, respectively +# the 'down' flag is also cleared/set to control service runs in the future +# startService will cause the service to stop then start again !!! + +startService () +{ + # no service to remove + if [ ! -e "/service/$pkg" ]; then + return + fi + # no package specified + if [ $# -lt 1 ]; then + return + fi + + local pkg=$1 + + rm -f "/service/$pkg/down" + svc -t "/service/$pkg" +} + + +stopService () +{ + # no service to remove + if [ ! -e "/service/$pkg" ]; then + return + fi + # no package specified + if [ $# -lt 1 ]; then + return + fi + + local pkg=$1 + + touch "/service/$pkg/down" + svc -d "/service/$pkg" +} + +# killServiceProcesses makes sure there are no services +# internal function only !!!! +# must be called from other functions that have already made the necessary checks + +_killServiceProcesses () +{ + local pkg=$1 + ps -l | grep $pkg | grep -v -e grep -e $$ | awk '{print $3}' | xargs kill > /dev/null 2>&1 +} + +# +# removeService cleanly removes the service +# + +_removeService () +{ + # stop the service + svc -d "/service/$pkg" + + # remove the active service directory + rm -rf "/service/$pkg" + + # kill related processes + _killServiceProcesses $pkg +} + +removeService () +{ + # no service to remove + if [ ! -e "/service/$pkg" ]; then + return + fi + # no package specified + if [ $# -lt 1 ]; then + return + fi + + local pkg=$1 + + logMessage "removing $pkg service" + _removeService $pkg +} + + +# installService adds the service to the /service directory or updates an existing one +# +# If the service does not yet exist, it will start immediately unless +# it includes the 'down' flag file. This behavior is up to the service writer. +# +# If the service already exists, installService will stop it, +# update the service files and stop all child processes +# Then restart the service unless the down flag is set +# + +installService () +{ + # no service to install + if [ ! -e "$scriptDir/service" ]; then + return + fi + # no package specified + if [ $# -lt 1 ]; then + return + fi + + local pkg=$1 + local serviceRestartNeeded=false + + if [ -L "/service/$pkg" ]; then + logMessage "$pkg removing old service (was symbolic link)" + _removeService $pkg + fi + # service not yet installed, COPY service directory to the active locaiton + if [ ! -e "/service/$pkg" ]; then + logMessage "$pkg installing service" + cp -R "$scriptDir/service" "/service/$pkg" + # service already installed - only copy changed files + else + if [ -f "$scriptDir/service/run" ]; then + cmp -s "$scriptDir/service/run" "/service/$pkg/run" > /dev/null + if [ $? != 0 ]; then + cp "$scriptDir/service/run" "/service/$pkg/run" + serviceRestartNeeded=true + fi + fi + if [ -f "$scriptDir/service/log/run" ]; then + cmp -s "$scriptDir/service/log/run" "/service/$pkg/log/run" > /dev/null + if [ $? != 0 ]; then + cp "$scriptDir/service/log/run" "/service/$pkg/log/run" + serviceRestartNeeded=true + fi + fi + if $serviceRestartNeeded ; then + logMessage "$pkg updated service - restarting" + # stop the service + svc -d "/service/$pkg" + # kill related processes + _killServiceProcesses $pkg + # bring service back up + if [ ! -f "/service/down" ]; then + svc -u "/service/$pkg" + fi + fi + fi +} + + + diff --git a/UpdateResources b/UpdateResources new file mode 100755 index 0000000..f059b3f --- /dev/null +++ b/UpdateResources @@ -0,0 +1,153 @@ + +# This file provides utilities for automatic and manual package updates/installation +# +# Include the following lines in the update scripts +# source "/data/SetupHelper/LogHandler" +# source "/data/SetupHelper/UpdateResources" + +allPackages="GeneratorConnector GuiMods RpiDisplaySetup RpiGpioSetup TankRepeater SetupHelper" + +gitHubUser="kwindrem" +gitHubBranch="current" + +# directory that holds script's options +setupOptionsRoot="/data/setupOptions" + +exitReboot=123 + +# get the package from a USB file +# The package is left in $package-$gitHubBranch for processing later +# $1 is the name of the package +# returns 0 if updated should NOT occur or 1 if update is acceptable for update + +getFromUsb () +{ + local package=$1 + local packageArchive="/media/$dir/$package"-$gitHubBranch.tar.gz + local packageDir="/data/$package" + local archiveTimeStamp="" + local lastUpdate="" + + # archive found on USB stick - process it + if [ -f "$packageArchive" ]; then + tar -xzf "$packageArchive" -C /data + if [ ! -d "$packageDir-$gitHubBranch" ]; then + logMessage "ERROR: $packageArchive did not contain $package" + return 0 + fi + fi + + # get the timeStamp from local copy of package + if [ -f "$packageDir/timeStamp" ]; then + lastUpdate=$(cat "$packageDir/gitHubTimeStamp") + else + lastUpdate="" + fi + + # get archive timeStamp + archiveTimeStamp=$(cat "/data/$package-$gitHubBranch/timeStamp") + if [ -z $archiveTimeStamp ]; then + logMessage "ERROR: No timestamp in $package archive - can't update" + return 0 + elif [ -z $lastUpdate ]; then + logMessage "WARNING: no timestamp for $package current installation - proceeding" + return 1 + elif (( $lastUpdate >= $archiveTimeStamp )); then + if $logToConsole ; then + echo "$package is up to date" + fi + return 0 + # archive is newer + else + return 1 + fi +} + + +# get the package from a GitHub +# The package is left in $package-$gitHubBranch for processing later +# $1 is the name of the package +# returns 0 if updated should NOT occur or 1 if update is acceptable for update + +getFromGitHub () +{ + local package=$1 + local packageDir="/data/$package" + local archiveTimeStamp="" + local lastUpdate="" + # get the timeStamp from local copy of package + if [ -f "$packageDir/timeStamp" ]; then + lastUpdate=$(cat "$packageDir/timeStamp") + else + lastUpdate="" + fi + + # fetch archive timeStamp + archiveTimeStamp=$(wget -qO - https://raw.githubusercontent.com/$gitHubUser/$package/$gitHubBranch/timeStamp) + if [ -z $archiveTimeStamp ]; then + logMessage "ERROR: Can't access GitHub archive or no timestamp for $package - can't update" + return 0 + elif [ -z $lastUpdate ]; then + logMessage "WARNING: no timestamp for $package current installation - proceeding" + elif (( $lastUpdate >= $archiveTimeStamp )); then + if $logToConsole ; then + echo "$package is up to date" + fi + return 0 + fi + # update the package and reinstall it + wget -qO - https://github.com/$gitHubUser/$package/archive/$gitHubBranch.tar.gz | tar -xzf - -C /data + if [ ! -d "$packageDir-$gitHubBranch" ]; then + logMessage "ERROR: $packageArchive did not contain $package" + return 0 + else + return 1 + fi +} + + +# install the archive and run setup script +# +# $1 is the flag to allow running the script with user interaction + +doUpdate () +{ + local pkgService="/service/$package" + local pkgServiceDir="$packageDir/service" + + if [ $# > 0 ] && [ $1 == 'prompting' ]; then + installOk=true + else + installOk=false + fi + + rm -rf "$packageDir" + mv "$packageDir-$gitHubBranch" "$packageDir" + if [ -f "$packageDir/setup" ]; then + # if options have been set previously, reinstall automatically + if [ -f "$setupOptionsRoot/$package/optionsSet" ]; then + logMessage "reinstalling $package" + "$packageDir/setup" "reinstall" "force" + # defer reboot until all updates and reinstalls have been done + if [ $? == $exitReboot ] ; then + rebootNeeded=true + fi + # no options set, run manual setup - but defer reboot until all scripts have been run + elif $installOk ; then + logMessage "running $package setup script - choose install to complete installation" + "$packageDir/setup" "deferReboot" + if [ $? == $exitReboot ] ; then + rebootNeeded=true + fi + else + logMessage "$package can't reinstall - options from previous install not found" + fi + fi + if [ -e "$pkgServiceDir" ]; then + if [ ! -f "$pkgService" ]; then + ln -s "$pkgServiceDir" "$pkgService" + logMessage "restarting $package service" + svc -u "$pkgService" + fi + fi +} diff --git a/packageAutoUpdater b/packageAutoUpdater new file mode 100755 index 0000000..9c548a9 --- /dev/null +++ b/packageAutoUpdater @@ -0,0 +1,138 @@ +#!/bin/sh + +# This script keeps Venus modification packages up to date +# It looks on GitHub for package updates +# The timestamp of the current tag on GitHub is compatred to the one that was last installed +# if GitHub is newer, the local copy is updated and the package is reinstalled +# +# This script also checks for updates on a USB stick and gives priority to that location +# +# An automatic update will only occur if the package is installed and the autoUpdate setup option is set +# +# the script runs as a service so any updates that also update this script will restart automatically + +# get the full, unambiguous path to this script +scriptDir="$( cd "$(dirname $0)" >/dev/null 2>&1 ; /bin/pwd -P )" +packageName=$(basename "$scriptDir") +shortScriptName=$(basename "$scriptDir")/$(basename "$0") + +source "/data/SetupHelper/LogHandler" + +# this flag is tested by LogHandler to determine if messages should be output to the console +logToConsole=false + +source "/data/SetupHelper/UpdateResources" + + +#### main code starts here + +logMessage "starting up" + +checkUSB=true +lastUpdateTime=0 +checkingPackage=false +updateSetupHelper=false + +# 10 minutes between GitHub checks to minimize network traffic +gitHubCheckDelay=600 + +# loop forever +while true ; do + rebootNeeded=false + updatedFromUsb=false + + # loop through packages, but don't look at new package until time has expired + # this loop will run every 5 seconds even if waiting to access GitHub again + # this permits detection of USB media during the long wait for the next GitHub check + i=0 + pkgArray=($allPackages) + while (( i < ${#pkgArray[@]} )); do + # look for new package + if ! $checkingPackage ; then + package=${pkgArray[i]} + packageDir="/data/$package" + doUpdate=false + setupOptionsDir="$setupOptionsRoot"/$package + # package has been installed, look for it's archives + if [ -f "$setupOptionsDir/optionsSet" ]; then + checkingPackage=true + fi + fi + # if no removable media is present allow usbUpdates to occur next time there is + # this prevents repeated processing of same files + mediaList=($(ls /media)) + if [ -z $mediaList ] ; then + checkUSB=true + fi + if $checkingPackage ; then + # media found, look for package archives + if $checkUSB && [ ! -z $mediaList ]; then + for dir in ${mediaList[@]} ; do + getFromUsb $package + if [ $? -eq 1 ]; then + logMessage "found $package on USB" + updatedFromUsb=true + doUpdate=true + if [ -f "$setupOptionsRoot/SetupHelper/autoGitHubUpdate" ]; then + logMessage "USB update disables GitHub updates - must be reenabled manually !!" + rm -f "$setupOptionsRoot/SetupHelper/autoGitHubUpdate" + fi + break; + fi + done + checkingPackage=false + fi + if [ -f "$setupOptionsRoot/SetupHelper/autoGitHubUpdate" ]; then + currentTime=$(date '+%s') + # wait between GitHub updates to minimize traffic + if (( $currentTime >= $lastUpdateTime + $gitHubCheckDelay )) ; then + lastUpdateTime=$currentTime + getFromGitHub $package + if [ $? -eq 1 ]; then + logMessage "found $package on GitHub" + doUpdate=true + fi + checkingPackage=false + fi + fi + if $doUpdate ; then + # defer updating SetupHelper since this would terminate this script before all actions have been completed + if [ $package == "SetupHelper" ]; then + updateSetupHelper=true + # update the package with user interaction if it is needed to do a full install (not reinstall) + else + # update via unattended reinstall only + logMessage "updating $package" + doUpdate $package + fi + else + rm -rf "$packageDir-$gitHubBranch" + fi + fi + # current package was checked - move on to the next one + # if checking was deferred, say on this package + if ! $checkingPackage ; then + ((i++)) + fi + sleep 1 + done + + # prevent further USB updates until media is removed and reinserted + if $updatedFromUsb ; then + checkUSB=false + fi + + # continue execution in packageAutoUpdateCleanup + # so that this script and the service can exit cleanly + if $updateSetupHelper || $rebootNeeded ; then + logMessage "continuing in cleanup script - $packageName exiting" + + nohup "$scriptDir/packageAutoUpdateCleanup" $updateSetupHleper $rebootNeeded 2>&1 | awk '{print "packageAutoUpdater" $0}'| tai64n >> /var/log/SetupHelper & + + # shutdown the service which runs this script - this will end this script + svc -d "/service/SetupHelper" + + # wait here for service to end the script + sleep 10000 + fi +done diff --git a/packageAutoUpdaterCleanup b/packageAutoUpdaterCleanup new file mode 100755 index 0000000..130e28e --- /dev/null +++ b/packageAutoUpdaterCleanup @@ -0,0 +1,36 @@ +#!/bin/sh + +# This script keeps cleans up at the end of packageAutoUpdater processing +# It is called as a nohup (no hang up) process so that the main script can terminate cleanly +# Mainly, this script will run SetupHelper's setup script to reinstall itself +# which can't be done from packageAutoUpdater. Doing so would abort the installation prematurely. +# This script also reboots the system if needed. + +# get parameters from command line +updateSetupHelper=$1 +rebootNeeded=$2 + +# get the full, unambiguous path to this script +scriptDir="$( cd "$(dirname $0)" >/dev/null 2>&1 ; /bin/pwd -P )" +shortScriptName=$(basename "$scriptDir")/$(basename "$0") + +source "/data/SetupHelper/LogHandler" + +# this flag is tested by LogHandler to determine if messages should be output to the console +logToConsole=false + +source "/data/SetupHelper/UpdateResources" + +# give calling script time to stop SetupHelper service +sleep 2 + +if $updateSetupHelper ; then + logMessage "running SetupHelper setup script to reinstall" + doUpdate "SetupHelper" +fi + +# reboot now if any script reboots were indicated +if $rebootNeeded ; then + logMessage "rebooting ..." + reboot +fi diff --git a/packageInstaller b/packageInstaller new file mode 100755 index 0000000..a48f268 --- /dev/null +++ b/packageInstaller @@ -0,0 +1,138 @@ +#!/bin/bash + +# This script prompts for manual installation of packages from the allPackages list +# it is called from SetupHelper/setup or can be run from the command line +# Packages are installed either from GitHub or from a USB stick +# + +source "/data/SetupHelper/CommonResources" +source "/data/SetupHelper/UpdateResources" + +echo +echo "This script allows Venus modificaiton packages to be installed" +echo "Previously installed packages are updated automatically, not from this script" +echo "If updating from USB stick, insert it now before proceeding" +echo +while true ; do + read -p "Select the package source GitHub (g) or USB (u): " response + case $response in + [gG]*) + updateFromUsb=false + break;; + [uU]*) + updateFromUsb=true + break;; + *) + ;; + esac +done + +if $updateFromUsb ; then + # wait up to 20 seconds for USB stick + timeoutCount=20 + /bin/echo -n "Waiting for USB media" + while true ; do + mediaList=($(ls /media)) + if [ ! -z $mediaList ] ; then + break; + elif ((timeoutCount-- == 0)); then + echo + echo "no usb media found - exiting" + exit + else + /bin/echo -n "." + sleep 1 + fi + done + echo +fi + +# allow reinstallations if auto updates are not enabled +# or if they are disabled here +reinstallOk=false +if [ ! -f "$setupOptionsRoot/SetupHelper/$setupOptionsRoot/SetupHelper" ]; then + reinstallOk=true +else + echo "manual package reinstallation may conflict with automatic updates" + echo "automatic must be disabled to manually reinstall packages" + yesNoPrompt "Do you wish to disable automatic updates? (y/n): " + if $yesResponse ; then + logMessage "Disabling automatic package updates" + echo "package reinstallation is now possible" + rm -rf "$setupOptionsRoot/SetupHelper/$setupOptionsRoot/SetupHelper" + reinstallOk=true + fi +fi + + + +rebootNeeded=false +updateSetupHelper=false +for package in $allPackages; do + doUpdate=false + packageDir="/data/$package" + + # if automatic updates are enabled, skip packages that have alreay been installed + if [ -e $packageDir ] && [ -f "$setupOptionsRoot/$package/optionsSet" ]; then + if ! $reinstallOk ; then + echo "$package found on but has already been installed - skipping" + continue + fi + installText="reinstall" + else + installText="install" + fi + + + if $updateFromUsb ; then + for dir in ${mediaList[@]} ; do + getFromUsb $package + if [ $? -eq 1 ]; then + echo + yesNoPrompt "$installText $package from USB? (y/n): " + if $yesResponse ; then + doUpdate=true + fi + break; + fi + done + + # check GitHub + else + getFromGitHub $package + if [ $? -eq 1 ]; then + echo + yesNoPrompt "$installText $package from GitHub? (y/n): " + if $yesResponse ; then + doUpdate=true + fi + fi + fi + if $doUpdate ; then + # defer updating SetupHelper + if [ $package == "SetupHelper" ]; then + updateSetupHelper=true + # update the package with user interaction if it is needed to do a full install (not reinstall) + else + doUpdate 'propmting' + fi + else + rm -rf "$packageDir-$gitHubBranch" + fi +done + +# if an update was found for SetupHelper, update it now +# this prevents stepping on SetupHelper resources +# (this script will complete even if it's replaced) +if $updateSetupHelper ; then + doUpdate "SetupHelper" +fi +if $rebootNeeded ; then + echo + yesNoPrompt "Reboot needed to complete installation, do it now? (y/n): " + if $yesResponse ; then + echo "rebooting ..." + else + echo "You must reboot manually to complete installation" + fi +fi diff --git a/reinstallMods b/reinstallMods index ea1ef86..5230e9e 100755 --- a/reinstallMods +++ b/reinstallMods @@ -13,8 +13,6 @@ source "$scriptDir/LogHandler" # disable outputting log messaged to console and package log runningAtBoot=true -pruneSetupLogFile - # reinstallScriptsList is hard coded to the location below # if changed, it needs to atch in CommonResources, all setup scripts # and reinstallMods diff --git a/service/.DS_Store b/service/.DS_Store new file mode 100644 index 0000000..5ab65af Binary files /dev/null and b/service/.DS_Store differ diff --git a/service/run b/service/run new file mode 100755 index 0000000..9d69edc --- /dev/null +++ b/service/run @@ -0,0 +1,4 @@ +#!/bin/sh +#exec 2>&1 +exec /data/SetupHelper/packageAutoUpdater 2>&1 | awk '{print "packageAutoUpdater" $0}'| tai64n >> /var/log/SetupHelper + diff --git a/setup b/setup new file mode 100755 index 0000000..70fefb0 --- /dev/null +++ b/setup @@ -0,0 +1,114 @@ +#!/bin/bash + +# this script sets up the SetupHelper service +# This service provides automatic and manual updates for Venus modificaiton packages +# + +#### following lines incorporate SetupHelper utilities into this script +# Refer to the SetupHelper ReadMe file for details. + +source "/data/SetupHelper/CommonResources" + +#### end of lines to include SetupHelper + +#### running manually and OK to proceed - prompt for input +if [ $scriptAction == 'NONE' ] ; then + # display innitial message + echo + echo "This package provides support functions and utilities for Venus modificaiton packages" + echo "Packages are automatically reinstalled following a Venus OS update" + echo "Packages may also be automatically updated from GitHub" + echo " or a USB stick" + echo "Prevouslly uninstalled packages can also be installed and configured" + echo " as an option from the menu either from GitHub or from a USB stick" + echo + echo "If internet access is not available, you can manually update/install from a USB stick" + echo "Note: installing from a USB stick disables automatic GitHub updates" + echo + + if [ -f "$setupOptionsDir/optionsSet" ]; then + enableReinstall=true + else + enableReinstall=false + fi + + response='' + fullPrompt=true + while true; do + if $fullPrompt ; then + echo + echo "Available actions:" + echo " Install and activate (i)" + if $enableReinstall ; 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 setup log (s) outputs the last 100 lines of the log" + echo + echo " Enable/disable automatic GitHub package updates (g)" + echo " Manually install packages from GitHub or USB stick (p)" + echo + fullPrompt=false + fi + /bin/echo -n "Choose an action from the list above: " + read response + case $response in + [iI]*) + scriptAction='INSTALL' + break;; + [rR]*) + if $enableReinstall ; then + scriptAction='INSTALL' + break + fi + ;; + [uU]*) + scriptAction='UNINSTALL' + break + ;; + [qQ]*) + exit + ;; + [sS]*) + displayLog $setupLogFile + ;; + [gG]*) + if [ -f "$setupOptionsDir/autoGitHubUpdate" ]; then + echo "Automatic GitHub updates are currently ENABLED" + else + echo "Automatic GitHub updates are currently disabled" + fi + read -p "Enable (e) / Disable (d) / no change(cr)?: " response + if [ ! -z $response ]; then + if [ $response == 'e' ] || [ $response == 'E' ]; then + logMessage "enabling automatic GitHub package updates" + touch "$setupOptionsDir/autoGitHubUpdate" + elif [ $response == 'd' ] || [ $response == 'D' ]; then + logMessage "disabling automatic GitHub package updates" + rm -f "$setupOptionsDir/autoGitHubUpdate" + fi + fi + ;; + [pP]*) + "$scriptDir/packageInstaller" + fullPrompt=true + ;; + *) + esac + done + + # next step is to install + scriptAction='INSTALL' +fi + +if [ $scriptAction == 'INSTALL' ] ; then + installService $packageName +fi + +if [ $scriptAction == 'UNINSTALL' ] ; then + removeService $packageName +fi + +# thats all folks - SCRIPT EXITS INSIDE THE FUNCTION +endScript diff --git a/timeStamp b/timeStamp index 931f185..732411f 100644 --- a/timeStamp +++ b/timeStamp @@ -1 +1 @@ -1623005150 +1623542549