Duplicati and Telegram

And here is my Telegram Notifier bash script for Debian Linux. Don’t forget to chmod +x it before setting it to “run-script-after” in Duplicati.

#!/bin/bash
#
# Script Configuration.
DEBUG_MODE="0"
SCRIPT_FULLFN="$(basename "${0}")"
LOGFILE="/tmp/${SCRIPT_FULLFN%.*}.log"
LOG_MAX_LINES="1000"
SERVICE_NAME="Duplicati"
#
#
# -----------------
# --- Functions ---
# -----------------
logAdd ()
{
	TMP_DATETIME="$(date '+%Y-%m-%d [%H-%M-%S]')"
	TMP_LOGSTREAM="$(tail -n ${LOG_MAX_LINES} ${LOGFILE} 2>/dev/null)"
	echo "${TMP_LOGSTREAM}" > "$LOGFILE"
	if [ "$1" == "-q" ]; then
		#
		# Quiet mode.
		#
		echo "${TMP_DATETIME} ${@:2}" >> "${LOGFILE}"
	else
		#
		# Loud mode.
		#
		echo "${TMP_DATETIME} $*" | tee -a "${LOGFILE}"
	fi
	return
}
sendTelegramNotification ()
{
	#
	# Usage:			sendTelegramNotification "<PN_TEXT>"
	# Example:			sendTelegramNotification "Test push message"
	#
	# Returns:
	#
	# - "0" on FAILURE.
	# - "1" on SUCCESS.
	#
	# Consts.
	CURL_BIN="curl"
	CURL_TIMEOUT="3"
	# 
	# Telegram Bot
	STN_TELEGRAM_BOT_ID="TO_FILL"
	STN_TELEGRAM_BOT_APIKEY="TO_FILL"
	# 
	# 	cf
	STN_TELEGRAM_CHAT_ID="TO_FILL"
	#
	# Variables.
	#
	STN_TEXT="$1"
	STN_TEXT="${STN_TEXT//\"/\\\"}"
	#
	# Log message.
	#
	if [ "${DEBUG_MODE}" == "1" ]; then
		logAdd -q "${MY_SERVICE_NAME}::sendTelegramNotification() fired."
	fi
	#
	# Send push message to Telegram Bot Chat.
	if ( eval ${CURL_BIN} -q \
			--insecure \
			--max-time \""${CURL_TIMEOUT}\"" \
			 "\"https://api.telegram.org/bot${STN_TELEGRAM_BOT_ID}:${STN_TELEGRAM_BOT_APIKEY}/sendMessage?chat_id=${STN_TELEGRAM_CHAT_ID}&text=${STN_TEXT}\"" \
			 2> /dev/null \| grep -Fiq "\"ok\\\":true\"" ); then
		#
		# Log message.
		logAdd -q "${MY_SERVICE_NAME}::sendTelegramNotification: ... SUCCESS."
		# Return SUCCESS.
		#
		echo -n "1"
	else
		#
		# Log message.
		logAdd -q "${MY_SERVICE_NAME}::sendTelegramNotification: ... FAILED."
		#
		# Return FAILURE.
		echo -n "0"
	fi
	#
	return
}
# -----------------------------------------------------
# --------------- END OF FUNCTION BLOCK ---------------
# -----------------------------------------------------
#
# Script Main.
#
# Validate env vars we got from Duplicati.
if  [ -z "${DUPLICATI__backup_name}" ]; then
	export DUPLICATI__backup_name="API_ERR"
fi
#
if  [ -z "${DUPLICATI__PARSED_RESULT}" ]; then
	export DUPLICATI__PARSED_RESULT="API_ERR"
fi
#
if  [ -z "${DUPLICATI__EVENTNAME}" ]; then
	export DUPLICATI__EVENTNAME="API_ERR"
fi
#
if  [ -z "${DUPLICATI__OPERATIONNAME}" ]; then
	export DUPLICATI__OPERATIONNAME="API_ERR"
fi
#
if [ "${DEBUG_MODE}" == "1" ]; then
	env | grep "DUPLICATI__"
fi
#
# Prepare chat message.
CHAT_MESSAGE="${SERVICE_NAME}: \"${DUPLICATI__backup_name}\" ${DUPLICATI__PARSED_RESULT} ${DUPLICATI__EVENTNAME} ${DUPLICATI__OPERATIONNAME}"
#
# Do not send chat message on SUCCESS.
if [ "${DUPLICATI__PARSED_RESULT}" == "Success" ]; then
	logAdd "[INFO] Suppressed sending chat message [${CHAT_MESSAGE}]."
	exit 0
fi
logAdd "[INFO] Sending chat message [${CHAT_MESSAGE}] ..."
#
# Send chat message.
SEND_RESULT="$(sendTelegramNotification "${CHAT_MESSAGE}")"
if [ "${SEND_RESULT}" == "0" ]; then
	logAdd "[ERROR] Failed to send telegram notification."
	exit 99
fi
#
logAdd "[INFO] Successfully sent telegram notification."
exit 0

Hello Catfriend1!
Are two separated files? with ‘cmd’ extension? May I change the name of script files??

Hello @jonathancaldeira

the two cms files need to be placed in the same directory. You can rename them as long as you understood the relation given in the batch script between them.
E.g.
main.cmd
main_env.cmd

The _env script is just holding your personal config params. You could also put the _env SET lines into the main cmd file to have it all in one file. Replace the line

IF EXIST %SCRIPT_ENV_FULLFN% call %SCRIPT_ENV_FULLFN%

by the contents of the _env.cmd to do so.

1 Like

I was slow to understand that it only sends messages in case of failure, so I set it to send in all cases.
Thank you for your help!

1 Like

@bd8392 thanks for the instruction. Some additions:

  apprise:
    image: caronc/apprise:latest
    container_name: apprise
    ports:
      - 8000:8000
    volumes:
      - /var/lib/apprise/config:/config
    privileged: true

And you should grant the correct access rights for the config folder according to https://hub.docker.com/r/caronc/apprise

2 Likes

Welcome to the forum @aleksej.kuznecow and thanks for contributing the addition.
It’s wonderful to see the community helping to build what is truly a community effort.

1 Like

good morning everyone, after working all day to make the script better for my needs I would like to share it with you, I created a script called “telegram.sh” and inserted it inside /opt/scripts which is mounted inside /script in the docker container (volumes: - /opt/duplicati/scripts:/scripts) then I ran sudo chmod +x ./telegram.sh to give execution permissions. Then I modified my backup in the Duplicati GUI and in the advanced options I selected “run-script-after” and in the text box I inserted the path of the sh file or “/scripts/telegram.sh”. You can change the text to your liking for your language in my case I am Italian and I will share it with the text in Italian but feel free to modify it, whoever decides to improve it would be a good thing if they shared it as I did so that it can help other people like you did it with me. Attached I leave the terminal.sh script:

#!/bin/bash
#
# Script Configuration.
DEBUG_MODE="0"  # Impostalo su "1" per abilitare il debug
SCRIPT_FULLFN="$(basename "${0}")"
LOGFILE="/tmp/${SCRIPT_FULLFN%.*}.log"
LOG_MAX_LINES="1000"
SERVICE_NAME="Duplicati"
#
#
# -----------------
# --- Functions ---
# -----------------
logAdd ()
{
    TMP_DATETIME="$(date '+%Y-%m-%d [%H-%M-%S]')"
    TMP_LOGSTREAM="$(tail -n ${LOG_MAX_LINES} ${LOGFILE} 2>/dev/null)"
    echo "${TMP_LOGSTREAM}" > "$LOGFILE"
    if [ "$1" == "-q" ]; then
        #
        # Quiet mode.
        #
        echo "${TMP_DATETIME} ${@:2}" >> "${LOGFILE}"
    else
        #
        # Loud mode.
        #
        echo "${TMP_DATETIME} $*" | tee -a "${LOGFILE}"
    fi
    return
}
sendTelegramNotification ()
{
    #
    # Usage:            sendTelegramNotification "<PN_TEXT>"
    # Example:          sendTelegramNotification "Test push message"
    #
    # Returns:
    #
    # - "0" on FAILURE.
    # - "1" on SUCCESS.
    #
    # Consts.
    CURL_BIN="curl"
    CURL_TIMEOUT="10"
    # 
    # Telegram Bot
    STN_TELEGRAM_BOT_ID="TO_FILL"
    STN_TELEGRAM_BOT_APIKEY="TO_FILL"
    # 
    #  cf
    STN_TELEGRAM_CHAT_ID="TO_FILL"
    #
    # Variables.
    #
    STN_TEXT="$1"
    # Encode the text for URL
    STN_TEXT=$(echo -n "$STN_TEXT" | sed 's/ /%20/g; s/\n/%0A/g; s/<b>/%3Cb%3E/g; s/<\/b>/%3C%2Fb%3E/g; s/<i>/%3Ci%3E/g; s/<\/i>/%3C%2Fi%3E/g')

    #
    # Log message.
    #
    if [ "${DEBUG_MODE}" == "1" ]; then
        logAdd -q "${MY_SERVICE_NAME}::sendTelegramNotification() fired."
    fi

    CURL_COMMAND="${CURL_BIN} -s --max-time ${CURL_TIMEOUT} \"https://api.telegram.org/bot${STN_TELEGRAM_BOT_ID}:${STN_TELEGRAM_BOT_APIKEY}/sendMessage?chat_id=${STN_TELEGRAM_CHAT_ID}&parse_mode=HTML&text=${STN_TEXT}\""
    
    # Log the CURL command for debugging
    logAdd "[DEBUG] CURL command: ${CURL_COMMAND}"

    RESPONSE=$(eval ${CURL_COMMAND})
    logAdd "[DEBUG] CURL response: ${RESPONSE}"

    if echo "${RESPONSE}" | grep -Fiq "\"ok\":true"; then
        logAdd -q "${MY_SERVICE_NAME}::sendTelegramNotification: ... SUCCESS."
        echo -n "1"
    else
        logAdd -q "${MY_SERVICE_NAME}::sendTelegramNotification: ... FAILED."
        echo -n "0"
    fi
    return
}
# -----------------------------------------------------
# --------------- END OF FUNCTION BLOCK ---------------
# -----------------------------------------------------
#
# Script Main.
#
# Validate env vars we got from Duplicati.
if  [ -z "${DUPLICATI__backup_name}" ]; then
    export DUPLICATI__backup_name="API_ERR"
fi
#
if  [ -z "${DUPLICATI__PARSED_RESULT}" ]; then
    export DUPLICATI__PARSED_RESULT="API_ERR"
fi
#
if  [ -z "${DUPLICATI__EVENTNAME}" ]; then
    export DUPLICATI__EVENTNAME="API_ERR"
fi
#
if  [ -z "${DUPLICATI__OPERATIONNAME}" ]; then
    export DUPLICATI__OPERATIONNAME="API_ERR"
fi
#
if [ "${DEBUG_MODE}" == "1" ]; then
    env | grep "DUPLICATI__"
fi
#
# Prepare chat message.
if [ "${DUPLICATI__PARSED_RESULT}" == "Success" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A✅ Il backup è stato completato con successo alle ore $(date '+%H:%M')."
elif [ "${DUPLICATI__PARSED_RESULT}" == "Warning" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A⚠️ Attenzione: Il backup ha completato con avvisi alle ore $(date '+%H:%M'). Verifica i log per ulteriori dettagli."
elif [ "${DUPLICATI__PARSED_RESULT}" == "Error" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A❌ Errore: Il backup non è riuscito alle ore $(date '+%H:%M'). Controlla i log per identificare il problema."
elif [ "${DUPLICATI__PARSED_RESULT}" == "Fatal" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A💥 Errore grave: Il backup è fallito irreparabilmente alle ore $(date '+%H:%M'). Intervieni subito per risolvere il problema!"
elif [ "${DUPLICATI__PARSED_RESULT}" == "Unknown" ]; then
    CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A🤔 Stato sconosciuto: Non è stato possibile determinare il risultato del backup alle ore $(date '+%H:%M'). Verifica manualmente."
fi
#
# Always send chat message.
logAdd "[INFO] Sending chat message [${CHAT_MESSAGE}] ..."
#
# Send chat message.
SEND_RESULT="$(sendTelegramNotification "${CHAT_MESSAGE}")"
if [ "${SEND_RESULT}" == "0" ]; then
    logAdd "[ERROR] Failed to send telegram notification."
    exit 99
fi
#
logAdd "[INFO] Successfully sent telegram notification."
exit 0
2 Likes

Great Work,Your script worked after some corrections.

For all those who are not fluent in Italian here is English version.

#!/bin/bash
#
# Script Configuration.
DEBUG_MODE="0"  # Set to "1" to enable debugging
SCRIPT_FULLFN="$(basename "${0}")"
LOGFILE="/tmp/${SCRIPT_FULLFN%.*}.log"
LOG_MAX_LINES="1000"
SERVICE_NAME="Duplicati"
#
# -----------------
# --- Functions ---
# -----------------
logAdd ()
{
    TMP_DATETIME="$(date '+%Y-%m-%d [%H-%M-%S]')"
    TMP_LOGSTREAM="$(tail -n ${LOG_MAX_LINES} ${LOGFILE} 2>/dev/null)"
    echo "${TMP_LOGSTREAM}" > "$LOGFILE"
    if [ "$1" == "-q" ]; then
        #
        # Quiet mode.
        #
        echo "${TMP_DATETIME} ${@:2}" >> "${LOGFILE}"
    else
        #
        # Loud mode.
        #
        echo "${TMP_DATETIME} $*" | tee -a "${LOGFILE}"
    fi
    return
}

sendTelegramNotification ()
{
    #
    # Usage:            sendTelegramNotification "<MESSAGE_TEXT>"
    # Example:          sendTelegramNotification "Test push message"
    #
    # Returns:
    #
    # - "0" on FAILURE.
    # - "1" on SUCCESS.
    #
    # Constants.
    CURL_BIN="curl"
    CURL_TIMEOUT="10"
    # 
    # Telegram Bot
    STN_TELEGRAM_BOT_ID="56834254534"  # Replace with your bot ID   Token Example - 56834254534:AAEBVSefdisfndi45345ifndis
    STN_TELEGRAM_BOT_APIKEY="AAEBVSefdisfndi45345ifndis"  # Replace with your bot API key
    # 
    # Chat ID
    STN_TELEGRAM_CHAT_ID="434346356"  # Replace with your chat ID
    #
    # Variables.
    #
    STN_TEXT="$1"
    # Encode the text for URL
    STN_TEXT=$(echo -n "$STN_TEXT" | sed 's/ /%20/g; s/\n/%0A/g; s/<b>/%3Cb%3E/g; s/<\/b>/%3C%2Fb%3E/g; s/<i>/%3Ci%3E/g; s/<\/i>/%3C%2Fi%3E/g')

    #
    # Log message.
    #
    if [ "${DEBUG_MODE}" == "1" ]; then
        logAdd -q "${SERVICE_NAME}::sendTelegramNotification() fired."
    fi

    CURL_COMMAND="${CURL_BIN} -s --max-time ${CURL_TIMEOUT} \"https://api.telegram.org/bot${STN_TELEGRAM_BOT_ID}:${STN_TELEGRAM_BOT_APIKEY}/sendMessage?chat_id=${STN_TELEGRAM_CHAT_ID}&parse_mode=HTML&text=${STN_TEXT}\""
    
    # Log the CURL command for debugging
    logAdd "[DEBUG] CURL command: ${CURL_COMMAND}"

    RESPONSE=$(eval ${CURL_COMMAND})
    logAdd "[DEBUG] CURL response: ${RESPONSE}"

    if echo "${RESPONSE}" | grep -Fiq "\"ok\":true"; then
        logAdd -q "${SERVICE_NAME}::sendTelegramNotification: ... SUCCESS."
        echo -n "1"
    else
        logAdd -q "${SERVICE_NAME}::sendTelegramNotification: ... FAILED."
        echo -n "0"
    fi
    return
}

# -----------------------------------------------------
# --------------- END OF FUNCTION BLOCK ---------------
# -----------------------------------------------------
#
# Script Main.
#
# Validate environment variables from Duplicati.
if  [ -z "${DUPLICATI__backup_name}" ]; then
    export DUPLICATI__backup_name="API_ERR"
fi
#
if  [ -z "${DUPLICATI__PARSED_RESULT}" ]; then
    export DUPLICATI__PARSED_RESULT="API_ERR"
fi
#
if  [ -z "${DUPLICATI__EVENTNAME}" ]; then
    export DUPLICATI__EVENTNAME="API_ERR"
fi
#
if  [ -z "${DUPLICATI__OPERATIONNAME}" ]; then
    export DUPLICATI__OPERATIONNAME="API_ERR"
fi
#
if [ "${DEBUG_MODE}" == "1" ]; then
    env | grep "DUPLICATI__"
fi
#
# Prepare chat message.
case "${DUPLICATI__PARSED_RESULT}" in
    "Success")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A✅ The backup completed successfully at $(date '+%H:%M')."
        ;;
    "Warning")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A⚠️ Warning: The backup completed with warnings at $(date '+%H:%M'). Check the logs for more details."
        ;;
    "Error")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A❌ Error: The backup failed at $(date '+%H:%M'). Check the logs to identify the problem."
        ;;
    "Fatal")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A💥 Fatal Error: The backup failed irreparably at $(date '+%H:%M'). Take immediate action to resolve the issue!"
        ;;
    "Unknown")
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A🤔 Unknown Status: The result of the backup could not be determined at $(date '+%H:%M'). Check manually."
        ;;
    *)
        CHAT_MESSAGE="<b>${SERVICE_NAME}</b>%0A<i>💾 ${DUPLICATI__backup_name}</i>%0A%0A❓ Unknown result at $(date '+%H:%M')."
        ;;
esac

#
# Always send chat message.
logAdd "[INFO] Sending chat message [${CHAT_MESSAGE}] ..."
#
# Send chat message.
SEND_RESULT="$(sendTelegramNotification "${CHAT_MESSAGE}")"
if [ "${SEND_RESULT}" == "0" ]; then
    logAdd "[ERROR] Failed to send Telegram notification."
    exit 99
fi
#
logAdd "[INFO] Successfully sent Telegram notification."
exit 0

2 Likes

Based on the script here I have implemented a Telegram reporting module.

It does not do the prettified HTML formatting, but that could be added.

1 Like

Thanks @kenkendk for adding this! Working great.

For me there are two things that could be improved.

  1. Telegram Messages on failing run-script-before-required Scripts. I guess that would be part of this Pull Request, that is currently marked as draft: Changed logic so all modules are loaded before any are started #5245.

  2. Option to send Messages to dedicated threads/topics. I think this would be a rather simple addition as it is one optional parameter more. I unfortunately don’t have the time for a Pull-Request but it should be something like this in SendTelegramMessage.cs + some Command-Line Parameter Updates.

...

var p = new
    {
        chat_id = Uri.EscapeDataString(m_channelId),
+       message_thread_id = string.IsNullOrEmpty(topicId) ? null : Uri.EscapeDataString(topicId)
        parse_mode = "Markdown",
        text = Uri.EscapeDataString(totalParts > 1
            ? $"Part {partNumber}/{totalParts}:\n{message}"
            : message),
        botId = Uri.EscapeDataString(m_botid),
        apiKey = Uri.EscapeDataString(m_apikey)
    };

var url = $"https://api.telegram.org/bot{p.botId}:{p.apiKey}/sendMessage?chat_id={p.chat_id}&parse_mode={p.parse_mode}&text={p.text}";

+  // Append message_thread_id only if it is not null or empty
+  if (!string.IsNullOrEmpty(p.message_thread_id))
+  {
+      url += $"&message_thread_id={p.message_thread_id}";
+  }

...

Thanks for suggesting it.

I have created an issue requesting improved Telegram support.

Hi @BoThomas FYI: I’ve implemented the suggestion on this PR: Added support for Topic ID on Telegram by marceloduplicati · Pull Request #5939 · duplicati/duplicati · GitHub

2 Likes