diff --git a/.gitignore b/.gitignore index da6210f..e5a2ded 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /dockcheck.config # ignore the auto-installed regctl regctl +# ignore snooze file +snooze.list diff --git a/README.md b/README.md index ce036c3..751e7b2 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,11 @@ ___ ## :bell: Changelog +- **v0.6.7**: Snooze feature, curl, and consolidation + - Added snooze feature to delay notifications + - Added configurable default curl arguments + - Consolidated and standardized notify template update notifications + - Added curl error handling - **v0.6.6**: Notify_v2 bugfixes - Clearer readme and error messages - Sourcing templates from either project root or subdirectory @@ -34,16 +39,6 @@ ___ - Added support for notification management via environment variables. - Moved notification secrets to **dockcheck.config**. - Added retries to wget/curl to not get empty responses when github is slow. -- **v0.6.4**: Restructured the update process - first pulls all updates, then recreates all containers. - - Added logic to skip update check on non-compose containers (unless `-r` option). - - Added option `-F` to revert to `compose up -d ` targeting specific container and not the stack. - - Also added corresponding label and config-option. - - Added markdown formatting to `notify_ntfy.sh` template. -- **v0.6.3**: Some fixes and changes: - - Stops when a container recreation (compose up -d) fails, also `up`s the whole stack now. - - `-M`, Markdown format url-releasenotes in notification (requires template rework, look at gotify!) - - Added [addons/DSM/README.md](./addons/DSM/README.md) for more info Synology DSM info. - - Permission checks - graceful exit if no docker permissions + checking if root for pkg-manager. ___ @@ -137,8 +132,7 @@ If `notify.sh` is present and configured, it will be used. Otherwise, `notify_v2 Will send a list of containers with updates available and a notification when `dockcheck.sh` itself has an update. Run it scheduled with `-ni` to only get notified when there's updates available! -V2 installation and configuration (tag v0.6.5 or later): -Remove or rename `notify.sh` if previously configured using the legacy method. +#### Installation and configuration: Make certain your project directory is laid out as below. You only need the notify_v2.sh file and any notification templates you wish to enable, but there is no harm in having all of them present. ``` . @@ -160,17 +154,30 @@ Make certain your project directory is laid out as below. You only need the noti ├── dockcheck.sh └── urls.list # optional ``` -If you wish to customize `notify_v2.sh` or the notify templates yourself, you may copy them to your project root directory alongside the main dockcheck.sh script (where they will also be ignored by git). Uncomment and set the NOTIFY_CHANNELS environment variable in `dockcheck.config` to a space separated string of your desired notification channels to enable. -Uncomment and set the environment variables related to the enabled notification channels. -It is recommended not to make changes directly to the `notify_X.sh` template files within the `notify_templates` subdirectory and instead use only environment variables defined in `dockcheck.config` using this method. +Uncomment and set the environment variables related to the enabled notification channels. +It is recommended to only edit the environmental variables in `dockcheck.config` and not make changes directly to the `notify_X.sh` template files within the `notify_templates` subdirectory. +If you wish to customize the notify templates yourself, you may copy them to your project root directory alongside the main `dockcheck.sh` script (where they will also be ignored by git). +Customizing `notify_v2.sh` is handled the same as customizing the templates, but it must be renamed to `notify.sh` within the `dockcheck.sh` root directory. -Legacy installation and configuration: + +#### Legacy installation and configuration: Use a previous version of a `notify_X.sh` template file (tag v0.6.4 or earlier) from the **notify_templates** directory, copy it to `notify.sh` alongside the script, modify it to your needs! (notify.sh is added to .gitignore) +#### Snooze feature: +**Use case:** You wish to be notified of available updates in a timely manner, but do not require reminders after the initial notification with the same frequency. +e.g. *Dockcheck is scheduled to run every hour. You will receive an update notification within an hour of availability.* +**Snooze enabled:** you will not receive another notification about updates for this container for a configurable period of time. +**Snooze disabled:** you will receive additional notifications every hour. -**Current templates:** +To enable snooze, uncomment the `SNOOZE_SECONDS` variable in your `dockcheck.config` file and set it to the number of seconds you wish to prevent duplicate alerts. +The true snooze duration will be 60 seconds less than your configure value to account for minor scheduling or script run time issues. +If an update becomes available for an item that is not snoozed, notifications will be sent and include all available updates for that item's category, even snoozed items. +`dockcheck.sh` updates, notification template updates, and container updates are considered three separate categories. + + +#### Current notify templates: - Synology [DSM](https://www.synology.com/en-global/dsm) - Email with [mSMTP](https://wiki.debian.org/msmtp) (or deprecated alternative [sSMTP](https://wiki.debian.org/sSMTP)) - Apprise (with it's [multitude](https://github.com/caronc/apprise#supported-notifications) of notifications) @@ -185,7 +192,7 @@ copy it to `notify.sh` alongside the script, modify it to your needs! (notify.sh - [Discord](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) - Discord webhooks. - [Slack](https://api.slack.com/tutorials/tracks/posting-messages-with-curl) - Slack curl api -Further additions are welcome - suggestions or PR! +Further additions are welcome - suggestions or PRs! Initiated and first contributed by [yoyoma2](https://github.com/yoyoma2). ### :date: Release notes addon @@ -228,7 +235,7 @@ See the [README.md](./addons/prometheus/README.md) for more detailed information Contributed by [tdralle](https://github.com/tdralle). ### :small_orange_diamond: Zabbix config to monitor docker image updates -If you already use Zabbix - this config will Shows number of available docker image updates on host. +If you already use Zabbix - this config will show numbers of available docker image updates on host. Example: *2 Docker Image updates on host-xyz* See project: [thetorminal/zabbix-docker-image-updates](https://github.com/thetorminal/zabbix-docker-image-updates) diff --git a/default.config b/default.config index 0e590c2..2831591 100644 --- a/default.config +++ b/default.config @@ -23,6 +23,9 @@ #PrintReleaseURL=true # Prints custom releasenote urls alongside each container with updates (requires urls.list)` #PrintMarkdownURL=true # Prints custom releasenote urls as markdown #OnlySpecific=true # Only compose up the specific container, not the whole compose. (useful for master-compose structure). +#CurlRetryDelay=1 # Time between curl retries +#CurlRetryCount=3 # Max number of curl retries +#CurlConnectTimeout=5 # Time to wait for curl to establish a connection before failing ### Notify settings ## All commented values are examples only. Modify as needed. @@ -30,6 +33,9 @@ ## Uncomment the line below and specify the notification channels you wish to enable in a space separated string # NOTIFY_CHANNELS="apprise discord DSM generic gotify matrix ntfy pushbullet pushover slack smtp telegram" # +## Uncomment the line below and specify the number of seconds to delay notifications to enable snooze +# SNOOZE_SECONDS=86400 +# ## Uncomment to not send notifications when dockcheck itself has updates. # DISABLE_DOCKCHECK_NOTIFICATION=false ## Uncomment to not send notifications when notify scripts themselves have updates. diff --git a/dockcheck.sh b/dockcheck.sh index 45e03cc..d341ce9 100755 --- a/dockcheck.sh +++ b/dockcheck.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -VERSION="v0.6.6" -# ChangeNotes: notify_v2 bugfixes - clarify readme and error messages, better sourcing templates, tweaks. +VERSION="v0.6.7" +# ChangeNotes: snooze feature (see readme), curl arguments, cleanup. Github="https://github.com/mag37/dockcheck" RawUrl="https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh" @@ -13,10 +13,6 @@ ScriptArgs=( "$@" ) ScriptPath="$(readlink -f "$0")" ScriptWorkDir="$(dirname "$ScriptPath")" -# Check if there's a new release of the script -LatestRelease="$(curl --retry 3 --retry-delay 1 --retry-max-time 10 -s -r 0-50 "$RawUrl" | sed -n "/VERSION/s/VERSION=//p" | tr -d '"')" -LatestChanges="$(curl --retry 3 --retry-delay 1 --retry-max-time 10 -s -r 0-200 "$RawUrl" | sed -n "/ChangeNotes/s/# ChangeNotes: //p")" - # Source helper functions source_if_exists() { if [[ -s "$1" ]]; then source "$1"; fi @@ -60,31 +56,32 @@ Help() { } # Initialise variables -Timeout=${Timeout:=10} -MaxAsync=${MaxAsync:=1} -BarWidth=${BarWidth:=50} -AutoMode=${AutoMode:=false} -DontUpdate=${DontUpdate:=false} -AutoPrune=${AutoPrune:=false} -AutoSelfUpdate=${AutoSelfUpdate:=false} -OnlyLabel=${OnlyLabel:=false} -Notify=${Notify:=false} -ForceRestartStacks=${ForceRestartStacks:=false} -DRunUp=${DRunUp:=false} -MonoMode=${MonoMode:=false} -PrintReleaseURL=${PrintReleaseURL:=false} -PrintMarkdownURL=${PrintMarkdownURL:=false} -Stopped=${Stopped:=""} +Timeout=${Timeout:-10} +MaxAsync=${MaxAsync:-1} +BarWidth=${BarWidth:-50} +AutoMode=${AutoMode:-false} +DontUpdate=${DontUpdate:-false} +AutoPrune=${AutoPrune:-false} +AutoSelfUpdate=${AutoSelfUpdate:-false} +OnlyLabel=${OnlyLabel:-false} +Notify=${Notify:-false} +ForceRestartStacks=${ForceRestartStacks:-false} +DRunUp=${DRunUp:-false} +MonoMode=${MonoMode:-false} +PrintReleaseURL=${PrintReleaseURL:-false} +PrintMarkdownURL=${PrintMarkdownURL:-false} +Stopped=${Stopped:-""} CollectorTextFileDirectory=${CollectorTextFileDirectory:-} Exclude=${Exclude:-} DaysOld=${DaysOld:-} -OnlySpecific=${OnlySpecific:=false} -SpecificContainer=${SpecificContainer:=""} +OnlySpecific=${OnlySpecific:-false} +SpecificContainer=${SpecificContainer:-""} Excludes=() GotUpdates=() NoUpdates=() GotErrors=() SelectedUpdates=() +CurlArgs="--retry ${CurlRetryCount:=3} --retry-delay ${CurlRetryDelay:=1} --connect-timeout ${CurlConnectTimeout:=5} -sf" regbin="" jqbin="" @@ -125,6 +122,11 @@ shift "$((OPTIND-1))" # Set $1 to a variable for name filtering later SearchName="${1:-}" +# Check if there's a new release of the script +LatestSnippet="$(curl ${CurlArgs} -r 0-200 "$RawUrl" || printf "undefined")" +LatestRelease="$(echo "${LatestSnippet}" | sed -n "/VERSION/s/VERSION=//p" | tr -d '"')" +LatestChanges="$(echo "${LatestSnippet}" | sed -n "/ChangeNotes/s/# ChangeNotes: //p")" + # Basic notify configuration check if [[ "${Notify}" == true ]] && [[ ! -s "${ScriptWorkDir}/notify.sh" ]] && [[ -z "${NOTIFY_CHANNELS:-}" ]]; then printf "Using v2 notifications with -i flag passed but no notify channels configured in dockcheck.config. This will result in no notifications being sent.\n" @@ -166,7 +168,7 @@ exec_if_exists_or_fail() { self_update_curl() { cp "$ScriptPath" "$ScriptPath".bak if command -v curl &>/dev/null; then - curl --retry 3 --retry-delay 1 --retry-max-time 10 -L $RawUrl > "$ScriptPath"; chmod +x "$ScriptPath" + curl ${CurlArgs} -L $RawUrl > "$ScriptPath"; chmod +x "$ScriptPath" || { printf "ERROR: Failed to curl updated Dockcheck.sh script. Skipping update.\n"; return 1; } printf "\n%b---%b starting over with the updated version %b---%b\n" "$c_yellow" "$c_teal" "$c_yellow" "$c_reset" exec "$ScriptPath" "${ScriptArgs[@]}" # run the new script with old arguments exit 1 # Exit the old instance @@ -270,7 +272,7 @@ binary_downloader() { *) printf "\n%bArchitecture not supported, exiting.%b\n" "$c_red" "$c_reset"; exit 1;; esac GetUrl="${BinaryUrl/TEMP/"$architecture"}" - if command -v curl &>/dev/null; then curl --retry 3 --retry-delay 1 --retry-max-time 10 -L "$GetUrl" > "$ScriptWorkDir/$BinaryName"; + if command -v curl &>/dev/null; then curl ${CurlArgs} -L "$GetUrl" > "$ScriptWorkDir/$BinaryName" || { printf "ERROR: Failed to curl binary dependency. Rerun the script to retry.\n"; exit 1; } elif command -v wget &>/dev/null; then wget --waitretry=1 --timeout=15 -t 10 "$GetUrl" -O "$ScriptWorkDir/$BinaryName"; else printf "\n%bcurl/wget not available - get %s manually from the repo link, exiting.%b" "$c_red" "$BinaryName" "$c_reset"; exit 1; fi @@ -346,15 +348,19 @@ list_options() { } # Version check & initiate self update -if [[ "$VERSION" != "$LatestRelease" ]]; then - printf "New version available! %b%s%b ⇒ %b%s%b \n Change Notes: %s \n" "$c_yellow" "$VERSION" "$c_reset" "$c_green" "$LatestRelease" "$c_reset" "$LatestChanges" - if [[ "$AutoMode" == false ]]; then - read -r -p "Would you like to update? y/[n]: " SelfUpdate - [[ "$SelfUpdate" =~ [yY] ]] && self_update - elif [[ "$AutoMode" == true ]] && [[ "$AutoSelfUpdate" == true ]]; then self_update; - else - [[ "$Notify" == true ]] && { exec_if_exists_or_fail dockcheck_notification "$VERSION" "$LatestRelease" "$LatestChanges" || printf "Could not source notification function.\n"; } +if [[ "$LatestRelease" != "undefined" ]]; then + if [[ "$VERSION" != "$LatestRelease" ]]; then + printf "New version available! %b%s%b ⇒ %b%s%b \n Change Notes: %s \n" "$c_yellow" "$VERSION" "$c_reset" "$c_green" "$LatestRelease" "$c_reset" "$LatestChanges" + if [[ "$AutoMode" == false ]]; then + read -r -p "Would you like to update? y/[n]: " SelfUpdate + [[ "$SelfUpdate" =~ [yY] ]] && self_update + elif [[ "$AutoMode" == true ]] && [[ "$AutoSelfUpdate" == true ]]; then self_update; + else + [[ "$Notify" == true ]] && { exec_if_exists_or_fail dockcheck_notification "$VERSION" "$LatestRelease" "$LatestChanges" || printf "Could not source notification function.\n"; } + fi fi +else + printf "ERROR: Failed to curl latest Dockcheck.sh release version.\n" fi # Version check for notify templates @@ -420,7 +426,7 @@ check_image() { # Checking for errors while setting the variable if RegHash=$($t_out "$regbin" -v error image digest --list "$RepoUrl" 2>&1); then - if [[ "$LocalHash" = *"$RegHash"* ]]; then + if [[ "$LocalHash" == *"$RegHash"* ]]; then printf "%s\n" "NoUpdates $i" else if [[ -n "${DaysOld:-}" ]] && ! datecheck; then @@ -568,7 +574,7 @@ if [[ -n "${GotUpdates:-}" ]]; then # cd to the compose-file directory to account for people who use relative volumes cd "$ContPath" || { printf "\n%bPath error - skipping%b %s" "$c_red" "$c_reset" "$i"; continue; } ## Reformatting path + multi compose - if [[ $ContConfigFile = '/'* ]]; then + if [[ $ContConfigFile == '/'* ]]; then CompleteConfs=$(for conf in ${ContConfigFile//,/ }; do printf -- "-f %s " "$conf"; done) else CompleteConfs=$(for conf in ${ContConfigFile//,/ }; do printf -- "-f %s/%s " "$ContPath" "$conf"; done) diff --git a/notify_templates/notify_DSM.sh b/notify_templates/notify_DSM.sh index edfb51f..08d85c1 100644 --- a/notify_templates/notify_DSM.sh +++ b/notify_templates/notify_DSM.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_DSM_VERSION="v0.2" +NOTIFY_DSM_VERSION="v0.3" # INFO: ssmtp is deprecated - consider to use msmtp instead. # # mSMTP/sSMTP has to be installed and configured manually. @@ -45,6 +45,11 @@ Content-Transfer-Encoding: 7bit $MessageBody From $SenderName __EOF + +if [[ $? -gt 0 ]]; then + NotifyError=true +fi + # This ensures DSM's container manager will also see the update /var/packages/ContainerManager/target/tool/image_upgradable_checker } diff --git a/notify_templates/notify_apprise.sh b/notify_templates/notify_apprise.sh index 4da3d71..1fa94de 100644 --- a/notify_templates/notify_apprise.sh +++ b/notify_templates/notify_apprise.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_APPRISE_VERSION="v0.2" +NOTIFY_APPRISE_VERSION="v0.3" # # Required receiving services must already be set up. # Leave (or place) this file in the "notify_templates" subdirectory within the same directory as the main dockcheck.sh script. @@ -18,6 +18,10 @@ trigger_apprise_notification() { if [[ -n "${APPRISE_PAYLOAD:-}" ]]; then apprise -vv -t "$MessageTitle" -b "$MessageBody" \ ${APPRISE_PAYLOAD} + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi fi # e.g. APPRISE_PAYLOAD='mailto://myemail:mypass@gmail.com @@ -27,6 +31,10 @@ trigger_apprise_notification() { if [[ -n "${APPRISE_URL:-}" ]]; then AppriseURL="${APPRISE_URL}" - curl -X POST -F "title=$MessageTitle" -F "body=$MessageBody" -F "tags=all" $AppriseURL # e.g. APPRISE_URL=http://apprise.mydomain.tld:1234/notify/apprise + curl -S -o /dev/null ${CurlArgs} -X POST -F "title=$MessageTitle" -F "body=$MessageBody" -F "tags=all" $AppriseURL # e.g. APPRISE_URL=http://apprise.mydomain.tld:1234/notify/apprise + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi fi } \ No newline at end of file diff --git a/notify_templates/notify_discord.sh b/notify_templates/notify_discord.sh index 97bf1fa..fa1a32d 100644 --- a/notify_templates/notify_discord.sh +++ b/notify_templates/notify_discord.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_DISCORD_VERSION="v0.3" +NOTIFY_DISCORD_VERSION="v0.4" # # Required receiving services must already be set up. # Leave (or place) this file in the "notify_templates" subdirectory within the same directory as the main dockcheck.sh script. @@ -20,5 +20,9 @@ trigger_discord_notification() { --arg body "$MessageBody" \ '{"username": $username, "content": $body}' ) - curl -sS -o /dev/null --fail -X POST -H "Content-Type: application/json" -d "$JsonData" "$DiscordWebhookUrl" + curl -S -o /dev/null ${CurlArgs} -X POST -H "Content-Type: application/json" -d "$JsonData" "$DiscordWebhookUrl" + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi } diff --git a/notify_templates/notify_gotify.sh b/notify_templates/notify_gotify.sh index 4e373d6..d3d2c67 100644 --- a/notify_templates/notify_gotify.sh +++ b/notify_templates/notify_gotify.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_GOTIFY_VERSION="v0.3" +NOTIFY_GOTIFY_VERSION="v0.4" # # Required receiving services must already be set up. # Leave (or place) this file in the "notify_templates" subdirectory within the same directory as the main dockcheck.sh script. @@ -28,5 +28,9 @@ trigger_gotify_notification() { --arg type "$ContentType" \ '{message: $body, title: $title, priority: 5, extras: {"client::display": {"contentType": $type}}}' ) - curl -s -S --data "${JsonData}" -H 'Content-Type: application/json' -X POST "${GotifyUrl}" 1> /dev/null + curl -S -o /dev/null ${CurlArgs} --data "${JsonData}" -H 'Content-Type: application/json' -X POST "${GotifyUrl}" 1> /dev/null + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi } diff --git a/notify_templates/notify_matrix.sh b/notify_templates/notify_matrix.sh index 8cf20b5..bcff5d2 100644 --- a/notify_templates/notify_matrix.sh +++ b/notify_templates/notify_matrix.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_MATRIX_VERSION="v0.2" +NOTIFY_MATRIX_VERSION="v0.3" # # Required receiving services must already be set up. # Leave (or place) this file in the "notify_templates" subdirectory within the same directory as the main dockcheck.sh script. @@ -19,5 +19,9 @@ trigger_matrix_notification() { MsgBody="{\"msgtype\":\"m.text\",\"body\":\"$MessageBody\"}" # URL Example: https://matrix.org/_matrix/client/r0/rooms/!xxxxxx:example.com/send/m.room.message?access_token=xxxxxxxx - curl -sS -o /dev/null --fail -X POST "$MatrixServer/_matrix/client/r0/rooms/$Room_id/send/m.room.message?access_token=$AccessToken" -H 'Content-Type: application/json' -d "$MsgBody" + curl -S -o /dev/null ${CurlArgs} -X POST "$MatrixServer/_matrix/client/r0/rooms/$Room_id/send/m.room.message?access_token=$AccessToken" -H 'Content-Type: application/json' -d "$MsgBody" + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi } \ No newline at end of file diff --git a/notify_templates/notify_ntfy.sh b/notify_templates/notify_ntfy.sh index b977d6d..d413a2c 100644 --- a/notify_templates/notify_ntfy.sh +++ b/notify_templates/notify_ntfy.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_NTFY_VERSION="v0.4" +NOTIFY_NTFYSH_VERSION="v0.5" # # Setup app and subscription at https://ntfy.sh # Leave (or place) this file in the "notify_templates" subdirectory within the same directory as the main dockcheck.sh script. @@ -24,9 +24,13 @@ trigger_ntfy_notification() { ContentType="Markdown: no" #text/plain fi - curl -sS -o /dev/null --show-error --fail \ + curl -S -o /dev/null ${CurlArgs} \ -H "Title: $MessageTitle" \ -H "$ContentType" \ -d "$MessageBody" \ "$NtfyUrl" + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi } diff --git a/notify_templates/notify_pushbullet.sh b/notify_templates/notify_pushbullet.sh index 182c78d..78dec0b 100644 --- a/notify_templates/notify_pushbullet.sh +++ b/notify_templates/notify_pushbullet.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_PUSHBULLET_VERSION="v0.2" +NOTIFY_PUSHBULLET_VERSION="v0.3" # # Required receiving services must already be set up. # Requires jq installed and in PATH. @@ -18,5 +18,9 @@ trigger_pushbullet_notification() { PushToken="${PUSHBULLET_TOKEN}" # e.g. PUSHBULLET_TOKEN=token-value # Requires jq to process json data - "$jqbin" -n --arg title "$MessageTitle" --arg body "$MessageBody" '{body: $body, title: $title, type: "note"}' | curl -sS -o /dev/null --show-error --fail -X POST -H "Access-Token: $PushToken" -H "Content-type: application/json" $PushUrl -d @- + "$jqbin" -n --arg title "$MessageTitle" --arg body "$MessageBody" '{body: $body, title: $title, type: "note"}' | curl -S -o /dev/null ${CurlArgs} -X POST -H "Access-Token: $PushToken" -H "Content-type: application/json" $PushUrl -d @- + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi } diff --git a/notify_templates/notify_pushover.sh b/notify_templates/notify_pushover.sh index 2f8bdda..60ffad6 100644 --- a/notify_templates/notify_pushover.sh +++ b/notify_templates/notify_pushover.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_PUSHOVER_VERSION="v0.2" +NOTIFY_PUSHOVER_VERSION="v0.3" # # Required receiving services must already be set up. # Requires jq installed and in PATH. @@ -19,10 +19,14 @@ trigger_pushover_notification() { PushoverToken="${PUSHOVER_TOKEN}" # e.g. PUSHOVER_TOKEN=token-value # Sending the notification via Pushover - curl -sS -o /dev/null --show-error --fail -X POST \ + curl -S -o /dev/null ${CurlArgs} -X POST \ -F "token=$PushoverToken" \ -F "user=$PushoverUserKey" \ -F "title=$MessageTitle" \ -F "message=$MessageBody" \ $PushoverUrl + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi } \ No newline at end of file diff --git a/notify_templates/notify_slack.sh b/notify_templates/notify_slack.sh index a760f3d..0a9cd7a 100644 --- a/notify_templates/notify_slack.sh +++ b/notify_templates/notify_slack.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_SLACK_VERSION="v0.2" +NOTIFY_SLACK_VERSION="v0.3" # # Setup app and token at https://api.slack.com/tutorials/tracks/posting-messages-with-curl # Leave (or place) this file in the "notify_templates" subdirectory within the same directory as the main dockcheck.sh script. @@ -17,8 +17,12 @@ trigger_slack_notification() { ChannelID="${SLACK_CHANNEL_ID}" # e.g. CHANNEL_ID=mychannel SlackUrl="https://slack.com/api/chat.postMessage" - curl -sS -o /dev/null --show-error --fail \ + curl -S -o /dev/null ${CurlArgs} \ -d "text=$MessageBody" -d "channel=$ChannelID" \ -H "Authorization: Bearer $AccessToken" \ -X POST $SlackUrl + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi } diff --git a/notify_templates/notify_smtp.sh b/notify_templates/notify_smtp.sh index 2889475..f07588c 100644 --- a/notify_templates/notify_smtp.sh +++ b/notify_templates/notify_smtp.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_SMTP_VERSION="v0.2" +NOTIFY_SMTP_VERSION="v0.3" # INFO: ssmtp is depcerated - consider to use msmtp instead. # # mSMTP/sSMTP has to be installed and configured manually. @@ -40,4 +40,8 @@ Content-Transfer-Encoding: 7bit $MessageBody __EOF + +if [[ $? -gt 0 ]]; then + NotifyError=true +fi } diff --git a/notify_templates/notify_telegram.sh b/notify_templates/notify_telegram.sh index d254490..4f114eb 100644 --- a/notify_templates/notify_telegram.sh +++ b/notify_templates/notify_telegram.sh @@ -1,5 +1,5 @@ ### DISCLAIMER: This is a third party addition to dockcheck - best effort testing. -NOTIFY_TELEGRAM_VERSION="v0.3" +NOTIFY_TELEGRAM_VERSION="v0.4" # # Required receiving services must already be set up. # Leave (or place) this file in the "notify_templates" subdirectory within the same directory as the main dockcheck.sh script. @@ -31,5 +31,9 @@ trigger_telegram_notification() { --arg parse_mode "$ParseMode" \ '{"chat_id": $chatid, "text": $text, "message_thread_id": $thread, "disable_notification": false, "parse_mode": $parse_mode, "disable_web_page_preview": true}' ) - curl -sS -o /dev/null --fail -X POST "$TelegramUrl/sendMessage" -H 'Content-Type: application/json' -d "$JsonData" + curl -S -o /dev/null ${CurlArgs} -X POST "$TelegramUrl/sendMessage" -H 'Content-Type: application/json' -d "$JsonData" + + if [[ $? -gt 0 ]]; then + NotifyError=true + fi } diff --git a/notify_templates/notify_v2.sh b/notify_templates/notify_v2.sh index 5990e1a..a34acfa 100644 --- a/notify_templates/notify_v2.sh +++ b/notify_templates/notify_v2.sh @@ -1,17 +1,28 @@ -NOTIFY_V2_VERSION="v0.2" +NOTIFY_V2_VERSION="v0.3" # # If migrating from an older notify template, remove your existing notify.sh file. # Leave (or place) this file in the "notify_templates" subdirectory within the same directory as the main dockcheck.sh script. -# If you instead wish make your own modifications, make a copy in the same directory as the main dockcheck.sh script. +# If you instead wish make your own modifications, make a copy in the same directory as the main dockcheck.sh script and rename to notify.sh. # Enable and configure all required notification variables in your dockcheck.config file, e.g.: # NOTIFY_CHANNELS=apprise gotify slack # SLACK_TOKEN=xoxb-some-token-value # GOTIFY_TOKEN=some.token +# Number of seconds to snooze identical update notifications based on local image name +# or dockcheck.sh/notify.sh template file updates. +# Actual snooze will be 60 seconds less to avoid the chance of missed notifications due to minor scheduling or script run time issues. +snooze="${SNOOZE_SECONDS:-}" +SnoozeFile="${ScriptWorkDir}/snooze.list" + enabled_notify_channels=( ${NOTIFY_CHANNELS:-} ) FromHost=$(cat /etc/hostname) +CurrentEpochTime=$(date +"%Y-%m-%dT%H:%M:%S") +CurrentEpochSeconds=$(date +%s) + +NotifyError=false + remove_channel() { local temp_array=() for channel in "${enabled_notify_channels[@]}"; do @@ -26,71 +37,222 @@ for channel in "${enabled_notify_channels[@]}"; do printf "The notification channel ${channel} is enabled, but notify_${channel}.sh was not found. Check the ${ScriptWorkDir} directory or the notify_templates subdirectory.\n" done +notify_containers_count() { + unset NotifyContainers + NotifyContainers=() + + [[ ! -f "${SnoozeFile}" ]] && touch "${SnoozeFile}" + + for update in "$@" + do + read -a container <<< "${update}" + found=$(grep -w "${container[0]}" "${SnoozeFile}" || printf "") + + if [[ -n "${found}" ]]; then + read -a arr <<< "${found}" + CheckEpochSeconds=$(( $(date -d "${arr[1]}" +%s 2>/dev/null) + ${snooze} - 60 )) || CheckEpochSeconds=$(( $(date -f "%Y-%m-%d" -j "${arr[1]}" +%s) + ${snooze} - 60 )) + if [[ "${CurrentEpochSeconds}" -gt "${CheckEpochSeconds}" ]]; then + NotifyContainers+=("${update}") + fi + else + NotifyContainers+=("${update}") + fi + done + + printf "${#NotifyContainers[@]}" +} + +update_snooze() { + + [[ ! -f "${SnoozeFile}" ]] && touch "${SnoozeFile}" + + for arg in "$@" + do + read -a entry <<< "${arg}" + found=$(grep -w "${entry[0]}" "${SnoozeFile}" || printf "") + + if [[ -n "${found}" ]]; then + sed -e "s/${entry[0]}.*/${entry[0]} ${CurrentEpochTime}/" "${SnoozeFile}" > "${SnoozeFile}.new" + mv "${SnoozeFile}.new" "${SnoozeFile}" + else + printf "${entry[0]} ${CurrentEpochTime}\n" >> "${SnoozeFile}" + fi + done +} + +cleanup_snooze() { + unset NotifyEntries + NotifyEntries=() + switch="" + + [[ ! -f "${SnoozeFile}" ]] && touch "${SnoozeFile}" + + for arg in "$@" + do + read -a entry <<< "${arg}" + NotifyEntries+=("${entry[0]}") + done + + if [[ ! "${NotifyEntries[@]}" == *".sh"* ]]; then + switch="-v" + fi + + while read -r entry datestamp; do + if [[ ! "${NotifyEntries[@]}" == *"$entry"* ]]; then + sed -e "/${entry}/d" "${SnoozeFile}" > "${SnoozeFile}.new" + mv "${SnoozeFile}.new" "${SnoozeFile}" + fi + done <<< "$(grep ${switch} '\.sh ' ${SnoozeFile})" +} + send_notification() { [[ -s "$ScriptWorkDir"/urls.list ]] && releasenotes || Updates=("$@") - UpdToString=$( printf '%s\\n' "${Updates[@]}" ) - UpdToString=${UpdToString%\\n} - for channel in "${enabled_notify_channels[@]}"; do - printf "\nSending ${channel} notification\n" + if [[ -n "${snooze}" ]] && [[ -f "${SnoozeFile}" ]]; then + UpdNotifyCount=$(notify_containers_count "${Updates[@]}") + else + UpdNotifyCount="${#Updates[@]}" + fi - # To be added in the MessageBody if "-d X" was used - # leading space is left intentionally for clean output - [[ -n "$DaysOld" ]] && msgdaysold="with images ${DaysOld}+ days old " || msgdaysold="" + NotifyError=false - MessageTitle="$FromHost - updates ${msgdaysold}available." - # Setting the MessageBody variable here. - printf -v MessageBody "🐋 Containers on $FromHost with updates available:\n${UpdToString}\n" + if [[ "${UpdNotifyCount}" -gt 0 ]]; then + UpdToString=$( printf '%s\\n' "${Updates[@]}" ) + UpdToString=${UpdToString%\\n} - exec_if_exists_or_fail trigger_${channel}_notification || \ - printf "Attempted to send notification to channel ${channel}, but the function was not found. Make sure notify_${channel}.sh is available in the ${ScriptWorkDir} directory or notify_templates subdirectory.\n" - done + for channel in "${enabled_notify_channels[@]}"; do + printf "\nSending ${channel} notification\n" + + # To be added in the MessageBody if "-d X" was used + # leading space is left intentionally for clean output + [[ -n "$DaysOld" ]] && msgdaysold="with images ${DaysOld}+ days old " || msgdaysold="" + + MessageTitle="$FromHost - updates ${msgdaysold}available." + # Setting the MessageBody variable here. + printf -v MessageBody "🐋 Containers on $FromHost with updates available:\n${UpdToString}\n" + + exec_if_exists_or_fail trigger_${channel}_notification || \ + printf "Attempted to send notification to channel ${channel}, but the function was not found. Make sure notify_${channel}.sh is available in the ${ScriptWorkDir} directory or notify_templates subdirectory.\n" + done + + [[ -n "${snooze}" ]] && [[ "${NotifyError}" == "false" ]] && update_snooze "${Updates[@]}" + fi + + [[ -n "${snooze}" ]] && cleanup_snooze "${Updates[@]}" } ### Set DISABLE_DOCKCHECK_NOTIFICATION=false in dockcheck.config ### to not send notifications when dockcheck itself has updates. dockcheck_notification() { - if [[ ! "${DISABLE_DOCKCHECK_NOTIFICATION:-}" = "true" ]]; then - MessageTitle="$FromHost - New version of dockcheck available." - # Setting the MessageBody variable here. - printf -v MessageBody "Installed version: $1\nLatest version: $2\n\nChangenotes: $3\n" + if [[ ! "${DISABLE_DOCKCHECK_NOTIFICATION:-}" == "true" ]]; then + DockcheckNotify=false + NotifyError=false - if [[ ${#enabled_notify_channels[@]} -gt 0 ]]; then printf "\n"; fi - for channel in "${enabled_notify_channels[@]}"; do - printf "Sending dockcheck update notification - ${channel}\n" - exec_if_exists_or_fail trigger_${channel}_notification || \ - printf "Attempted to send notification to channel ${channel}, but the function was not found. Make sure notify_${channel}.sh is available in the ${ScriptWorkDir} directory or notify_templates subdirectory.\n" - done + if [[ -n "${snooze}" ]] && [[ -f "${SnoozeFile}" ]]; then + found=$(grep -w "dockcheck\.sh" "${SnoozeFile}" || printf "") + if [[ -n "${found}" ]]; then + read -a arr <<< "${found}" + CheckEpochSeconds=$(( $(date -d "${arr[1]}" +%s 2>/dev/null) + ${snooze} - 60 )) || CheckEpochSeconds=$(( $(date -f "%Y-%m-%d" -j "${arr[1]}" +%s) + ${snooze} - 60 )) + if [[ "${CurrentEpochSeconds}" -gt "${CheckEpochSeconds}" ]]; then + DockcheckNotify=true + fi + else + DockcheckNotify=true + fi + else + DockcheckNotify=true + fi + + if [[ "${DockcheckNotify}" == "true" ]]; then + MessageTitle="$FromHost - New version of dockcheck available." + # Setting the MessageBody variable here. + printf -v MessageBody "Installed version: $1\nLatest version: $2\n\nChangenotes: $3\n" + + if [[ ${#enabled_notify_channels[@]} -gt 0 ]]; then printf "\n"; fi + for channel in "${enabled_notify_channels[@]}"; do + printf "Sending dockcheck update notification - ${channel}\n" + exec_if_exists_or_fail trigger_${channel}_notification || \ + printf "Attempted to send notification to channel ${channel}, but the function was not found. Make sure notify_${channel}.sh is available in the ${ScriptWorkDir} directory or notify_templates subdirectory.\n" + done + + if [[ -n "${snooze}" ]] && [[ -f "${SnoozeFile}" ]]; then + if [[ "${NotifyError}" == "false" ]]; then + if [[ -n "${found}" ]]; then + sed -e "s/dockcheck\.sh.*/dockcheck\.sh ${CurrentEpochTime}/" "${SnoozeFile}" > "${SnoozeFile}.new" + mv "${SnoozeFile}.new" "${SnoozeFile}" + else + printf "dockcheck.sh ${CurrentEpochTime}\n" >> "${SnoozeFile}" + fi + fi + fi + fi fi } ### Set DISABLE_NOTIFY_UPDATE_NOTIFICATION=false in dockcheck.config ### to not send notifications when notify scripts themselves have updates. notify_update_notification() { - if [[ ! "${DISABLE_NOTIFY_UPDATE_NOTIFICATION:-}" = "true" ]]; then - update_channels=( "${enabled_notify_channels[@]}" "v2" ) + if [[ ! "${DISABLE_NOTIFY_UPDATE_NOTIFICATION:-}" == "true" ]]; then + NotifyUpdateNotify=false + NotifyError=false - for notify_script in "${update_channels[@]}"; do - upper_channel=$(tr '[:lower:]' '[:upper:]' <<< "$notify_script") - VersionVar="NOTIFY_${upper_channel}_VERSION" + UpdateChannels=( "${enabled_notify_channels[@]}" "v2" ) + + for NotifyScript in "${UpdateChannels[@]}"; do + UpperChannel=$(tr '[:lower:]' '[:upper:]' <<< "$NotifyScript") + VersionVar="NOTIFY_${UpperChannel}_VERSION" if [[ -n "${!VersionVar:-}" ]]; then - RawNotifyUrl="https://raw.githubusercontent.com/mag37/dockcheck/main/notify_templates/notify_${notify_script}.sh" - LatestNotifyRelease="$(curl -s -r 0-150 $RawNotifyUrl | sed -n "/NOTIFY_${upper_channel}_VERSION/s/NOTIFY_${upper_channel}_VERSION=//p" | tr -d '"')" - LatestNotifyRelease=${LatestNotifyRelease:-undefined} - if [[ ! "${LatestNotifyRelease}" = "undefined" ]]; then - if [[ "${!VersionVar}" != "$LatestNotifyRelease" ]] ; then - MessageTitle="$FromHost - New version of notify_${notify_script}.sh available." - - printf -v MessageBody "notify_${notify_script}.sh update available:\n ${!VersionVar} -> $LatestNotifyRelease\n" - - for channel in "${enabled_notify_channels[@]}"; do - printf "Sending notify_${notify_script}.sh update notification - ${channel}\n" - exec_if_exists_or_fail trigger_${channel}_notification || \ - printf "Attempted to send notification to channel ${channel}, but the function was not found. Make sure notify_${channel}.sh is available in the ${ScriptWorkDir} directory or notify_templates subdirectory.\n" - done + RawNotifyUrl="https://raw.githubusercontent.com/mag37/dockcheck/main/notify_templates/notify_${NotifyScript}.sh" + LatestNotifySnippet="$(curl ${CurlArgs} -r 0-150 "$RawNotifyUrl" || printf "undefined")" + LatestNotifyRelease="$(echo "$LatestNotifySnippet" | sed -n "/${VersionVar}/s/${VersionVar}=//p" | tr -d '"')" + if [[ ! "${LatestNotifyRelease}" == "undefined" ]]; then + if [[ "${!VersionVar}" != "${LatestNotifyRelease}" ]] ; then + Updates+=("${NotifyScript}.sh ${!VersionVar} -> ${LatestNotifyRelease}") fi fi fi done + + if [[ -n "${snooze}" ]] && [[ -f "${SnoozeFile}" ]]; then + for update in "${Updates[@]}"; do + read -a NotifyScript <<< "${update}" + found=$(grep -w "${NotifyScript}" "${SnoozeFile}" || printf "") + if [[ -n "${found}" ]]; then + read -a arr <<< "${found}" + CheckEpochSeconds=$(( $(date -d "${arr[1]}" +%s 2>/dev/null) + ${snooze} - 60 )) || CheckEpochSeconds=$(( $(date -f "%Y-%m-%d" -j "${arr[1]}" +%s) + ${snooze} - 60 )) + if [[ "${CurrentEpochSeconds}" -gt "${CheckEpochSeconds}" ]]; then + NotifyUpdateNotify=true + fi + else + NotifyUpdateNotify=true + fi + done + else + NotifyUpdateNotify=true + fi + + if [[ "${NotifyUpdateNotify}" == "true" ]]; then + if [[ "${#Updates[@]}" -gt 0 ]]; then + UpdToString=$( printf '%s\\n' "${Updates[@]}" ) + UpdToString=${UpdToString%\\n} + NotifyError=false + + MessageTitle="$FromHost - New version of notify templates available." + + printf -v MessageBody "Notify templates on $FromHost with updates available:\n${UpdToString}\n" + + for channel in "${enabled_notify_channels[@]}"; do + printf "Sending notify template update notification - ${channel}\n" + exec_if_exists_or_fail trigger_${channel}_notification || \ + printf "Attempted to send notification to channel ${channel}, but the function was not found. Make sure notify_${channel}.sh is available in the ${ScriptWorkDir} directory or notify_templates subdirectory.\n" + done + + [[ -n "${snooze}" ]] && [[ "${NotifyError}" == "false" ]] && update_snooze "${Updates[@]}" + fi + fi + + UpdatesPlusDockcheck=("${Updates[@]}") + UpdatesPlusDockcheck+=("dockcheck.sh") + [[ -n "${snooze}" ]] && cleanup_snooze "${UpdatesPlusDockcheck[@]}" fi }