├── README.md ├── ironfish ├── README.md └── ironfish.py ├── nym ├── README.md ├── crontab.example ├── nym.conf.example ├── nym.sh └── slash.sh.example ├── solana └── README.md └── tendermint ├── auto_stake └── README.md └── node_status ├── README.md ├── cosmos.conf ├── cosmos.sh ├── crontab ├── curl.md └── name.conf /README.md: -------------------------------------------------------------------------------- 1 | # status 2 | This system will alert you with telegram about jails and inactive status. Also it sends you every hour short info about your nodes status. 3 | 4 |

5 | image 6 |

7 |

8 | image 9 |

10 |

11 | image 12 |

13 |

14 | image 15 |

16 |

17 | image 18 |

19 | 20 | Donations, just in case: 21 | 22 | `SOL >>> 8UM1sHHShTgNa4vjQV6v1SEvi3BwDn4wG1pRRZnbFvRY` 23 | 24 | `BTC >>> bc1qqpllwvwj3vrp6p5qq5t698j6fx2zaxlucrchru` 25 | 26 | `DOT >>> 15rCbyqHZnS6oWon2ntp1JGPBZsTxt66EThMDMxdPxs67Y2K` 27 | 28 | `ATOM >> cosmos1kc5305r9ngjvl99nm9vztlwlfhvlys5facz96e` 29 | 30 | `TRC >>> TMZczdd7LZJSCp83WrLn245t6rSeEYeBTh` 31 | 32 | `ETH >>> 0x81fb0dF0F16ABC3BE334aB619154C9b3736aB9c1` 33 | -------------------------------------------------------------------------------- /ironfish/README.md: -------------------------------------------------------------------------------- 1 | # status 2 | This system will alert you with telegram about ironfish account points and rank every hour. 3 | 4 | Instruction: 5 | 6 | 1. Create telegram bot via `@BotFather`, customize it and `get bot API token` ([how_to](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token)). 7 | 2. Create a group, for example: `ironfish`. Customize it, add your bot into your chat and `get chat ID` ([how_to](https://stackoverflow.com/questions/32423837/telegram-bot-how-to-get-a-group-chat-id)). 8 | 3. Connect to your server and create `status` folder in the `$HOME directory` with `mkdir $HOME/status/`. 9 | 4. In this folder you have to create `ironfish.py` file with `nano $HOME/status/ironfish.py` and set following variables: `telegram_bot_api`, `telegram_chat_id` and `urls`. 10 | > You can find `ironfish.py` in this repository. 11 | 5. Install `python3`, `pip`, if you don't have them yet and run `pip install requests datetime` ([how_to](https://www.makeuseof.com/install-python-ubuntu/)). 12 | 6. Run `python3 ironfish.py` to check your settings. Normal output: 13 | 14 | ``` 15 | root@cyberomanov:~# python3 ironfish.py 16 | 17 | /// 27-05-2022 06:44:12 /// 18 | 19 | ironfish 20 | 21 | cyberomanov >>>> 658 points, #654. 22 | cyberpunk >>>>>> 129 points, #913. 23 | cyberG >>>>>>>>> 11 points, #12099. 24 | 25 | root@cyberomanov:~# 26 | ``` 27 | 7. Check your telegram, message must be arrived. 28 | 8. Edit crontab with `crontab -e`. Example where I get message every 3rd minute of an hour. 29 | ``` 30 | 3 */1 * * * python3 $HOME/status/ironfish.py >> $HOME/status/ironfish.log 31 | ``` 32 | 9. Check your logs with `cat $HOME/status/ironfish.log` or `tail $HOME/status/ironfish.log -f`. 33 | -------------------------------------------------------------------------------- /ironfish/ironfish.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from datetime import datetime 3 | 4 | # set variables, don't touch anything except this 3 things 5 | telegram_bot_api = "xxx:xxx-xxx_xxx" 6 | telegram_chat_id = "-228" 7 | urls = ["https://api.ironfish.network/users/1", 8 | "https://api.ironfish.network/users/2", 9 | "https://api.ironfish.network/users/3", 10 | "https://api.ironfish.network/users/4" 11 | ] 12 | 13 | if __name__ == "__main__": 14 | 15 | # get current time and print it 16 | time = datetime.now().strftime("%d-%m-%Y %H:%M:%S") 17 | print(f"/// {time} ///\n") 18 | print("ironfish\n") 19 | 20 | # graffiti and points max length 21 | graffiti_max = 0 22 | points_max = 0 23 | 24 | # create dict 25 | url_dict = dict.fromkeys(urls) 26 | 27 | # run loop with links 28 | for url in urls: 29 | 30 | # get account info 31 | account_info = requests.get(url).json() 32 | 33 | # save values 34 | graffiti = account_info["graffiti"] 35 | points = account_info["total_points"] 36 | rank = account_info["rank"] 37 | 38 | # add account info into the dict 39 | url_dict[url] = { 40 | "graffiti": graffiti, 41 | "total_points": points, 42 | "rank": rank 43 | } 44 | 45 | # get the longest graffiti and points 46 | if len(graffiti) > graffiti_max: 47 | graffiti_max = len(graffiti) 48 | 49 | if len(str(points)) > points_max: 50 | points_max = len(str(points)) 51 | 52 | # start to collect telegram message 53 | text = "ironfish\n\n" 54 | 55 | # run loop with links 56 | for url in urls: 57 | 58 | # create pretty output 59 | graffiti_out = (url_dict[url]["graffiti"] + ' ').ljust(graffiti_max + 2, '>') 60 | points_out = str(url_dict[url]["total_points"]).rjust(points_max) 61 | rank_out = url_dict[url]["rank"] 62 | 63 | print(f"{graffiti_out} {points_out} points, #{rank_out}.") 64 | text = text + f"{graffiti_out} {points_out} points, %23{rank_out}.\n" 65 | 66 | # end the message 67 | text = text + "" 68 | print() 69 | 70 | # send message with telegram bot 71 | telegram = f'https://api.telegram.org/bot{telegram_bot_api}/sendMessage?chat_id={telegram_chat_id}&parse_mode=html&text={text}' 72 | message = requests.get(telegram) 73 | -------------------------------------------------------------------------------- /nym/README.md: -------------------------------------------------------------------------------- 1 | # status 2 | This system will alert you with telegram about outdated and inactive status. Also it sends you every hour short info about your node status. 3 | 4 | Instruction: 5 | 6 | 1. Create telegram bot via `@BotFather`, customize it and `get bot API token` ([how_to](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token)). 7 | 2. Create at least 2 groups: `alarm` and `log`. Customize them, add your bot into your chats and `get chats IDs` ([how_to](https://stackoverflow.com/questions/32423837/telegram-bot-how-to-get-a-group-chat-id)). 8 | 3. Connect to your server and create `status` folder in the `$HOME` directory with `mkdir $HOME/status/`. 9 | 4. In this folder you have to create `nym.sh` file with `nano $HOME/status/nym.sh`. You don't have to do any edits on `nym.sh` file, it's ready to use. 10 | > You can find `nym.sh` in this repository. 11 | 5. Also you have to create as many `nym.conf` files with `nano $HOME/status/nym.conf`, as many nodes you have on the current server. Customize your config files. 12 | > You can find `nym.conf.example` in this repository. 13 | 6. Install `jq` and `bc` packages with `sudo apt-get install jq bc -y`. 14 | 7. Run `bash nym.sh` to check your settings. Normal output: 15 | 16 | ``` 17 | root@ubuntu:~/status# bash nym.sh 18 | 19 | /// 2022-05-21 20:07:32 /// 20 | 21 | nym-m | cyberomanov 22 | 23 | stake >>>>> 127314.73 | $50473.92. 24 | salary/m >> 367.20 | $145.58. 25 | unpaid >>>> 77.26 | $30.63. 26 | 27 | root@ubuntu:~/status# 28 | ``` 29 | 30 | 8. Create `slash.sh` with `nano $HOME/status/slash.sh`, if you don't have one yet. This bash script will divide group of messages. 31 | > You can find `slash.sh.example` in this repository. 32 | 9. Add some rules with `chmod u+x nym.sh slash.sh`. 33 | 10. Edit crontab with `crontab -e`. 34 | > You can find `crontab.example` in this repository. 35 | 11. Check your logs with `cat $HOME/status/nym.log` or `tail $HOME/status/nym.log -f`. 36 | -------------------------------------------------------------------------------- /nym/crontab.example: -------------------------------------------------------------------------------- 1 | # status 2 | 2,12,22,32,42,52 * * * * bash $HOME/status/nym.sh >> $HOME/status/nym.log 2>&1 3 | 4 | # slash 5 | 0 * * * * $HOME/status/slash.sh 6 | -------------------------------------------------------------------------------- /nym/nym.conf.example: -------------------------------------------------------------------------------- 1 | # mixnode identity 2 | IDENTITY="7TWEw9qQxsc8w4WhPAX6zjZ8vuNBdtP21zUVN8K26RkD" 3 | 4 | # moniker 5 | MONIKER="cyberomanov" 6 | 7 | # project note 8 | PROJECT="nym-mainnet" 9 | 10 | # if 'last hour uptime' is lower than X > alarm, 0 to disable 11 | UPTIME="80" 12 | 13 | # chat id for alarm messages with notification enabled 14 | CHAT_ID_ALARM="-187" 15 | 16 | # chat id for log messages 17 | CHAT_ID_STATUS="-1703" 18 | 19 | # bot token for send messages 20 | BOT_TOKEN="228322:xxx-xxx_xxx" 21 | -------------------------------------------------------------------------------- /nym/nym.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function __Send() { 4 | # print 'TEXT' into 'nym.log' for the sake of history 5 | echo -e "${TEXT}" 6 | 7 | # add new text to the 'MESSAGE', which will be sent as 'log' or 'alarm' 8 | # if 'SEND' == 1, it becomes 'alarm', otherwise it's 'log' 9 | MESSAGE=${MESSAGE}''$(echo "${TEXT}")'\n' 10 | } 11 | 12 | function __NodeStatus() { 13 | MESSAGE="${PROJECT} ⠀|⠀ ${MONIKER}\n\n" 14 | echo -e "${PROJECT} | ${MONIKER}\n" 15 | SEND=0; MINUTE=10 16 | 17 | RESPONSE=$(curl -s ${CURL}${IDENTITY}) 18 | if [[ ${RESPONSE} != *"error"* ]]; then 19 | # get uptime 20 | UPTIME_TOTAL=$(curl -s ${CURL}${IDENTITY}"/uptime") 21 | LAST_HOUR=$(echo ${UPTIME_TOTAL} | jq ".last_hour" | tr -d '"') 22 | LAST_DAY=$(echo ${UPTIME_TOTAL} | jq ".last_day" | tr -d '"') 23 | 24 | # if 'last hour uptime' is low > alarm 25 | if (( ${LAST_HOUR} < ${UPTIME} )); then 26 | SEND=1 27 | TEXT="_hour/day > $LAST_HOUR%/$LAST_DAY%." 28 | __Send 29 | fi 30 | 31 | # get info about node status 32 | TOTAL_INFO=$(curl -s ${CURL}${IDENTITY}) 33 | 34 | # if node is outdated > alarm 35 | OUTDATED=$(echo ${TOTAL_INFO} | jq ".outdated" | tr -d '"') 36 | if [[ "${OUTDATED}" == "true" ]]; then 37 | SEND=1 38 | VERSION=$(echo ${TOTAL_INFO} | jq ".mixnode.mix_node.version" | tr -d '"') 39 | TEXT="_version >> ${VERSION}.\n_outdated > ${OUTDATED}." 40 | __Send 41 | fi 42 | 43 | # if status is not active > alarm 44 | STATUS=$(echo ${TOTAL_INFO} | jq ".mixnode.status" | tr -d '"') 45 | if [[ "${STATUS}" != "active" ]]; then 46 | SEND=1 47 | TEXT="_status >>> ${STATUS}." 48 | __Send 49 | fi 50 | 51 | # get info about stake 52 | DELEGATION=$(echo $(echo "scale=2;$(echo ${TOTAL_INFO} | jq ".mixnode.total_delegation.amount" | tr -d '"')/${DENOM}" | bc)) 53 | SELF_STAKE=$(echo $(echo "scale=2;$(echo ${TOTAL_INFO} | jq ".mixnode.pledge_amount.amount" | tr -d '"')/${DENOM}" | bc)) 54 | TOTAL_STAKE=$(echo ${SELF_STAKE} + ${DELEGATION} | bc -l) 55 | TOTAL_STAKE_CUR=$(printf "%.2f" $(echo "scale=2;${TOTAL_STAKE}*${TOKEN_PRICE_CUR}" | bc)) 56 | 57 | # get info about rewards 58 | TOTAL_REWARDS=$(curl -s ${CURL}${IDENTITY}"/estimated_reward") 59 | 60 | # get info about estimated rewards 61 | ESTIMATED_REWARDS_H=$(echo $(echo "scale=2;$(echo ${TOTAL_REWARDS} | jq ".estimated_operator_reward" | tr -d '"')/${DENOM}" | bc)) 62 | ESTIMATED_REWARDS_M=$(echo ${ESTIMATED_REWARDS_H}*720 | bc -l) 63 | if (( $(bc <<< "${ESTIMATED_REWARDS_H} < 1") )); then ESTIMATED_REWARDS_H="0${ESTIMATED_REWARDS_H}"; fi 64 | if (( $(bc <<< "${ESTIMATED_REWARDS_M} < 1") )); then ESTIMATED_REWARDS_M="0${ESTIMATED_REWARDS_M}"; fi 65 | ESTIMATED_REWARDS_M_CUR=$(printf "%.2f" $(echo "scale=2;${ESTIMATED_REWARDS_M}*${TOKEN_PRICE_CUR}" | bc)) 66 | 67 | # get unpaid rewards 68 | DELEGATOR=$(echo ${TOTAL_INFO} | jq ".address" | tr -d '"') 69 | DELEGATOR_CLAIMABLE=$(curl -sk https://mixnet.api.explorers.guru/api/accounts/${DELEGATOR}/balance | jq ".claimable.amount" | tr -d '"') 70 | UNPAID=$(echo $(echo "scale=2;${DELEGATOR_CLAIMABLE}/${DENOM}" | bc)) 71 | if (( $(bc <<< "${UNPAID} < 1") )); then UNPAID="0${UNPAID}"; fi 72 | UNPAID_CUR=$(printf "%.2f" $(echo "scale=2;${UNPAID}*${TOKEN_PRICE_CUR}" | bc)) 73 | 74 | # find the longest value 75 | MAX=${#UNPAID} 76 | if (( ${#ESTIMATED_REWARDS_M} > ${MAX})); then MAX=${#ESTIMATED_REWARDS_M}; fi 77 | if (( ${#TOTAL_STAKE} > ${MAX})); then MAX=${#TOTAL_STAKE}; fi 78 | MAX=$((MAX+1)) 79 | 80 | # pretty output 81 | TOTAL_STAKE_SPACE=$((MAX-${#TOTAL_STAKE})); ESTIMATED_REWARDS_M_SPACE=$((MAX-${#ESTIMATED_REWARDS_M})); UNPAID_SPACE=$((MAX-${#UNPAID})) 82 | TOTAL_STAKE_TEXT=$(printf "stake >>>>>%${TOTAL_STAKE_SPACE}s${TOTAL_STAKE} | ${CURRENCY_SYMB}${TOTAL_STAKE_CUR}.\n") 83 | ESTIMATED_REWARDS_M_TEXT=$(printf "salary/m >>%${ESTIMATED_REWARDS_M_SPACE}s${ESTIMATED_REWARDS_M} | ${CURRENCY_SYMB}${ESTIMATED_REWARDS_M_CUR}.\n") 84 | UNPAID_SPACE_TEXT=$(printf "unpaid >>>>%${UNPAID_SPACE}s${UNPAID} | ${CURRENCY_SYMB}${UNPAID_CUR}.\n") 85 | 86 | TEXT="${TOTAL_STAKE_TEXT}\n${ESTIMATED_REWARDS_M_TEXT}\n${UNPAID_SPACE_TEXT}" 87 | __Send 88 | else 89 | TEXT="_explorer is down." 90 | __Send 91 | fi 92 | 93 | if [[ ${SEND} == "1" ]]; then 94 | curl --header 'Content-Type: application/json' \ 95 | --request 'POST' \ 96 | --data '{"chat_id":"'"${CHAT_ID_ALARM}"'", "text":"'"$(echo -e "${MESSAGE}")"'", "parse_mode": "html"}' "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ 97 | > /dev/null 2>&1 98 | elif (( $(echo "$(date +%M) < ${MINUTE}" | bc -l) )); then 99 | curl --header 'Content-Type: application/json' \ 100 | --request 'POST' \ 101 | --data '{"chat_id":"'"${CHAT_ID_STATUS}"'", "text":"'"$(echo -e "${MESSAGE}")"'", "parse_mode": "html"}' "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ 102 | > /dev/null 2>&1 103 | fi 104 | } 105 | 106 | function Main() { 107 | CURL="https://mixnet.api.explorers.guru/api/mixnodes/" 108 | IP=$(wget -qO- eth0.me) 109 | DENOM="1000000" 110 | TOKEN="nym" 111 | CURRENCY="usd" 112 | CURRENCY_SYMB="$" 113 | TOKEN_PRICE_CUR=$(curl -sk -X GET "https://api.coingecko.com/api/v3/simple/price?ids=${TOKEN}&vs_currencies=${CURRENCY}" -H "accept: application/json" | jq ".nym.usd") 114 | 115 | echo -e " " 116 | echo -e "/// $(date '+%F %T') ///" 117 | echo -e " " 118 | 119 | . $HOME/status/nym.conf 120 | __NodeStatus 121 | } 122 | 123 | # run 'Main' 124 | Main 125 | -------------------------------------------------------------------------------- /nym/slash.sh.example: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # chat id for log messages, not alarm 4 | CHAT_ID_LOG="-1703" 5 | 6 | # bot token 7 | BOT_TOKEN="228322:xxx-xxx_xxx" 8 | 9 | MESSAGE="/// $(date '+%F %T') ///" 10 | 11 | curl --header 'Content-Type: application/json' \ 12 | --request 'POST' \ 13 | --data '{"chat_id":"'"$CHAT_ID_LOG"'","text":"'"$(echo -e $MESSAGE)"'", "parse_mode": "html"}' "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" 14 | -------------------------------------------------------------------------------- /solana/README.md: -------------------------------------------------------------------------------- 1 | https://youtu.be/pbruFZqHkUc 2 | -------------------------------------------------------------------------------- /tendermint/auto_stake/README.md: -------------------------------------------------------------------------------- 1 | https://youtu.be/pbruFZqHkUc 2 | -------------------------------------------------------------------------------- /tendermint/node_status/README.md: -------------------------------------------------------------------------------- 1 | # status 2 | This system will alert you with telegram about jails and inactive status. Also it sends you every hour short info about your node status. 3 | 4 | Instruction: 5 | 6 | 1. Create telegram bot via `@BotFather`, customize it and `get bot API token` ([how_to](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token)). 7 | 2. Create at least 2 groups: `alarm` and `log`. Customize them, add your bot into your chats and `get chats IDs` ([how_to](https://stackoverflow.com/questions/32423837/telegram-bot-how-to-get-a-group-chat-id)). 8 | 3. Connect to your server and create `status` folder in the `$HOME directory` with `mkdir $HOME/status/`. 9 | 4. In this folder, `$HOME/status/`, you have to create `cosmos.sh` file with `nano $HOME/status/cosmos.sh`. You don't have to do any edits on `cosmos.sh` file, it's ready to use. 10 | > You can find `cosmos.sh` in this repository. 11 | 5. In this folder, `$HOME/status/`, you have to create `cosmos.conf` file with `nano $HOME/status/cosmos.conf`. Customize it. 12 | > You can find `cosmos.conf` in this repository. 13 | 6. Also you have to create as many `name.conf` files with `nano $HOME/status/name.conf`, as many nodes you have on the current server. Customize your config files. For ex: I have agoric, gravity and sifchain on the same server, so I have to create 3 files: `agoric.conf`, `gravity.conf` and `sifchain.conf`. 14 | > You can find `name.conf` and `curl.md` in this repository. 15 | 7. Install some packages with `sudo apt-get install jq sysstat bc smartmontools fdisk -y`. 16 | 8. Run `bash cosmos.sh` to check your settings. Normal output: 17 | ``` 18 | root@v1131623:~/status# bash cosmos.sh 19 | 20 | /// 2022-07-09 11:42:37 /// 21 | 22 | testnets | load 23 | 24 | cpu >>>>> 68%. 25 | ram >>>>> 47%. 26 | part >>>> 55%. 27 | load >>>> 14.03. 28 | 29 | dws-t | cyberomanov 30 | 31 | exp/me >> 955540/955540. 32 | place >>> 88/200. 33 | stake >>> 34.98 dws. 34 | 35 | root@v1131623:~/status# 36 | ``` 37 | 9. Add some rules with `chmod u+x $HOME/status/cosmos.sh`. 38 | 10. Edit crontab with `crontab -e`. 39 | > You can find `crontab` in this repository. 40 | 11. Check your logs with `cat $HOME/status/cosmos.log` or `tail $HOME/status/cosmos.log -f`. 41 | -------------------------------------------------------------------------------- /tendermint/node_status/cosmos.conf: -------------------------------------------------------------------------------- 1 | # send 'alarm_message' when chain upgrade is in N minutes 2 | # ex: 'UPGRADE_ALARM_IN_MIN=30' will send 'alarm_message' 30, 20 and 10 mins before the chain upgrade 3 | UPGRADE_ALARM_IN_MIN=30 4 | 5 | # timezone from 'https://en.wikipedia.org/wiki/List_of_tz_database_time_zones' > 'TZ database name' 6 | # ex: 'TIMEZONE="Asia/Tbilisi"' is +04:00 UTC, 'TIMEZONE="Africa/Abidjan"' is +00:00 UTC. 7 | TIMEZONE="Africa/Abidjan" 8 | 9 | # server load monitoring: set "false" to disable, set "true" to enable 10 | SEND_LOAD="false" 11 | 12 | # server nickname for 'server load monitoring' message 13 | SERVER="mainnets" 14 | 15 | # cpu/ram/partition threshold in percent for 'alarm_message' trigger 16 | CPU_ALARM=85 17 | RAM_ALARM=85 18 | PARTITION_ALARM=85 19 | 20 | # physical disk percentage used, not partition 21 | DISK_PERCENTAGE_USED_ALARM=80 22 | 23 | # chat_id for alarm messages with enabled notifications 24 | CHAT_ID_ALARM="-187" 25 | 26 | # bot_token for sending messages 27 | BOT_TOKEN="228322:xxx-xxx_xxx" 28 | -------------------------------------------------------------------------------- /tendermint/node_status/cosmos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function __Send() { 4 | 5 | # print 'TEXT' into 'cosmos.log' for the sake of history 6 | echo -e ${TEXT} 7 | 8 | # add new text to the 'MESSAGE', which will be sent as 'log_message' or 'alarm_message' 9 | # if 'SEND' == 1, it becomes 'alarm_message', otherwise it's 'log_message' 10 | MESSAGE=${MESSAGE}''${TEXT}'\n' 11 | } 12 | 13 | function __PreMessage() { 14 | 15 | # init some premessages 16 | CPU_T="cpu_used >>>>" 17 | RAM_T="ram_used >>>>" 18 | SWAP_T="swap_used >>>" 19 | PART_T="part_used >>>" 20 | LOAD_T="serv_load >>>" 21 | DISK_SPARE_T="disk_spare >>" 22 | DISK_USED_T="disk_used >>>" 23 | 24 | MISSED_T="missed >>>>>>" 25 | TRAIN_T="train >>>>>>>" 26 | JAILED_A="_jailed >>>>>" 27 | 28 | GOV_T="gov >>>>>>>>>" 29 | 30 | UPGRADE_T="upgrade >>>>>" 31 | TIME_L_T="_time_left >>" 32 | APPR_T_T="_appr_time >>" 33 | 34 | EXP_ME_T="exp/me >>>>>>" 35 | ACTIVE_A="_active >>>>>" 36 | PLACE_T="place >>>>>>>" 37 | STAKE_T="stake >>>>>>>" 38 | 39 | PRIVKEY_T="priv_key >>>>" 40 | } 41 | 42 | function __DiskVitality() { 43 | 44 | # init some variables 45 | KEY=0 46 | 47 | # trying to install 'smartmontools' 48 | if [[ $(/usr/sbin/smartctl -V 2>&1) == *"not found"* || $(/usr/sbin/fdisk -v 2>&1) == *"not found"* ]]; then 49 | apt-get install smartmontools fdisk -y > /dev/null 2>&1 50 | fi 51 | 52 | # if successfuly installed > check disk 53 | if [[ $(/usr/sbin/smartctl -V 2>&1) != *"not found"* || $(/usr/sbin/fdisk -v 2>&1) != *"not found"* ]]; then 54 | DISK_NAME_STRING=$(/usr/sbin/fdisk -l | grep -e "Disk /dev/*" | grep -oE "/dev/[[:alnum:]]*") 55 | DISK_NAME_ARRAY=($(echo "${DISK_NAME_STRING}" | tr ' ' '\n')) 56 | for i in "${!DISK_NAME_ARRAY[@]}"; do 57 | DISK_INFO=$(/usr/sbin/smartctl -s on -a ${DISK_NAME_ARRAY[i]}) 58 | if [[ ${DISK_INFO} != *"Unable to detect device"* ]]; then 59 | KEY=1 60 | DISK_NAME=$(echo ${DISK_NAME_ARRAY[i]}) 61 | SPARE=$(echo ${DISK_INFO} | grep -o "Available Spare: [0-9]*" | grep -o "[0-9]*") 62 | SPARE_THRESHOLD=$(echo ${DISK_INFO} | grep -o "Available Spare Threshold: [0-9]*" | grep -o "[0-9]*") 63 | PERCENTAGE_USED=$(echo ${DISK_INFO} | grep -o "Percentage Used: [0-9]*" | grep -o "[0-9]*") 64 | 65 | if [[ $(echo "${SPARE} < ${SPARE_THRESHOLD}" | bc) -eq 1 ]]; then 66 | SEND=1 67 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"\n_${DISK_SPARE_T::-1} ${DISK_NAME:5} has only ${SPARE}% spare." 68 | else 69 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"\n${DISK_SPARE_T} ${DISK_NAME:5} has ${SPARE}% spare." 70 | fi 71 | 72 | if [[ $(echo "${PERCENTAGE_USED} > ${DISK_PERCENTAGE_USED_ALARM}" | bc) -eq 1 ]]; then 73 | SEND=1 74 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"\n_${DISK_USED_T::-1} ${DISK_NAME:5} has ${PERCENTAGE_USED}% used." 75 | else 76 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"\n${DISK_USED_T} ${DISK_NAME:5} has ${PERCENTAGE_USED}% used." 77 | fi 78 | fi 79 | done 80 | 81 | if [[ ${KEY} == 0 ]]; then 82 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"\nthere is no disk which can be tested." 83 | fi 84 | else 85 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"\ninstall tools manually: 'apt-get install smartmontools fdisk -y'." 86 | fi 87 | } 88 | 89 | function __ServerLoad() { 90 | 91 | # add new text to the 'MESSAGE', which will be sent as 'log_message' or 'alarm_message' 92 | MESSAGE="${SERVER} ⠀|⠀ load\n\n" 93 | echo -e "${SERVER} | load\n" 94 | 95 | # init some variables 96 | CPU_ALARM=80 97 | RAM_ALARM=80 98 | PARTITION_ALARM=80 99 | CPU_LOAD_MESSAGE="" 100 | TEXT="" 101 | SEND=0 102 | 103 | # read 'cosmos.conf' 104 | . ./cosmos.conf 105 | 106 | # get CPU load 107 | CPU=$(printf "%.0f" $(echo "scale=2; 100-$(mpstat | tail -1 | awk 'NF {print $NF}')" | bc)) 108 | if (( $(echo "${CPU} > ${CPU_ALARM}" | bc -l) )); then 109 | SEND=1 110 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"_${CPU_T::-1} ${CPU}%.\n" 111 | else 112 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"${CPU_T} ${CPU}%.\n" 113 | fi 114 | 115 | # get RAM load 116 | free -g > ~/temp.txt 117 | RAM_TOTAL=$(cat ~/temp.txt | awk '{print $2}' | awk 'NR==2 {print; exit}')"G" 118 | RAM_USED=$(cat ~/temp.txt | awk '{print $3}' | awk 'NR==2 {print; exit}')"G" 119 | RAM_PERC=$(printf "%.0f" $(echo "scale=2; ${RAM_USED}/${RAM_TOTAL}*100" | bc | grep -oE "[0-9]*" | awk 'NR==1 {print; exit}')) 120 | if (( $(echo "${RAM_PERC} > ${RAM_ALARM}" | bc -l) )); then 121 | SEND=1 122 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"_${RAM_T::-1} ${RAM_PERC}%.\n" 123 | else 124 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"${RAM_T} ${RAM_PERC}%.\n" 125 | fi 126 | 127 | # get SWAP load 128 | SWAP_TOTAL=$(cat ~/temp.txt | grep "Swap" | awk '{print $2}')"G" 129 | SWAP_USED=$(cat ~/temp.txt | grep "Swap" | awk '{print $3}')"G" 130 | SWAP_PERC=$(printf "%.0f" $(echo "scale=2; ${SWAP_USED}/${SWAP_TOTAL}*100" | bc | grep -oE "[0-9]*" | awk 'NR==1 {print; exit}')) 131 | if [[ ${SWAP_TOTAL} != "0G" && ${SWAP_TOTAL} != "G" && ${SWAP_USED} != "0G" && ${SWAP_USED} != "G" ]]; then 132 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"${SWAP_T} ${SWAP_PERC}%.\n" 133 | fi 134 | 135 | # get disk load 136 | df -h / > ~/temp.txt 137 | DISK_PERC=$(printf "%.0f" $(cat ~/temp.txt | awk '{print $5}' | awk 'NR==2 {print; exit}' | tr -d '%')) 138 | if (( $(echo "${DISK_PERC} > ${PARTITION_ALARM}" | bc -l) )); then 139 | SEND=1 140 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"_${PART_T::-1} ${DISK_PERC}%.\n" 141 | else 142 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"${PART_T} ${DISK_PERC}%.\n" 143 | fi 144 | 145 | # get system load 146 | SYSTEM_LOAD=$(cat /proc/loadavg | awk '{print $2}') 147 | CPU_LOAD_MESSAGE=${CPU_LOAD_MESSAGE}"${LOAD_T} ${SYSTEM_LOAD}.\n" 148 | 149 | __DiskVitality 150 | 151 | # delete the temp file 152 | rm ~/temp.txt 153 | 154 | # if 'SEND' == 1 > send 'MESSAGE' into 'alarm telegram channel' 155 | if [[ ${SEND} == "1" ]]; then 156 | TEXT=${CPU_LOAD_MESSAGE} 157 | __Send 158 | 159 | curl --header 'Content-Type: application/json' \ 160 | --request 'POST' \ 161 | --data '{"chat_id":"'"${CHAT_ID_ALARM}"'", "text":"'"$(echo -e "${MESSAGE}")"'", "parse_mode": "html"}' "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ 162 | > /dev/null 2>&1 163 | else 164 | echo -e "${CPU_LOAD_MESSAGE}" 165 | fi 166 | } 167 | 168 | function __LastChainBlock() { 169 | # get the last explorer's block 170 | if [[ ${CURL} == *"v1/status"* ]]; then 171 | LATEST_CHAIN_BLOCK=$(curl -sk ${CURL} | jq ".block_height" | tr -d '"') 172 | if [[ ${LATEST_CHAIN_BLOCK} == "null" ]]; then 173 | LATEST_CHAIN_BLOCK=$(curl -sk ${CURL} | jq ".data.block_height" | tr -d '"') 174 | fi 175 | elif [[ ${CURL} == *"bank/total"* ]] || [[ ${CURL} == *"blocks/latest"* ]]; then 176 | LATEST_CHAIN_BLOCK=$(curl -sk ${CURL} | jq ".height" | tr -d '"') 177 | if [[ ${LATEST_CHAIN_BLOCK} == "null" ]]; then 178 | LATEST_CHAIN_BLOCK=$(curl -sk ${CURL} | jq ".block.header.height" | tr -d '"') 179 | fi 180 | elif [[ ${CURL} == *"block?latest"* ]]; then 181 | LATEST_CHAIN_BLOCK=$(curl -sk ${CURL} | jq ".result.block.header.height" | tr -d '"') 182 | else 183 | LATEST_CHAIN_BLOCK="0" 184 | fi 185 | echo ${LATEST_CHAIN_BLOCK} 186 | } 187 | 188 | function __SignedAndMissedBlocks() { 189 | # init some variables 190 | SIGNED=0 191 | MISSED=0 192 | MAX_ROW=0 193 | LOOKBEHIND_BLOCKS=100 194 | 195 | # get slashing params 196 | SLASHING=$(${COSMOS} q slashing params -o json --node ${NODE} --home ${NODE_HOME}) 197 | WINDOW=$(echo ${SLASHING} | jq ".signed_blocks_window" | tr -d '"') 198 | MIN_SIGNED=$(echo ${SLASHING} | jq ".min_signed_per_window" | tr -d '"') 199 | JAILED_AFTER=$(echo ${WINDOW}-${WINDOW}*${MIN_SIGNED} | bc -l | grep -oE "[0-9]*" | awk 'NR==1 {print; exit}') 200 | # LOOKBEHIND_BLOCKS=${JAILED_AFTER} 201 | MISSED_BLOCKS_FOR_ALARM=$(echo ${JAILED_AFTER}/10 | bc -l | grep -oE "[0-9]*" | awk 'NR==1 {print; exit}') 202 | # echo ${MISSED_BLOCKS_FOR_ALARM} 203 | 204 | # get some info about node 205 | NODE_STATUS_TOTAL=$(curl -s localhost:${PORT}/status) 206 | VALIDATOR_ADDRESS=$(echo ${NODE_STATUS_TOTAL} | jq .result.validator_info.address | tr -d '"') 207 | LATEST_BLOCK_HEIGHT=$(echo ${NODE_STATUS_TOTAL} | jq .result.sync_info.latest_block_height | tr -d '"') 208 | LATEST_BLOCK_TIME=$(echo ${NODE_STATUS_TOTAL} | jq .result.sync_info.latest_block_time | tr -d '"') 209 | 210 | # check only 100 last blocks 211 | START_BLOCK=$((${LATEST_BLOCK_HEIGHT}-${LOOKBEHIND_BLOCKS}+1)) 212 | for (( BLOCK = ${START_BLOCK}; BLOCK <= ${LATEST_BLOCK_HEIGHT}; BLOCK++ )); do 213 | # check for validator signature 214 | SIGNATURE=$(curl -s localhost:${PORT}/block?height=${BLOCK} | jq .result.block.last_commit.signatures[].validator_address | tr -d '"' | grep ${VALIDATOR_ADDRESS}) 215 | 216 | # if signature exists > signed + 1 217 | if [[ ${SIGNATURE} != "" ]]; then 218 | MISSED_IN_A_ROW=0 219 | ((SIGNED=SIGNED+1)) 220 | 221 | # if signature does not exist > missed + 1 222 | else 223 | ((MISSED_IN_A_ROW=MISSED_IN_A_ROW+1)) 224 | ((MISSED=MISSED+1)) 225 | if (( ${MISSED_IN_A_ROW} > ${MAX_ROW} )); then MAX_ROW=${MISSED_IN_A_ROW}; fi 226 | fi 227 | done 228 | 229 | # if missed is more than 10 > check the whole window before jail 230 | if [[ ${MISSED} > "10" ]]; then 231 | 232 | # zeroing variables 233 | SIGNED=0 234 | MISSED=0 235 | MAX_ROW=0 236 | LOOKBEHIND_BLOCKS=${JAILED_AFTER} 237 | 238 | START_BLOCK=$((${LATEST_BLOCK_HEIGHT}-${LOOKBEHIND_BLOCKS}+1)) 239 | for (( BLOCK = ${START_BLOCK}; BLOCK <= ${LATEST_BLOCK_HEIGHT}; BLOCK++ )); do 240 | # check for validator signature 241 | SIGNATURE=$(curl -s localhost:${PORT}/block?height=${BLOCK} | jq .result.block.last_commit.signatures[].validator_address | tr -d '"' | grep ${VALIDATOR_ADDRESS}) 242 | 243 | # if signature exists > signed + 1 244 | if [[ ${SIGNATURE} != "" ]]; then 245 | MISSED_IN_A_ROW=0 246 | ((SIGNED=SIGNED+1)) 247 | 248 | # if signature does not exist > missed + 1 249 | else 250 | ((MISSED_IN_A_ROW=MISSED_IN_A_ROW+1)) 251 | ((MISSED=MISSED+1)) 252 | if (( ${MISSED_IN_A_ROW} > ${MAX_ROW} )); then MAX_ROW=${MISSED_IN_A_ROW}; fi 253 | fi 254 | done 255 | fi 256 | 257 | # if missed more than 10% of allowed missed blocks before jail > alarm 258 | if (( ${MISSED} > ${MISSED_BLOCKS_FOR_ALARM} )); then 259 | SEND=1 260 | TEXT="_${MISSED_T::-1} ${MISSED}/${LOOKBEHIND_BLOCKS} last blocks.\n_${TRAIN_T::-1} ${MAX_ROW} missed in a row.\n${JAILED_A} after ${JAILED_AFTER} missed blocks." 261 | __Send 262 | elif (( ${MISSED} > 0 )); then 263 | TEXT="${MISSED_T} ${MISSED} blocks.\n${TRAIN_T} ${MAX_ROW} missed in a row." 264 | __Send 265 | else 266 | echo "${MISSED_T} ${MISSED} blocks." 267 | fi 268 | } 269 | 270 | function __UnvotedProposals() { 271 | 272 | # get proposals 273 | PROPOSALS=$(${COSMOS} q gov proposals --node ${NODE} --limit 999999999 --output json 2>&1) 274 | 275 | # if at least one proposal exists 276 | if [[ ${PROPOSALS} != *"no proposals found"* ]]; then 277 | # get array of active proposals 278 | ACTIVE_PROPOSALS_STRING=$(echo ${PROPOSALS} | jq '.proposals[] | select(.status=="PROPOSAL_STATUS_VOTING_PERIOD")' | jq '.proposal_id' | tr -d '"') 279 | ACTIVE_PROPOSALS_ARRAY=($(echo "${ACTIVE_PROPOSALS_STRING}" | tr ' ' '\n')) 280 | 281 | # init array of unvoted proposals 282 | UNVOTED_ARRAY=( ) 283 | 284 | # run loop on each proposal 285 | for i in "${!ACTIVE_PROPOSALS_ARRAY[@]}"; do 286 | # if vote does not exist, add proposal id to 'UNVOTED_ARRAY' 287 | VOTE=$(${COSMOS} q gov votes ${ACTIVE_PROPOSALS_ARRAY[i]} --limit 999999999 --node ${NODE} --output json | jq '.votes[].voter' | tr -d '"' | grep ${DELEGATOR_ADDRESS}) 288 | if [[ ${VOTE} == "" ]]; then UNVOTED_ARRAY+=(${ACTIVE_PROPOSALS_ARRAY[i]}); fi 289 | done 290 | 291 | # if exists at least one unvoted proposal 292 | if (( ${#UNVOTED_ARRAY[@]} > 0 )); then 293 | TEXT="_${GOV_T::-1}" 294 | 295 | # add proposal id to message 296 | for i in "${!UNVOTED_ARRAY[@]}"; do 297 | TEXT=${TEXT}' #'${UNVOTED_ARRAY[i]} 298 | if (( ${i} < ${#UNVOTED_ARRAY[@]}-1 )); then TEXT=${TEXT}','; else TEXT=${TEXT}'.'; fi 299 | done 300 | __Send 301 | else 302 | echo "${GOV_T} no unvoted proposals." 303 | fi 304 | else 305 | echo "${GOV_T} no any proposals." 306 | fi 307 | } 308 | 309 | function __AverageBlockExecutionTime() { 310 | 311 | # init some variables 312 | LOOKBEHIND_BLOCKS=100 313 | 314 | # get some info about node 315 | NODE_STATUS_TOTAL=$(curl -s localhost:${PORT}/status) 316 | 317 | # get last block time and height 318 | LATEST_BLOCK_HEIGHT=$(echo ${NODE_STATUS_TOTAL} | jq .result.sync_info.latest_block_height | tr -d '"') 319 | LATEST_BLOCK_TIME=$(echo ${NODE_STATUS_TOTAL} | jq .result.sync_info.latest_block_time | tr -d '"' | grep -oE "[0-9]*:[0-9]*:[0-9]*") 320 | IFS=':' read -ra HMS <<< "$LATEST_BLOCK_TIME" 321 | LATEST_BLOCK_TIME_IN_SEC=$(echo ${HMS[0]}*3600+${HMS[1]}*60+${HMS[2]} | bc -l) 322 | 323 | # get upgrade block time and height 324 | UPGRADE_BLOCK_HEIGHT=$((${LATEST_BLOCK_HEIGHT}-${LOOKBEHIND_BLOCKS})) 325 | UPGRADE_BLOCK_TIME=$(${COSMOS} q block ${UPGRADE_BLOCK_HEIGHT} --node ${NODE} | jq ".block.header.time" | tr -d '"' | grep -oE "[0-9]*:[0-9]*:[0-9]*") 326 | IFS=':' read -ra HMS <<< "${UPGRADE_BLOCK_TIME}" 327 | UPGRADE_BLOCK_TIME_IN_SEC=$(echo ${HMS[0]}*3600+${HMS[1]}*60+${HMS[2]} | bc -l) 328 | 329 | # find the max and the min values 330 | MAX=${LATEST_BLOCK_TIME_IN_SEC}; MIN=${LATEST_BLOCK_TIME_IN_SEC} 331 | if (( ${UPGRADE_BLOCK_TIME_IN_SEC} > ${MAX} )); then MAX=${UPGRADE_BLOCK_TIME_IN_SEC}; fi 332 | if (( ${UPGRADE_BLOCK_TIME_IN_SEC} < ${MIN} )); then MIN=${UPGRADE_BLOCK_TIME_IN_SEC}; fi 333 | 334 | # find the difference between blocks in seconds 335 | DIFF_IN_SEC=$((${MAX}-${MIN})) 336 | if (( $(echo "86400 - ${DIFF_IN_SEC}" | bc) < ${DIFF_IN_SEC} )); then 337 | DIFF_IN_SEC=$((86400 - ${DIFF_IN_SEC})); echo ${DIFF_IN_SEC} 338 | fi 339 | 340 | # get estimated block exectuion time 341 | BLOCK_EXECUTION_TIME=$(echo "scale=2;${DIFF_IN_SEC}/100" | bc) 342 | } 343 | 344 | function __UpgradePlan() { 345 | 346 | # init some variables 347 | UPGRADE_ALARM_IN_MIN=30 348 | ETBU_TEXT="" 349 | ETBU_APPR="" 350 | 351 | # read 'cosmos.conf' 352 | . ./cosmos.conf 353 | 354 | # get some info about chain upgrade plan 355 | UPGRADE_PLAN=$(${COSMOS} q upgrade plan --node ${NODE} --output json 2>&1) 356 | 357 | # if smth is planned, then calculate approximate upgrade time 358 | if [[ ${UPGRADE_PLAN} != *"no upgrade scheduled"* ]]; then 359 | UPGRADE_HEIGHT=$(echo ${UPGRADE_PLAN} | jq ".height" | tr -d '"') 360 | UPGRADE_NAME=$(echo ${UPGRADE_PLAN} | jq ".name" | tr -d '"') 361 | 362 | __AverageBlockExecutionTime 363 | BLOCKS_BEFORE_UPGRADE=$((${UPGRADE_HEIGHT}-${LATEST_BLOCK_HEIGHT})) 364 | ESTIMATED_TIME_BEFORE_UPGRADE_IN_SEC=$(printf "%.0f" $(echo "scale=2; ${BLOCKS_BEFORE_UPGRADE}*${BLOCK_EXECUTION_TIME}" | bc)) 365 | ESTIMATED_TIME_BEFORE_UPGRADE_IN_MIN=$(echo "scale=2; ${BLOCKS_BEFORE_UPGRADE}*${BLOCK_EXECUTION_TIME}/60" | bc) 366 | 367 | ETBU_D=$((${ESTIMATED_TIME_BEFORE_UPGRADE_IN_SEC}/60/60/24)) 368 | ETBU_H=$((${ESTIMATED_TIME_BEFORE_UPGRADE_IN_SEC}/60/60%24)) 369 | ETBU_M=$((${ESTIMATED_TIME_BEFORE_UPGRADE_IN_SEC}/60%60)) 370 | 371 | if (( ${ETBU_D} > 0 )); then ETBU_TEXT="${ETBU_TEXT}${ETBU_D}d "; fi 372 | if (( ${ETBU_D} > 0 )); then ETBU_APPR="${ETBU_APPR}${ETBU_D}day "; fi 373 | if (( ${ETBU_H} > 0 )); then ETBU_TEXT="${ETBU_TEXT}${ETBU_H}h "; fi 374 | if (( ${ETBU_H} > 0 )); then ETBU_APPR="${ETBU_APPR}${ETBU_H}hour "; fi 375 | if (( ${ETBU_M} > 0 )); then ETBU_TEXT="${ETBU_TEXT}${ETBU_M}m "; fi 376 | if (( ${ETBU_M} > 0 )); then ETBU_APPR="${ETBU_APPR}${ETBU_M}min "; fi 377 | 378 | APPROXIMATE_UPGRADE_TIME=$(date -d "+${ETBU_APPR}" +"%b %d, %H:%M") 379 | 380 | if [[ $(echo "${ESTIMATED_TIME_BEFORE_UPGRADE_IN_MIN} < ${UPGRADE_ALARM_IN_MIN}" | bc) -eq 1 && ${ESTIMATED_TIME_BEFORE_UPGRADE_IN_MIN} != 0 ]]; then 381 | SEND=1 382 | TEXT="\n_${UPGRADE_T::-1} ${UPGRADE_NAME}.\n${TIME_L_T} ${ETBU_TEXT::-1}.\n${APPR_T_T} ${APPROXIMATE_UPGRADE_TIME}." 383 | __Send 384 | else 385 | TEXT="\n_${UPGRADE_T::-1} ${UPGRADE_NAME}.\n${TIME_L_T} ${ETBU_TEXT::-1}.\n${APPR_T_T} ${APPROXIMATE_UPGRADE_TIME}." 386 | __Send 387 | fi 388 | else 389 | echo "${UPGRADE_T} no." 390 | fi 391 | } 392 | 393 | function __NodeStatus() { 394 | 395 | # init some variables 396 | MESSAGE="${PROJECT} ⠀|⠀ ${MONIKER}\n\n" 397 | echo -e "${PROJECT} | ${MONIKER}\n" 398 | INACTIVE="" 399 | SEND=0 400 | 401 | # get some info about node 402 | NODE_STATUS=$(timeout 5s ${COSMOS} status 2>&1 --node ${NODE} --home ${NODE_HOME}) 403 | 404 | # if 'NODE_STATUS' response contains 'connection refused' > instant alarm 405 | if [[ ${NODE_STATUS} != *"connection refused"* ]] && [[ ${NODE_STATUS} != "" ]]; then 406 | # get the lastest node and explorer blocks height 407 | LATEST_NODE_BLOCK=$(echo ${NODE_STATUS} | jq .'SyncInfo'.'latest_block_height' | tr -d '"') 408 | LATEST_CHAIN_BLOCK=$(__LastChainBlock) 409 | 410 | # if 'CURL' was not set > no compare with explorer height 411 | if [[ ${CURL} != "" ]] && [[ ${LATEST_CHAIN_BLOCK} != "" ]] && [[ ${LATEST_CHAIN_BLOCK} != "0" ]] && [[ ${LATEST_CHAIN_BLOCK} != "null" ]]; then 412 | # if we are in the past more than 100 block > alarm 413 | if ((${LATEST_CHAIN_BLOCK}-${BLOCK_GAP_ALARM} > ${LATEST_NODE_BLOCK})); then 414 | if [[ ${ALLOW_SERVICE_RESTART} == "true" ]]; then 415 | systemctl restart ${SERVICE} > /dev/null 2>&1 416 | TEXT="_${EXP_ME_T::-1} ${LATEST_CHAIN_BLOCK}/${LATEST_NODE_BLOCK}. \n\nbut service has been restarted." 417 | else 418 | TEXT="_${EXP_ME_T::-1} ${LATEST_CHAIN_BLOCK}/${LATEST_NODE_BLOCK}." 419 | fi 420 | SEND=1 421 | __Send 422 | else 423 | echo "${EXP_ME_T} ${LATEST_CHAIN_BLOCK}/${LATEST_NODE_BLOCK}." 424 | fi 425 | else 426 | echo "${EXP_ME_T} 0/${LATEST_NODE_BLOCK}." 427 | fi 428 | 429 | # if there is no problem with height 430 | if [[ ${SEND} == "0" ]]; then 431 | 432 | # check 'priv_key' actuality 433 | CONSENSUS_PUBKEY=$(${COSMOS} q staking validator ${VALIDATOR_ADDRESS} -oj --node ${NODE} --home ${NODE_HOME} | jq -r ".consensus_pubkey.key") 434 | CURRENT_PUBKEY=$(curl -s localhost:${PORT}/status | jq -r ".result.validator_info.pub_key.value") 435 | if [[ ${CONSENSUS_PUBKEY} == ${CURRENT_PUBKEY} ]]; then 436 | PRIVKEY="right" 437 | echo "${PRIVKEY_T} right." 438 | else 439 | PRIVKEY="wrong" 440 | if [[ ${IGNORE_WRONG_PRIVKEY} == "true" ]]; then 441 | TEXT="_${PRIVKEY_T::-1} wrong, but we know." 442 | __Send 443 | else 444 | SEND=1 445 | TEXT="_${PRIVKEY_T::-1} wrong." 446 | __Send 447 | fi 448 | fi 449 | 450 | # get validator info 451 | VALIDATOR_INFO=$(${COSMOS} query staking validator ${VALIDATOR_ADDRESS} --node ${NODE} --output json --home ${NODE_HOME}) 452 | BOND_STATUS=$(echo ${VALIDATOR_INFO} | jq .'status' | tr -d '"') 453 | 454 | # if 'BOND_STATUS' is different than 'BOND_STATUS_BONDED' > alarm 455 | if [[ "${BOND_STATUS}" != "BOND_STATUS_BONDED" ]]; then 456 | # if 'JAILED_STATUS' is 'true' > alarm with 'jailed > true.' 457 | # if 'JAILED_STATUS' is 'true' > alarm with 'active > false.' 458 | INACTIVE="true" 459 | 460 | JAILED_STATUS=$(echo ${VALIDATOR_INFO} | jq .'jailed') 461 | if [[ "${JAILED_STATUS}" == "true" ]]; then 462 | if [[ ${IGNORE_WRONG_PRIVKEY} == "true" && ${PRIVKEY} == "wrong" ]]; then 463 | TEXT="${JAILED_A} ${JAILED_STATUS}, but we know." 464 | else 465 | SEND=1 466 | TEXT="${JAILED_A} ${JAILED_STATUS}." 467 | fi 468 | else 469 | # if 'ignore_inactive_status' is not set or 'false' > alarm 470 | if [[ ${IGNORE_INACTIVE_STATUS} != "true" ]]; then SEND=1; fi 471 | TEXT="${ACTIVE_A} false." 472 | fi 473 | __Send 474 | 475 | # if 'BOND_STATUS' is 'BOND_STATUS_BONDED' > continue 476 | else 477 | # get local explorer snapshot and request some info about our validator 478 | EXPLORER=$(${COSMOS} q staking validators --node ${NODE} --output json --home ${NODE_HOME} --limit=999999999) 479 | VALIDATORS_COUNT=$(echo ${EXPLORER} | jq '.validators[] | select(.status=="BOND_STATUS_BONDED")' | jq -r '.tokens' | sort -gr | wc -l) 480 | VALIDATOR_STRING=$(echo ${EXPLORER} | jq '.validators[] | select(.status=="BOND_STATUS_BONDED")' | jq -r '.tokens + " " + .description.moniker' | sort -gr | nl | grep -F ${MONIKER}) 481 | VALIDATOR_POSITION=$(echo ${VALIDATOR_STRING} | awk '{print $1}') 482 | ACTIVE_VALIDATOR_SET=$(${COSMOS} q staking params --node ${NODE} --output json --home ${NODE_HOME} | jq ."max_validators") 483 | 484 | # alarm if validator is close to become inactive 485 | SAFE_VALIDATOR_PLACE=$(echo ${ACTIVE_VALIDATOR_SET}-${POSITION_GAP_ALARM} | bc -l) 486 | 487 | if ((${VALIDATOR_POSITION} > ${SAFE_VALIDATOR_PLACE})); then 488 | if [[ ${IGNORE_INACTIVE_STATUS} != "true" ]]; then 489 | SEND=1 490 | TEXT="_${PLACE_T::-1} ${VALIDATOR_POSITION}/${ACTIVE_VALIDATOR_SET}." 491 | else 492 | TEXT="${PLACE_T} ${VALIDATOR_POSITION}/${ACTIVE_VALIDATOR_SET}." 493 | fi 494 | else 495 | TEXT="${PLACE_T} ${VALIDATOR_POSITION}/${ACTIVE_VALIDATOR_SET}." 496 | fi 497 | __Send 498 | 499 | # validator active stake 500 | VALIDATOR_STAKE=$(echo ${VALIDATOR_STRING} | awk '{print $2}') 501 | TEXT="${STAKE_T} $(echo "scale=2;${VALIDATOR_STAKE}/${DENOM}" | bc) ${TOKEN}." 502 | __Send 503 | fi 504 | 505 | if [[ ${INACTIVE} != "true" ]]; then 506 | if [[ ${IGNORE_WRONG_PRIVKEY} != "true" ]]; then 507 | __SignedAndMissedBlocks 508 | else 509 | echo "missed >>>>> ignore, cause of 'IWP'." 510 | fi 511 | else 512 | echo "missed >>>>> ignore, cause of inactive status." 513 | fi 514 | 515 | # get info about proposals 516 | __UnvotedProposals 517 | 518 | # get info about upgrades 519 | __UpgradePlan 520 | fi 521 | else 522 | if [[ ${NODE_STATUS} == "" ]]; then 523 | if [[ ${ALLOW_SERVICE_RESTART} == "true" ]]; then 524 | systemctl restart ${SERVICE} > /dev/null 2>&1 525 | TEXT="_we lost any connection. \n\nbut service has been restarted." 526 | else 527 | TEXT="_we lost any connection." 528 | fi 529 | else 530 | if [[ ${ALLOW_SERVICE_RESTART} == "true" ]]; then 531 | systemctl restart ${SERVICE} > /dev/null 2>&1 532 | TEXT="_connection is refused. \n\nbut service has been restarted." 533 | else 534 | TEXT="_connection is refused." 535 | fi 536 | fi 537 | 538 | SEND=1 539 | __Send 540 | fi 541 | 542 | # read the config 543 | # echo ${CONF} 544 | . ./${CONF} 545 | 546 | # if 'SEND' == 1 > send 'MESSAGE' into 'alarm telegram channel' 547 | if [[ ${SEND} == "1" ]]; then 548 | curl --header 'Content-Type: application/json' \ 549 | --request 'POST' \ 550 | --data '{"chat_id":"'"${CHAT_ID_ALARM}"'", "text":"'"$(echo -e "${MESSAGE}")"'", "parse_mode": "html"}' "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ 551 | > /dev/null 2>&1 552 | fi 553 | 554 | # send 'log_message' only one time per hour 555 | # if the current minute value is less than 'MINUTE' > send 'log_message', else > ignore 556 | if (( $(echo "$(date +%M) < ${MINUTE}" | bc -l) )); then 557 | # write message text into temp file to send all logs via one message 558 | echo ${MESSAGE} >> "./${CHAT_ID_STATUS}" 559 | fi 560 | } 561 | 562 | function Main() { 563 | # init some variables 564 | SEND_LOAD="" 565 | MINUTE=10 566 | TIMEZONE="Africa/Abidjan" 567 | DISK_PERCENTAGE_USED_ALARM=100 568 | __PreMessage 569 | 570 | # print the current time 571 | echo -e " "; echo -e "/// $(date '+%F %T') ///"; echo -e " " 572 | 573 | # read 'cosmos.conf' 574 | cd $HOME/status/ && . ./cosmos.conf 575 | export TZ=${TIMEZONE} 576 | 577 | # get ServerLoad info 578 | __ServerLoad 579 | 580 | # run 'NodeStatus' with every '*.conf' file in the 'status' folder 581 | for CONF in *.conf; do 582 | 583 | # if config at least contains 'COSMOS' string, then go 584 | if [[ $(cat ${CONF}) == *"COSMOS"* ]]; then 585 | 586 | # init some variables 587 | IGNORE_INACTIVE_STATUS="" 588 | IGNORE_WRONG_PRIVKEY="" 589 | ALLOW_SERVICE_RESTART="" 590 | POSITION_GAP_ALARM=0 591 | BLOCK_GAP_ALARM=100 592 | 593 | # read the config 594 | . ./${CONF} 595 | echo -e " " 596 | 597 | # if config directory, config.toml and genesis.json exist 598 | if [[ -e "${CONFIG}" && -e "${CONFIG}/config.toml" && -e "${CONFIG}/genesis.json" ]]; then 599 | 600 | # get '--node' and '--chain' value 601 | NODE=$(cat ${CONFIG}/config.toml | grep -oPm1 "(?<=^laddr = \")([^%]+)(?=\")") 602 | NODE_HOME=$(echo ${CONFIG} | rev | cut -c 8- | rev) 603 | CHAIN=$(cat ${CONFIG}/genesis.json | jq .chain_id | sed -E 's/.*"([^"]+)".*/\1/') 604 | PORT=$(echo ${NODE} | awk 'NR==1 {print; exit}' | grep -o ":[0-9]*" | awk 'NR==2 {print; exit}' | cut -c 2-) 605 | 606 | # run 'NodeStatus' 607 | __NodeStatus 608 | else 609 | echo -e "${PROJECT} | ${MONIKER}\n" 610 | echo "we have some problems with config. maybe config files do not exist." 611 | MESSAGE="${PROJECT} ⠀|⠀ ${MONIKER}\n\nwe have some problems with config.\nremove '${CONF}' or fix it.\n\n" 612 | 613 | # send 'alarm_message' 614 | curl --header 'Content-Type: application/json' \ 615 | --request 'POST' \ 616 | --data '{"chat_id":"'"${CHAT_ID_ALARM}"'", "text":"'"$(echo -e "${MESSAGE}")"'", "parse_mode": "html"}' "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ 617 | > /dev/null 2>&1 618 | fi 619 | fi 620 | done 621 | 622 | # send 'log_messages' from temp files into chats 623 | CHAT_IDS=($(ls | grep -E "[0-9]{2,}")) 624 | for i in "${!CHAT_IDS[@]}"; do 625 | SLASH="/// $(date '+%F %T') ///" 626 | 627 | curl --header 'Content-Type: application/json' \ 628 | --request 'POST' \ 629 | --data '{"chat_id":"'"${CHAT_IDS[i]}"'","text":"'"$(echo -e ${SLASH})"'", "parse_mode": "html"}' "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ 630 | > /dev/null 2>&1 631 | 632 | curl --header 'Content-Type: application/json' \ 633 | --request 'POST' \ 634 | --data '{"chat_id":"'"${CHAT_IDS[i]}"'", "text":"'"$(cat "./${CHAT_IDS[i]}")"'", "parse_mode": "html"}' "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \ 635 | > /dev/null 2>&1 636 | done 637 | 638 | # delete temp log files 639 | rm ./-?[0-9]* > /dev/null 2>&1 640 | } 641 | 642 | # run 'main' 643 | Main 644 | -------------------------------------------------------------------------------- /tendermint/node_status/crontab: -------------------------------------------------------------------------------- 1 | # status 2 | 1,11,21,31,41,51 * * * * bash $HOME/status/cosmos.sh >> $HOME/status/cosmos.log 2>&1 3 | -------------------------------------------------------------------------------- /tendermint/node_status/curl.md: -------------------------------------------------------------------------------- 1 | **PROJECT** | **CURL** | 2 | --- | --- | 3 | gravity-bridge-m | CURL="https://api-gravity-bridge.cosmostation.io/v1/status" | 4 | sifchain-m | CURL="https://api-sifchain.cosmostation.io/v1/status" | 5 | omniflix-m | CURL="https://api-omniflix.cosmostation.io/v1/status" | 6 | provenance-m | CURL="https://api-provenance.cosmostation.io/v1/status" | 7 | evmos-m | CURL="https://api-evmos.cosmostation.io/v1/status" | 8 | galaxy-m | CURL="https://galaxy.postcapitalist.io/bank/total/uglx" | 9 | meme-m | CURL="https://api-meme-1.meme.sx/bank/total/umeme" | 10 | beezee-m | CURL="https://rest.getbze.com/bank/total/ubze" | 11 | desmos-m | CURL="https://api-desmos.cosmostation.io/v1/status" | 12 | vidulum-m | CURL="https://mainnet-lcd.vidulum.app/bank/total/uvdl" | 13 | chromic-m | CURL="https://chtd-api.skynetvalidators.com/bank/total/ucht" | 14 | dig-m | CURL="https://api-1-dig.notional.ventures/bank/total/udig" | 15 | konstellation-m | CURL="https://api-konstellation.cosmostation.io/v1/status" | 16 | mises-m | CURL="https://rpc.gw.mises.site/block?latest" | 17 | idep-m | CURL="https://rpc.idep.sgtstake.com/block?latest" | 18 | bitsong-m | CURL="https://rpc.bitsong.forbole.com/block?latest" | 19 | | | 20 | kyve-t | CURL="https://api.explorer.kyve.network/bank/total/tkyve" | 21 | stafi-t | CURL="https://test-rest-rpc1.stafihub.io/bank/total/ufis" | 22 | pylons-t | CURL="https://pylons.api.explorers.guru/api/blocks/latest" | 23 | dws-t | CURL="https://dws.api.explorers.guru/api/blocks/latest" | 24 | quicksilver-t | CURL="https://quicksilver.api.explorers.guru/api/blocks/latest" | 25 | kqcosmos-t | CURL="https://api-3.quick.testnet.run/bank/total/uatom" | 26 | defund-t | CURL="https://defund.api.explorers.guru/api/blocks/latest" | 27 | sei-t | CURL="https://sei.api.explorers.guru/api/blocks/latest" | 28 | celestia-t | CURL="https://celestia.api.explorers.guru/api/blocks/latest" | 29 | another-t | CURL="https://api-anone.notional.ventures/bank/total/uan1" | 30 | uptik-t | CURL="https://seed1.testnet.uptick.network:1318/bank/total" | 31 | cosmic-t | CURL="https://coho.api.explorers.guru/api/blocks/latest" | 32 | aura-t | CURL="https://halo-api.aurascan.io/api/v1/status" | 33 | clan-t | CURL="https://api.clan.testnet.run/bank/total/uclan" | 34 | kujira-t | CURL="https://kujira-testnet.synergynodes.com/bank/total/ukuji" | 35 | 36 | If you want to add your custom CURL into script, you have to open the needed explorer, open `Inspect` with your mouse's right click, open `Network` tab and find a request with `v1/status`, `bank/total` or `blocks/latest` words in the request link. 37 | 38 | Or just ping `@cyberomanov` via telegram. 39 | -------------------------------------------------------------------------------- /tendermint/node_status/name.conf: -------------------------------------------------------------------------------- 1 | # ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | # ////////////////// important variables for script logic, required to be filled ////////////////// 3 | # ///////////////////////////////////////////////////////////////////////////////////////////////// 4 | 5 | # validator moniker 6 | MONIKER="cyberomanov" 7 | # delegator address 8 | DELEGATOR_ADDRESS="cosmos12af...24" 9 | # validator address 10 | VALIDATOR_ADDRESS="cosmosvaloper12af...24" 11 | # token name 12 | TOKEN="atom" 13 | # token denomination (count of nulls) 14 | DENOM=1000000 15 | # project name, used in 'log_messages' and 'alarm_messages' for easy reading 16 | PROJECT="cosmos-t" 17 | # exact full path to bin 18 | COSMOS="/root/go/bin/cosmosd" 19 | # exact full path to config folder 20 | CONFIG="/root/.cosmos/config/" 21 | # 'chat_id' for 'alarm_messages' with enabled notifications 22 | CHAT_ID_ALARM="-11223344" 23 | # 'chat_id' for 'log_messages' 24 | CHAT_ID_STATUS="-44332211" 25 | # 'bot_token' for sending messages 26 | BOT_TOKEN="55667788:xxx-xxx_xxx" 27 | 28 | # ///////////////////////////////////////////////////////////////////////////////////////////////// 29 | # /////// custom configuration, uncomment a specific variable to enable a specific function /////// 30 | # ///////////////////////////////////////////////////////////////////////////////////////////////// 31 | 32 | # 1. link to an explorer API to get a difference between 'ideal_latest_block' and 'local_latest_block' 33 | # 34 | # if validator is in the past more than 'N' blocks > 'alarm_message' 35 | # try to find your 'curl' in 'curl.md' file or ping @cyberomanov via telegram for help 36 | # 37 | # uncomment the following variable and set your value to enable the function, disabled by default 38 | # CURL="https://api-cosmos.cosmostation.io/v1/status" 39 | 40 | # 2. definition of 'N' 41 | # 42 | # doesn't work without correctly filled 'CURL' value 43 | # 44 | # examples: 45 | # conditions: 'CURL' is set, 'BLOCK_GAP_ALARM' is set to '100' and 'ideal_latest_block' is 21000 46 | # result #1: if 'local_latest_block' is 21003 > no 'alarm_message' 47 | # result #2: if 'local_latest_block' is 20997 > no 'alarm_message' 48 | # result #3: if 'local_latest_block' is 20895 > 'alarm_message' 49 | # 50 | # uncomment the following variable and set your value to enable the function, set '0' to disable 51 | # BLOCK_GAP_ALARM=100 52 | 53 | # ------------------------------------------------------------------------------------------------- 54 | 55 | # 3. acceptable gap between validator position and max set of active validators 56 | # 57 | # examples: 58 | # conditions: max set is 200 active validators and 'POSITION_GAP_ALARM' is set to '10' 59 | # result #1: if validator place is from 1st to 190th > no 'alarm_message' 60 | # result #2: if validator place is from 191st to 200th > 'alarm_message' 61 | # 62 | # uncomment the following variable and set your value to enable the function, disabled by default 63 | # POSITION_GAP_ALARM=10 64 | 65 | # ------------------------------------------------------------------------------------------------- 66 | 67 | # 4. ignore alarm trigger when validator has inactive status 68 | # 69 | # uncomment the following variable, if you want to do ignore inactive status alarm trigger 70 | # or leave it commented, if you want to receive 'alarm_messages' about inactive status 71 | # IGNORE_INACTIVE_STATUS="true" 72 | 73 | # 5. ignore alarm trigger when validator has wrong 'priv_validator_key' 74 | # 75 | # if you know that validator is running with a wrong priv_key 76 | # than you may want to ignore 'jailed_status' and 'many_missed_blocks' trigger for 'alarm_messages' 77 | # 78 | # uncomment the following variable, if you want to do ignore mentioned alarm triggers 79 | # or leave it commented, if you want to receive 'alarm_messages' about jails/missed_blocks 80 | # IGNORE_WRONG_PRIVKEY="true" 81 | 82 | # ------------------------------------------------------------------------------------------------- 83 | 84 | # 6. allow the script or not to allow to restart a specific service 85 | # 86 | # doesn't work without correctly filled 'SERVICE' value 87 | # 88 | # examples: 89 | # conditions #1: 'BLOCK_GAP_ALARM' is '100', 'ideal_latest_block' is 21000, 'ALLOW_SERVICE_RESTART' is 'true' 90 | # result #1: if 'local_latest_block' is 20895 > 'alarm_message' AND 'service_restart' 91 | # conditions #2: service is down and 'ALLOW_SERVICE_RESTART' is 'true' 92 | # result #2: 'alarm_message' AND 'service_restart' 93 | # conditions #3: service is up, but smth is wrong and 'ALLOW_SERVICE_RESTART' is 'true' 94 | # result #3: 'alarm_message' AND 'service_restart' 95 | # 96 | # uncomment the following variable, if you want to do 'service_restart' made by the script 97 | # or leave it commented, if you do not want to do 'service_restart' made by the script 98 | # ALLOW_SERVICE_RESTART="true" 99 | 100 | # 7. service name 101 | # 102 | # is not used anywhere if 'ALLOW_SERVICE_RESTART' is 'false' or commented 103 | # but used for 'service_restart' if 'ALLOW_SERVICE_RESTART' is 'true' or uncommented 104 | # 105 | # uncomment the following variable and set your value to enable the function 106 | # SERVICE="cosmosd" 107 | --------------------------------------------------------------------------------