diff --git a/.gitignore b/.gitignore index 9f10b34e1..d8a7c692f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ .vscode dist/** !dist/setup.sh -!dist/sr.sh \ No newline at end of file +!dist/sr.sh diff --git a/cyphernodeconf_docker/features.json b/cyphernodeconf_docker/features.json index 9045fde70..76b50f25f 100644 --- a/cyphernodeconf_docker/features.json +++ b/cyphernodeconf_docker/features.json @@ -18,5 +18,9 @@ { "name": "Specter Cypherapp", "value": "specter" + }, + { + "name": "Telegram notifications", + "value": "telegram" } ] diff --git a/cyphernodeconf_docker/help.json b/cyphernodeconf_docker/help.json index 9e41b6a37..9898f3a9e 100644 --- a/cyphernodeconf_docker/help.json +++ b/cyphernodeconf_docker/help.json @@ -48,5 +48,6 @@ "installer_mode": "Only one installation mode is supported, right now: local docker (self-hosted). Choose wisely ;-)", "installer_cleanup": "Do you want to remove this configurator Docker image after installation? This would free about 150MB of disk space.", "docker_mode": "Cyphernode Docker services can be run using Docker Swarm (https://docs.docker.com/engine/swarm/) or docker-compose (https://docs.docker.com/compose/overview/). Both will work, some users prefer one to another depending on deployment types, scalability, current framework, etc.", + "telegram": "Would you like to enable Telegram notifications - Some manual steps need to be carried once before first use. See /notifier_docker/sample-config.sh", "__default__": "" } diff --git a/cyphernodeconf_docker/lib/app.js b/cyphernodeconf_docker/lib/app.js index e9beae1d0..e3847d479 100644 --- a/cyphernodeconf_docker/lib/app.js +++ b/cyphernodeconf_docker/lib/app.js @@ -561,6 +561,13 @@ module.exports = class App { clearnet: !this.isChecked('features', 'tor') || this.isChecked('clearnet', 'clearnet_lightning'), tor_hostname: this.sessionData.tor_lightning_hostname } + }, + telegram: { + networks: ['cyphernodenet'], + docker: "cypernode/notifier", + extra:{ + torified: this.torifyables.find(data => data.value === 'tor_telegram').checked, + } } } diff --git a/cyphernodeconf_docker/prompters/040_tor.js b/cyphernodeconf_docker/prompters/040_tor.js index 871be6c10..a65783770 100644 --- a/cyphernodeconf_docker/prompters/040_tor.js +++ b/cyphernodeconf_docker/prompters/040_tor.js @@ -66,6 +66,7 @@ module.exports = { // - OTS Callbacks (webhooks) // - Address Watches Callbacks (webhooks) // - TXID Watches Callbacks (webhooks) +// - Telegram // Certain services can also use clearnet. What do you want to allow to use clearnet? // - Bitcoin Node diff --git a/cyphernodeconf_docker/prompters/300_notifier.js b/cyphernodeconf_docker/prompters/300_notifier.js new file mode 100644 index 000000000..d1176a210 --- /dev/null +++ b/cyphernodeconf_docker/prompters/300_notifier.js @@ -0,0 +1,27 @@ +const chalk = require('chalk'); + +const name = 'notifier'; + +const capitalise = function( txt ) { + return txt.charAt(0).toUpperCase() + txt.substr(1); +}; + +const prefix = function() { + return chalk.green(capitalise(name)+': '); +}; + +const featureCondition = function(props) { + return props.features && props.features.indexOf( 'telegram' ) != -1; +}; + +module.exports = { + name: function() { + return name; + }, + prompts: function( utils ) { + return []; + }, + templates: function( props ) { + return [ 'notifier.env' ]; + } +}; diff --git a/cyphernodeconf_docker/schema/config-v0.2.6.json b/cyphernodeconf_docker/schema/config-v0.2.6.json index 3fdb34cc0..7d27b4377 100644 --- a/cyphernodeconf_docker/schema/config-v0.2.6.json +++ b/cyphernodeconf_docker/schema/config-v0.2.6.json @@ -189,7 +189,8 @@ "lightning", "otsclient", "batcher", - "specter" + "specter", + "telegram" ], "title": "The feature", "default": "", @@ -198,7 +199,8 @@ "lightning", "otsclient", "batcher", - "specter" + "specter", + "telegram" ] } }, @@ -217,7 +219,8 @@ "tor_otsoperations", "tor_otswebhooks", "tor_addrwatcheswebhooks", - "tor_txidwatcheswebhooks" + "tor_txidwatcheswebhooks", + "tor_telegram" ], "title": "The Torified feature", "default": "", @@ -228,7 +231,8 @@ "tor_otsoperations", "tor_otswebhooks", "tor_addrwatcheswebhooks", - "tor_txidwatcheswebhooks" + "tor_txidwatcheswebhooks", + "tor_telegram" ] } }, diff --git a/cyphernodeconf_docker/templates/installer/config.sh b/cyphernodeconf_docker/templates/installer/config.sh index 0936b433d..26f0abfea 100644 --- a/cyphernodeconf_docker/templates/installer/config.sh +++ b/cyphernodeconf_docker/templates/installer/config.sh @@ -1,6 +1,7 @@ INSTALLER_MODE=<%= installer_mode %> BITCOIN_INTERNAL=<%= (bitcoin_mode==="internal"?'true':'false') %> FEATURE_LIGHTNING=<%= (features.indexOf('lightning') != -1)?'true':'false' %> +FEATURE_TELEGRAM=<%= (features.indexOf('telegram') != -1)?'true':'false' %> FEATURE_BATCHER=<%= (features.indexOf('batcher') != -1)?'true':'false' %> FEATURE_SPECTER=<%= (features.indexOf('specter') != -1)?'true':'false' %> FEATURE_OTSCLIENT=<%= (features.indexOf('otsclient') != -1)?'true':'false' %> @@ -20,6 +21,7 @@ TOR_TXID_WATCH_WEBHOOKS=<%= (torifyables && torifyables.indexOf('tor_txidwatches TOR_TRAEFIK=<%= (torifyables && torifyables.indexOf('tor_traefik') !== -1)?'true':'false' %> TOR_BITCOIN=<%= (torifyables && torifyables.indexOf('tor_bitcoin') !== -1)?'true':'false' %> TOR_LIGHTNING=<%= (torifyables && torifyables.indexOf('tor_lightning') !== -1)?'true':'false' %> +TOR_TELEGRAM=<%= (torifyables && torifyables.indexOf('tor_telegram') !== -1)?'true':'false' %> <% } %> DOCKER_MODE=<%= docker_mode %> RUN_AS_USER=<%= run_as_different_user?username:'' %> diff --git a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml index 439d95041..526880f05 100644 --- a/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml +++ b/cyphernodeconf_docker/templates/installer/docker/docker-compose.yaml @@ -280,11 +280,14 @@ services: - .env/notifier.env volumes: - "<%= logs_datapath %>:/cnlogs" + - "<%= proxy_datapath %>/pgpass:/proxy/db/pgpass" + - container_monitor:/container_monitor networks: - cyphernodenet - cyphernodeappsnet depends_on: - broker + - postgres <% if ( docker_mode === 'swarm' ) { %> deploy: replicas: 1 diff --git a/cyphernodeconf_docker/templates/installer/testdeployment.sh b/cyphernodeconf_docker/templates/installer/testdeployment.sh index 60755d217..dd52235e3 100644 --- a/cyphernodeconf_docker/templates/installer/testdeployment.sh +++ b/cyphernodeconf_docker/templates/installer/testdeployment.sh @@ -56,6 +56,7 @@ export USER=$(id -u <%= default_username %>):$(id -g <%= default_username %>) # Will test if Cyphernode is fully up and running... docker run --rm -it -v $current_path/testfeatures.sh:/testfeatures.sh \ -v <%= gatekeeper_datapath %>:/gatekeeper \ +-v ${current_path}/.cyphernodeconf/installer/config.sh:/config.sh \ -v $current_path:/dist \ -v cyphernode_container_monitor:/container_monitor:ro \ --network cyphernodenet eclipse-mosquitto:<%= mosquitto_version %> /testfeatures.sh @@ -83,6 +84,11 @@ if [ "$EXIT_STATUS" -ne "0" ]; then exit 1 fi +if [ "$RUN_TELEGRAM_SETUP" -ne "0" ]; then + printf "\r\n\e[1;32mStarting Telegram setup\e[0m\n" + ../notifier_docker/script/start-tg-setup.sh +fi + printf "\r\n\033[0;92mDepending on your current location and DNS settings, point your favorite browser to one of the following URLs to access Cyphernode's status page:\r\n" printf "\r\n" printf "\033[0;95m<% cns.forEach(cn => { %><%= ('https://' + cn + ':' + traefik_https_port + '/welcome\\r\\n') %><% }) %>\033[0m\r\n" diff --git a/cyphernodeconf_docker/templates/installer/testfeatures.sh b/cyphernodeconf_docker/templates/installer/testfeatures.sh index 0c516b29a..e2f9335b5 100644 --- a/cyphernodeconf_docker/templates/installer/testfeatures.sh +++ b/cyphernodeconf_docker/templates/installer/testfeatures.sh @@ -4,6 +4,8 @@ apk add --update --no-cache openssl curl jq coreutils postgresql > /dev/null . /gatekeeper/keys.properties +. ./config.sh + checkgatekeeper() { echo -e "\r\n\e[1;36mTesting Gatekeeper...\e[0;32m" > /dev/console @@ -121,6 +123,30 @@ checknotifier() { return 0 } +checknotifiertelegram() { + echo -en "\r\n\e[1;36mTesting Notifier Telegram... " > /dev/console + local response + local returncode + local msg + + if [ "$TOR_TELEGRAM" = "true" ]; then + msg="{\"response-topic\":\"response/$$\",\"cmd\":\"sendToTelegramNoop\",\"tor\":true}" + else + msg="{\"response-topic\":\"response/$$\",\"cmd\":\"sendToTelegramNoop\"}" + fi + + response=$(mosquitto_rr -h broker -W 15 -t notifier -e "response/$$" -m "$msg") + returncode=$? + [ "${returncode}" -ne "0" ] && echo -e "\e[1;31mNotifier Telegram needs to be configured" > /dev/console && return 115 + http_code=$(echo "${response}" | jq -r ".http_code") + [ "${http_code}" -ge "400" ] && echo -e "\e[1;31mNotifier Telegram needs to be configured" > /dev/console && return 118 + [ "${http_code}" -eq "0" ] && echo -e "\e[1;31mNotifier Telegram needs to be configured" > /dev/console && return 119 + + echo -e "\e[1;36mNotifier Telegram rocks!" > /dev/console + + return 0 +} + checkots() { echo -en "\r\n\e[1;36mTesting OTSclient... " > /dev/console local rc @@ -289,6 +315,7 @@ feature_status() { # Let's first see if everything is up. echo "EXIT_STATUS=1" > /dist/exitStatus.sh +RUN_TELEGRAM_SETUP=0 ############################# # Ping containers and PROXY # @@ -306,7 +333,7 @@ if [ "${returncode}" -ne "0" ]; then workingproxy="false" fi else - echo -e "\e[1;36mCyphernode seems to be correctly deployed. Let's run more thourough tests..." > /dev/console + echo -e "\e[1;36mCyphernode seems to be correctly deployed. Let's run more thorough tests..." > /dev/console fi # Let's now check each feature fonctionality... @@ -405,6 +432,25 @@ fi finalreturncode=$((${returncode} | ${finalreturncode})) result="${result}$(feature_status ${returncode} 'Notifier error!')}" +<% if (features.indexOf('telegram') != -1) { %> +############################# +# NOTIFIER TELEGRAM # +############################# + +result="${result},{\"coreFeature\":true, \"name\":\"notifier telegram\",\"working\":" +status=$(echo "{${containers}}" | jq ".containers[] | select(.name == \"notifier\") | .active") +if [[ "${workingproxy}" = "true" && "${status}" = "true" ]]; then + checknotifiertelegram + returncode=$? + # Non critical error - run telegram setup at the end + [ "${returncode}" -ne "0" ] && RUN_TELEGRAM_SETUP=1 && returncode=0 +else + returncode=1 +fi +finalreturncode=$((${returncode} | ${finalreturncode})) +result="${result}$(feature_status ${returncode} 'Notifier Telegram error! - Please run Telegram setup - See doc/TELEGRAM.md')}" +<% } %> + ############################# # PYCOIN # ############################# @@ -500,6 +546,7 @@ result="{${result}]}" echo "${result}" > /gatekeeper/installation.json echo "EXIT_STATUS=${finalreturncode}" > /dist/exitStatus.sh +echo "RUN_TELEGRAM_SETUP=$RUN_TELEGRAM_SETUP" >> /dist/exitStatus.sh <% if (features.indexOf('tor') !== -1 && torifyables && torifyables.indexOf('tor_traefik') !== -1) { %> echo "TOR_TRAEFIK_HOSTNAME=$(cat /dist/.cyphernodeconf/tor/traefik/hidden_service/hostname)" >> /dist/exitStatus.sh diff --git a/cyphernodeconf_docker/templates/notifier/notifier.env b/cyphernodeconf_docker/templates/notifier/notifier.env index d1283b99e..160e122ef 100644 --- a/cyphernodeconf_docker/templates/notifier/notifier.env +++ b/cyphernodeconf_docker/templates/notifier/notifier.env @@ -3,3 +3,12 @@ TRACING=1 TOR_HOST=tor TOR_PORT=9050 <% } %> + +<% if ( features.indexOf('telegram') !== -1 ) { %> +FEATURE_TELEGRAM=true +<% if ( features.indexOf('tor') !== -1 && torifyables && torifyables.indexOf('tor_telegram') !== -1 ) { %> +TOR_TELEGRAM=true +<% } %> +<% } %> + +PGPASSFILE=/proxy/db/pgpass diff --git a/cyphernodeconf_docker/torifyables.json b/cyphernodeconf_docker/torifyables.json index 87c201270..51c861baa 100644 --- a/cyphernodeconf_docker/torifyables.json +++ b/cyphernodeconf_docker/torifyables.json @@ -26,5 +26,9 @@ { "name": "TXID Watches Callbacks (webhooks)", "value": "tor_txidwatcheswebhooks" + }, + { + "name": "Telegram", + "value": "tor_telegram" } ] diff --git a/dist/setup.sh b/dist/setup.sh index 9f4b2b198..734cbb2b1 100755 --- a/dist/setup.sh +++ b/dist/setup.sh @@ -493,7 +493,6 @@ install_docker() { copy_file $cyphernodeconf_filepath/otsclient/otsclient.env $current_path/.env/otsclient.env 1 $SUDO_REQUIRED copy_file $cyphernodeconf_filepath/proxycron/proxycron.env $current_path/.env/proxycron.env 1 $SUDO_REQUIRED - if [[ $BITCOIN_INTERNAL == true ]]; then if [ ! -d $BITCOIN_DATAPATH ]; then step " create $BITCOIN_DATAPATH" @@ -856,6 +855,15 @@ install_apps() { next fi fi + + if [[ $FEATURE_TELEGRAM == true ]]; then + step " enabled Telegram - Manual configuration needed before first time use (Bot and Group creation, API key) - see doc/TELEGRAM.md" + next + else + step " disabled Telegram" + next + fi + } install() { diff --git a/doc/TELEGRAM.md b/doc/TELEGRAM.md new file mode 100644 index 000000000..c1ecfabce --- /dev/null +++ b/doc/TELEGRAM.md @@ -0,0 +1,91 @@ +# Telegram integration in Cyphernode. + +The first time you run Cyphernode, you will get an error concerning Telegram beacause Telegram has to be setup with the next few steps. Go to [STEP 1] + +Build and setup Cyphernode - Choose to enable Telegram. + +==> START CYPHERNODE by running /dist/start.sh + +[STEP 1]: In directory cyphernode/notifier_docker/script, you will find the script `start-tg-setup.sh` to start the Telegram setup. It runs inside the notifier container with this command : + `docker exec -it $(docker ps -q -f "name=cyphernode_notifier") ./tgsetup.sh` + +Follow the steps of the installer - example output follows: + +In directory notifier_docker/script, run ** ./start-tg-setup.sh ** + +Testing database before starting the configuration +Database is alive + +Do you wish to configure Telegram for Cyphernode? [yn] yA + +dding the Telegram base URL in database config table cyphernode_props +[sql] psql -qAtX -h postgres -U cyphernode -c "INSERT INTO ...." + + +Please go into your Telegram App and start chatting with the @BotFather + +==> (Step 1) Enter @Botfather in the search tab and choose this bot + +==> Note, official Telegram bots have a blue checkmark beside their name + +==> (Step 2) Click “Start” to activate BotFather bot. In response, you receive a list of commands to manage bots + +==> (Step 3) Choose or type the /newbot command and send it + +==> @BotFather replies: Alright, a new bot. How are we going to call it? Please choose a name for your bot + +==> (Step 4) Choose a name for your bot. And choose a username for your bot — the bot can be found by its username in searches. The username must be unique and end with the word 'bot' + +==> After you choose a suitable name for your bot — the bot is created. You will receive a message with a link to your bot t.me/ + +==> Cyphernode needs the generated token to access the API: Copy the line below following the message 'Use this token to access the HTTP API' + +Enter the token here: 5172851233:AAHkpd4T1ILyhXyqDelNnOTgFE4hl-AQSVM + +Telegram Setup will now try to obtain the chat ID from the Telgram server. + +To make this happen, please go into the Telegram App and send a message to the new bot + +Click on the link in the @BotFather's answer : Congratulations on your new bot. You will find it at t.me/your-new-bot. + +Trying to contact Telegram server... + +Reloading configs + +Sending message to Telegram [Tue May 3 16:29:03 UTC 2022] +Ok. Done. + + + +=============================================================== +How it works : + +calling Telegram API + example : + https://api.telegram.org/bot+TELEGRAM_API_KEY/your-action + https://api.telegram.org/botTELEGRAM_API_KEY/getMe + returns: + {"ok":true,"result":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot","can_join_groups":true,"can_read_all_group_messages":false,"supports_inline_queries":false}} + + Add your Bot to a group and then get updates to get the chat.ID in order to send messages to this group afterwards. + Below, the chat.id is chat.id:-TELEGRAM_CHAT_ID + + https://api.telegram.org/botTELEGRAM_API_KEY/getUpdates + + {"ok":true,"result":[{"update_id":701180871, + #"my_chat_member":{"chat":{"id":-TELEGRAM_CHAT_ID,"title":"Logging","type":"group","all_members_are_administrators":false},"from":{"id":1609436204,"is_bot":false,"first_name":"Roger","last_name":"Brulotte","username":"RogerBrulotte","language_code":"en"},"date":1635877254,"old_chat_member":{"user":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"},"status":"member"},"new_chat_member":{"user":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"},"status":"left"}}},{"update_id":701180872, + #"message":{"message_id":7,"from":{"id":1609436204,"is_bot":false,"first_name":"Roger","last_name":"Brulotte","username":"RogerBrulotte","language_code":"en"},"chat":{"id":-TELEGRAM_CHAT_ID,"title":"Logging","type":"group","all_members_are_administrators":true},"date":1635877254,"left_chat_participant":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"},"left_chat_member":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"}}},{"update_id":701180873, + #"my_chat_member":{"chat":{"id":-TELEGRAM_CHAT_ID,"title":"Logging","type":"group","all_members_are_administrators":true},"from":{"id":1609436204,"is_bot":false,"first_name":"Roger","last_name":"Brulotte","username":"RogerBrulotte","language_code":"en"},"date":1635877290,"old_chat_member":{"user":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"},"status":"left"},"new_chat_member":{"user":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"},"status":"member"}}},{"update_id":701180874, + #"message":{"message_id":8,"from":{"id":1609436204,"is_bot":false,"first_name":"Roger","last_name":"Brulotte","username":"RogerBrulotte","language_code":"en"},"chat":{"id":-TELEGRAM_CHAT_ID,"title":"Logging","type":"group","all_members_are_administrators":true},"date":1635877290,"new_chat_participant":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"},"new_chat_member":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"},"new_chat_members":[{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"}]}}]} + +Bot says Hello World using the chat id returned previously + https://api.telegram.org/botTOKEN/sendMessage?chat_id=CHAT-ID&text=Hello+World + https://api.telegram.org/botTELEGRAM_API_KEY/sendMessage?chat_id=-TELEGRAM_CHAT_ID&text=Hello+World + + returns: + {"ok":true,"result":{"message_id":9,"from":{"id":2084591315,"is_bot":true,"first_name":"Roger-logger","username":"RogerLoggerBot"},"chat":{"id":-TELEGRAM_CHAT_ID,"title":"Logging","type":"group","all_members_are_administrators":true},"date":1635877783,"text":"Hello World"}} + + + +curl POST example + curl -X POST https://api.telegram.org/botTELEGRAM_API_KEY/sendMessage?chat_id=TELEGRAM_CHAT_ID -H 'Content-Type: application/json' -d '{"text":"text in POST data"}' diff --git a/notifier_docker/Dockerfile b/notifier_docker/Dockerfile index 2b858a8cd..76cb08b2a 100644 --- a/notifier_docker/Dockerfile +++ b/notifier_docker/Dockerfile @@ -2,7 +2,7 @@ FROM eclipse-mosquitto:1.6-openssl ENV HOME /notifier -RUN apk --no-cache --update add jq curl su-exec +RUN apk --no-cache --update add jq curl su-exec postgresql WORKDIR ${HOME} diff --git a/notifier_docker/script/colors.sh b/notifier_docker/script/colors.sh new file mode 100644 index 000000000..883d58eeb --- /dev/null +++ b/notifier_docker/script/colors.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +# Reset +Color_Off="\033[0m" # Text Reset + +# Regular Colors +Black="\033[0;30m" # Black +Red="\033[0;31m" # Red +Green="\033[0;32m" # Green +Yellow="\033[0;33m" # Yellow +Blue="\033[0;34m" # Blue +Purple="\033[0;35m" # Purple +Cyan="\033[0;36m" # Cyan +White="\033[0;37m" # White + +# Bold +BBlack="\033[1;30m" # Black +BRed="\033[1;31m" # Red +BGreen="\033[1;32m" # Green +BYellow="\033[1;33m" # Yellow +BBlue="\033[1;34m" # Blue +BPurple="\033[1;35m" # Purple +BCyan="\033[1;36m" # Cyan +BWhite="\033[1;37m" # White + +# Underline +UBlack="\033[4;30m" # Black +URed="\033[4;31m" # Red +UGreen="\033[4;32m" # Green +UYellow="\033[4;33m" # Yellow +UBlue="\033[4;34m" # Blue +UPurple="\033[4;35m" # Purple +UCyan="\033[4;36m" # Cyan +UWhite="\033[4;37m" # White + +# Background +On_Black="\033[40m" # Black +On_Red="\033[41m" # Red +On_Green="\033[42m" # Green +On_Yellow="\033[43m" # Yellow +On_Blue="\033[44m" # Blue +On_Purple="\033[45m" # Purple +On_Cyan="\033[46m" # Cyan +On_White="\033[47m" # White + +# High Intensty +IBlack="\033[0;90m" # Black +IRed="\033[0;91m" # Red +IGreen="\033[0;92m" # Green +IYellow="\033[0;93m" # Yellow +IBlue="\033[0;94m" # Blue +IPurple="\033[0;95m" # Purple +ICyan="\033[0;96m" # Cyan +IWhite="\033[0;97m" # White + +# Bold High Intensty +BIBlack="\033[1;90m" # Black +BIRed="\033[1;91m" # Red +BIGreen="\033[1;92m" # Green +BIYellow="\033[1;93m" # Yellow +BIBlue="\033[1;94m" # Blue +BIPurple="\033[1;95m" # Purple +BICyan="\033[1;96m" # Cyan +BIWhite="\033[1;97m" # White + +# High Intensty backgrounds +On_IBlack="\033[0;100m" # Black +On_IRed="\033[0;101m" # Red +On_IGreen="\033[0;102m" # Green +On_IYellow="\033[0;103m" # Yellow +On_IBlue="\033[0;104m" # Blue +On_IPurple="\033[10;95m" # Purple +On_ICyan="\033[0;106m" # Cyan +On_IWhite="\033[0;107m" # White diff --git a/notifier_docker/script/requesthandler.sh b/notifier_docker/script/requesthandler.sh index 5a571025f..45cebbc33 100644 --- a/notifier_docker/script/requesthandler.sh +++ b/notifier_docker/script/requesthandler.sh @@ -3,14 +3,28 @@ . ./trace.sh . ./web.sh . ./response.sh +. ./sql.sh + main() { trace "Entering main()..." + while true; do + loadConfig + + readLoop + done + +} + +readLoop(){ local msg local cmd local response local response_topic + local url + + trace "[readLoop] Starting" # Messages should have this form: # {"response-topic":"response/5541","cmd":"web","url":"2557df870b9a:1111/callback1conf","body":"eyJpZCI6IjUxIiwiYWRkc...dCI6MTUxNzYwMH0K"} @@ -29,9 +43,90 @@ main() { response=$(web "${msg}") publish_response "${response}" "${response_topic}" ${?} ;; + sendToTelegramGroup) + # example: + # local body=$(echo "{\"text\":\"Hello world in Telegram at `date -u +"%FT%H%MZ"`\"}" | base64) + # response=$(mosquitto_rr -h broker -W 15 -t notifier -e "response/$$" -m "{\"response-topic\":\"response/$$\",\"cmd\":\"sendToTelegramGroup\",\"body\":\"${body}\"}") + if [ "${FEATURE_TELEGRAM}" = "true" ]; then + url=$(echo ${TG_BOT_URL}${TG_API_KEY}/sendMessage?chat_id=${TG_CHAT_ID}) + trace "[main] telegram-url=${url}" + + msg=$(echo ${msg} | jq --arg url ${url} '. += {"url":$url}' ) + trace "[main] web-msg=${msg}" + + response=$(web "${msg}") + publish_response "${response}" "${response_topic}" ${?} + else + trace "[main] Telegram is NOT enabled - message not sent" + fi + ;; + sendToTelegramNoop) + if [ "${FEATURE_TELEGRAM}" = "true" ]; then + url=$(echo ${TG_BOT_URL}${TG_API_KEY}/getMe) + trace "[main] telegram-url=${url}" + + msg=$(echo ${msg} | jq --arg url ${url} '. += {"url":$url}' ) + trace "[main] web-msg=${msg}" + + response=$(web "${msg}") + publish_response "${response}" "${response_topic}" ${?} + else + trace "[main] Telegram is NOT enabled - message not sent" + fi + ;; + reloadConfig) + trace "[main] Reloading configs Now" + response="{\"return_code\":\"(loadConfig)\"}" + trace "[main] response=${response}" + publish_response "${response}" "${response_topic}" ${?} + trace "[main] Reloading configs - Done" + + # restart read loop + break + ;; esac trace "[main] msg processed" done + + trace "[readLoop] Exiting read loop" +} + +loadConfig(){ + if [ "${FEATURE_TELEGRAM}" = "true" ]; then + trace "[loadConfig] FEATURE_TELEGRAM is ENABLED" + + if [ "${TOR_TELEGRAM}" = "true" ]; then + trace "[loadConfig] Telegram will be used with tor" + fi + + # wait for table to exist in DB - for clean install + waitfortable "cyphernode_props" + + trace "[loadConfig] Looking up TG_BOT_URL in database" + + TG_BOT_URL=$(sql "SELECT value FROM cyphernode_props WHERE category='notifier' AND property='tg_base_url'") + returncode=$? + trace "[loadConfig] TG_BOT_URL [${TG_BOT_URL}]" + trace_rc ${returncode} + + [ "${returncode}" -ne "0" ] && return 10 + + trace "[loadConfig] Looking up TG_API_KEY in database" + TG_API_KEY=$(sql "SELECT value FROM cyphernode_props WHERE category='notifier' AND property='tg_api_key'") + returncode=$? + trace "[loadConfig] TG_API_KEY [${TG_API_KEY}]" + trace_rc ${returncode} + [ "${returncode}" -ne "0" ] && return 20 + + trace "[loadConfig] Looking up TG_CHAT_ID in database" + TG_CHAT_ID=$(sql "SELECT value FROM cyphernode_props WHERE category='notifier' AND property='tg_chat_id'") + returncode=$? + trace "[loadConfig] TG_CHAT_ID [${TG_CHAT_ID}]" + trace_rc ${returncode} + [ "${returncode}" -ne "0" ] && return 30 + else + trace "[loadConfig] FEATURE_TELEGRAM is DISABLED" + fi } main diff --git a/notifier_docker/script/sql.sh b/notifier_docker/script/sql.sh new file mode 100644 index 000000000..bee2e6dcf --- /dev/null +++ b/notifier_docker/script/sql.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +. ./trace.sh + +sql() { + trace "Entering sql()..." + + local select_id=${2} + local response + local inserted_id + + trace "[sql] psql -qAtX -h postgres -U cyphernode -c \"${1}\"" + response=$(psql -qAtX -h postgres -U cyphernode -c "${1}") + returncode=$? + trace_rc ${returncode} + + if [ -n "${select_id}" ]; then + if [ "${returncode}" -eq "0" ]; then + inserted_id=$(echo "${response}" | cut -d ' ' -f1) + else + trace "[sql] psql -qAtX -h postgres -U cyphernode -c \"${select_id}\"" + inserted_id=$(psql -qAtX -h postgres -U cyphernode -c "${select_id}") + returncode=$? + trace_rc ${returncode} + fi + echo -n "${inserted_id}" + else + echo -n "${response}" + fi + + return ${returncode} +} + +waitfortable(){ + TABLE_NAME=$1 + + trace "Entering waitfortable [$TABLE_NAME]" + + while true; do + + exists=$(psql -qAtX -h postgres -U cyphernode -c "SELECT EXISTS (SELECT FROM pg_tables WHERE schemaname='public' and tablename='$TABLE_NAME')") + + if [ "${exists}" = "t" ]; then + trace "Table found [$TABLE_NAME] - Exiting" + break + fi + + trace "wainting for table [$TABLE_NAME] to exist" + sleep 5 + done +} diff --git a/notifier_docker/script/start-tg-setup.sh b/notifier_docker/script/start-tg-setup.sh new file mode 100755 index 000000000..674a2ff02 --- /dev/null +++ b/notifier_docker/script/start-tg-setup.sh @@ -0,0 +1,2 @@ +docker exec -it $(docker ps -q -f "name=cyphernode_notifier") ./tgsetup.sh + diff --git a/notifier_docker/script/startnotifier.sh b/notifier_docker/script/startnotifier.sh index 4df3e153f..e5a5198cc 100644 --- a/notifier_docker/script/startnotifier.sh +++ b/notifier_docker/script/startnotifier.sh @@ -1,7 +1,21 @@ #!/bin/sh - . ./trace.sh +wait_for_broker() { + trace "[startnotifier-wait_for_broker] Waiting for broker to be ready" + + while true ; do ping -c 1 broker ; [ "$?" -eq "0" ] && break ; sleep 5; done +} + trace "Starting mosquitto and subscribing to the notifier topic..." +if [ "${FEATURE_TELEGRAM}" = "true" ]; then + trace "[startnotifier] Waiting for PostgreSQL to be ready..." + while [ ! -f "/container_monitor/postgres_ready" ]; do trace "[startnotifier] PostgreSQL not ready" ; sleep 10 ; done + trace "[startnotifier] PostgreSQL ready!" +fi + +# Wait for broker to be ready +wait_for_broker + exec sh -c 'mosquitto_sub -h broker -t notifier | ./requesthandler.sh' diff --git a/notifier_docker/script/tgsetup.sh b/notifier_docker/script/tgsetup.sh new file mode 100755 index 000000000..8ce594dae --- /dev/null +++ b/notifier_docker/script/tgsetup.sh @@ -0,0 +1,166 @@ +#!/bin/sh + +. ./colors.sh + +# Cyphernode Telegram configuration +# +# +sql() { + local select_id=${2} + local response + local inserted_id + + response=$(psql -qAtX -h postgres -U cyphernode -c "${1}") + returncode=$? + + if [ -n "${select_id}" ]; then + if [ "${returncode}" -eq "0" ]; then + inserted_id=$(echo -e "${response}" | cut -d ' ' -f1) + else + inserted_id=$(psql -qAtX -h postgres -U cyphernode -c "${select_id}") + returncode=$? + fi + fi + + return ${returncode} +} + +# Ping the database an make sure it's UP +echo -e "\r\n$BIBlue"; echo -e "[TG Setup] Testing database before starting the configuration" + +ping -c 1 postgres 2>&1 > /dev/null +rc=$? + +if [ $rc != 0 ]; then + echo -e "\r\n$BIRed"; echo -e "[TG Setup] Database is not up. Make sure Cyphernode is running before setting up Telegram. Exiting\r\n" + exit 1 +else + echo -e "\r\n$Green"; echo -e "[TG Setup] Database is alive" +fi + +if [ -n "$TOR_TELEGRAM" ] && [ "$TOR_TELEGRAM" = "1" ]; then + echo -e "[TG Setup] Sending Telegram messages usging tor\r\n" +else + echo -e "[TG Setup] Sending Telegram messages usging clearnet\r\n" +fi + +while true; do + echo -e "\r\n$Green"; + read -p "[TG Setup] Do you wish to configure Telegram for Cyphernode? [yn] " -n 1 -r + + case $REPLY in + [Yy]* ) break;; + [Nn]* ) echo -e "\r\n[TG Setup] Got it! You can always come back later"; exit;; + * ) echo -e "Please answer yes or no.";; + esac +done + +# Set the base Telegram URL in DB +echo -e $Blue; echo -e "\r\n[TG Setup] Adding the Telegram base URL in database config table cyphernode_props\r\n" + +TG_BASE_URL="https://api.telegram.org/bot" +sql "INSERT INTO cyphernode_props (category, property, value) VALUES ('notifier', 'tg_base_url', '$TG_BASE_URL') \ + ON CONFLICT (category, property) DO NOTHING" + +echo -e "" +echo -e "[TG Setup] Please go into your Telegram App and start chatting with the @BotFather\r\n" +echo -e "\r\n$Blue"; +echo -e "==> (Step 1) Enter @Botfather in the search tab and choose this bot" +echo -e "==> Note, official Telegram bots have a blue checkmark beside their name" +echo -e "==> (Step 2) Click “Start” to activate BotFather bot. In response, you receive a list of commands to manage bots" +echo -e "==> (Step 3) Choose or type the /newbot command and send it" +echo -e "==> @BotFather replies: Alright, a new bot. How are we going to call it? Please choose a name for your bot" +echo -e "==> (Step 4) Choose a name for your bot. And choose a username for your bot — the bot can be found by its username in searches. The username must be unique and end with the word 'bot'" +echo -e "==> After you choose a suitable name for your bot — the bot is created. You will receive a message with a link to your bot t.me/" +echo -e "==> Cyphernode needs the generated token to access the API: Copy the line below following the message 'Use this token to access the HTTP API' " +echo -e "\r\n\r\n" + +while true; do + echo -e "\r\n$Green"; + + # 46 characters 1234567890:ABCrWd1mHlWzGM-2ovbxRnOF_g3V2-csY4E + # matching '^[0-9]{10}:.{35}$' + read -p "[TG Setup] Enter the token here: " -n 46 -r + + if [[ ${#REPLY} -gt 0 ]] && [[ $REPLY =~ ^[0-9]{10}:.{35}$ ]]; then + # Token is good - continue + break + else + echo -e "$BIRed" + echo -e "[TG Setup] Oooops, it doesn't seem to be a valid token." + echo -e "[TG Setup] The token should be a string with this format 1234567890:ABCrWd1mHlWzGM-2ovbxRnOF_g3V2-csY4E." + echo -e "[TG Setup] Please enter the token again - 10 digits:35 characters" + fi +done + +# Now let's ping Telegram while we ask the user to type a message in Telegram + +echo -e "\r\n$Green"; +echo -e "\r\n[TG Setup] Telegram Setup will now try to obtain the chat ID from the Telgram server.\r\n" + +echo -e "To make this happen, please go into the Telegram App and send a message to the new bot" +echo -e "Click on the link in the @BotFather's answer : Congratulations on your new bot. You will find it at t.me/your-new-bot." + +# +# The server will return something like below after the user sends a message +# '{"ok":true,"result":[{"update_id":846048856, +# "message":{"message_id":1,"from":{"id":6666666666,"is_bot":false,"first_name":"Phil","last_name":"","username":"phil","language_code":"en"},"chat":{"id":6666666666,"first_name":"Phil","last_name":"","username":"phil","type":"private"},"date":1649860823,"text":"/start","entities":[{"offset":0,"length":6,"type":"bot_command"}]}},{"update_id":666048857, +# "message":{"message_id":2,"from":{"id":6666666666,"is_bot":false,"first_name":"Phil","last_name":"","username":"phil","language_code":"en"},"chat":{"id":6666666666,"first_name":"Phil","last_name":"","username":"phil","type":"private"},"date":1649860826,"text":"hello"}}]}' +# + +while true; do + echo -e "Trying to contact Telegram server..." + httpStatusCode=$(curl -o /dev/null -s -w '%{http_code}' $TG_BASE_URL$REPLY/getUpdates) + + if [[ $httpStatusCode == 200 ]]; then + TG_API_KEY=$REPLY + sql "INSERT INTO cyphernode_props (category, property, value) VALUES ('notifier', 'tg_api_key', '$TG_API_KEY') \ + ON CONFLICT (category, property) DO UPDATE SET value='$TG_API_KEY'" + + loop=1 + while [ $loop -le 10 ]; do + response=$(curl -s $TG_BASE_URL$TG_API_KEY/getUpdates) + isOk=$(echo -e $response | jq '.ok') + if [ "$isOk" = "true" ]; then + # get the chat id from the last message + TG_CHAT_ID=$(echo -e $response | jq '.result[-1].message.chat.id') + + if [[ -z $TG_CHAT_ID || "$TG_CHAT_ID" == "null" ]]; then + echo -e $Yellow; echo -e "[$loop] [TG Setup] Received positive answer from Telegram without a chat id - Waiting for$IRed YOU$Yellow to send a message in the chat..." + sleep 10 + loop=$(( $loop + 1 )) + else + # Save the TG_CHAT_ID + today=`date -u` + sql "INSERT INTO cyphernode_props (category, property, value) VALUES ('notifier', 'tg_chat_id', '$TG_CHAT_ID') \ + ON CONFLICT (category, property) DO UPDATE SET value=$TG_CHAT_ID" + + echo -e "$Green" + echo -e "[TG Setup] Reloading configs\r\n" + + response=$(mosquitto_rr -h broker -W 15 -t notifier -e "response/$$" -m "{\"response-topic\":\"response/$$\",\"cmd\":\"reloadConfig\"}") + + echo -e "" + echo -e "[TG Setup] Sending message to Telegram [$today]" + + if [ "${TOR_TELEGRAM}" = "true" ]; then + body=$(echo -e "{\"text\":\"Hello from Cyphernode [$today] using tor - setup is complete\"}" | base64 | tr -d '\n') + response=$(mosquitto_rr -h broker -W 15 -t notifier -e "response/$$" -m "{\"response-topic\":\"response/$$\",\"cmd\":\"sendToTelegramGroup\",\"body\":\"${body}\",\"tor\":true}") + else + body=$(echo -e "{\"text\":\"Hello from Cyphernode [$today] using clearnet - setup is complete\"}" | base64 | tr -d '\n') + response=$(mosquitto_rr -h broker -W 15 -t notifier -e "response/$$" -m "{\"response-topic\":\"response/$$\",\"cmd\":\"sendToTelegramGroup\",\"body\":\"${body}\"}") + fi + + echo -e "$BBlue" + echo -e "\r\n[TG Setup] Ok. Done." + exit + fi + else + echo -e "\r\n[TG Setup] Server returned an error [$response] - exiting"; exit + fi + done + echo -e "\r\n[TG Setup] No message found. Please go into the Telegram App and send a message to the new bot - exiting"; exit + else + echo -e "\r\n[TG Setup] Server returned a HTTP error code [$httpStatusCode] - exiting"; break + fi +done diff --git a/notifier_docker/script/web.sh b/notifier_docker/script/web.sh index 390dbb9c5..b7ad0b9fd 100644 --- a/notifier_docker/script/web.sh +++ b/notifier_docker/script/web.sh @@ -68,7 +68,7 @@ curl_it() { rc=$(curl ${tor} -o webresponse-${rnd} -m 20 -w "%{http_code}" -H "Content-Type: application/json" -H "X-Forwarded-Proto: https" -d "${data}" -k ${url}) returncode=$? else - trace "[curl_it] curl ${tor} -o webresponse-$$ -m 20 -w \"%{http_code}\" -k ${url}" + trace "[curl_it] curl ${tor} -o webresponse-${rnd} -m 20 -w \"%{http_code}\" -k ${url}" rc=$(curl ${tor} -o webresponse-${rnd} -m 20 -w "%{http_code}" -k ${url}) returncode=$? fi @@ -76,10 +76,13 @@ curl_it() { trace_rc ${returncode} if [ "${returncode}" -eq "0" ]; then - response=$(cat webresponse-${rnd} | base64 | tr -d '\n' ; rm webresponse-${rnd}) + response=$(cat webresponse-${rnd} | base64 | tr -d '\n') else response= fi + + rm webresponse-${rnd} + # When curl is unable to connect, http_code is "000" which is not a valid JSON number [ "${rc}" -eq "0" ] && rc=0 response="{\"curl_code\":${returncode},\"http_code\":${rc},\"body\":\"${response}\"}" diff --git a/proxy_docker/app/script/notify.sh b/proxy_docker/app/script/notify.sh index 42d9fa7b5..8148e2bf6 100644 --- a/proxy_docker/app/script/notify.sh +++ b/proxy_docker/app/script/notify.sh @@ -49,4 +49,53 @@ notify_web() { return ${curl_code} || ${returncode} fi +} + +# +# call notify_telegram "text to send". See https://core.telegram.org/bots/api#sendmessage +# ex in shell script: notify_telegram "Unit testing notify_telegram at `date -u +"%FT%H%MZ"`" +# +notify_telegram() { + trace "Entering notify_telegram()..." + + local body=$(echo {\"text\":\"$1\"} | base64 | tr -d '\n') + + local returncode + local response + local http_code + local curl_code + local msg + + if [ "$TOR_TELEGRAM" = "true" ]; then + msg="{\"response-topic\":\"response/$$\",\"cmd\":\"sendToTelegramGroup\",\"body\":\"${body}\",\"tor\":true}" + else + msg="{\"response-topic\":\"response/$$\",\"cmd\":\"sendToTelegramGroup\",\"body\":\"${body}\"}" + fi + + # We use the pid as the response-topic, so there's no conflict in responses. + trace "[notify_telegram] mosquitto_rr -h broker -W 21 -t notifier -e \"response/$$\" -m \"${msg}\"" + response=$(mosquitto_rr -h broker -W 21 -t notifier -e "response/$$" -m ${msg}) + returncode=$? + trace_rc ${returncode} + + # The response looks like this: {"curl_code":0,"http_code":200,"body":"..."} where the body + # is the base64(response body) but we don't need the response content here. + trace "[notify_telegram] response=${response}" + curl_code=$(echo "${response}" | jq -r ".curl_code") + trace "[notify_telegram] curl_code=${curl_code}" + http_code=$(echo "${response}" | jq -r ".http_code") + trace "[notify_telegram] http_code=${http_code}" + + echo "${response}" + + if [ "${curl_code}" -eq "0" ] && [ "${returncode}" -eq "0" ]; then + if [ "${http_code}" -lt "400" ]; then + return 0 + else + return ${http_code} + fi + else + return ${curl_code} || ${returncode} + fi + } \ No newline at end of file diff --git a/proxy_docker/app/script/requesthandler.sh b/proxy_docker/app/script/requesthandler.sh index e6078d16e..5d89021ec 100644 --- a/proxy_docker/app/script/requesthandler.sh +++ b/proxy_docker/app/script/requesthandler.sh @@ -1,8 +1,5 @@ #!/bin/sh # -# -# -# . ./db/config.sh . ./sendtobitcoinnode.sh diff --git a/proxy_docker/app/tests/test-telegram.sh b/proxy_docker/app/tests/test-telegram.sh new file mode 100755 index 000000000..6405a0e0f --- /dev/null +++ b/proxy_docker/app/tests/test-telegram.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +# Tests the notify_telegram (in notify.sh) function by calling it directly - not going through the proxy, there is no endpoint. + +cd .. +. ./notify.sh + +echo "Calling notify_telegram..." + +notify_telegram "Unit testing notify_telegram at `date -u +"%FT%H%MZ"`" + +echo "Done..." +