#!/bin/bash ######################################################## # # This script will update your existing VanPi OS # to the latest release from the Pekaway VanPi server! # Simply download the script to your VanPi OS and # execute it. # (see https://github.com/Pekaway/VAN_PI for details) # ######################################################## # define variables Server='https://raw.githubusercontent.com/Pekaway/VAN_PI/main/VanPi-Core-OS/' ServerFiles='https://github.com/Pekaway/VAN_PI/raw/main/VanPi-Core-OS/' LfsServerFiles='https://media.githubusercontent.com/media/Pekaway/VAN_PI/main/VanPi-Core-OS/' Version='v2.0.4' ### <--- set new version number VanPi OS NSPanelVersion='0.0.1' ### <--- set new version number NSPanel TouchdisplayVersion='2.0.0' ### <--- set new version number Touchdisplay currentVersion=`cat ~/pekaway/version` # Define the file for logrotate and the desired line value LOGROTATE_CONFIG_FILE="/etc/logrotate.conf" LOGROTATE_TARGET_KEY="maxsize" LOGROTATE_TARGET_VALUE="60M" # Define the file for log2ram and the desired line value LOG2RAM_CONFIG_FILE="/etc/log2ram.conf" LOG2RAM_TARGET_KEY="LOG_DISK_SIZE" LOG2RAM_TARGET_VALUE="120M" steps='10' ### <--- number of total steps for progessbar # prepare variables to be compared VersionToCheck='v2.0.0' # Version that has relevant changes in update script # (if current version number is below that number than this script will execute without the need for confirmation) # (script will not ask for confirmation if started from Node-RED dashboard directly) # create file for progressbar in NR dashboard Progress=/var/log/pekaway-update_progress.log sudo truncate -s 0 ${Progress} sudo chmod 0666 ${Progress} echo "PID="$$ | sudo tee ${Progress} # get the PID sleep 7 echo "Step 1/${steps}: comparing versions" | sudo tee ${Progress} # create logfile and make it writable LOG_FILE=/var/log/pekaway-update_$(date +"%Y_%m_%d-%H_%M").log sudo touch ${LOG_FILE} sudo chmod 0666 ${LOG_FILE} exec 3<&1 coproc mytee { tee ${LOG_FILE} >&3; } exec >&${mytee[1]} 2>&1 # compare current and new version numbers VersionSubstring=${VersionToCheck#*v} currentVersionSubstring=${currentVersion#*v} IFS='.' read -ra currentVersion_array <<< "$currentVersionSubstring" IFS='.' read -ra version_array <<< "$VersionSubstring" currentVersion1=${currentVersion_array[0]} currentVersion2=${currentVersion_array[1]} currentVersion3=${currentVersion_array[2]} newVersion1=${version_array[0]} newVersion2=${version_array[1]} newVersion3=${version_array[2]} echo "This script needs to be run as a sudo user. Press CTRL+C to abort at any time." echo -e "logfile will be at ${LOG_FILE}" ##################################################### # Compare OS version and kernel version # # Get the Debian codename from /etc/os-release OS_CODENAME=$(grep "VERSION_CODENAME" /etc/os-release | cut -d'=' -f2) # Get the current kernel version KERNEL_VERSION=$(uname -r | cut -d'-' -f1) # Get the current Python version PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}') # Define the minimum required OS version (Debian Bookworm) and kernel version MIN_VERSION="bookworm" MIN_KERNEL_VERSION="6.1" # Function to compare two version numbers version_greater_equal() { # Compare version numbers [ "$(printf '%s\n' "$1" "$2" | sort -V | head -n1)" == "$2" ] } # Function to compare OS versions using codename compare_os_versions() { # Array of Debian codenames in chronological order versions=("buster" "bullseye" "bookworm" "trixie") # Find the index of current and minimum versions for i in "${!versions[@]}"; do if [[ "${versions[$i]}" == "$1" ]]; then current_index=$i fi if [[ "${versions[$i]}" == "$2" ]]; then min_index=$i fi done # Compare the index values to determine if an update is needed if [[ "$current_index" -lt "$min_index" ]]; then echo "Error: System is running $1. Please upgrade to $2 or later." | sudo tee ${Progress} exit 1 fi } # Check OS version compare_os_versions "$OS_CODENAME" "$MIN_VERSION" # Check Kernel version if ! version_greater_equal "$KERNEL_VERSION" "$MIN_KERNEL_VERSION"; then echo "Error: Kernel version is $KERNEL_VERSION. Please upgrade to kernel $MIN_KERNEL_VERSION or newer." | sudo tee ${Progress} exit 1 fi # Check Python version if ! version_greater_equal "$PYTHON_VERSION" "$MIN_PYTHON_VERSION"; then echo "Your Python version is $PYTHON_VERSION. Please upgrade to Python $MIN_PYTHON_VERSION or newer." | sudo tee ${Progress} exit 1 fi echo "Your system is running Debian $OS_CODENAME, kernel $KERNEL_VERSION, and Python $PYTHON_VERSION, all of which are up-to-date." echo "Continuing..." ##################################################### if [[ $currentVersion1 -lt $newVersion1 ]]; then echo "currentVersion ($currentVersion) is older than newVersion ($Version)" elif [[ $currentVersion1 -eq $newVersion1 ]] && [[ $currentVersion2 -lt $newVersion2 ]]; then echo "currentVersion ($currentVersion) is older than newVersion ($Version)" needUpdate='true' elif [[ $currentVersion1 -eq $newVersion1 ]] && [[ $currentVersion2 -eq $newVersion2 ]] && [[ $currentVersion3 -lt $newVersion3 ]]; then echo "currentVersion ($currentVersion) is older than newVersion ($Version)" needUpdate='true' elif [[ $currentVersion1 -eq $newVersion1 ]] && [[ $currentVersion2 -eq $newVersion2 ]] && [[ $currentVersion3 -eq $newVersion3 ]]; then echo "currentVersion ($currentVersion) is the same as newVersion ($Version)" needUpdate='false' else echo "currentVersion ($currentVersion) is newer than newVersion ($Version)" needUpdate='false' fi # get confirmation to continue on manual update if [[ "$1" == "node-red-auto-update" ]] || [[ "$needUpdate" == 'true' ]]; then echo -e "not asking for confirmation, proceeding automatically." else while true; do read -r -p "This will update to version ${Version}! Currently you are running version ${currentVersion}. Do you want to continue [y/n]" input case $input in [yY][eE][sS]|[yY]) echo "Ok, proceeding..." break;; [nN][oO]|[nN]) echo "Aborting... bye bye." exit ;; *) echo "Invalid input... please type 'y' (yes) or 'n' (no)" ;; esac done fi sleep 3 echo "Step 2/${steps}: getting new files" | sudo tee ${Progress} # remove packages.txt and package.json if they already exist cd ~/pekaway rm -f updatescript.sh rm -f packages.txt rm -f package.json rm -f pip3list.txt rm -f VanPI_NSPANEL.tft rm -f autoexec.be rm -f /ads_py/simplelevel.py rm -f /ads_py/web2.py rm -f /ds18b20_py/ds18b20.py sudo rm -f /boot/*.tft # delete old .tft for touchdisplay if it exists #delete files to be replaced here (e.g. updates in scripts) #rm -f ~/pekaway/ble_py/supervolt_flybat.py # download new files packages.txt and package.json echo "downloading new files" wget -N --no-use-server-timestamps ${ServerFiles}packages.txt wget -N --no-use-server-timestamps ${ServerFiles}node-red/package.json wget -N --no-use-server-timestamps ${ServerFiles}piplist.txt wget -N --no-use-server-timestamps ${ServerFiles}data/userdata/NSPanel/VanPI_NSPANEL.tft wget -N --no-use-server-timestamps ${ServerFiles}data/userdata/NSPanel/autoexec.be wget -N --no-use-server-timestamps ${ServerFiles}data/ads_py/simplelevel.py wget -N --no-use-server-timestamps ${ServerFiles}data/ads_py/web2.py wget -N --no-use-server-timestamps ${ServerFiles}data/ds18b20_py/ds18b20.py wget -N --no-use-server-timestamps ${ServerFiles}misc/boot_config.txt wget -N --no-use-server-timestamps ${ServerFiles}misc/98-pekaway-tty.rules wget -N --no-use-server-timestamps ${ServerFiles}nginx/pekaway1 wget -N --no-use-server-timestamps ${ServerFiles}misc/pythonsqlite.db # get new files here #wget ${Server}newFilesForUpdate/supervolt_flybat.py wget -N --no-use-server-timestamps ${ServerFiles}touchdisplay/PekawayTouch.tft # create files for mcp inputs if the don't exist # add json into files if they don't exist for i in {1..8} do touch ~/pekaway/mcpinput"$i" touch ~/pekaway/mcpinput"$i"_type relays=$(cat < ~/pekaway/mcpinput"$i" | jq 'has("relays")') dimmers=$(cat < ~/pekaway/mcpinput"$i" | jq 'has("dimmers")') type=$(cat ~/pekaway/mcpinput"$i"_type) echo "Relays$i: $relays" echo "Dimmers$i: $dimmers" echo "Type$i: $type" if [[ "$type" != "switch" && "$type" != "button" ]]; then echo 'switch' > ~/pekaway/mcpinput"$i"_type fi if [[ "$relays" == "true" ]] then if [[ "$dimmers" != "true" ]]; then input=$(jq ' . += {"dimmers":{"d1":false,"d2":false,"d3":false,"d4":false,"d5":false,"d6":false,"d7":false}}' ~/pekaway/mcpinput"$i") echo "$input" > ~/pekaway/mcpinput"$i" fi elif [[ "$relays" != "true" ]]; then echo '{"relays":{"one":false,"two":false,"three":false,"four":false,"five":false,"six":false,"seven":false,"eight":false},"dimmers":{"d1":false,"d2":false,"d3":false,"d4":false,"d5":false,"d6":false,"d7":false}}' > ~/pekaway/mcpinput"$i" fi done # create needed files if they dont exist touch ~/pekaway/combined_temp_chart touch ~/pekaway/combined_ruuvi_chart # move TouchDisplay .tft file to /boot to be able to use SD-card to update Touchdisplay sudo chown root:root PekawayTouch.tft # cannot preserve ownership in root directory sudo rm /boot/*.tft sudo rm /boot/firmware/*.tft sudo mv PekawayTouch.tft /boot/firmware/PekawayTouch${TouchdisplayVersion}.tft # move new files here mv -f ds18b20.py ~/pekaway/ds18b20_py/ds18b20.py mv -f simplelevel.py ~/pekaway/ads_py/simplelevel.py mv -f web2.py ~/pekaway/ads_py/web2.py mv -f pythonsqlite.db ~/pekaway/pythonsqlite.db sudo mv -f boot_config.txt /boot/firmware/config.txt sudo mv -f 98-pekaway-tty.rules /etc/udev/rules.d/98-pekaway-tty.rules sudo mv -f pekaway1 /etc/nginx/sites-available/pekaway1 # create symlink for nginx pekaway1 sudo ln -s /etc/nginx/sites-available/pekaway1 /etc/nginx/sites-enabled/pekaway1 #mv supervolt_flybat.py ~/pekaway/ble_py/supervolt_flybat.py sleep 2 # reload udev rules and nginx sudo udevadm control --reload-rules && sudo udevadm trigger sudo systemctl restart nginx sleep 1 echo "Step 3/${steps}: installing packages" | sudo tee ${Progress} # copy NSPanel .tft file to ~/pekaway/userdata/NSPanel to show up in NR-Dashboard mkdir -p ~/pekaway/userdata/NSPanel cp -f VanPI_NSPANEL.tft ~/pekaway/userdata/NSPanel/VanPI_NSPANEL${NSPanelVersion}.tft cp -f autoexec.be ~/pekaway/userdata/NSPanel/autoexec.be # make a backup of the existing package.json and replace it with the new file cp ~/.node-red/package.json ~/pekaway/nrbackups/package-backup.json cp ~/pekaway/package.json ~/.node-red/package.json # Update package list sudo apt update # Upgrade all installed packages while keeping existing configuration files sudo apt upgrade -y -o Dpkg::Options::="--force-confold" # Install packages listed in ~/pekaway/packages.txt and keep old config files sudo apt install -y -o Dpkg::Options::="--force-confold" $(cat ~/pekaway/packages.txt) # Clean up unnecessary package files to free space sudo apt clean # Remove unused packages and dependencies sudo apt autoremove -y cd ~/.node-red # compare older package.json with new one and ask for merging echo "comparing original package.json with the new one:" sleep 3 echo "Step 4/${steps}: checking package.json" | sudo tee ${Progress} extramodules=$(diff <(jq --sort-keys .dependencies ~/.node-red/package.json) <(jq --sort-keys .dependencies ~/pekaway/nrbackups/package-backup.json) | grep '>') if [[ -n $extramodules ]]; then echo -e "Your original package.json file has the following additonal modules listed:" echo -e "$extramodules" if [[ "$1" == "node-red-auto-update" ]] || [[ "$needUpdate" == 'true' ]]; then echo -e "updating from Node-RED, adding additional lines automatically." # cd ~/.node-red echo `jq -s '.[0] * .[1]' ~/.node-red/package.json ~/pekaway/nrbackups/package-backup.json` > ~/pekaway/nrbackups/package1.json && jq . ~/pekaway/nrbackups/package1.json > ~/pekaway/nrbackups/pretty.json && rm ~/pekaway/nrbackups/package1.json && mv ~/pekaway/nrbackups/pretty.json ~/pekaway/nrbackups/package1.json echo "Missing lines have been added to package.json" echo "New package.json:" cat ~/.node-red/package.json else while true; do read -r -p "Do you want them to be added to the new package.json? [y/n]" input case $input in [yY][eE][sS]|[yY]) # cd ~/.node-red echo `jq -s '.[0] * .[1]' ~/.node-red/package.json ~/pekaway/nrbackups/package-backup.json` > ~/pekaway/nrbackups/package-backup1.json jq . ~/pekaway/nrbackups/package-backup1.json > ~/pekaway/nrbackups/pretty.json mv ~/pekaway/nrbackups/package-backup.json ~/pekaway/package-backup.json # keep backup just in case rm -f ~/pekaway/nrbackups/package1.json mv ~/pekaway/nrbackups/pretty.json ~/.node-red/package.json echo "Missing lines have been added to package.json" echo "New ~/.node-red/package.json:" cat ~/.node-red/package.json break ;; [nN][oO]|[nN]) echo "No modules added to package.json, proceeding..." break ;; *) echo "Invalid input... please type 'y' (yes) or 'n' (no)" ;; esac done fi else echo "modules are identical, proceeding..." fi #install npm modules from package.json sleep 3 echo "Step 5/${steps}: executing npm install" | sudo tee ${Progress} echo "installing npm modules, please stand by..." npm install echo "done" cd ~/pekaway # Install/update python modules locally (user pi) and globally (root) sleep 3 echo "Step 6/${steps}: checking python modules" | sudo tee ${Progress} echo "Installing Python modules with --break-system-packages, please stand by..." sudo -H pip3 install --upgrade pip --break-system-packages sudo pip3 install -r ~/pekaway/piplist.txt --break-system-packages sudo pip3 install bottle --break-system-packages pip3 install -r ~/pekaway/piplist.txt --break-system-packages pip3 install bottle --break-system-packages echo "Done." sleep 3 echo "Step 7/${steps}: checking logrotate and log2ram" | sudo tee ${Progress} # Check logrotate configuration # Check if the file exists if [ ! -f "$LOGROTATE_CONFIG_FILE" ]; then echo "The file $LOGROTATE_CONFIG_FILE does not exist. Exiting this step." exit 0 fi # Check if the line exists and matches the target value if grep -q "^$LOGROTATE_TARGET_KEY $LOGROTATE_TARGET_VALUE" "$LOGROTATE_CONFIG_FILE"; then echo "The line '$LOGROTATE_TARGET_KEY $LOGROTATE_TARGET_VALUE' is already set and correct." else # Check if the line exists with a different value if grep -q "^$LOGROTATE_TARGET_KEY " "$LOGROTATE_CONFIG_FILE"; then echo "The line '$LOGROTATE_TARGET_KEY' exists but has a different value. Updating it..." sudo sed -i "s|^$LOGROTATE_TARGET_KEY .*|$LOGROTATE_TARGET_KEY $LOGROTATE_TARGET_VALUE|" "LOGROTATE_$CONFIG_FILE" else # Add the line if it doesn't exist echo "The line '$LOGROTATE_TARGET_KEY' is missing. Adding it..." sudo echo "$LOGROTATE_TARGET_KEY $LOGROTATE_TARGET_VALUE" >> "$LOGROTATE_CONFIG_FILE" fi fi # Check log2ram configuration # Check if the file exists if [ ! -f "$LOG2RAM_CONFIG_FILE" ]; then echo "The file $LOG2RAM_CONFIG_FILE does not exist. Exiting this step." exit 0 fi # Check if the line exists and matches the target value if grep -q "^$LOG2RAM_TARGET_KEY=$LOG2RAM_TARGET_VALUE" "$LOG2RAM_CONFIG_FILE"; then echo "The line '$LOG2RAM_TARGET_KEY=$LOG2RAM_TARGET_VALUE' is already set and correct." else # Check if the line exists with a different value if grep -q "^$LOG2RAM_TARGET_KEY=" "$LOG2RAM_CONFIG_FILE"; then echo "The line '$LOG2RAM_TARGET_KEY' exists but has a different value. Updating it..." sudo sed -i "s|^$LOG2RAM_TARGET_KEY=.*|$LOG2RAM_TARGET_KEY=$LOG2RAM_TARGET_VALUE|" "$LOG2RAM_CONFIG_FILE" else # Add the line if it doesn't exist echo "The line '$LOG2RAM_TARGET_KEY' is missing. Adding it..." sudo echo "$LOG2RAM_TARGET_KEY=$LOG2RAM_TARGET_VALUE" >> "$LOG2RAM_CONFIG_FILE" fi fi # Restart log2ram echo "Restarting log2ram..." systemctl restart log2ram && echo "log2ram restarted successfully." || echo "Failed to restart log2ram." echo "Step 8/${steps}: backing up flows" | sudo tee ${Progress} # remove downloaded files rm -f ~/pekaway/packages.txt && rm -f ~/pekaway/package.json && rm -f ~/pekaway/piplist.txt # backup Node-RED flows echo "backing up original Node-RED flows" cd ~/pekaway/nrbackups cp ~/.node-red/flows_pekaway.json "flows_pekaway_$(date +%d-%m-%Y_%I:%M:%S%p).json" echo "replacing version number of VanPi OS" # replace version number echo ${Version} >| ~/pekaway/version # set update = true to show up when opening dashboard echo "1" >| ~/pekaway/update # Define the path to the Node-RED settings.js file NR_SETTINGS_FILE="$HOME/.node-red/settings.js" # Define the desired global context key-value pairs declare -A GLOBAL_CONTEXT=( ["zlib"]="require('zlib')" #["os"]="require('os')" # Add more key-value pairs as needed ) # Check if the settings.js file exists if [ ! -f "$NR_SETTINGS_FILE" ]; then echo "The settings.js file does not exist at $NR_SETTINGS_FILE. Exiting." exit 1 fi # Check if functionGlobalContext exists if grep -q "functionGlobalContext:" "$NR_SETTINGS_FILE"; then echo "Found functionGlobalContext in settings.js." # Loop through desired global context entries for KEY in "${!GLOBAL_CONTEXT[@]}"; do VALUE=${GLOBAL_CONTEXT[$KEY]} # Check if the key already exists and is uncommented if grep -q "^ $KEY:$VALUE" "$NR_SETTINGS_FILE"; then echo "$KEY is already included in functionGlobalContext and uncommented." # Check if the key exists but is commented out elif grep -q "^ *// *$KEY:$VALUE" "$NR_SETTINGS_FILE"; then echo "$KEY is commented out. Uncommenting it..." sed -i "s|^ *// *$KEY:$VALUE| $KEY:$VALUE|" "$NR_SETTINGS_FILE" echo "$KEY uncommented." else echo "$KEY is missing. Adding it to functionGlobalContext..." # Insert the key-value pair into functionGlobalContext sed -i "/functionGlobalContext:/,/}/ s|}| $KEY:$VALUE,\n}|" "$NR_SETTINGS_FILE" echo "$KEY added to functionGlobalContext." fi done else echo "functionGlobalContext not found. Adding it with the desired entries..." # Build the functionGlobalContext block dynamically CONTEXT_BLOCK="functionGlobalContext: {\n" for KEY in "${!GLOBAL_CONTEXT[@]}"; do CONTEXT_BLOCK+=" $KEY:${GLOBAL_CONTEXT[$KEY]},\n" done CONTEXT_BLOCK+="}," # Add the block at the end of the file echo -e "\n$CONTEXT_BLOCK" >> "$NR_SETTINGS_FILE" echo "functionGlobalContext added with the desired entries." fi # download new flows and replace the old file sleep 3 echo "Step 9/${steps}: getting new flows" | sudo tee ${Progress} echo "replacing local flows.json file with new one from the server" wget --no-use-server-timestamps -P ~/pekaway/pkwUpdate/ ${LfsServerFiles}node-red/flows_pekaway.json cp ~/pekaway/pkwUpdate/flows_pekaway.json ~/.node-red/flows_pekaway.json echo "update script finished! You can find the logfile at ${LOG_FILE}." rm ~/pekaway/pkwUpdate/flows_pekaway.json sleep 3 echo "Step 10/${steps}: restarting Node-RED..." | sudo tee ${Progress} sleep 5 sudo truncate -s 0 ${Progress} # Get confirmation to continue on manual update if [[ "$1" == "node-red-auto-update" ]]; then echo -e "Not asking for confirmation, rebooting automatically." sleep 3 sudo reboot else while true; do read -r -p "Do you want to reboot now? [y/n] " input case $input in [yY][eE][sS]|[yY]) sudo reboot break ;; [nN][oO]|[nN]) echo "Reboot cancelled. Please remember to reboot for the VanPi system to work properly!" exit ;; *) echo "Invalid input... please type 'y' (yes) or 'n' (no)" ;; esac done fi # when no reboot, only restart nodered sudo systemctl restart nodered.service