Add extra/etc/check-containers
This commit is contained in:
parent
85d34874c5
commit
86daeb2a66
890
extra/etc/check-containers
Normal file
890
extra/etc/check-containers
Normal file
@ -0,0 +1,890 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# check-containers
|
||||||
|
#
|
||||||
|
# statuses:
|
||||||
|
# online = http response exactly 200
|
||||||
|
# error = reachable but non-200 response
|
||||||
|
# offline = dns/tls/connectivity/timeout failure
|
||||||
|
# skipped = unlabeled / wildcard / intentionally not checked
|
||||||
|
#
|
||||||
|
# exit codes:
|
||||||
|
# 0 = all checked domains online, or domains-only output
|
||||||
|
# 1 = at least one error
|
||||||
|
# 2 = at least one offline
|
||||||
|
# 3 = runtime/config/dependency error
|
||||||
|
|
||||||
|
set -u
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
if [ -n "${ZSH_VERSION:-}" ]; then
|
||||||
|
emulate -L sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS='
|
||||||
|
'
|
||||||
|
|
||||||
|
PROGRAM="check-containers"
|
||||||
|
VERSION="1.1.1"
|
||||||
|
|
||||||
|
CONFIG_FILE="${CHECK_CONTAINERS_CONFIG:-/etc/check-containers.conf}"
|
||||||
|
|
||||||
|
USE_COLOR="${CHECK_CONTAINERS_COLOR:-auto}"
|
||||||
|
DOMAINS_ONLY=0
|
||||||
|
LABEL_KEY="${CHECK_CONTAINERS_LABEL_KEY:-}"
|
||||||
|
AUTO_DISCOVER="${CHECK_CONTAINERS_AUTO_DISCOVER:-1}"
|
||||||
|
NO_HEADER=0
|
||||||
|
TIMEOUT="${CHECK_CONTAINERS_TIMEOUT:-8}"
|
||||||
|
FORCE_SCHEME="${CHECK_CONTAINERS_SCHEME:-}"
|
||||||
|
SHOW_ALL="${CHECK_CONTAINERS_SHOW_ALL:-0}"
|
||||||
|
MONITOR=0
|
||||||
|
REFRESH="${CHECK_CONTAINERS_REFRESH:-10}"
|
||||||
|
OUTPUT_FORMAT="${CHECK_CONTAINERS_OUTPUT:-table}"
|
||||||
|
STRICT_TLS="${CHECK_CONTAINERS_STRICT_TLS:-1}"
|
||||||
|
EDIT_CONFIG=0
|
||||||
|
INTERACTIVE=0
|
||||||
|
EXIT_MODE="${CHECK_CONTAINERS_EXIT_MODE:-default}"
|
||||||
|
OS_FAMILY="unknown"
|
||||||
|
OS_NAME="unknown"
|
||||||
|
|
||||||
|
RESET=''
|
||||||
|
BOLD=''
|
||||||
|
DIM=''
|
||||||
|
RED=''
|
||||||
|
GREEN=''
|
||||||
|
YELLOW=''
|
||||||
|
BLUE=''
|
||||||
|
MAGENTA=''
|
||||||
|
CYAN=''
|
||||||
|
GRAY=''
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'USAGE'
|
||||||
|
usage:
|
||||||
|
check-containers [options]
|
||||||
|
check-containers monitor [options]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-c, --color force color output
|
||||||
|
-n, --no-color disable color output
|
||||||
|
-d, --domains print domains only
|
||||||
|
--label key docker label key to use
|
||||||
|
--no-discover disable auto-detection of domain label key
|
||||||
|
--no-header do not print table header
|
||||||
|
--timeout seconds http timeout per domain (default: 8)
|
||||||
|
--http check http only
|
||||||
|
--https check https only
|
||||||
|
--all include containers without valid domain labels
|
||||||
|
--refresh seconds monitor refresh interval (default: 10)
|
||||||
|
--json output json
|
||||||
|
--table output table
|
||||||
|
--strict-tls verify tls certificates
|
||||||
|
--insecure ignore tls certificate validation
|
||||||
|
--config file use alternate config file
|
||||||
|
--edit-config edit config interactively with prompts
|
||||||
|
--interactive prompt for missing config values before run
|
||||||
|
--exit-default 0 all online, 1 any error, 2 any offline, 3 runtime failure
|
||||||
|
-h, --help show help
|
||||||
|
|
||||||
|
commands:
|
||||||
|
monitor live refreshing view
|
||||||
|
|
||||||
|
config precedence:
|
||||||
|
cli > environment > config file > built-in defaults
|
||||||
|
|
||||||
|
environment:
|
||||||
|
check_containers_config
|
||||||
|
check_containers_label_key
|
||||||
|
check_containers_auto_discover
|
||||||
|
check_containers_timeout
|
||||||
|
check_containers_scheme
|
||||||
|
check_containers_show_all
|
||||||
|
check_containers_refresh
|
||||||
|
check_containers_output
|
||||||
|
check_containers_strict_tls
|
||||||
|
check_containers_color
|
||||||
|
check_containers_exit_mode
|
||||||
|
|
||||||
|
examples:
|
||||||
|
check-containers
|
||||||
|
check-containers --label caddy
|
||||||
|
check-containers --domains
|
||||||
|
check-containers --json
|
||||||
|
check-containers monitor --refresh 5
|
||||||
|
check-containers --edit-config
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
printf 'error: %s\n' "$*" >&2
|
||||||
|
exit 3
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
printf 'warning: %s\n' "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# a dog wearing shoes is a top 5 out of all the phenomenons
|
||||||
|
|
||||||
|
need() {
|
||||||
|
command -v "$1" >/dev/null 2>&1 || die "missing required command: $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
trim() {
|
||||||
|
local value
|
||||||
|
value="${1:-}"
|
||||||
|
value="${value#"${value%%[![:space:]]*}"}"
|
||||||
|
value="${value%"${value##*[![:space:]]}"}"
|
||||||
|
printf '%s' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
lower() {
|
||||||
|
printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
load_config_file() {
|
||||||
|
[ -f "$CONFIG_FILE" ] || return 0
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$CONFIG_FILE"
|
||||||
|
LABEL_KEY="${CHECK_CONTAINERS_LABEL_KEY:-$LABEL_KEY}"
|
||||||
|
AUTO_DISCOVER="${CHECK_CONTAINERS_AUTO_DISCOVER:-$AUTO_DISCOVER}"
|
||||||
|
TIMEOUT="${CHECK_CONTAINERS_TIMEOUT:-$TIMEOUT}"
|
||||||
|
FORCE_SCHEME="${CHECK_CONTAINERS_SCHEME:-$FORCE_SCHEME}"
|
||||||
|
SHOW_ALL="${CHECK_CONTAINERS_SHOW_ALL:-$SHOW_ALL}"
|
||||||
|
REFRESH="${CHECK_CONTAINERS_REFRESH:-$REFRESH}"
|
||||||
|
OUTPUT_FORMAT="${CHECK_CONTAINERS_OUTPUT:-$OUTPUT_FORMAT}"
|
||||||
|
STRICT_TLS="${CHECK_CONTAINERS_STRICT_TLS:-$STRICT_TLS}"
|
||||||
|
USE_COLOR="${CHECK_CONTAINERS_COLOR:-$USE_COLOR}"
|
||||||
|
EXIT_MODE="${CHECK_CONTAINERS_EXIT_MODE:-$EXIT_MODE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_os() {
|
||||||
|
if [ -r /etc/os-release ]; then
|
||||||
|
OS_NAME="$(awk -F= '/^id=/{gsub(/"/,"",$2); print $2}' /etc/os-release 2>/dev/null | head -n1)"
|
||||||
|
[ -n "$OS_NAME" ] || OS_NAME="unknown"
|
||||||
|
case "$OS_NAME" in
|
||||||
|
alpine) OS_FAMILY="alpine" ;;
|
||||||
|
arch|endeavouros|manjaro) OS_FAMILY="arch" ;;
|
||||||
|
ubuntu) OS_FAMILY="ubuntu" ;;
|
||||||
|
debian|raspbian) OS_FAMILY="debian" ;;
|
||||||
|
*) OS_FAMILY="$OS_NAME" ;;
|
||||||
|
esac
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if command -v apk >/dev/null 2>&1; then
|
||||||
|
OS_FAMILY="alpine"
|
||||||
|
OS_NAME="alpine"
|
||||||
|
elif command -v pacman >/dev/null 2>&1; then
|
||||||
|
OS_FAMILY="arch"
|
||||||
|
OS_NAME="arch"
|
||||||
|
elif command -v apt-get >/dev/null 2>&1; then
|
||||||
|
OS_FAMILY="debian"
|
||||||
|
OS_NAME="debian"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_random_theme() {
|
||||||
|
local idx
|
||||||
|
idx=$(( ( $(date +%s) + $$ ) % 5 ))
|
||||||
|
|
||||||
|
case "$idx" in
|
||||||
|
0)
|
||||||
|
RED=$'\033[0;31m'
|
||||||
|
GREEN=$'\033[0;32m'
|
||||||
|
YELLOW=$'\033[1;33m'
|
||||||
|
BLUE=$'\033[0;34m'
|
||||||
|
MAGENTA=$'\033[0;35m'
|
||||||
|
CYAN=$'\033[0;36m'
|
||||||
|
GRAY=$'\033[0;90m'
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
RED=$'\033[1;31m'
|
||||||
|
GREEN=$'\033[1;32m'
|
||||||
|
YELLOW=$'\033[0;33m'
|
||||||
|
BLUE=$'\033[1;34m'
|
||||||
|
MAGENTA=$'\033[1;35m'
|
||||||
|
CYAN=$'\033[1;36m'
|
||||||
|
GRAY=$'\033[0;37m'
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
RED=$'\033[38;5;196m'
|
||||||
|
GREEN=$'\033[38;5;46m'
|
||||||
|
YELLOW=$'\033[38;5;226m'
|
||||||
|
BLUE=$'\033[38;5;39m'
|
||||||
|
MAGENTA=$'\033[38;5;201m'
|
||||||
|
CYAN=$'\033[38;5;51m'
|
||||||
|
GRAY=$'\033[38;5;245m'
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
RED=$'\033[38;5;160m'
|
||||||
|
GREEN=$'\033[38;5;34m'
|
||||||
|
YELLOW=$'\033[38;5;178m'
|
||||||
|
BLUE=$'\033[38;5;27m'
|
||||||
|
MAGENTA=$'\033[38;5;129m'
|
||||||
|
CYAN=$'\033[38;5;44m'
|
||||||
|
GRAY=$'\033[38;5;240m'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
RED=$'\033[0;31m'
|
||||||
|
GREEN=$'\033[38;5;70m'
|
||||||
|
YELLOW=$'\033[38;5;214m'
|
||||||
|
BLUE=$'\033[38;5;33m'
|
||||||
|
MAGENTA=$'\033[38;5;141m'
|
||||||
|
CYAN=$'\033[38;5;45m'
|
||||||
|
GRAY=$'\033[38;5;243m'
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_colors() {
|
||||||
|
local tty=0
|
||||||
|
[ -t 1 ] && tty=1
|
||||||
|
|
||||||
|
case "$USE_COLOR" in
|
||||||
|
1|yes|true|always|force) USE_COLOR="force" ;;
|
||||||
|
0|no|false|never) USE_COLOR="never" ;;
|
||||||
|
auto|*) USE_COLOR="auto" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$USE_COLOR" = "never" ]; then
|
||||||
|
RESET='' BOLD='' DIM=''
|
||||||
|
RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' GRAY=''
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$USE_COLOR" = "force" ] || [ "$tty" -eq 1 ]; then
|
||||||
|
RESET=$'\033[0m'
|
||||||
|
BOLD=$'\033[1m'
|
||||||
|
DIM=$'\033[2m'
|
||||||
|
pick_random_theme
|
||||||
|
else
|
||||||
|
RESET='' BOLD='' DIM=''
|
||||||
|
RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' GRAY=''
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_default() {
|
||||||
|
local prompt default answer
|
||||||
|
prompt="${1:-}"
|
||||||
|
default="${2:-}"
|
||||||
|
printf '%s [%s]: ' "$prompt" "$default" >&2
|
||||||
|
IFS= read -r answer || true
|
||||||
|
answer="$(trim "$answer")"
|
||||||
|
[ -n "$answer" ] || answer="$default"
|
||||||
|
printf '%s' "$answer"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_config_file() {
|
||||||
|
local dir
|
||||||
|
dir=$(dirname "$CONFIG_FILE")
|
||||||
|
[ -d "$dir" ] || mkdir -p "$dir" || die "cannot create config directory: $dir"
|
||||||
|
|
||||||
|
cat > "$CONFIG_FILE" <<EOF
|
||||||
|
# $PROGRAM configuration
|
||||||
|
CHECK_CONTAINERS_LABEL_KEY="${LABEL_KEY}"
|
||||||
|
CHECK_CONTAINERS_AUTO_DISCOVER="${AUTO_DISCOVER}"
|
||||||
|
CHECK_CONTAINERS_TIMEOUT="${TIMEOUT}"
|
||||||
|
CHECK_CONTAINERS_SCHEME="${FORCE_SCHEME}"
|
||||||
|
CHECK_CONTAINERS_SHOW_ALL="${SHOW_ALL}"
|
||||||
|
CHECK_CONTAINERS_REFRESH="${REFRESH}"
|
||||||
|
CHECK_CONTAINERS_OUTPUT="${OUTPUT_FORMAT}"
|
||||||
|
CHECK_CONTAINERS_STRICT_TLS="${STRICT_TLS}"
|
||||||
|
CHECK_CONTAINERS_COLOR="${USE_COLOR}"
|
||||||
|
CHECK_CONTAINERS_EXIT_MODE="${EXIT_MODE}"
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
interactive_edit_config() {
|
||||||
|
printf '%sinteractive configuration for %s%s\n' "$BOLD" "$PROGRAM" "$RESET" >&2
|
||||||
|
printf '%sdetected os:%s %s (%s)\n' "$CYAN" "$RESET" "$OS_NAME" "$OS_FAMILY" >&2
|
||||||
|
|
||||||
|
LABEL_KEY="$(prompt_default "docker label key" "${LABEL_KEY:-caddy}")"
|
||||||
|
AUTO_DISCOVER="$(prompt_default "auto-discover label key (1/0)" "${AUTO_DISCOVER:-1}")"
|
||||||
|
TIMEOUT="$(prompt_default "http timeout seconds" "${TIMEOUT:-8}")"
|
||||||
|
FORCE_SCHEME="$(prompt_default "force scheme: empty/http/https" "${FORCE_SCHEME:-}")"
|
||||||
|
SHOW_ALL="$(prompt_default "include unlabeled containers (1/0)" "${SHOW_ALL:-0}")"
|
||||||
|
REFRESH="$(prompt_default "monitor refresh seconds" "${REFRESH:-10}")"
|
||||||
|
OUTPUT_FORMAT="$(prompt_default "output format: table/json" "${OUTPUT_FORMAT:-table}")"
|
||||||
|
STRICT_TLS="$(prompt_default "strict tls verify (1/0)" "${STRICT_TLS:-1}")"
|
||||||
|
USE_COLOR="$(prompt_default "color mode: auto/force/never" "${USE_COLOR:-auto}")"
|
||||||
|
EXIT_MODE="$(prompt_default "exit mode" "${EXIT_MODE:-default}")"
|
||||||
|
|
||||||
|
write_config_file
|
||||||
|
printf '%ssaved config:%s %s\n' "$GREEN" "$RESET" "$CONFIG_FILE" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
interactive_fill_missing() {
|
||||||
|
[ -n "$LABEL_KEY" ] || LABEL_KEY="$(prompt_default "docker label key" "caddy")"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_escape() {
|
||||||
|
printf '%s' "${1:-}" | sed \
|
||||||
|
-e 's/\\/\\\\/g' \
|
||||||
|
-e 's/"/\\"/g' \
|
||||||
|
-e 's/ /\\t/g' \
|
||||||
|
-e 's/
|
||||||
|
/\\r/g' \
|
||||||
|
-e ':a;N;$!ba;s/\n/\\n/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
strip_domain() {
|
||||||
|
local domain
|
||||||
|
domain="$(trim "${1:-}")"
|
||||||
|
domain="${domain#http://}"
|
||||||
|
domain="${domain#https://}"
|
||||||
|
domain="${domain%%/*}"
|
||||||
|
domain="${domain%%\?*}"
|
||||||
|
domain="${domain%%\#*}"
|
||||||
|
domain="${domain%%:*}"
|
||||||
|
printf '%s' "$domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_domain() {
|
||||||
|
local domain
|
||||||
|
domain="$(strip_domain "${1:-}")"
|
||||||
|
[ -n "$domain" ] || return 1
|
||||||
|
printf '%s' "$domain" | grep -Eq '^(\*\.)?[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'
|
||||||
|
}
|
||||||
|
|
||||||
|
split_domains() {
|
||||||
|
local raw
|
||||||
|
raw="${1:-}"
|
||||||
|
raw="${raw//$'\r'/}"
|
||||||
|
raw="${raw//$'\n'/,}"
|
||||||
|
raw="${raw//;/,}"
|
||||||
|
printf '%s\n' "$raw" |
|
||||||
|
tr ',' '\n' |
|
||||||
|
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' |
|
||||||
|
sed '/^$/d'
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_state() {
|
||||||
|
case "${1:-}" in
|
||||||
|
running) printf 'running' ;;
|
||||||
|
restarting) printf 'restarting' ;;
|
||||||
|
exited|created|paused|dead) printf 'stopped' ;;
|
||||||
|
*) printf '%s' "${1:-unknown}" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_domains_from_value() {
|
||||||
|
local value line
|
||||||
|
value="${1:-}"
|
||||||
|
|
||||||
|
if printf '%s' "$value" | grep -q 'Host('; then
|
||||||
|
printf '%s' "$value" |
|
||||||
|
grep -oE '`[^`]+`' |
|
||||||
|
tr -d '`' |
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[ -n "$line" ] && printf '%s\n' "$line"
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
split_domains "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
discover_label_key() {
|
||||||
|
local candidate found
|
||||||
|
for candidate in \
|
||||||
|
"${LABEL_KEY:-}" \
|
||||||
|
caddy \
|
||||||
|
CADDY \
|
||||||
|
VIRTUAL_HOST \
|
||||||
|
virtual.host \
|
||||||
|
domain \
|
||||||
|
domains \
|
||||||
|
traefik.domain \
|
||||||
|
nginxproxymanager.domain
|
||||||
|
do
|
||||||
|
[ -n "$candidate" ] || continue
|
||||||
|
found="$(
|
||||||
|
docker ps -a --format "{{.Label \"$candidate\"}}" 2>/dev/null |
|
||||||
|
grep -E '([A-Za-z0-9.-]+\.[A-Za-z]{2,}|Host\()' | head -n 1 || true
|
||||||
|
)"
|
||||||
|
if [ -n "$found" ]; then
|
||||||
|
printf '%s' "$candidate"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
check_url() {
|
||||||
|
local url curl_args code rc
|
||||||
|
url="$1"
|
||||||
|
|
||||||
|
curl_args="--silent --show-error --location --output /dev/null --write-out %{http_code} --connect-timeout $TIMEOUT --max-time $TIMEOUT --user-agent $PROGRAM/$VERSION"
|
||||||
|
|
||||||
|
if [ "$STRICT_TLS" = "0" ]; then
|
||||||
|
curl_args="$curl_args --insecure"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
code="$(curl $curl_args "$url" 2>/dev/null)"
|
||||||
|
rc=$?
|
||||||
|
|
||||||
|
if [ "$rc" -ne 0 ] || [ -z "$code" ] || [ "$code" = "000" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "$code"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
http_check() {
|
||||||
|
local domain code
|
||||||
|
domain="$1"
|
||||||
|
code=""
|
||||||
|
|
||||||
|
case "$domain" in
|
||||||
|
\*.*)
|
||||||
|
printf 'skipped|wildcard|--'
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$FORCE_SCHEME" = "http" ]; then
|
||||||
|
if code="$(check_url "http://$domain")"; then
|
||||||
|
[ "$code" = "200" ] &&
|
||||||
|
printf 'online|http 200|http' ||
|
||||||
|
printf 'error|http %s|http' "$code"
|
||||||
|
else
|
||||||
|
printf 'offline|no connection|http'
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$FORCE_SCHEME" = "https" ]; then
|
||||||
|
if code="$(check_url "https://$domain")"; then
|
||||||
|
[ "$code" = "200" ] &&
|
||||||
|
printf 'online|http 200|https' ||
|
||||||
|
printf 'error|http %s|https' "$code"
|
||||||
|
else
|
||||||
|
printf 'offline|no connection|https'
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if code="$(check_url "https://$domain")"; then
|
||||||
|
[ "$code" = "200" ] &&
|
||||||
|
printf 'online|http 200|https' ||
|
||||||
|
printf 'error|http %s|https' "$code"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if code="$(check_url "http://$domain")"; then
|
||||||
|
[ "$code" = "200" ] &&
|
||||||
|
printf 'online|http 200|http' ||
|
||||||
|
printf 'error|http %s|http' "$code"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf 'offline|no connection|--'
|
||||||
|
}
|
||||||
|
|
||||||
|
print_solid_line() {
|
||||||
|
local width char i
|
||||||
|
width="${1:-80}"
|
||||||
|
char="${2:-─}"
|
||||||
|
i=0
|
||||||
|
while [ "$i" -lt "$width" ]; do
|
||||||
|
printf '%s' "$char"
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
printf '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_rows() {
|
||||||
|
local rows_tmp label raw found name state uptime created_at domains domain
|
||||||
|
local container_state result check_status check_detail check_scheme domain_found
|
||||||
|
rows_tmp="$1"
|
||||||
|
found=0
|
||||||
|
|
||||||
|
label="$LABEL_KEY"
|
||||||
|
if [ -z "$label" ] && [ "$AUTO_DISCOVER" = "1" ]; then
|
||||||
|
label="$(discover_label_key || true)"
|
||||||
|
fi
|
||||||
|
[ -n "$label" ] || label="caddy"
|
||||||
|
|
||||||
|
raw="$(
|
||||||
|
docker ps -a \
|
||||||
|
--format "{{.Names}}|{{.State}}|{{.RunningFor}}|{{.CreatedAt}}|{{.Label \"$label\"}}" \
|
||||||
|
2>/dev/null || true
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [ -z "$raw" ]; then
|
||||||
|
warn "no docker containers found, or docker is not accessible"
|
||||||
|
printf '%s' "$label" > "${rows_tmp}.label"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "$label" > "${rows_tmp}.label"
|
||||||
|
|
||||||
|
while IFS='|' read -r name state uptime created_at domains; do
|
||||||
|
[ -n "${name:-}" ] || continue
|
||||||
|
container_state="$(docker_state "$state")"
|
||||||
|
domain_found=0
|
||||||
|
|
||||||
|
if [ -n "${domains:-}" ]; then
|
||||||
|
while IFS= read -r domain; do
|
||||||
|
domain="$(strip_domain "$domain")"
|
||||||
|
valid_domain "$domain" || continue
|
||||||
|
domain_found=1
|
||||||
|
found=1
|
||||||
|
|
||||||
|
if [ "$DOMAINS_ONLY" = "1" ]; then
|
||||||
|
printf '%s\t%s\n' "$name" "$domain" >> "$rows_tmp"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
result="$(http_check "$domain")"
|
||||||
|
IFS='|' read -r check_status check_detail check_scheme <<EOF
|
||||||
|
$result
|
||||||
|
EOF
|
||||||
|
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||||
|
"$name" \
|
||||||
|
"$container_state" \
|
||||||
|
"$uptime" \
|
||||||
|
"$domain" \
|
||||||
|
"$check_status" \
|
||||||
|
"$check_detail" \
|
||||||
|
"$check_scheme" >> "$rows_tmp"
|
||||||
|
done <<EOF
|
||||||
|
$(extract_domains_from_value "$domains")
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$SHOW_ALL" = "1" ] && [ "$domain_found" = "0" ] && [ "$DOMAINS_ONLY" = "0" ]; then
|
||||||
|
found=1
|
||||||
|
printf '%s\t%s\t%s\t-\tskipped\tno domain label\t--\n' \
|
||||||
|
"$name" \
|
||||||
|
"$container_state" \
|
||||||
|
"$uptime" >> "$rows_tmp"
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
$raw
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "$found" = "0" ]; then
|
||||||
|
warn "no valid domain labels found using label key: $label"
|
||||||
|
warn "try: $PROGRAM --label your_label_key"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
render_domains_only() {
|
||||||
|
local rows_tmp
|
||||||
|
rows_tmp="$1"
|
||||||
|
cut -f2 "$rows_tmp" 2>/dev/null | sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
render_json() {
|
||||||
|
local rows_tmp label first
|
||||||
|
rows_tmp="$1"
|
||||||
|
label="$(cat "${rows_tmp}.label" 2>/dev/null || printf '%s' "${LABEL_KEY:-caddy}")"
|
||||||
|
|
||||||
|
# printf '{\n'
|
||||||
|
# printf ' "program": "%s",\n' "$(json_escape "$PROGRAM")"
|
||||||
|
# printf ' "version": "%s",\n' "$(json_escape "$VERSION")"
|
||||||
|
# printf ' "os_family": "%s",\n' "$(json_escape "$OS_FAMILY")"
|
||||||
|
# printf ' "os_name": "%s",\n' "$(json_escape "$OS_NAME")"
|
||||||
|
printf ' "label": "%s",\n' "$(json_escape "$label")"
|
||||||
|
printf ' "containers": [\n'
|
||||||
|
|
||||||
|
first=1
|
||||||
|
while IFS=$'\t' read -r name state uptime domain check_status check_detail check_scheme; do
|
||||||
|
[ -n "${name:-}" ] || continue
|
||||||
|
[ "$first" -eq 1 ] || printf ',\n'
|
||||||
|
first=0
|
||||||
|
printf ' {"container":"%s","docker_state":"%s","running_for":"%s","domain":"%s","status":"%s","result":"%s","scheme":"%s"}' \
|
||||||
|
"$(json_escape "$name")" \
|
||||||
|
"$(json_escape "$state")" \
|
||||||
|
"$(json_escape "$uptime")" \
|
||||||
|
"$(json_escape "$domain")" \
|
||||||
|
"$(json_escape "$check_status")" \
|
||||||
|
"$(json_escape "$check_detail")" \
|
||||||
|
"$(json_escape "$check_scheme")"
|
||||||
|
done < "$rows_tmp"
|
||||||
|
|
||||||
|
printf '\n ]\n'
|
||||||
|
printf '}\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
render_table() {
|
||||||
|
local rows_tmp label
|
||||||
|
local container_width state_width uptime_width domain_width status_width detail_width scheme_width total_width
|
||||||
|
local name state uptime domain check_status check_detail check_scheme
|
||||||
|
local online error offline skipped total
|
||||||
|
local domain_count matched_count container_count
|
||||||
|
local state_color status_color
|
||||||
|
|
||||||
|
rows_tmp="$1"
|
||||||
|
label="$(cat "${rows_tmp}.label" 2>/dev/null || printf '%s' "${LABEL_KEY:-caddy}")"
|
||||||
|
|
||||||
|
container_width=12
|
||||||
|
state_width=12
|
||||||
|
uptime_width=14
|
||||||
|
domain_width=24
|
||||||
|
status_width=8
|
||||||
|
detail_width=14
|
||||||
|
scheme_width=6
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r name state uptime domain check_status check_detail check_scheme; do
|
||||||
|
[ "${#name}" -gt "$container_width" ] && container_width="${#name}"
|
||||||
|
[ "${#state}" -gt "$state_width" ] && state_width="${#state}"
|
||||||
|
[ "${#uptime}" -gt "$uptime_width" ] && uptime_width="${#uptime}"
|
||||||
|
[ "${#domain}" -gt "$domain_width" ] && domain_width="${#domain}"
|
||||||
|
[ "${#check_status}" -gt "$status_width" ] && status_width="${#check_status}"
|
||||||
|
[ "${#check_detail}" -gt "$detail_width" ] && detail_width="${#check_detail}"
|
||||||
|
[ "${#check_scheme}" -gt "$scheme_width" ] && scheme_width="${#check_scheme}"
|
||||||
|
done < "$rows_tmp"
|
||||||
|
|
||||||
|
total_width=$((container_width + state_width + uptime_width + domain_width + status_width + detail_width + scheme_width + 18))
|
||||||
|
|
||||||
|
if [ "$NO_HEADER" = "0" ]; then
|
||||||
|
printf '\n'
|
||||||
|
print_solid_line "$total_width"
|
||||||
|
printf "${BOLD}${BLUE}%-${container_width}s %-${state_width}s %-${uptime_width}s %-${domain_width}s %-${status_width}s %-${detail_width}s %-${scheme_width}s${RESET}\n" \
|
||||||
|
"container" "docker_state" "running_for" "domain" "status" "result" "scheme"
|
||||||
|
print_solid_line "$total_width"
|
||||||
|
fi
|
||||||
|
|
||||||
|
online=0
|
||||||
|
error=0
|
||||||
|
offline=0
|
||||||
|
skipped=0
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r name state uptime domain check_status check_detail check_scheme; do
|
||||||
|
state_color="$GRAY"
|
||||||
|
status_color="$RED"
|
||||||
|
|
||||||
|
case "$state" in
|
||||||
|
running) state_color="$GREEN" ;;
|
||||||
|
restarting) state_color="$YELLOW" ;;
|
||||||
|
stopped) state_color="$RED" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$check_status" in
|
||||||
|
online)
|
||||||
|
status_color="$GREEN"
|
||||||
|
online=$((online + 1))
|
||||||
|
;;
|
||||||
|
error)
|
||||||
|
status_color="$YELLOW"
|
||||||
|
error=$((error + 1))
|
||||||
|
;;
|
||||||
|
offline)
|
||||||
|
status_color="$RED"
|
||||||
|
offline=$((offline + 1))
|
||||||
|
;;
|
||||||
|
skipped)
|
||||||
|
status_color="$GRAY"
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
printf "%-${container_width}s %s%-${state_width}s${RESET} %-${uptime_width}s %-${domain_width}s %s%-${status_width}s${RESET} %-${detail_width}s %-${scheme_width}s\n" \
|
||||||
|
"$name" \
|
||||||
|
"$state_color" "$state" \
|
||||||
|
"$uptime" \
|
||||||
|
"$domain" \
|
||||||
|
"$status_color" "$check_status" \
|
||||||
|
"$check_detail" \
|
||||||
|
"$check_scheme"
|
||||||
|
done < "$rows_tmp"
|
||||||
|
|
||||||
|
total=$((online + error + offline + skipped))
|
||||||
|
domain_count="$(cut -f4 "$rows_tmp" 2>/dev/null | grep -v '^-$' | sort -u | wc -l | tr -d ' ')"
|
||||||
|
matched_count="$(cut -f1 "$rows_tmp" 2>/dev/null | sort -u | wc -l | tr -d ' ')"
|
||||||
|
container_count="$(docker ps -aq 2>/dev/null | wc -l | tr -d ' ')"
|
||||||
|
|
||||||
|
if [ "$NO_HEADER" = "0" ]; then
|
||||||
|
print_solid_line "$total_width"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "${GREEN}${BOLD}online${RESET}: %d " "$online"
|
||||||
|
printf "${YELLOW}${BOLD}error${RESET}: %d " "$error"
|
||||||
|
printf "${RED}${BOLD}offline${RESET}: %d " "$offline"
|
||||||
|
printf "${GRAY}${BOLD}skipped${RESET}: %d " "$skipped"
|
||||||
|
printf "${BLUE}${BOLD}total${RESET}: %d\n" "$total"
|
||||||
|
|
||||||
|
printf "${CYAN}domains${RESET}: %s " "$domain_count"
|
||||||
|
printf "${CYAN}matched_containers${RESET}: %s " "$matched_count"
|
||||||
|
printf "${CYAN}all_containers${RESET}: %s " "$container_count"
|
||||||
|
printf "${CYAN}label${RESET}: %s " "$label"
|
||||||
|
printf "${CYAN}os${RESET}: %s\n" "$OS_FAMILY"
|
||||||
|
}
|
||||||
|
|
||||||
|
determine_exit_code() {
|
||||||
|
local rows_tmp status rc
|
||||||
|
rows_tmp="$1"
|
||||||
|
rc=0
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r _ _ _ _ status _ _; do
|
||||||
|
case "$status" in
|
||||||
|
offline) rc=2 ;;
|
||||||
|
error)
|
||||||
|
[ "$rc" -lt 2 ] && rc=1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < "$rows_tmp"
|
||||||
|
|
||||||
|
return "$rc"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_check() {
|
||||||
|
local rows_tmp=""
|
||||||
|
rows_tmp="$(mktemp "${TMPDIR:-/tmp}/check-containers.XXXXXX")" || die "failed to create temp file"
|
||||||
|
trap '[ -n "${rows_tmp:-}" ] && rm -f "${rows_tmp}" "${rows_tmp}.label"' RETURN INT TERM EXIT
|
||||||
|
|
||||||
|
collect_rows "$rows_tmp"
|
||||||
|
|
||||||
|
if [ "$DOMAINS_ONLY" = "1" ]; then
|
||||||
|
render_domains_only "$rows_tmp"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$OUTPUT_FORMAT" in
|
||||||
|
json) render_json "$rows_tmp" ;;
|
||||||
|
*) render_table "$rows_tmp" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
determine_exit_code "$rows_tmp"
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
if [ "${1:-}" = "monitor" ]; then
|
||||||
|
MONITOR=1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-c|--color)
|
||||||
|
USE_COLOR="force"
|
||||||
|
;;
|
||||||
|
-n|--no-color)
|
||||||
|
USE_COLOR="never"
|
||||||
|
;;
|
||||||
|
-d|--domains)
|
||||||
|
DOMAINS_ONLY=1
|
||||||
|
;;
|
||||||
|
--label)
|
||||||
|
shift
|
||||||
|
[ "$#" -gt 0 ] || die "--label requires a value"
|
||||||
|
LABEL_KEY="$1"
|
||||||
|
AUTO_DISCOVER=0
|
||||||
|
;;
|
||||||
|
--no-discover)
|
||||||
|
AUTO_DISCOVER=0
|
||||||
|
;;
|
||||||
|
--no-header)
|
||||||
|
NO_HEADER=1
|
||||||
|
;;
|
||||||
|
--timeout)
|
||||||
|
shift
|
||||||
|
[ "$#" -gt 0 ] || die "--timeout requires whole seconds"
|
||||||
|
printf '%s' "$1" | grep -Eq '^[0-9]+$' || die "--timeout requires whole seconds"
|
||||||
|
TIMEOUT="$1"
|
||||||
|
;;
|
||||||
|
--refresh)
|
||||||
|
shift
|
||||||
|
[ "$#" -gt 0 ] || die "--refresh requires whole seconds"
|
||||||
|
printf '%s' "$1" | grep -Eq '^[0-9]+$' || die "--refresh requires whole seconds"
|
||||||
|
REFRESH="$1"
|
||||||
|
;;
|
||||||
|
--http)
|
||||||
|
FORCE_SCHEME="http"
|
||||||
|
;;
|
||||||
|
--https)
|
||||||
|
FORCE_SCHEME="https"
|
||||||
|
;;
|
||||||
|
--all)
|
||||||
|
SHOW_ALL=1
|
||||||
|
;;
|
||||||
|
--json)
|
||||||
|
OUTPUT_FORMAT="json"
|
||||||
|
;;
|
||||||
|
--table)
|
||||||
|
OUTPUT_FORMAT="table"
|
||||||
|
;;
|
||||||
|
--strict-tls)
|
||||||
|
STRICT_TLS=1
|
||||||
|
;;
|
||||||
|
--insecure)
|
||||||
|
STRICT_TLS=0
|
||||||
|
;;
|
||||||
|
--config)
|
||||||
|
shift
|
||||||
|
[ "$#" -gt 0 ] || die "--config requires a file path"
|
||||||
|
CONFIG_FILE="$1"
|
||||||
|
;;
|
||||||
|
--edit-config)
|
||||||
|
EDIT_CONFIG=1
|
||||||
|
;;
|
||||||
|
--interactive)
|
||||||
|
INTERACTIVE=1
|
||||||
|
;;
|
||||||
|
--exit-default)
|
||||||
|
EXIT_MODE="default"
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "unknown option: $1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
parse_args "$@"
|
||||||
|
detect_os
|
||||||
|
load_config_file
|
||||||
|
setup_colors
|
||||||
|
|
||||||
|
need docker
|
||||||
|
need curl
|
||||||
|
need awk
|
||||||
|
need grep
|
||||||
|
need sed
|
||||||
|
need tr
|
||||||
|
need cut
|
||||||
|
need sort
|
||||||
|
need wc
|
||||||
|
need mktemp
|
||||||
|
need date
|
||||||
|
|
||||||
|
if [ "$EDIT_CONFIG" = "1" ]; then
|
||||||
|
interactive_edit_config
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$INTERACTIVE" = "1" ]; then
|
||||||
|
interactive_fill_missing
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$MONITOR" = "1" ]; then
|
||||||
|
trap 'printf "\n"; exit 0' INT TERM
|
||||||
|
while true; do
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
printf '\033[H\033[2J'
|
||||||
|
fi
|
||||||
|
# printf "${BOLD}${CYAN}%s monitor${RESET} " "$PROGRAM"
|
||||||
|
# printf "${DIM}%s | refresh: %ss | os: %s | ctrl+c to exit${RESET}\n" \
|
||||||
|
printf "$(date '+%Y-%m-%d %H:%M:%S')" \
|
||||||
|
"$REFRESH" \
|
||||||
|
"$OS_FAMILY"
|
||||||
|
run_check || true
|
||||||
|
sleep "$REFRESH"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
run_check
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Loading…
x
Reference in New Issue
Block a user