Sync all scripts from website downloads — 352 scripts total

Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
This commit is contained in:
2026-05-25 03:31:08 +02:00
parent dbd6bf0324
commit a1a17e81a1
332 changed files with 174509 additions and 1106 deletions
+410
View File
@@ -0,0 +1,410 @@
#!/usr/bin/env bash
#########################################################################################
#### container-update-checker.sh — Check Docker/Podman containers for image updates ####
#### Compares local image digests against remote registry digests ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version 1.00 ####
#### ####
#### Usage: ####
#### ./container-update-checker.sh ####
#### ./container-update-checker.sh --docker --filter nginx ####
#### ./container-update-checker.sh --json --quiet ####
#### ####
#### See --help for all options. ####
#########################################################################################
set -euo pipefail
# ── Defaults ──────────────────────────────────────────────────────────
RUNTIME="${CONTAINER_RUNTIME:-auto}"
TIMEOUT="${REGISTRY_TIMEOUT:-10}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"
JSON_OUTPUT="false"
QUIET="false"
FILTER=""
LABEL=""
TEXTFILE_DIR="/var/lib/node_exporter"
PROM_FILE=""
# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
COUNT_CURRENT=0
COUNT_UPDATE=0
COUNT_ERROR=0
COUNT_TOTAL=0
JSON_ITEMS=""
PROM_LINES=""
# ── Colors ────────────────────────────────────────────────────────────
setup_colors() {
if [[ "$COLOR" == "never" ]]; then
RED="" GREEN="" YELLOW="" BOLD="" DIM="" RESET=""
return
fi
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
BOLD='\033[1m' DIM='\033[2m' RESET='\033[0m'
else
RED="" GREEN="" YELLOW="" BOLD="" DIM="" RESET=""
fi
}
# ── Logging ───────────────────────────────────────────────────────────
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*" >&2; fi; }
# ── Runtime Detection ─────────────────────────────────────────────────
detect_runtime() {
if [[ "$RUNTIME" == "docker" || "$RUNTIME" == "podman" ]]; then
if ! command -v "$RUNTIME" &>/dev/null; then
err "${RUNTIME^} not found"; exit 2
fi
return
fi
if command -v docker &>/dev/null && docker info &>/dev/null; then
RUNTIME="docker"
elif command -v podman &>/dev/null; then
RUNTIME="podman"
else
err "Neither Docker nor Podman found"; exit 2
fi
verbose "Auto-detected runtime: ${RUNTIME}"
}
# ── Auth Helper ───────────────────────────────────────────────────────
get_auth_header() {
local registry="$1" config_file=""
if [[ "$RUNTIME" == "podman" ]]; then
config_file="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/containers/auth.json"
[[ -f "$config_file" ]] || config_file="${HOME}/.config/containers/auth.json"
fi
[[ -f "${config_file:-}" ]] || config_file="${HOME}/.docker/config.json"
[[ -f "$config_file" ]] || return 0
local auth
auth=$(grep -A1 "\"${registry}\"" "$config_file" 2>/dev/null \
| grep '"auth"' | head -1 | sed 's/.*"auth"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') || true
if [[ -n "$auth" ]]; then
echo "Authorization: Basic ${auth}"
fi
}
# ── Parse Image Reference ────────────────────────────────────────────
parse_image_ref() {
local image="$1" registry="" path="" tag=""
local without_tag="${image%%@*}"
if [[ "$without_tag" == *:* && "${without_tag##*:}" != */* ]]; then
tag="${without_tag##*:}"
without_tag="${without_tag%:*}"
fi
[[ -z "$tag" ]] && tag="latest"
if [[ "$without_tag" == *"."*"/"* ]] || [[ "$without_tag" == *":"*"/"* ]] || [[ "$without_tag" == "localhost/"* ]]; then
registry="${without_tag%%/*}"
path="${without_tag#*/}"
else
registry="docker.io"
[[ "$without_tag" == *"/"* ]] && path="$without_tag" || path="library/${without_tag}"
fi
echo "${registry}" "${path}" "${tag}"
}
# ── Get Local Digest ─────────────────────────────────────────────────
get_local_digest() {
local image="$1" digest
digest=$($RUNTIME image inspect "$image" --format '{{index .RepoDigests 0}}' 2>/dev/null) || true
if [[ -n "$digest" && "$digest" == *"@"* ]]; then
echo "${digest##*@}"; return
fi
digest=$($RUNTIME image inspect "$image" --format '{{.Id}}' 2>/dev/null) || true
echo "${digest:-}"
}
# ── Extract JSON Value (pure bash, no python/jq) ─────────────────────
json_value() {
local key="$1"
sed -n "s/.*\"${key}\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -1
}
# ── Get Remote Digest via Skopeo ──────────────────────────────────────
get_remote_digest_skopeo() {
local registry="$1" path="$2" tag="$3"
local digest
digest=$(timeout "$TIMEOUT" skopeo inspect --no-tags "docker://${registry}/${path}:${tag}" 2>/dev/null \
| json_value "Digest") || true
echo "${digest:-}"
}
# ── Get Remote Digest via Curl ────────────────────────────────────────
get_remote_digest_curl() {
local registry="$1" path="$2" tag="$3"
local token="" digest=""
if [[ "$registry" == "docker.io" || "$registry" == "registry-1.docker.io" ]]; then
token=$(curl -sf --max-time "$TIMEOUT" \
"https://auth.docker.io/token?service=registry.docker.io&scope=repository:${path}:pull" \
| json_value "token") || true
[[ -z "$token" ]] && return
digest=$(curl -sf --max-time "$TIMEOUT" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
-H "Accept: application/vnd.oci.image.index.v1+json" \
-H "Authorization: Bearer ${token}" \
"https://registry-1.docker.io/v2/${path}/manifests/${tag}" \
-o /dev/null -D - 2>/dev/null \
| grep -i "docker-content-digest" | tr -d '\r' | awk '{print $2}') || true
else
local auth_hdr auth_args=()
auth_hdr=$(get_auth_header "$registry")
[[ -n "$auth_hdr" ]] && auth_args=(-H "$auth_hdr")
digest=$(curl -sf --max-time "$TIMEOUT" \
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
-H "Accept: application/vnd.oci.image.index.v1+json" \
"${auth_args[@]+"${auth_args[@]}"}" \
"https://${registry}/v2/${path}/manifests/${tag}" \
-o /dev/null -D - 2>/dev/null \
| grep -i "docker-content-digest" | tr -d '\r' | awk '{print $2}') || true
fi
echo "${digest:-}"
}
# ── Get Remote Digest (skopeo then curl fallback) ─────────────────────
get_remote_digest() {
local registry="$1" path="$2" tag="$3" digest=""
if command -v skopeo &>/dev/null; then
verbose "Trying skopeo for ${registry}/${path}:${tag}"
digest=$(get_remote_digest_skopeo "$registry" "$path" "$tag")
fi
if [[ -z "$digest" ]]; then
verbose "Trying curl fallback for ${registry}/${path}:${tag}"
digest=$(get_remote_digest_curl "$registry" "$path" "$tag")
fi
echo "${digest:-}"
}
# ── Check Single Container ────────────────────────────────────────────
check_container() {
local name="$1" image="$2"
local status="" local_digest="" remote_digest="" registry path tag
read -r registry path tag <<< "$(parse_image_ref "$image")"
verbose "Container=${name} image=${image} registry=${registry} path=${path} tag=${tag}"
local_digest=$(get_local_digest "$image")
verbose "Local digest: ${local_digest:-none}"
if [[ -z "$local_digest" ]]; then
status="error"
else
remote_digest=$(get_remote_digest "$registry" "$path" "$tag")
verbose "Remote digest: ${remote_digest:-none}"
if [[ -z "$remote_digest" ]]; then
status="error"
elif [[ "$local_digest" == "$remote_digest" ]]; then
status="current"
else
status="update"
fi
fi
COUNT_TOTAL=$((COUNT_TOTAL + 1))
case "$status" in
current) COUNT_CURRENT=$((COUNT_CURRENT + 1)) ;;
update) COUNT_UPDATE=$((COUNT_UPDATE + 1)) ;;
error) COUNT_ERROR=$((COUNT_ERROR + 1)) ;;
esac
if [[ -n "$PROM_FILE" ]]; then
local val=1; [[ "$status" == "update" ]] && val=0
PROM_LINES+="container_image_up_to_date{name=\"${name}\",image=\"${image}\"} ${val}"$'\n'
fi
[[ "$QUIET" == "true" && "$status" != "update" ]] && return
if [[ "$JSON_OUTPUT" == "true" ]]; then
local item
item=$(printf '{"container":"%s","image":"%s","status":"%s"}' "$name" "$image" "$status")
[[ -n "$JSON_ITEMS" ]] && JSON_ITEMS="${JSON_ITEMS},${item}" || JSON_ITEMS="${item}"
else
local color symbol
case "$status" in
current) color="$GREEN"; symbol="up-to-date" ;;
update) color="$YELLOW"; symbol="update available" ;;
error) color="$RED"; symbol="check failed" ;;
*) color=""; symbol="?" ;;
esac
printf " %-30s %-40s %b%s%b\n" "$name" "$image" "$color" "$symbol" "$RESET"
fi
}
# ── List Containers ───────────────────────────────────────────────────
list_containers() {
local filter_args=()
[[ -n "$LABEL" ]] && filter_args+=(--filter "label=${LABEL}")
$RUNTIME ps --format '{{.Names}}\t{{.Image}}' "${filter_args[@]}" 2>/dev/null
}
# ── Write Prometheus Metrics ──────────────────────────────────────────
write_prom_metrics() {
local file="$1"
local output_dir
output_dir="$(dirname "$file")"
mkdir -p "$output_dir"
local tmp
tmp=$(mktemp "${output_dir}/.container_updates.XXXXXX")
{
echo "# HELP container_image_up_to_date Whether the container image is up to date (1=yes, 0=no)"
echo "# TYPE container_image_up_to_date gauge"
printf '%s' "$PROM_LINES"
echo "# HELP container_update_check_timestamp Unix timestamp of last update check"
echo "# TYPE container_update_check_timestamp gauge"
echo "container_update_check_timestamp $(date +%s)"
echo "# HELP container_update_check_total Total containers checked"
echo "# TYPE container_update_check_total gauge"
echo "container_update_check_total ${COUNT_TOTAL}"
echo "# HELP container_update_available_total Containers with updates available"
echo "# TYPE container_update_available_total gauge"
echo "container_update_available_total ${COUNT_UPDATE}"
} > "$tmp"
chmod 644 "$tmp"
mv -f "$tmp" "$file"
verbose "Metrics written to ${file}"
}
# ══════════════════════════════════════════════════════════════════════
# USAGE
# ══════════════════════════════════════════════════════════════════════
usage() {
cat <<EOF
${SCRIPT_NAME} — Check Docker/Podman containers for available image updates
USAGE:
${SCRIPT_NAME} [OPTIONS]
OPTIONS:
--docker Force Docker as runtime
--podman Force Podman as runtime
--filter NAME Only check containers matching name pattern
--label KEY=VALUE Only check containers with matching label
--json Output results in JSON format
--quiet Only show containers with available updates
--no-color Disable colored output
--textfile Write metrics to node_exporter textfile collector
-o, --output PATH Write metrics to custom file path
--timeout SECONDS Registry check timeout (default: ${TIMEOUT})
--verbose Enable debug output
--help Show this help
ENVIRONMENT VARIABLES:
CONTAINER_RUNTIME Force runtime: docker or podman (default: auto)
REGISTRY_TIMEOUT Registry check timeout in seconds (default: 10)
COLOR Color mode: auto, always, never (default: auto)
VERBOSE Enable debug output (true/false)
EXAMPLES:
# Check all running containers
./${SCRIPT_NAME}
# Force Docker, filter by name
./${SCRIPT_NAME} --docker --filter nginx
# JSON output, quiet mode
./${SCRIPT_NAME} --json --quiet
# Write Prometheus metrics to textfile collector
./${SCRIPT_NAME} --textfile
# Write Prometheus metrics to custom path
./${SCRIPT_NAME} -o /tmp/container_updates.prom
EOF
}
# ══════════════════════════════════════════════════════════════════════
# ARGUMENT PARSING
# ══════════════════════════════════════════════════════════════════════
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--docker) RUNTIME="docker"; shift ;;
--podman) RUNTIME="podman"; shift ;;
--filter) FILTER="$2"; shift 2 ;;
--label) LABEL="$2"; shift 2 ;;
--json) JSON_OUTPUT="true"; shift ;;
--quiet) QUIET="true"; shift ;;
--no-color) COLOR="never"; shift ;;
--textfile) PROM_FILE="$TEXTFILE_DIR/container_updates.prom"; shift ;;
-o|--output) PROM_FILE="$2"; shift 2 ;;
--timeout) TIMEOUT="$2"; shift 2 ;;
--verbose) VERBOSE="true"; shift ;;
--help|-h) setup_colors; usage; exit 0 ;;
-*)
err "Unknown option: $1"
echo "Run ${SCRIPT_NAME} --help for usage" >&2
exit 2 ;;
*)
err "Unexpected argument: $1"
echo "Run ${SCRIPT_NAME} --help for usage" >&2
exit 2 ;;
esac
done
}
# ══════════════════════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════════════════════
main() {
parse_args "$@"
setup_colors
detect_runtime
local containers=()
while IFS=$'\t' read -r name image; do
[[ -z "$name" ]] && continue
[[ -n "$FILTER" && "$name" != *"${FILTER}"* ]] && continue
containers+=("${name} ${image}")
done < <(list_containers)
if [[ ${#containers[@]} -eq 0 ]]; then
if [[ "$JSON_OUTPUT" == "true" ]]; then
echo '{"results":[],"summary":{"total":0,"current":0,"update_available":0,"errors":0}}'
else
warn "No running containers found"
fi
exit 0
fi
verbose "Found ${#containers[@]} containers to check"
if [[ "$JSON_OUTPUT" != "true" ]]; then
echo ""
echo -e "${BOLD}Container Update Checker${RESET}"
echo -e "${DIM}Runtime: ${RUNTIME} | Timeout: ${TIMEOUT}s${RESET}"
echo ""
printf " ${BOLD}%-30s %-40s %s${RESET}\n" "CONTAINER" "IMAGE" "STATUS"
printf " %s\n" "$(printf '%.0s─' {1..82})"
fi
for entry in "${containers[@]}"; do
check_container "${entry%% *}" "${entry#* }"
done
if [[ "$JSON_OUTPUT" == "true" ]]; then
printf '{"results":[%s],"summary":{"total":%d,"current":%d,"update_available":%d,"errors":%d}}\n' \
"$JSON_ITEMS" "$COUNT_TOTAL" "$COUNT_CURRENT" "$COUNT_UPDATE" "$COUNT_ERROR"
else
echo ""
echo -e " ${BOLD}Summary${RESET}"
printf " %-20s %d\n" "Total checked:" "$COUNT_TOTAL"
printf " %-20s %b%d%b\n" "Up-to-date:" "$GREEN" "$COUNT_CURRENT" "$RESET"
printf " %-20s %b%d%b\n" "Update available:" "$YELLOW" "$COUNT_UPDATE" "$RESET"
printf " %-20s %b%d%b\n" "Errors:" "$RED" "$COUNT_ERROR" "$RESET"
echo ""
fi
[[ -n "$PROM_FILE" ]] && write_prom_metrics "$PROM_FILE"
if [[ "$COUNT_ERROR" -gt 0 ]]; then exit 2
elif [[ "$COUNT_UPDATE" -gt 0 ]]; then exit 1
fi
exit 0
}
main "$@"