diff --git a/README.md b/README.md index dfa5397..9dcbfbb 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,22 @@ Once you have your wallet ready for validation, you can start the foundation val $ python3 validators/openvalidators/neuron.py --wallet.name --wallet.hotkey ``` +# Automatic Mode +You can run the validator with automatic execution and updates so that it will remain up to date with the latest changes without you needing to continuously update it manually. + +The script `run.sh` in the root of this repository accomplishes this in conjuction with PM2. + +To do this, first ensure you have installed the [`jq` package](https://jqlang.github.io/jq/) on your system. + + On Ubuntu: `sudo apt update && sudo apt install jq` + On OS X: `brew update && brew install jq` + +Once you have installed `jq`, you can start the validator in automatic mode by running the following command: + +```bash +$ pm2 start autorun.sh -- script validators/openvalidators/neuron.py --wallet.name --wallet.hotkey +``` + # Real-time monitoring with wandb integration By default, the validator sends data to wandb, allowing users to monitor running validators and access key metrics in real time, such as: - Gating model loss diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..e84a501 --- /dev/null +++ b/run.sh @@ -0,0 +1,255 @@ +#!/bin/bash + +# Initialize variables +script="" +autoRunLoc=$(readlink -f "$0") +proc_name="auto_run_validator" +args=() +version_location="./openvalidators/__init__.py" +version="__version__" + +args=$@ + +# Check if pm2 is installed +if ! command -v pm2 &> /dev/null +then + echo "pm2 could not be found. To install see: https://pm2.keymetrics.io/docs/usage/quick-start/" + exit 1 +fi + +# Checks if $1 is smaller than $2 +# If $1 is smaller than or equal to $2, then true. +# else false. +version_less_than_or_equal() { + [ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ] +} + +# Checks if $1 is smaller than $2 +# If $1 is smaller than $2, then true. +# else false. +version_less_than() { + [ "$1" = "$2" ] && return 1 || version_less_than_or_equal $1 $2 +} + +# Returns the difference between +# two versions as a numerical value. +get_version_difference() { + local tag1="$1" + local tag2="$2" + + # Extract the version numbers from the tags + local version1=$(echo "$tag1" | sed 's/v//') + local version2=$(echo "$tag2" | sed 's/v//') + + # Split the version numbers into an array + IFS='.' read -ra version1_arr <<< "$version1" + IFS='.' read -ra version2_arr <<< "$version2" + + # Calculate the numerical difference + local diff=0 + for i in "${!version1_arr[@]}"; do + local num1=${version1_arr[$i]} + local num2=${version2_arr[$i]} + + # Compare the numbers and update the difference + if (( num1 > num2 )); then + diff=$((diff + num1 - num2)) + elif (( num1 < num2 )); then + diff=$((diff + num2 - num1)) + fi + done + + strip_quotes $diff +} + +read_version_value() { + # Read each line in the file + while IFS= read -r line; do + # Check if the line contains the variable name + if [[ "$line" == *"$version"* ]]; then + # Extract the value of the variable + local value=$(echo "$line" | awk -F '=' '{print $2}' | tr -d ' ') + strip_quotes $value + return 0 + fi + done < "$version_location" + + echo "" +} + +check_package_installed() { + local package_name="$1" + os_name=$(uname -s) + + if [[ "$os_name" == "Linux" ]]; then + # Use dpkg-query to check if the package is installed + if dpkg-query -W -f='${Status}' "$package_name" 2>/dev/null | grep -q "installed"; then + return 1 + else + return 0 + fi + elif [[ "$os_name" == "Darwin" ]]; then + if brew list --formula | grep -q "^$package_name$"; then + return 1 + else + return 0 + fi + else + echo "Unknown operating system" + return 0 + fi +} + +check_variable_value_on_github() { + local repo="$1" + local file_path="$2" + local variable_name="$3" + + local url="https://api.github.com/repos/$repo/contents/$file_path" + local response=$(curl -s "$url") + + # Check if the response contains an error message + if [[ $response =~ "message" ]]; then + echo "Error: Failed to retrieve file contents from GitHub." + return 1 + fi + + # Extract the content from the response + local content=$(echo "$response" | tr -d '\n' | jq -r '.content') + + if [[ "$content" == "null" ]]; then + echo "File '$file_path' not found in the repository." + return 1 + fi + + # Decode the Base64-encoded content + local decoded_content=$(echo "$content" | base64 --decode) + + # Extract the variable value from the content + local variable_value=$(echo "$decoded_content" | grep "$variable_name" | awk -F '=' '{print $2}' | tr -d ' ') + + if [[ -z "$variable_value" ]]; then + echo "Variable '$variable_name' not found in the file '$file_path'." + return 1 + fi + + strip_quotes $variable_value +} + +strip_quotes() { + local input="$1" + + # Remove leading and trailing quotes using parameter expansion + local stripped="${input#\"}" + stripped="${stripped%\"}" + + echo "$stripped" +} + +# Parse command line arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --script) script="$2"; shift ;; + --name) name="$2"; shift ;; + --*) args+=("$1=$2"); shift ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +# Check if script argument was provided +if [[ -z "$script" ]]; then + echo "The --script argument is required." + exit 1 +fi + +branch=$(git branch --show-current) # get current branch. +echo watching branch: $branch +echo pm2 process name: $proc_name + +# Get the current version locally. +current_version=$(read_version_value) + +# Check if script is already running with pm2 +if pm2 status | grep -q $proc_name; then + echo "The script is already running with pm2. Stopping and restarting..." + pm2 delete $proc_name +fi + +# Run the Python script with the arguments using pm2 +echo "Running $script with the following arguments with pm2:" +echo "${args[@]}" +pm2 start "$script" --name $proc_name --interpreter python3 -- "${args[@]}" + +# Check if packages are installed. +check_package_installed "jq" +if [ "$?" -eq 1 ]; then + while true; do + + # First ensure that this is a git installation + if [ -d "./.git" ]; then + + # check value on github remotely + latest_version=$(check_variable_value_on_github "opentensor/validators" "openvalidators/__init__.py" "__version__ ") + + # If the file has been updated + if version_less_than $current_version $latest_version; then + echo "latest version $latest_version" + echo "current version $current_version" + diff=$(get_version_difference $latest_version $current_version) + if [ "$diff" -eq 1 ]; then + echo "current validator version:" "$current_version" + echo "latest validator version:" "$latest_version" + + # Pull latest changes + # Failed git pull will return a non-zero output + if git pull origin $branch; then + # latest_version is newer than current_version, should download and reinstall. + echo "New version published. Updating the local copy." + + # Install latest changes just in case. + pip install -e ../ + + # Check if script is already running with pm2 + if pm2 status | grep -q $proc_name; then + echo "The script is already running with pm2. Stopping and restarting..." + pm2 delete $proc_name + fi + + # # Run the Python script with the arguments using pm2 + echo "Running $script with the following arguments with pm2:" + echo "${args[@]}" + pm2 start "$script" --name $proc_name --interpreter python3 -- "${args[@]}" + + # Update current version: + current_version=$(read_version_value) + echo "" + + # Restart autorun script + echo "Restarting script..." + ./$(basename $0) $args && exit + else + echo "**Will not update**" + echo "It appears you have made changes on your local copy. Please stash your changes using git stash." + fi + else + # current version is newer than the latest on git. This is likely a local copy, so do nothing. + echo "**Will not update**" + echo "The local version is $diff versions behind. Please manually update to the latest version and re-run this script." + fi + else + echo "**Skipping update **" + echo "$current_version is the same as or more than $latest_version. You are likely running locally." + fi + else + echo "The installation does not appear to be done through Git. Please install from source at https://github.com/opentensor/validators and rerun this script." + fi + + # Wait about 30 minutes + # This should be plenty of time for validators to catch up + # and should prevent any rate limitations by GitHub. + sleep 1800 + done +else + echo "Missing package 'jq'. Please install it for your system first." +fi \ No newline at end of file