diff --git a/README.md b/README.md index db5103a..9baaf53 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,100 @@

- +

bash GPLv3 - release - Buy me a Coffee - LiberaPay - Github Sponsor + release + Buy Me A Coffee

-

CLI tool to automate docker image updates.
No pre-pull, selective, optional notifications and prune when done.

+

CLI tool to automate Podman image updates.
Selective updates, optional notifications, and image pruning when done.

Now with simple notification integrations!

-

With features like excluding specific containers, custom container labels, auto-prune when done and more.

+

Features include excluding specific containers, custom container labels, auto-prune when done, and more.

___ ## :bell: Changelog - -- **v0.5.0**: Rewritten notify logic - all templates are adjusted and should be migrated! - - Copy the custom settings from your current template to the new version of the same template. - - Look into, copy and customize the `urls.list` file if that's of interest. - - Other changes: - - Added Discord notify template. - - Verbosity changed of `regctl`. -- **v0.4.9**: Added a function to enrich the notify-message with release note URLs. See [Release notes addon](https://github.com/mag37/dockcheck#date-release-notes-addon-to-notifications) -- **v0.4.8**: Rewrote prune logic to not prompt with options `-a|-y` or `-n`. Auto prune with `-p`. -- **v0.4.7**: Notification Template changes to gotify(new!), DSM(improved), SMTP(deprecation alternative). -- **v0.4.6**: Compatibility changes to timeout, due to busybox. -- **v0.4.5**: Bugfixes, compatibility changes to timeout and arrays. -- **v0.4.3**: Added timeout option to skip container if registry check takes too long (10s default). +- **v0.5.6**: Directly checking for systemd units matching container names. + - Improved Quadlet detection by checking for systemd units named after the container. + - Ensures better compatibility with Quadlet-managed containers. +- **v0.5.5**: Switched to podman compose command. + - Adjusted the script to use podman compose instead of podman-compose. + - Removed unnecessary messages. +- **v0.5.4**: Improved Quadlet detection by matching container IDs with systemd units. + - The script now searches systemd unit files for references to the container ID. + - Provides reliable detection of Quadlet-managed containers. +- **v0.5.0**: Initial release of Podcheck, inspired by Dockcheck. + - Supports updating containers managed by Podman Compose and Quadlet. + - Includes options for automatic updates, notifications, and more. ___ - -![](extras/example.gif) - -## :mag_right: `dockcheck.sh` +## :mag_right: `podcheck.sh` ``` -$ ./dockcheck.sh -h -Syntax: dockcheck.sh [OPTION] [part of name to filter] -Example: dockcheck.sh -y -d 10 -e nextcloud,heimdall +$ ./podcheck.sh -h +Syntax: podcheck.sh [OPTION] [part of name to filter] +Example: podcheck.sh -y -d 10 -e nextcloud,heimdall -Options:" +Options: -a|y Automatic updates, without interaction. --d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower. +-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. -e X Exclude containers, separated by comma. --f Force stack restart after update. Caution: restarts once for every updated container within stack. +-f Force pod restart after update. -h Print this Help. -i Inform - send a preconfigured notification. -l Only update if label is set. See readme. -m Monochrome mode, no printf color codes. --n No updates, only checking availability. --p Auto-Prune dangling images after update. --r Allow updating images for docker run, wont update the container. --s Include stopped containers in the check. (Logic: docker ps -a). +-n No updates; only checking availability. +-p Auto-prune dangling images after update. +-r Allow updating images for podman run; won't update the container. +-s Include stopped containers in the check. -t Set a timeout (in seconds) per container for registry checkups, 10 is default. -v Prints current version. ``` - ### Basic example: ``` -$ ./dockcheck.sh -. . . +$ ./podcheck.sh +... Containers on latest version: -glances -homer +filebrowser +foundryvtt Containers with updates available: -1) adguardhome -2) syncthing -3) whoogle-search +1) joplin-db +2) it-tools Choose what containers to update: Enter number(s) separated by comma, [a] for all - [q] to quit: ``` -Then it proceedes 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. +Then it proceeds to run podman pull and podman compose up -d, or restarts systemd units for every container with updates. +After the updates are complete, you'll be prompted if you'd like to prune dangling images ___ ## :nut_and_bolt: Dependencies -- Running docker (duh) and compose, either standalone or plugin. +- Podman: Ensure you have Podman installed and properly configured. +- Podman Compose: For containers managed with podman compose, make sure it's installed. + - Note: podman compose is included in recent versions of Podman. +- Quadlet: If you're using systemd units to manage your containers, ensure they are correctly set up. - Bash shell or compatible shell of at least v4.3 - [regclient/regctl](https://github.com/regclient/regclient) (Licensed under [Apache-2.0 License](http://www.apache.org/licenses/LICENSE-2.0)) - User will be prompted to download `regctl` if not in `PATH` or `PWD`. - regctl requires `amd64/arm64` - see [workaround](#roller_coaster-workaround-for-non-amd64--arm64) if other architecture is used. +- jq: Used for parsing JSON output from podman inspect. +- timeout: Optional but recommended for setting timeouts on registry checks. ## :tent: Install Instructions Download the script to a directory in **PATH**, I'd suggest using `~/.local/bin` as that's usually in **PATH**. ```sh -# basic example with curl: -curl -L https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh -o ~/.local/bin/dockcheck.sh -chmod +x ~/.local/bin/dockcheck.sh +# Using curl: +curl -L https://raw.githubusercontent.com/sudo-kraken/podcheck/main/podcheck.sh -o ~/.local/bin/podcheck.sh +chmod +x ~/.local/bin/podcheck.sh -# or oneliner with wget: -wget -O ~/.local/bin/dockcheck.sh "https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh" && chmod +x ~/.local/bin/dockcheck.sh +# Or using wget: +wget -O ~/.local/bin/podcheck.sh "https://raw.githubusercontent.com/sudo-kraken/podcheck/main/podcheck.sh" && chmod +x ~/.local/bin/podcheck.sh ``` -Then call the script anywhere with just `dockcheck.sh`. -Add preferred `notify.sh`-template to the same directory - this will not be touched by the scripts self-update function. - +Then call the script anywhere with `podcheck.sh`. +Add your preferred notify.sh template to the same directory—this will not be touched by the script's self-update function. ## :loudspeaker: Notifications Trigger with the `-i` flag. @@ -120,30 +116,33 @@ Use a `notify_X.sh` template file from the **notify_templates** directory, copy - [Discord](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) - Discord webhooks. Further additions are welcome - suggestions or PR! -Initiated and first contributed by [yoyoma2](https://github.com/yoyoma2). +Initiated and first contributed by [mag37](https://github.com/mag37) as dockcheck. ### :date: Release notes addon to Notifications -There's a function to use a lookup-file to add release note URL's to the notification message. -Copy the notify_templates/`urls.list` file to the script directory, it will be used automatically if it's there. Modify it as necessary, the names of interest in the left column needs to match your container names. +There's a function to use a lookup file to add release note URLs to the notification message. + +Copy the notify_templates/urls.list file to the script directory—it will be used automatically if it's there. Modify it as necessary; the names of interest in the left column need to match your container names. + The output of the notification will look something like this: ``` Containers on hostname with updates available: -apprise-api -> https://github.com/linuxserver/docker-apprise-api/releases -homer -> https://github.com/bastienwirtz/homer/releases -nginx -> https://github.com/docker-library/official-images/blob/master/library/nginx +joplin-db -> https://github.com/laurent22/joplin/releases +it-tools -> https://github.com/CorentinTh/it-tools/releases ... ``` The `urls.list` file is just an example and I'd gladly see that people contribute back when they add their preferred URLs to their lists. ## :bookmark: Labels -Optionally add labels to compose-files. Currently these are the usable labels: + +Optionally, you can add labels to your containers to control how Podcheck handles them. Currently, these are the usable labels: + +```yaml +labels: + sudo-kraken.podcheck.restart-stack: true + sudo-kraken.podcheck.update: true ``` - labels: - mag37.dockcheck.restart-stack: true - mag37.dockcheck.update: true -``` -- `mag37.dockcheck.restart-stack: true` works instead of the `-f` option, forcing stop+restart on the whole compose-stack (Caution: Will restart on every updated container within stack). -- `mag37.dockcheck.update: true` will when used with the `-l` option only update containers with this label and skip the rest. Will still list updates as usual. +- `sudo-kraken.podcheck.restart-stack`: true works instead of the `-f` option, forcing a restart of the entire pod or compose stack when an update is applied. Caution: This will restart the entire stack for every updated container within it. +- `sudo-kraken.podcheck.update`: true will, when used with the `-l` option, only update containers with this label and skip the rest. It will still list all available updates. ## :roller_coaster: Workaround for non **amd64** / **arm64** `regctl` provides binaries for amd64/arm64, to use on other architecture you could try this workaround. @@ -169,8 +168,8 @@ Test it with `./regctl --help` and then either add the file to the same path as **Example** - Change names, paths, and remove cat+password flag if you rather get prompted: ```sh function dchk { - cat ~/pwd.txt | docker login --username YourUser --password-stdin - ~/dockcheck.sh "$@" + cat ~/pwd.txt | podman login --username YourUser --password-stdin docker.io + ~/podcheck.sh "$@" } ``` @@ -182,21 +181,22 @@ function dchk { ## :warning: `-r flag` disclaimer and warning **Wont auto-update the containers, only their images. (compose is recommended)** -`docker run` dont support using new images just by restarting a container. +`podman run` does not support using new images just by restarting a container. Containers need to be manually stopped, removed and created again to run on the new image. ## :wrench: Debugging If you hit issues, you could check the output of the `extras/errorCheck.sh` script for clues. -Another option is to run the main script with debugging in a subshell `bash -x dockcheck.sh` - if there's a particular container/image that's causing issues you can filter for just that through `bash -x dockcheck.sh nginx`. +Another option is to run the main script with debugging in a subshell `bash -x podcheck.sh` - if there's a particular container/image that's causing issues you can filter for just that through `bash -x podcheck.sh nginx`. ## :scroll: License -dockcheck is created and released under the [GNU GPL v3.0](https://www.gnu.org/licenses/gpl-3.0-standalone.html) license. - -## :heartpulse: Sponsorlist - -- [avegy](https://github.com/avegy) - +podcheck is created and released under the [GNU GPL v3.0](https://www.gnu.org/licenses/gpl-3.0-standalone.html) license. ___ -### :floppy_disk: The [story](https://mag37.org/posts/project_dockcheck/) behind it. 1 year in retrospect. +### :floppy_disk: The Story Behind Podcheck +Podcheck was created to bring the convenience of automated container updates to the Podman ecosystem. As a user of Dockcheck for Docker, the need for a similar tool for Podman became apparent. Podcheck aims to provide the same ease of use and automation, tailored for Podman users. + +## :star2: Acknowledgments +Podcheck is inspired by the original Dockcheck script. Without Dockcheck, there wouldn't have been a Podcheck. Many thanks to mag37 and all the contributors to Dockcheck for their work and inspiration. +___ +Please feel free to contribute, open issues, or submit pull requests to improve Podcheck! diff --git a/extras/dc_brief.sh b/extras/dc_brief.sh index d496d06..61d56d8 100755 --- a/extras/dc_brief.sh +++ b/extras/dc_brief.sh @@ -15,14 +15,14 @@ IFS=',' read -r -a Excludes <<< "$Exclude" ; unset IFS SearchName="$1" -for i in $(docker ps --filter "name=$SearchName" --format '{{.Names}}') ; do +for i in $(podman ps --filter "name=$SearchName" --format '{{.Names}}') ; do for e in "${Excludes[@]}" ; do [[ "$i" == "$e" ]] && continue 2 ; done printf ". " - RepoUrl=$(docker inspect "$i" --format='{{.Config.Image}}') - LocalHash=$(docker image inspect "$RepoUrl" --format '{{.RepoDigests}}') + RepoUrl=$(podman inspect "$i" --format='{{.ImageName}}') + LocalHash=$(podman image inspect "$RepoUrl" --format '{{.Digest}}') ### Checking for errors while setting the variable: if RegHash=$($regbin image digest --list "$RepoUrl" 2>/dev/null) ; then - if [[ "$LocalHash" = *"$RegHash"* ]] ; then NoUpdates+=("$i"); else GotUpdates+=("$i"); fi + if [[ "$LocalHash" == "$RegHash" ]] ; then NoUpdates+=("$i"); else GotUpdates+=("$i"); fi else GotErrors+=("$i") fi @@ -41,7 +41,7 @@ if [[ -n ${NoUpdates[*]} ]] ; then printf "%s\n" "${NoUpdates[@]}" fi if [[ -n ${GotErrors[*]} ]] ; then - printf "\n\033[0;31mContainers with errors, wont get updated:\033[0m\n" + printf "\n\033[0;31mContainers with errors; won't get updated:\033[0m\n" printf "%s\n" "${GotErrors[@]}" fi if [[ -n ${GotUpdates[*]} ]] ; then diff --git a/extras/dockcheck_logo_by_booYah187.png b/extras/dockcheck_logo_by_booYah187.png deleted file mode 100644 index f78f72a..0000000 Binary files a/extras/dockcheck_logo_by_booYah187.png and /dev/null differ diff --git a/extras/errorCheck.sh b/extras/errorCheck.sh index 327f454..99f0950 100755 --- a/extras/errorCheck.sh +++ b/extras/errorCheck.sh @@ -1,13 +1,16 @@ #!/usr/bin/env bash SearchName="$1" -for i in $(docker ps --filter "name=$SearchName" --format '{{.Names}}') ; do +for i in $(podman ps --filter "name=$SearchName" --format '{{.Names}}') ; do echo "------------ $i ------------" - ContPath=$(docker inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}') - [ -z "$ContPath" ] && { "$i has no compose labels - skipping" ; continue ; } - ContConfigFile=$(docker inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.project.config_files" }}') - ContName=$(docker inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.service" }}') - ContEnv=$(docker inspect "$i" --format '{{index .Config.Labels "com.docker.compose.project.environment_file" }}') - ContImage=$(docker inspect "$i" --format='{{.Config.Image}}') + ContPath=$(podman inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}') + if [ -z "$ContPath" ]; then + echo "$i has no compose labels - skipping" + continue + fi + ContConfigFile=$(podman inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.project.config_files" }}') + ContName=$(podman inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.service" }}') + ContEnv=$(podman inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.project.environment_file" }}') + ContImage=$(podman inspect "$i" --format='{{.ImageName}}') if [[ $ContConfigFile = '/'* ]] ; then ComposeFile="$ContConfigFile" @@ -22,6 +25,6 @@ for i in $(docker ps --filter "name=$SearchName" --format '{{.Names}}') ; do echo -e "Container image:\t$ContImage" echo echo "Mounts:" - docker inspect -f '{{ range .Mounts }}{{ .Source }}:{{ .Destination }}{{ printf "\n" }}{{ end }}' "$i" + podman inspect -f '{{ range .Mounts }}{{ .Source }}:{{ .Destination }}{{ printf "\n" }}{{ end }}' "$i" echo done diff --git a/extras/example.gif b/extras/example.gif deleted file mode 100644 index 82acb5a..0000000 Binary files a/extras/example.gif and /dev/null differ diff --git a/extras/example_old.gif b/extras/example_old.gif deleted file mode 100644 index c0da81e..0000000 Binary files a/extras/example_old.gif and /dev/null differ diff --git a/extras/podcheck.png b/extras/podcheck.png new file mode 100644 index 0000000..86836eb Binary files /dev/null and b/extras/podcheck.png differ diff --git a/dockcheck.sh b/podcheck.sh old mode 100755 new mode 100644 similarity index 55% rename from dockcheck.sh rename to podcheck.sh index ffb7a21..20b7087 --- a/dockcheck.sh +++ b/podcheck.sh @@ -1,43 +1,43 @@ #!/usr/bin/env bash -VERSION="v0.5.0" -### ChangeNotes: Rewritten notify logic - all templates adjusted, transfer your current settings to a new template! See README. -Github="https://github.com/mag37/dockcheck" -RawUrl="https://raw.githubusercontent.com/mag37/dockcheck/main/dockcheck.sh" +VERSION="v0.5.6" +# ChangeNotes: Directly checking for systemd units matching container names. +Github="https://github.com/sudo-kraken/podcheck" +RawUrl="https://raw.githubusercontent.com/sudo-kraken/podcheck/main/podcheck.sh" -### Variables for self updating +# Variables for self-updating ScriptArgs=( "$@" ) ScriptPath="$(readlink -f "$0")" ScriptWorkDir="$(dirname "$ScriptPath")" -### Check if there's a new release of the script: -LatestRelease="$(curl -s -r 0-50 $RawUrl | sed -n "/VERSION/s/VERSION=//p" | tr -d '"')" -LatestChanges="$(curl -s -r 0-200 $RawUrl | sed -n "/ChangeNotes/s/### ChangeNotes: //p")" +# Check if there's a new release of the script +LatestRelease="$(curl -s -r 0-100 $RawUrl | sed -n "/VERSION/s/VERSION=//p" | tr -d '"')" +LatestChanges="$(curl -s -r 0-200 $RawUrl | sed -n "/ChangeNotes/s/# ChangeNotes: //p")" -### Help Function: +# Help Function Help() { - echo "Syntax: dockcheck.sh [OPTION] [part of name to filter]" - echo "Example: dockcheck.sh -y -d 10 -e nextcloud,heimdall" + echo "Syntax: podcheck.sh [OPTION] [part of name to filter]" + echo "Example: podcheck.sh -y -d 10 -e nextcloud,heimdall" echo echo "Options:" echo "-a|y Automatic updates, without interaction." - echo "-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age. 2xSlower." + echo "-d N Only update to new images that are N+ days old. Lists too recent with +prefix and age." echo "-e X Exclude containers, separated by comma." - echo "-f Force stack restart after update. Caution: restarts once for every updated container within stack." + echo "-f Force pod restart after update." echo "-h Print this Help." echo "-i Inform - send a preconfigured notification." echo "-l Only update if label is set. See readme." echo "-m Monochrome mode, no printf color codes." - echo "-n No updates, only checking availability." - echo "-p Auto-Prune dangling images after update." - echo "-r Allow updating images for docker run, wont update the container." - echo "-s Include stopped containers in the check. (Logic: docker ps -a)." + 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 "-v Prints current version." echo echo "Project source: $Github" } -### Colors: +# Colors c_red="\033[0;31m" c_green="\033[0;32m" c_yellow="\033[0;33m" @@ -54,7 +54,7 @@ while getopts "aynpfrhlisvme:d:t:" options; do r) DRunUp="yes" ;; p) AutoPrune="yes" ;; l) OnlyLabel=true ;; - f) ForceRestartStacks=true ;; + f) ForceRestartPods=true ;; i) [ -s "$ScriptWorkDir"/notify.sh ] && { source "$ScriptWorkDir"/notify.sh ; Notify="yes" ; } ;; e) Exclude=${OPTARG} ;; m) declare c_{red,green,yellow,blue,teal,reset}="" ;; @@ -68,18 +68,19 @@ while getopts "aynpfrhlisvme:d:t:" options; do done shift "$((OPTIND-1))" +# Self-update functions self_update_curl() { cp "$ScriptPath" "$ScriptPath".bak - if [[ $(builtin type -P curl) ]]; then + if [[ $(command -v curl) ]]; then curl -L $RawUrl > "$ScriptPath" ; chmod +x "$ScriptPath" printf "\n%s\n" "--- starting over with the updated version ---" - exec "$ScriptPath" "${ScriptArgs[@]}" # run the new script with old arguments - exit 1 # exit the old instance - elif [[ $(builtin type -P wget) ]]; then + exec "$ScriptPath" "${ScriptArgs[@]}" # Run the new script with old arguments + exit 1 # Exit the old instance + elif [[ $(command -v wget) ]]; then wget $RawUrl -O "$ScriptPath" ; chmod +x "$ScriptPath" printf "\n%s\n" "--- starting over with the updated version ---" - exec "$ScriptPath" "${ScriptArgs[@]}" # run the new script with old arguments - exit 1 # exit the old instance + exec "$ScriptPath" "${ScriptArgs[@]}" # Run the new script with old arguments + exit 1 # Exit the old instance else printf "curl/wget not available - download the update manually: %s \n" "$Github" fi @@ -87,20 +88,20 @@ self_update_curl() { self_update() { cd "$ScriptWorkDir" || { printf "Path error, skipping update.\n" ; return ; } - if [[ $(builtin type -P git) ]] && [[ "$(git ls-remote --get-url 2>/dev/null)" =~ .*"mag37/dockcheck".* ]] ; then + if [[ $(command -v git) ]] && [[ "$(git ls-remote --get-url 2>/dev/null)" =~ .*"yourusername/podcheck".* ]] ; then printf "\n%s\n" "Pulling the latest version." git pull --force || { printf "Git error, manually pull/clone.\n" ; return ; } printf "\n%s\n" "--- starting over with the updated version ---" cd - || { printf "Path error.\n" ; return ; } - exec "$ScriptPath" "${ScriptArgs[@]}" # run the new script with old arguments - exit 1 # exit the old instance + exec "$ScriptPath" "${ScriptArgs[@]}" # Run the new script with old arguments + exit 1 # Exit the old instance else cd - || { printf "Path error.\n" ; return ; } self_update_curl fi } -### Choose from list -function: +# Choose from list function choosecontainers() { while [[ -z "$ChoiceClean" ]]; do read -r -p "Enter number(s) separated by comma, [a] for all - [q] to quit: " Choice @@ -112,7 +113,7 @@ choosecontainers() { else ChoiceClean=${Choice//[,.:;]/ } for CC in $ChoiceClean ; do - if [[ "$CC" -lt 1 || "$CC" -gt $UpdCount ]] ; then # reset choice if out of bounds + if [[ "$CC" -lt 1 || "$CC" -gt $UpdCount ]] ; then echo "Number not in list: $CC" ; unset ChoiceClean ; break 1 else SelectedUpdates+=( "${GotUpdates[$CC-1]}" ) @@ -139,27 +140,16 @@ progress_bar() { QueCurrent="$1" QueTotal="$2" ((Percent=100*QueCurrent/QueTotal)) - ((Complete=50*Percent/100)) # change first number for width (50) - ((Left=50-Complete)) # change first number for width (50) + ((Complete=50*Percent/100)) # Change first number for width (50) + ((Left=50-Complete)) # Change first number for width (50) BarComplete=$(printf "%${Complete}s" | tr " " "#") BarLeft=$(printf "%${Left}s" | tr " " "-") [[ "$QueTotal" == "$QueCurrent" ]] || printf "\r[%s%s] %s/%s " "$BarComplete" "$BarLeft" "$QueCurrent" "$QueTotal" [[ "$QueTotal" == "$QueCurrent" ]] && printf "\r[%b%s%b] %s/%s \n" "$c_teal" "$BarComplete" "$c_reset" "$QueCurrent" "$QueTotal" } -### Function to add user-provided urls to releasenotes -releasenotes() { - for update in ${GotUpdates[@]}; do - found=false - while read -r container url; do - [[ $update == $container ]] && Updates+=("$update -> $url") && found=true - done < "$ScriptWorkDir"/urls.list - [[ $found == false ]] && Updates+=("$update -> url missing") || continue - done -} - -### Version check & initiate self update -if [[ "$VERSION" != "$LatestRelease" ]] ; then +# Version check & initiate self update +if [[ "$VERSION" != "$LatestRelease" ]] && [[ -n "$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 [[ -z "$AutoUp" ]] ; then read -r -p "Would you like to update? y/[n]: " SelfUpdate @@ -167,26 +157,26 @@ if [[ "$VERSION" != "$LatestRelease" ]] ; then fi fi -### Set $1 to a variable for name filtering later. +# Set $1 to a variable for name filtering later SearchName="$1" -### Create array of excludes +# Create array of excludes IFS=',' read -r -a Excludes <<< "$Exclude" ; unset IFS -### Check if required binary exists in PATH or directory: -if [[ $(builtin type -P "regctl") ]]; then regbin="regctl" ; +# Check if required binary exists in PATH or directory +if [[ $(command -v "regctl") ]]; then regbin="regctl" ; elif [[ -f "$ScriptWorkDir/regctl" ]]; then regbin="$ScriptWorkDir/regctl" ; else read -r -p "Required dependency 'regctl' missing, do you want it downloaded? y/[n] " GetDep if [[ "$GetDep" =~ [yY] ]] ; then - ### Check arch: + # Check architecture case "$(uname --machine)" in x86_64|amd64) architecture="amd64" ;; arm64|aarch64) architecture="arm64";; *) echo "Architecture not supported, exiting." ; exit 1;; esac RegUrl="https://github.com/regclient/regclient/releases/latest/download/regctl-linux-$architecture" - if [[ $(builtin type -P curl) ]]; then curl -L $RegUrl > "$ScriptWorkDir/regctl" ; chmod +x "$ScriptWorkDir/regctl" ; regbin="$ScriptWorkDir/regctl" ; - elif [[ $(builtin type -P wget) ]]; then wget $RegUrl -O "$ScriptWorkDir/regctl" ; chmod +x "$ScriptWorkDir/regctl" ; regbin="$ScriptWorkDir/regctl" ; + if [[ $(command -v curl) ]]; then curl -L $RegUrl > "$ScriptWorkDir/regctl" ; chmod +x "$ScriptWorkDir/regctl" ; regbin="$ScriptWorkDir/regctl" ; + elif [[ $(command -v wget) ]]; then wget $RegUrl -O "$ScriptWorkDir/regctl" ; chmod +x "$ScriptWorkDir/regctl" ; regbin="$ScriptWorkDir/regctl" ; else printf "%s\n" "curl/wget not available - get regctl manually from the repo link, quitting." fi @@ -195,21 +185,20 @@ else exit 1 fi fi -### final check if binary is correct +# Final check if binary is correct $regbin version &> /dev/null || { printf "%s\n" "regctl is not working - try to remove it and re-download it, exiting."; exit 1; } -### Check docker compose binary: -if docker compose version &> /dev/null ; then DockerBin="docker compose" ; -elif docker-compose -v &> /dev/null; then DockerBin="docker-compose" ; -elif docker -v &> /dev/null; then - printf "%s\n" "No docker compose binary available, using plain docker (Not recommended!)" - printf "%s\n" "'docker run' will ONLY update images, not the container itself." +# Check podman compose binary +if podman compose version &> /dev/null ; then PodmanComposeBin="podman compose" ; +elif command -v podman-compose &> /dev/null; then PodmanComposeBin="podman-compose" ; +elif podman version &> /dev/null; then + printf "%s\n" "No podman-compose binary available, using plain podman" else - printf "%s\n" "No docker binaries available, exiting." + printf "%s\n" "No podman binaries available, exiting." exit 1 fi -### Numbered List -function: +# Numbered List function options() { num=1 for i in "${GotUpdates[@]}"; do @@ -218,7 +207,7 @@ for i in "${GotUpdates[@]}"; do done } -### Listing typed exclusions: +# Listing typed exclusions if [[ -n ${Excludes[*]} ]] ; then printf "\n%bExcluding these names:%b\n" "$c_blue" "$c_reset" printf "%s\n" "${Excludes[@]}" @@ -226,11 +215,11 @@ if [[ -n ${Excludes[*]} ]] ; then fi # Variables for progress_bar function -DocCount=$(docker ps $Stopped --filter "name=$SearchName" --format '{{.Names}}' | wc -l) +DocCount=$(podman ps $Stopped --filter "name=$SearchName" --format '{{.Names}}' | wc -l) RegCheckQue=0 -### Testing and setting timeout binary -t_out=$(type -P "timeout") +# Testing and setting timeout binary +t_out=$(command -v "timeout") if [[ $t_out ]]; then t_out=$(realpath $t_out 2>/dev/null || readlink -f $t_out) if [[ $t_out =~ "busybox" ]]; then @@ -240,17 +229,17 @@ if [[ $t_out ]]; then else t_out="" fi -### Check the image-hash of every running container VS the registry -for i in $(docker ps $Stopped --filter "name=$SearchName" --format '{{.Names}}') ; do +# Check the image-hash of every running container VS the registry +for i in $(podman ps $Stopped --filter "name=$SearchName" --format '{{.Names}}') ; do ((RegCheckQue+=1)) progress_bar "$RegCheckQue" "$DocCount" - ### Looping every item over the list of excluded names and skipping: + # Looping every item over the list of excluded names and skipping for e in "${Excludes[@]}" ; do [[ "$i" == "$e" ]] && continue 2 ; done - RepoUrl=$(docker inspect "$i" --format='{{.Config.Image}}') - LocalHash=$(docker image inspect "$RepoUrl" --format '{{.RepoDigests}}') - # Checking for errors while setting the variable: + RepoUrl=$(podman inspect "$i" --format='{{.ImageName}}') + LocalHash=$(podman image inspect "$RepoUrl" --format '{{.Digest}}') + # 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 NoUpdates+=("$i") else if [[ -n "$DaysOld" ]] && ! datecheck ; then @@ -260,27 +249,27 @@ for i in $(docker ps $Stopped --filter "name=$SearchName" --format '{{.Names}}') fi fi else - # Here the RegHash is the result of an error code. + # Here the RegHash is the result of an error code GotErrors+=("$i - ${RegHash}") fi done -### Sort arrays alphabetically +# Sort arrays alphabetically IFS=$'\n' NoUpdates=($(sort <<<"${NoUpdates[*]}")) GotUpdates=($(sort <<<"${GotUpdates[*]}")) unset IFS -### Define how many updates are available +# Define how many updates are available UpdCount="${#GotUpdates[@]}" -### List what containers got updates or not +# List what containers got updates or not if [[ -n ${NoUpdates[*]} ]] ; then printf "\n%bContainers on latest version:%b\n" "$c_green" "$c_reset" printf "%s\n" "${NoUpdates[@]}" fi if [[ -n ${GotErrors[*]} ]] ; then - printf "\n%bContainers with errors, wont get updated:%b\n" "$c_red" "$c_reset" + printf "\n%bContainers with errors; won't get updated:%b\n" "$c_red" "$c_reset" printf "%s\n" "${GotErrors[@]}" printf "%binfo:%b 'unauthorized' often means not found in a public registry.\n" "$c_blue" "$c_reset" fi @@ -290,11 +279,11 @@ if [[ -n ${GotUpdates[*]} ]] ; then [[ -n "$Notify" ]] && { [[ $(type -t send_notification) == function ]] && send_notification "${GotUpdates[@]}" || printf "Could not source notification function.\n" ; } fi -### Optionally get updates if there's any +# Optionally get updates if there's any if [ -n "$GotUpdates" ] ; then if [ -z "$AutoUp" ] ; then - printf "\n%bChoose what containers to update.%b\n" "$c_teal" "$c_reset" - choosecontainers + printf "\n%bChoose what containers to update.%b\n" "$c_teal" "$c_reset" + choosecontainers else SelectedUpdates=( "${GotUpdates[@]}" ) fi @@ -305,47 +294,75 @@ if [ -n "$GotUpdates" ] ; then do ((CurrentQue+=1)) unset CompleteConfs - ContPath=$(docker inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}') - ContConfigFile=$(docker inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.project.config_files" }}') - ContName=$(docker inspect "$i" --format '{{ index .Config.Labels "com.docker.compose.service" }}') - ContEnv=$(docker inspect "$i" --format '{{index .Config.Labels "com.docker.compose.project.environment_file" }}') - ContImage=$(docker inspect "$i" --format='{{.Config.Image}}') - ContUpdateLabel=$(docker inspect "$i" --format '{{ index .Config.Labels "mag37.dockcheck.update" }}') - ContRestartStack=$(docker inspect "$i" --format '{{ index .Config.Labels "mag37.dockcheck.restart-stack" }}') - ### Checking if compose-values are empty - hence started with docker run: + # Extract labels and metadata + ContLabels=$(podman inspect "$i" --format '{{json .Config.Labels}}') + ContPath=$(echo "$ContLabels" | jq -r '."com.docker.compose.project.working_dir"') + [ "$ContPath" == "null" ] && ContPath="" + + ContConfigFile=$(echo "$ContLabels" | jq -r '."com.docker.compose.project.config_files"') + [ "$ContConfigFile" == "null" ] && ContConfigFile="" + + ContName=$(echo "$ContLabels" | jq -r '."com.docker.compose.service"') + [ "$ContName" == "null" ] && ContName="" + + ContEnv=$(echo "$ContLabels" | jq -r '."com.docker.compose.project.environment_file"') + [ "$ContEnv" == "null" ] && ContEnv="" + + ContImage=$(podman inspect "$i" --format='{{.ImageName}}') + + ContUpdateLabel=$(echo "$ContLabels" | jq -r '."sudo-kraken.podcheck.update"') + [ "$ContUpdateLabel" == "null" ] && ContUpdateLabel="" + + ContRestartStack=$(echo "$ContLabels" | jq -r '."sudo-kraken.podcheck.restart-stack"') + [ "$ContRestartStack" == "null" ] && ContRestartStack="" + + # Checking if compose-values are empty - possibly started with podman run or managed by Quadlet if [ -z "$ContPath" ] ; then - if [ "$DRunUp" == "yes" ] ; then - docker pull "$ContImage" - printf "%s\n" "$i got a new image downloaded, rebuild manually with preferred 'docker run'-parameters" + # Check if a systemd unit exists with the same name as the container + if systemctl --user status "$i.service" &> /dev/null; then + echo "Detected Quadlet-managed container: $i (unit: $i.service)" + podman pull "$ContImage" + systemctl --user restart "$i.service" + echo "Quadlet container $i updated and restarted." + elif [ "$(id -u)" -eq 0 ] && systemctl status "$i.service" &> /dev/null; then + echo "Detected Quadlet-managed container: $i (unit: $i.service)" + podman pull "$ContImage" + systemctl restart "$i.service" + echo "Quadlet container $i updated and restarted." else - printf "\n%b%s%b has no compose labels, probably started with docker run - %bskipping%b\n\n" "$c_yellow" "$i" "$c_reset" "$c_yellow" "$c_reset" + if [ "$DRunUp" == "yes" ] ; then + podman pull "$ContImage" + printf "%s\n" "$i got a new image downloaded; rebuild manually with preferred 'podman run' parameters" + else + printf "\n%b%s%b has no compose labels or associated systemd unit; %bskipping%b\n\n" "$c_yellow" "$i" "$c_reset" "$c_yellow" "$c_reset" + fi fi continue fi - ### cd to the compose-file directory to account for people who use relative volumes, eg - ${PWD}/data:data + # cd to the compose-file directory to account for people who use relative volumes cd "$ContPath" || { echo "Path error - skipping $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 CompleteConfs=$(for conf in ${ContConfigFile//,/ } ; do printf -- "-f %s/%s " "$ContPath" "$conf"; done) fi printf "\n%bNow updating (%s/%s): %b%s%b\n" "$c_teal" "$CurrentQue" "$NumberofUpdates" "$c_blue" "$i" "$c_reset" - ### Checking if Label Only -option is set, and if container got the label - [[ "$OnlyLabel" == true ]] && { [[ "$ContUpdateLabel" != true ]] && { echo "No update label, skipping." ; continue ; } } - docker pull "$ContImage" - ### Check if the container got an environment file set and reformat it + # Checking if Label Only option is set, and if container got the label + [[ "$OnlyLabel" == true ]] && { [[ "$ContUpdateLabel" != "true" ]] && { echo "No update label, skipping." ; continue ; } } + podman pull "$ContImage" + # Check if the container got an environment file set and reformat it if [ -n "$ContEnv" ]; then ContEnvs=$(for env in ${ContEnv//,/ } ; do printf -- "--env-file %s " "$env"; done) ; fi - ### Check if the whole stack should be restarted - if [[ "$ContRestartStack" == true ]] || [[ "$ForceRestartStacks" == true ]] ; then - $DockerBin ${CompleteConfs} stop ; $DockerBin ${CompleteConfs} ${ContEnvs} up -d + # Check if the whole pod should be restarted + if [[ "$ContRestartStack" == "true" ]] || [[ "$ForceRestartPods" == true ]] ; then + $PodmanComposeBin ${CompleteConfs} down ; $PodmanComposeBin ${CompleteConfs} ${ContEnvs} up -d else - $DockerBin ${CompleteConfs} ${ContEnvs} up -d ${ContName} + $PodmanComposeBin ${CompleteConfs} ${ContEnvs} up -d ${ContName} fi done printf "\n%bAll done!%b\n" "$c_green" "$c_reset" if [[ -z "$AutoPrune" ]] && [[ -z "$AutoUp" ]]; then read -r -p "Would you like to prune dangling images? y/[n]: " AutoPrune ; fi - [[ "$AutoPrune" =~ [yY] ]] && docker image prune -f + [[ "$AutoPrune" =~ [yY] ]] && podman image prune -f else printf "\nNo updates installed, exiting.\n" fi