diff --git a/default.config b/default.config new file mode 100644 index 0000000..a3790b7 --- /dev/null +++ b/default.config @@ -0,0 +1,92 @@ +### Custom user variables for podcheck +## Copy this file to "podcheck.config" to make it active +## Can be placed in ~/.config/ or alongside podcheck.sh +## +## Uncomment and set your preferred configuration variables here +## This will not be replaced on updates + +#Timeout=10 # Set a timeout (in seconds) per container for registry checkups. +#MaxAsync=10 # Set max asynchronous subprocesses, 1 default, 0 to disable. +#BarWidth=50 # The character width of the progress bar +#AutoMode=true # Automatic updates, without interaction. +#DontUpdate=true # No updates; only checking availability without interaction. +#AutoPrune=true # Auto-Prune dangling images after update. +#AutoSelfUpdate=true # Allow automatic self updates - caution as this will pull new code and autorun it. +#Notify=true # Inform - send a preconfigured notification. +#Exclude="one,two" # Exclude containers, separated by comma. +#DaysOld="5" # Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower. +#Stopped="-a" # Include stopped containers in the check. (Logic: podman ps -a). +#OnlyLabel=true # Only update if label is set. See readme. +#ForceRestartStacks=true # Force stop+start stack after update. Caution: restarts once for every updated container within stack. +#DRunUp=true # Allow updating images for podman run, won't update the container. +#MonoMode=true # Monochrome mode, no printf colour codes and hides progress bar. +#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 stack. (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 +#DisplaySourcedFiles=false # Display what files are being sourced/used + +### Notify settings +## All commented values are examples only. Modify as needed. +## +## Uncomment the line below and specify the notification channels you wish to enable in a space separated string +# +#NOTIFY_CHANNELS="apprise discord DSM file generic HA 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 and set to true to disable notifications when podcheck itself has updates. +#DISABLE_PODCHECK_NOTIFICATION=false +## Uncomment and set to true to disable notifications when notify scripts themselves have updates. +#DISABLE_NOTIFY_NOTIFICATION=false +# +## Apprise configuration variables. Set APPRISE_PAYLOAD to make a CLI call or set APPRISE_URL to make an API request instead. +#APPRISE_PAYLOAD='mailto://myemail:mypass@gmail.com +# mastodons://{token}@{host} +# pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b +# tgram://{bot_token}/{chat_id}/' +#APPRISE_URL="http://apprise.mydomain.tld:1234/notify/apprise" +# +#DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/" +# +#DSM_SENDMAILTO="me@mydomain.com" +#DSM_SUBJECTTAG="Email Subject Prefix" +# +#GOTIFY_DOMAIN="https://gotify.domain.tld" +#GOTIFY_TOKEN="token-value" +# +#HA_ENTITY="entity" +#HA_TOKEN="token" +#HA_URL="https://your.homeassistant.url" +# +#MATRIX_ACCESS_TOKEN="token-value" +#MATRIX_ROOM_ID="myroom" +#MATRIX_SERVER_URL="https://matrix.yourdomain.tld" +# +## https://ntfy.sh or your custom domain with https:// and no trailing / +#NTFY_DOMAIN="https://ntfy.sh" +#NTFY_TOPIC_NAME="YourUniqueTopicName" +#NTFY_AUTH="" # set to either format -> "user:password" OR ":tk_12345678". If using tokens, don't forget the ":" +# +#PUSHBULLET_URL="https://api.pushbullet.com/v2/pushes" +#PUSHBULLET_TOKEN="token-value" +# +#PUSHOVER_URL="https://api.pushover.net/1/messages.json" +#PUSHOVER_USER_KEY="userkey" +#PUSHOVER_TOKEN="token-value" +# +#SLACK_CHANNEL_ID=mychannel +#SLACK_ACCESS_TOKEN=xoxb-token-value +# +#SMTP_MAIL_FROM="me@mydomain.tld" +#SMTP_MAIL_TO="you@yourdomain.tld" +#SMTP_SUBJECT_TAG="podcheck" +# +#TELEGRAM_CHAT_ID="mychatid" +#TELEGRAM_TOKEN="token-value" +#TELEGRAM_TOPIC_ID="0" +# +#FILE_PATH="${ScriptWorkDir}/updates_available.txt" \ No newline at end of file diff --git a/podcheck.sh b/podcheck.sh index 83e0e47..ba8fe18 100755 --- a/podcheck.sh +++ b/podcheck.sh @@ -3,17 +3,31 @@ set -euo pipefail shopt -s nullglob shopt -s failglob -VERSION="v0.6.0" +VERSION="v0.7.1-podman" # Variables for self-updating ScriptArgs=( "$@" ) ScriptPath="$(readlink -f "$0")" ScriptWorkDir="$(dirname "$ScriptPath")" -# ChangeNotes: Rewrite of dependency installer. jq can now be installed via package manager or static binary. +# ChangeNotes: Sync with dockcheck v0.7.1 - Added advanced notifications, async processing, configuration system Github="https://github.com/sudo-kraken/podcheck" RawUrl="https://raw.githubusercontent.com/sudo-kraken/podcheck/main/podcheck.sh" +# Source helper functions +source_if_exists_or_fail() { + if [[ -s "$1" ]]; then + source "$1" + [[ "${DisplaySourcedFiles:-false}" == true ]] && echo " * sourced config: ${1}" + return 0 + else + return 1 + fi +} + +# User customizable defaults +source_if_exists_or_fail "${HOME}/.config/podcheck.config" || source_if_exists_or_fail "${ScriptWorkDir}/podcheck.config" + cleanup() { # Temporarily disable failglob for cleanup shopt -u failglob @@ -46,25 +60,30 @@ if [[ -n "$LatestRelease" && "$LatestRelease" != "$VERSION" ]]; then fi Help() { - echo "Syntax: podcheck.sh [OPTION] [part of name to filter]" - echo "Example: podcheck.sh -y -d 10 -e nextcloud,heimdall" + echo "Syntax: podcheck.sh [OPTION] [comma separated names to include]" + echo "Example: podcheck.sh -y -x 10 -d 10 -e nextcloud,heimdall" echo echo "Options:" echo "-a|y Automatic updates, without interaction." - echo "-c Exports metrics as prom file for the prometheus node_exporter. Provide the collector textfile directory." - echo "-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age." + echo "-c D Exports metrics as prom file for the prometheus node_exporter. Provide the collector textfile directory." + echo "-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower." echo "-e X Exclude containers, separated by comma." echo "-f Force pod restart after update." + echo "-F Only compose up the specific container, not the whole compose stack (useful for master-compose structure)." echo "-h Print this Help." echo "-i Inform - send a preconfigured notification." + echo "-I Prints custom releasenote urls alongside each container with updates in CLI output (requires urls.list)." echo "-l Only update if label is set. See readme." - echo "-m Monochrome mode, no printf colour codes." + echo "-m Monochrome mode, no printf color codes and hides progress bar." + echo "-M Prints custom releasenote urls as markdown (requires template support)." echo "-n No updates; only checking availability." echo "-p Auto-prune dangling images after update." echo "-r Allow updating images for podman run; won't update the container." echo "-s Include stopped containers in the check." - echo "-t Set a timeout (in seconds) per container for registry checkups, 10 is default." + echo "-t N Set a timeout (in seconds) per container for registry checkups, 10 is default." + echo "-u Allow automatic self updates - caution as this will pull new code and autorun it." echo "-v Prints current version." + echo "-x N Set max asynchronous subprocesses, 1 default, 0 to disable, 32+ tested." echo echo "Project source: $Github" } @@ -77,60 +96,113 @@ c_blue="\033[0;34m" c_teal="\033[0;36m" c_reset="\033[0m" -# Initialise variables first -AutoUp="no" -AutoPrune="" -Stopped="" -Timeout=10 -NoUpdateMode=false +# 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:-""} +CollectorTextFileDirectory=${CollectorTextFileDirectory:-} +Exclude=${Exclude:-} +DaysOld=${DaysOld:-} +OnlySpecific=${OnlySpecific:-false} +SpecificContainer=${SpecificContainer:-""} Excludes=() GotUpdates=() NoUpdates=() GotErrors=() -NotifyUpdates=() SelectedUpdates=() -OnlyLabel=false -ForceRestartPods=false - -# regbin will be set later. +CurlArgs="--retry ${CurlRetryCount:-3} --retry-delay ${CurlRetryDelay:-1} --connect-timeout ${CurlConnectTimeout:-5} -sf" regbin="" +jqbin="" set -euo pipefail -while getopts "aynpfrhlisvmc:e:d:t:v" options; do +while getopts "ayfFhiIlmMnprsuvc:e:d:t:x:" options; do case "${options}" in - a|y) AutoUp="yes" ;; - c) - CollectorTextFileDirectory="${OPTARG}" - if ! [[ -d $CollectorTextFileDirectory ]]; then - printf "The directory (%s) does not exist.\n" "${CollectorTextFileDirectory}" - exit 2 - fi - ;; - n) NoUpdateMode=true ;; - r) DRunUp="yes" ;; - p) AutoPrune="yes" ;; + a|y) AutoMode=true ;; + c) CollectorTextFileDirectory="${OPTARG}" ;; + d) DaysOld=${OPTARG} ;; + e) Exclude=${OPTARG} ;; + f) ForceRestartStacks=true ;; + F) OnlySpecific=true ;; + i) Notify=true ;; + I) PrintReleaseURL=true ;; l) OnlyLabel=true ;; - f) ForceRestartPods=true ;; - i) [ -s "$ScriptWorkDir/notify.sh" ] && { source "$ScriptWorkDir/notify.sh"; Notify="yes"; } ;; - e) Exclude="${OPTARG}" - IFS=',' read -ra Excludes <<< "$Exclude" - ;; - m) declare c_{red,green,yellow,blue,teal,reset}="" ;; + m) MonoMode=true ;; + M) PrintMarkdownURL=true ;; + n) DontUpdate=true; AutoMode=true;; + p) AutoPrune=true ;; + r) DRunUp=true ;; s) Stopped="-a" ;; t) Timeout="${OPTARG}" ;; - d) DaysOld="${OPTARG}" - if ! [[ $DaysOld =~ ^[0-9]+$ ]]; then - printf "Days -d argument given (%s) is not a number.\n" "${DaysOld}" - exit 2 - fi - ;; + u) AutoSelfUpdate=true ;; v) printf "%s\n" "$VERSION"; exit 0 ;; + x) MaxAsync=${OPTARG} ;; h|*) Help; exit 2 ;; esac done shift "$((OPTIND-1))" +# Set $1 to a variable for name filtering later, rewriting if multiple +SearchName="${1:-}" +if [[ ! -z "$SearchName" ]]; then + SearchName="^(${SearchName//,/|})$" +fi + +# 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 podcheck.config. This will result in no notifications being sent.\n" +fi + +# Setting up options and sourcing functions +if [[ "$DontUpdate" == true ]]; then AutoMode=true; fi +if [[ "$MonoMode" == true ]]; then declare c_{red,green,yellow,blue,teal,reset}=""; fi +if [[ "$Notify" == true ]]; then + source_if_exists_or_fail "${ScriptWorkDir}/notify.sh" || source_if_exists_or_fail "${ScriptWorkDir}/notify_templates/notify_v2.sh" || Notify=false +fi +if [[ -n "$Exclude" ]]; then + IFS=',' read -ra Excludes <<< "$Exclude" + unset IFS +fi +if [[ -n "$DaysOld" ]]; then + if ! [[ $DaysOld =~ ^[0-9]+$ ]]; then + printf "Days -d argument given (%s) is not a number.\n" "$DaysOld" + exit 2 + fi +fi +if [[ -n "$CollectorTextFileDirectory" ]]; then + if ! [[ -d $CollectorTextFileDirectory ]]; then + printf "The directory (%s) does not exist.\n" "$CollectorTextFileDirectory" + exit 2 + else + source "${ScriptWorkDir}/addons/prometheus/prometheus_collector.sh" + fi +fi + +exec_if_exists() { + if [[ $(type -t $1) == function ]]; then "$@"; fi +} + +exec_if_exists_or_fail() { + [[ $(type -t $1) == function ]] && "$@" +} + # Now get the search name from the first remaining positional parameter SearchName="${1:-}"