Compare commits

..

10 Commits

4 changed files with 58 additions and 115 deletions

View File

@@ -13,22 +13,15 @@
</p>
<h2 align="center">CLI tool to automate docker image updates or notifying when updates are available.</h2>
<h3 align="center">selective updates, include/exclude containers, image backups, custom labels, notification plugins, prune when done etc.</h3>
<h3 align="center">selective updates, exclude containers, custom labels, notification plugins, prune when done etc.</h3>
<h4 align="center">:whale: Docker Hub pull limit :chart_with_downwards_trend: not an issue for checks only for actual pulls - <a href="#whale-docker-hub-pull-limit-chart_with_downwards_trend-not-an-issue-for-checks-but-for-actual-pulls">read more</a></h4>
<h4 align="center">:whale: Docker Hub pull limit :chart_with_downwards_trend: not an issue for checks but for actual pulls - <a href="#whale-docker-hub-pull-limit-chart_with_downwards_trend-not-an-issue-for-checks-but-for-actual-pulls">read more</a></h4>
<h5 align="center">For Podman - see the fork <a href="https://github.com/sudo-kraken/podcheck">sudo-kraken/podcheck</a>!</h4>
___
## Changelog
- **v0.7.5**:
- Added new option **BackupForDays**; `-b N` and `-B`:
- Backup an image before pulling a new version for easy rollback in case of breakage.
- Removes backed up images older than *N* days.
- List currently backed up images with `-B`.
- Fixes:
- Bugfix for `-s` *Stopped* to not recreate stopped containers after update.
- **v0.7.4**:
- Added new option `-R`:
- Will skip container recreation after pulling images.
@@ -45,6 +38,16 @@ ___
- List reformatting for "available updates" numbering to easier highlight and copy:
- Padded with zero, changed `)` to `-`, example: `02 - homer`
- Can be selected by writing `2,3,4` or `02,03,04`.
- **v0.7.1**:
- Added support for multiple notifications using the same template
- Added support for notification output format
- Added support for file output
- Added optional configuration variables per channel to (replace `<channel>` with any channel name):
- `<channel>_TEMPLATE` : Specify a template
- `<channel>_SKIPSNOOZE` : Skip snooze
- `<channel>_CONTAINERSONLY` : Only notify for docker container related updates
- `<channel>_ALLOWEMPTY` : Always send notifications, even when empty
- `<channel>_OUTPUT` : Define output format
___
@@ -58,8 +61,6 @@ Example: dockcheck.sh -y -x 10 -d 10 -e nextcloud,heimdall
Options:
-a|y Automatic updates, without interaction.
-b N Enable image backups and sets number of days to keep from pruning.
-B List currently backed up images, then exit.
-c D Exports metrics as prom file for the prometheus node_exporter. Provide the collector textfile directory.
-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower.
-e X Exclude containers, separated by comma.
@@ -85,19 +86,18 @@ Options:
### Basic example:
```
$ ./dockcheck.sh
[##################################################] 5/5
. . .
Containers on latest version:
glances
homer
Containers with updates available:
01) adguardhome
02) syncthing
03) whoogle-search
1) adguardhome
2) syncthing
3) whoogle-search
Choose what containers to update:
Enter number(s) separated by comma, [a] for all - [q] to quit: 1,2
Enter number(s) separated by comma, [a] for all - [q] to quit:
```
Then it proceeds to run `pull` and `up -d` on every container with updates.
After the updates are complete, you'll get prompted if you'd like to prune dangling images.
@@ -245,23 +245,6 @@ The `urls.list` file is just an example and I'd gladly see that people contribut
Pass `-x N` where N is number of subprocesses allowed, experiment in your environment to find a suitable max!
Change the default value by editing the `MaxAsync=N` variable in `dockcheck.sh`. To disable the subprocess function set `MaxAsync=0`.
## Image Backups; `-b N` to backup previous images as custom (retagged) images for easy rollback
When the option `BackupForDays` is set **dockcheck** will store the image being updated as a backup, retagged with a different name and removed due to age configured (*BackupForDays*) in a future run.
Let's say we're updating `b4bz/homer:latest` - then before replacing the current image it will be retagged with the name `dockcheck/homer:2025-10-26_1132_latest`
- `dockcheck` as repo name to not interfere with others.
- `homer` is the image.
- `2025-10-26_1132` is the time when running the script.
- `latest` is the tag of the image.
Then if an update breaks, you could restore the image by stopping the container, delete the new image, eg. `docker rmi b4bz/homer:latest`, then retag the backup as latest `docker tag dockcheck/homer:<date>_latest b4bz/homer:latest`.
After that, start the container again (now with the backup image active) and it will be updated as usual next time you run dockcheck or other updates.
The backed up images will be removed if they're older than *BackupForDays* value (passed as `-b N` or set in the `dockcheck.config` with `BackupForDays=N`) and then pruned.
If configured for eg. 7 days, force earlier cleaning by just passing a lower number of days, eg. `-b 2` to clean everything older than 2 days.
Backed up images will not be removed if neither `-b` flag nor `BackupForDays` config variable is set.
Use the capital option `-B` to list currently backed up images. Or list all images with `docker images`.
To manually remove any backed up images, do `docker rmi dockcheck/homer:2025-10-26_1132_latest`.
## Extra plugins and tools:

View File

@@ -28,7 +28,6 @@
#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
#BackupForDays=7 # Enable backups of images and removes backups older than N days.
### Notify settings
## All commented values are examples only. Modify as needed.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
VERSION="v0.7.5"
# ChangeNotes: New option -b N to backup image before pulling for easy rollback.
VERSION="v0.7.4"
# ChangeNotes: New option -R to pull without recreation. Fixes: value too great error, legacy cleanups.
Github="https://github.com/mag37/dockcheck"
RawUrl="https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh"
@@ -34,8 +34,6 @@ Help() {
echo
echo "Options:"
echo "-a|y Automatic updates, without interaction."
echo "-b N Enable image backups and sets number of days to keep from pruning."
echo "-B List currently backed up images, then exit."
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. 2xSlower."
echo "-e X Exclude containers, separated by comma."
@@ -44,6 +42,7 @@ Help() {
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 "-k N Number of days to store image backups before pruning - this also enables the backup function."
echo "-l Only include containers with label set. See readme."
echo "-m Monochrome mode, no printf colour codes and hides progress bar."
echo "-M Prints custom releasenote urls as markdown (requires template support)."
@@ -60,12 +59,6 @@ Help() {
echo "Project source: $Github"
}
# Print current backups function
print_backups() {
printf "\n%b---%b Currently backed up images %b---%b\n\n" "$c_teal" "$c_blue" "$c_teal" "$c_reset"
docker images | sed -ne '/^REPOSITORY/p' -ne '/^dockcheck/p'
}
# Initialise variables
Timeout=${Timeout:-10}
MaxAsync=${MaxAsync:-1}
@@ -85,7 +78,7 @@ Stopped=${Stopped:-""}
CollectorTextFileDirectory=${CollectorTextFileDirectory:-}
Exclude=${Exclude:-}
DaysOld=${DaysOld:-}
BackupForDays=${BackupForDays:-}
DaysKept=${DaysKept:-}
OnlySpecific=${OnlySpecific:-false}
SpecificContainer=${SpecificContainer:-""}
SkipRecreate=${SkipRecreate:-false}
@@ -110,11 +103,9 @@ c_reset="\033[0m"
RunTimestamp=$(date +'%Y-%m-%d_%H%M')
RunEpoch=$(date +'%s')
while getopts "ayb:BfFhiIlmMnprsuvc:e:d:t:x:R" options; do
while getopts "ayfFhiIlmMnprsuvc:e:d:k:t:x:R" options; do
case "${options}" in
a|y) AutoMode=true ;;
b) BackupForDays="${OPTARG}" ;;
B) print_backups; exit 0 ;;
c) CollectorTextFileDirectory="${OPTARG}" ;;
d) DaysOld=${OPTARG} ;;
e) Exclude=${OPTARG} ;;
@@ -122,6 +113,7 @@ while getopts "ayb:BfFhiIlmMnprsuvc:e:d:t:x:R" options; do
F) OnlySpecific=true ;;
i) Notify=true ;;
I) PrintReleaseURL=true ;;
k) DaysKept="${OPTARG}" ;;
l) OnlyLabel=true ;;
m) MonoMode=true ;;
M) PrintMarkdownURL=true ;;
@@ -171,12 +163,11 @@ if [[ -n "$DaysOld" ]]; then
exit 2
fi
fi
if [[ -n "$BackupForDays" ]]; then
if ! [[ $BackupForDays =~ ^[0-9]+$ ]]; then
printf "-b argument given (%s) is not a number.\n" "$BackupForDays"
if [[ -n "$DaysKept" ]]; then
if ! [[ $DaysKept =~ ^[0-9]+$ ]]; then
printf "-k argument given (%s) is not a number.\n" "$DaysKept"
exit 2
fi
[[ "$AutoPrune" == true ]] && printf "%bWARNING: When -b option is used, -p has no function.%b\n" "$c_yellow" "$c_reset"
fi
if [[ -n "$CollectorTextFileDirectory" ]]; then
if ! [[ -d $CollectorTextFileDirectory ]]; then
@@ -231,7 +222,6 @@ choosecontainers() {
while [[ -z "${ChoiceClean:-}" ]]; do
read -r -p "Enter number(s) separated by comma, [a] for all - [q] to quit: " Choice
if [[ "$Choice" =~ [qQnN] ]]; then
[[ -n "${BackupForDays:-}" ]] && remove_backups
exit 0
elif [[ "$Choice" =~ [aAyY] ]]; then
SelectedUpdates=( "${GotUpdates[@]}" )
@@ -262,28 +252,6 @@ datecheck() {
fi
}
remove_backups() {
IFS=$'\n'
CleanupCount=0
for backup_img in $(docker images --format "{{.Repository}} {{.Tag}}" | sed -n '/^dockcheck/p'); do
repo_name=${backup_img% *}
backup_tag=${backup_img#* }
backup_date=${backup_tag%%_*}
# UNTAGGING HERE
if datecheck "$backup_date" "$BackupForDays"; then
[[ "$CleanupCount" == 0 ]] && printf "\n%bRemoving backed up images older then %s days.%b\n" "$c_blue" "$BackupForDays" "$c_reset"
docker rmi "${repo_name}:${backup_tag}" && ((CleanupCount+=1))
fi
done
unset IFS
if [[ "$CleanupCount" == 0 ]]; then
printf "\nNo backup images to remove.\n"
else
[[ "$CleanupCount" -gt 1 ]] && b_phrase="backups" || b_phrase="backup"
printf "\n%b%s%b %s removed.%b\n" "$c_green" "$CleanupCount" "$c_teal" "$b_phrase" "$c_reset"
fi
}
progress_bar() {
QueCurrent="$1"
QueTotal="$2"
@@ -503,7 +471,7 @@ check_image() {
# Make required functions and variables available to subprocesses
export -f check_image datecheck
export Excludes_string="${Excludes[*]:-}" # Can only export scalar variables
export t_out regbin RepoUrl DaysOld DRunUp jqbin OnlyLabel RunTimestamp RunEpoch
export t_out regbin RepoUrl DaysOld DRunUp jqbin OnlyLabel
# Check for POSIX xargs with -P option, fallback without async
if (echo "test" | xargs -P 2 >/dev/null 2>&1) && [[ "$MaxAsync" != 0 ]]; then
@@ -586,17 +554,18 @@ if [[ -n "${GotUpdates:-}" ]]; then
for i in "${SelectedUpdates[@]}"; do
((CurrentQue+=1))
printf "\n%bNow updating (%s/%s): %b%s%b\n" "$c_teal" "$CurrentQue" "$NumberofUpdates" "$c_blue" "$i" "$c_reset"
# ContLabels=$(docker inspect "$i" --format '{{json .Config.Labels}}')
# ContImage=$(docker inspect "$i" --format='{{.Config.Image}}')
# ContPath=$($jqbin -r '."com.docker.compose.project.working_dir"' <<< "$ContLabels")
ContConfig=$(docker inspect "$i" --format '{{json .}}')
ContImage=$($jqbin -r '."Config"."Image"' <<< "$ContConfig")
ContImage=$($jqbin -r '."Config"."Image"' <<< "$ContConfig") # OLD? Remove if replaced with ContFull
ImageId=$($jqbin -r '."Image"' <<< "$ContConfig")
# ContFull=$(docker image inspect "$ImageId" --format "{{index .RepoTags 0}}")
ContPath=$($jqbin -r '."Config"."Labels"."com.docker.compose.project.working_dir"' <<< "$ContConfig")
[[ "$ContPath" == "null" ]] && ContPath=""
# Add new backup tag prior to pulling if option is set
if [[ -n "${BackupForDays:-}" ]]; then
ImageConfig=$(docker image inspect "$ImageId" --format '{{ json . }}')
ContRepoDigests=$($jqbin -r '.RepoDigests[0]' <<< "$ImageConfig")
[[ "$ContRepoDigests" == "null" ]] && ContRepoDigests=""
if [[ -n "${DaysKept:-}" ]]; then
ContRepo=${ContImage%:*}
ContApp=${ContRepo#*/}
[[ "$ContImage" =~ ":" ]] && ContTag=${ContImage#*:} || ContTag="latest"
@@ -616,13 +585,7 @@ if [[ -n "${GotUpdates:-}" ]]; then
continue
fi
if docker pull "$ContImage"; then
# Removal of the <none>-tag image left behind from backup
if [[ ! -z "${ContRepoDigests:-}" ]] && [[ -n "${BackupForDays:-}" ]]; then docker rmi "$ContRepoDigests"; fi
else
printf "\n%bDocker error, exiting!%b\n" "$c_red" "$c_reset" ; exit 1
fi
docker pull "$ContImage" || { printf "\n%bDocker error, exiting!%b\n" "$c_red" "$c_reset" ; exit 1; }
done
printf "\n%bDone pulling updates.%b\n" "$c_green" "$c_reset"
@@ -635,8 +598,8 @@ if [[ -n "${GotUpdates:-}" ]]; then
((CurrentQue+=1))
unset CompleteConfs
# Extract labels and metadata
ContConfig=$(docker inspect "$i" --format '{{json .}}')
ContLabels=$($jqbin -r '."Config"."Labels"' <<< "$ContConfig")
ContLabels=$(docker inspect "$i" --format '{{json .Config.Labels}}')
ContImage=$(docker inspect "$i" --format='{{.Config.Image}}')
ContPath=$($jqbin -r '."com.docker.compose.project.working_dir"' <<< "$ContLabels")
[[ "$ContPath" == "null" ]] && ContPath=""
ContConfigFile=$($jqbin -r '."com.docker.compose.project.config_files"' <<< "$ContLabels")
@@ -649,22 +612,14 @@ if [[ -n "${GotUpdates:-}" ]]; then
[[ "$ContRestartStack" == "null" ]] && ContRestartStack=""
ContOnlySpecific=$($jqbin -r '."mag37.dockcheck.only-specific-container"' <<< "$ContLabels")
[[ "$ContOnlySpecific" == "null" ]] && ContRestartStack=""
ContStateRunning=$($jqbin -r '."State"."Running"' <<< "$ContConfig")
[[ "$ContStateRunning" == "null" ]] && ContStateRunning=""
if [[ "$ContStateRunning" == "true" ]]; then
printf "\n%bNow recreating (%s/%s): %b%s%b\n" "$c_teal" "$CurrentQue" "$NumberofUpdates" "$c_blue" "$i" "$c_reset"
else
printf "\n%bSkipping recreation of %b%s%b as it's not running.%b\n" "$c_yellow" "$c_blue" "$i" "$c_yellow" "$c_reset"
continue
fi
printf "\n%bNow recreating (%s/%s): %b%s%b\n" "$c_teal" "$CurrentQue" "$NumberofUpdates" "$c_blue" "$i" "$c_reset"
# Checking if compose-values are empty - hence started with docker run
[[ -z "$ContPath" ]] && { echo "Not a compose container, skipping."; continue; }
# 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
## Reformatting path + multi compose
if [[ $ContConfigFile == '/'* ]]; then
CompleteConfs=$(for conf in ${ContConfigFile//,/ }; do printf -- "-f %s " "$conf"; done)
else
@@ -685,13 +640,6 @@ if [[ -n "${GotUpdates:-}" ]]; then
done
fi
printf "\n%bAll updates done!%b\n" "$c_green" "$c_reset"
# Trigger pruning only when backup-function is not used
if [[ -z "${BackupForDays:-}" ]]; then
if [[ "$AutoPrune" == false ]] && [[ "$AutoMode" == false ]]; then printf "\n"; read -rep "Would you like to prune all dangling images? y/[n]: " AutoPrune; fi
if [[ "$AutoPrune" == true ]] || [[ "$AutoPrune" =~ [yY] ]]; then printf "\nAuto pruning.."; docker image prune -f; fi
fi
else
printf "\nNo updates installed.\n"
fi
@@ -699,7 +647,25 @@ else
printf "\nNo updates available.\n"
fi
# Clean up old backup image tags if -b is used
[[ -n "${BackupForDays:-}" ]] && remove_backups
# Clean up old backup image tags if -k is used
if [[ -n "${DaysKept:-}" ]]; then
IFS=$'\n'
CleanupCount=0
for backup_img in $(docker images --format "{{.Repository}} {{.Tag}}" | sed -n '/^dockcheck/p'); do
repo_name=${backup_img% *}
backup_tag=${backup_img#* }
backup_date=${backup_tag%%_*}
# UNTAGGING HERE
if datecheck "$backup_date" "$DaysKept"; then
[[ "$CleanupCount" == 0 ]] && echo "Removing backed up images older then $DaysKept days."
docker rmi "${repo_name}:${backup_tag}" && ((CleanupCount+=1))
fi
done
[[ "$CleanupCount" == 0 ]] && printf "No backup images to remove.\n" || printf "%b%s%b backup images removed.%b\n" "$c_green" "$CleanupCount" "$c_teal" "$c_reset"
unset IFS
fi
if [[ "$AutoPrune" == false ]] && [[ "$AutoMode" == false ]]; then printf "\n"; read -rep "Would you like to prune all dangling images? y/[n]: " AutoPrune; fi
if [[ "$AutoPrune" == true ]] || [[ "$AutoPrune" =~ [yY] ]]; then printf "\nAuto pruning.."; docker image prune -f; fi
exit 0

View File

@@ -15,14 +15,12 @@ calibre https://github.com/linuxserver/docker-calibre/releases
calibre-web https://github.com/linuxserver/docker-calibre-web/releases
cleanuperr https://github.com/flmorg/cleanuperr/releases
cross-seed https://github.com/cross-seed/cross-seed/releases
crowdsec https://github.com/crowdsecurity/crowdsec/releases
cup https://github.com/sergi0g/cup/releases
dockge https://github.com/louislam/dockge/releases
dozzle https://github.com/amir20/dozzle/releases
flatnotes https://github.com/dullage/flatnotes/releases
forgejo https://codeberg.org/forgejo/forgejo/releases
fressrss https://github.com/FreshRSS/FreshRSS/releases
gerbil https://github.com/fosrl/gerbil/releases
gluetun https://github.com/qdm12/gluetun/releases
go2rtc https://github.com/AlexxIT/go2rtc/releases
gotify https://github.com/gotify/server/releases
@@ -47,11 +45,9 @@ mealie https://github.com/mealie-recipes/mealie/releases
meilisearch https://github.com/meilisearch/meilisearch/releases
monica https://github.com/monicahq/monica/releases
mqtt https://github.com/eclipse/mosquitto/tags
newt https://github.com/fosrl/newt/releases
nextcloud-aio-mastercontainer https://github.com/nextcloud/all-in-one/releases
nginx https://github.com/docker-library/official-images/blob/master/library/nginx
owncast https://github.com/owncast/owncast/releases
pangolin https://github.com/fosrl/pangolin/releases
prowlarr https://github.com/Prowlarr/Prowlarr/releases
prowlarr-ls https://github.com/linuxserver/docker-prowlarr/releases
qbittorrent https://www.qbittorrent.org/news
@@ -70,7 +66,6 @@ snappymail https://github.com/the-djmaze/snappymail/releases
sonarr https://github.com/Sonarr/Sonarr/releases/
sonarr-ls https://github.com/linuxserver/docker-sonarr/releases
syncthing https://github.com/syncthing/syncthing/releases
tailscale https://github.com/tailscale/tailscale/releases
tautulli https://github.com/Tautulli/Tautulli/releases
thelounge https://github.com/thelounge/thelounge/releases
traefik https://github.com/traefik/traefik/releases