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
+608
View File
@@ -0,0 +1,608 @@
#!/usr/bin/env bash
#########################################################################################
#### contabo-fleet-manager.sh — Inventory, health checks, and bulk operations for ####
#### Contabo VPS/VDS instances via the REST API. Fleet-wide visibility and control ####
#### Requires: bash 4+, curl, jq ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version 1.01 ####
#### ####
#### Usage: ####
#### ./contabo-fleet-manager.sh --inventory --all ####
#### ####
#### See --help for all options. ####
#########################################################################################
set -euo pipefail
# ── Colors (pre-initialized) ─────────────────────────────────────────
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
setup_colors() {
if [[ "${COLOR:-auto}" == "never" ]]; then
return
fi
if [[ "${COLOR:-auto}" == "always" ]] || [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'
fi
}
# ── Logging ───────────────────────────────────────────────────────────
log() { echo -e "${BLUE}[INFO]${RESET} $*"; }
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }
die() { err "$*"; exit 1; }
section_header() {
echo ""
echo -e " ${BOLD}${CYAN}── $1 ──${RESET}"
echo ""
}
field() {
printf " ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
}
field_color() {
printf " ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
}
elapsed() {
local end_time
end_time=$(date +%s)
echo "$(( end_time - START_TIME ))s"
}
# ── Defaults ──────────────────────────────────────────────────────────
RUN_MODE=""
INSTANCE_ID=""
TARGET_ALL="false"
TAG_ID=""
TAG_SUB_MODE=""
OUTPUT_FORMAT="${CFM_FORMAT:-text}"
PING_CHECK="false"
FORCE="false"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"
# ── Credentials ───────────────────────────────────────────────────────
CONTABO_CLIENT_ID="${CONTABO_CLIENT_ID:-}"
CONTABO_CLIENT_SECRET="${CONTABO_CLIENT_SECRET:-}"
CONTABO_API_USER="${CONTABO_API_USER:-}"
CONTABO_API_PASS="${CONTABO_API_PASS:-}"
# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
START_TIME=""
ACTION_OK=0
ACTION_FAIL=0
# ── API helpers ──────────────────────────────────────────────────────
contabo_token() {
local resp
resp=$(curl -s -d "client_id=${CONTABO_CLIENT_ID}" \
-d "client_secret=${CONTABO_CLIENT_SECRET}" \
--data-urlencode "username=${CONTABO_API_USER}" \
--data-urlencode "password=${CONTABO_API_PASS}" \
-d "grant_type=password" \
"https://auth.contabo.com/auth/realms/contabo/protocol/openid-connect/token")
local token
token=$(echo "$resp" | jq -r '.access_token // empty' 2>/dev/null)
if [[ -z "$token" ]]; then
die "Failed to obtain access token — check credentials"
fi
echo "$token"
}
contabo_api() {
local method="$1" endpoint="$2"
shift 2
local attempt=0 max_attempts=3
while (( attempt < max_attempts )); do
local http_code
http_code=$(curl -s -o /tmp/cfm_resp.json -w "%{http_code}" \
-X "$method" \
-H "Authorization: Bearer $(contabo_token)" \
-H "Content-Type: application/json" \
-H "x-request-id: $(cat /proc/sys/kernel/random/uuid 2>/dev/null || date +%s%N)" \
"https://api.contabo.com/v1${endpoint}" "$@")
verbose "API ${method} ${endpoint} → HTTP ${http_code}"
if [[ "$http_code" == "429" ]]; then
((attempt++)) || true
local wait=$(( attempt * 5 ))
warn "Rate limited — retrying in ${wait}s (attempt ${attempt}/${max_attempts})"
sleep "$wait"
continue
fi
cat /tmp/cfm_resp.json
return 0
done
err "API request failed after ${max_attempts} attempts: ${method} ${endpoint}"
return 1
}
check_credentials() {
[[ -z "$CONTABO_CLIENT_ID" ]] && die "CONTABO_CLIENT_ID not set"
[[ -z "$CONTABO_CLIENT_SECRET" ]] && die "CONTABO_CLIENT_SECRET not set"
[[ -z "$CONTABO_API_USER" ]] && die "CONTABO_API_USER not set"
[[ -z "$CONTABO_API_PASS" ]] && die "CONTABO_API_PASS not set"
}
check_deps() {
command -v curl &>/dev/null || die "curl is required"
command -v jq &>/dev/null || die "jq is required"
}
# ── Instance helpers ─────────────────────────────────────────────────
get_all_instance_ids() {
local page=1 size=100 ids=""
while true; do
local resp
resp=$(contabo_api GET "/compute/instances?page=${page}&size=${size}")
local page_ids
page_ids=$(echo "$resp" | jq -r '.data[].instanceId' 2>/dev/null)
[[ -z "$page_ids" ]] && break
ids="${ids}${ids:+$'\n'}${page_ids}"
local count
count=$(echo "$page_ids" | wc -l)
(( count < size )) && break
((page++)) || true
done
echo "$ids"
}
get_instance_name() {
local iid="$1"
contabo_api GET "/compute/instances/${iid}" \
| jq -r '.data[0].name // .data[0].displayName // "unknown"' 2>/dev/null
}
get_instance_ids() {
if [[ "$TARGET_ALL" == "true" ]]; then
get_all_instance_ids
elif [[ -n "$INSTANCE_ID" ]]; then
echo "$INSTANCE_ID"
elif [[ -n "$TAG_ID" ]]; then
get_instances_by_tag "$TAG_ID"
else
die "Specify --instance ID, --all, or --tag TAG_ID"
fi
}
get_instances_by_tag() {
local tid="$1"
local page=1 size=100 ids=""
while true; do
local resp
resp=$(contabo_api GET "/compute/instances?page=${page}&size=${size}")
local page_ids
page_ids=$(echo "$resp" | jq -r --arg tid "$tid" \
'.data[] | select(.tags[]? | .tagId == ($tid | tonumber)) | .instanceId' 2>/dev/null)
[[ -z "$page_ids" ]] && break
ids="${ids}${ids:+$'\n'}${page_ids}"
local count
count=$(echo "$page_ids" | wc -l)
(( count < size )) && break
((page++)) || true
done
echo "$ids"
}
# ══════════════════════════════════════════════════════════════════════
# INVENTORY
# ══════════════════════════════════════════════════════════════════════
do_inventory() {
local page=1 size=100 all_data="[]"
while true; do
local resp
resp=$(contabo_api GET "/compute/instances?page=${page}&size=${size}")
local page_data
page_data=$(echo "$resp" | jq '.data // []' 2>/dev/null)
local page_count
page_count=$(echo "$page_data" | jq 'length' 2>/dev/null || echo 0)
[[ "$page_count" -eq 0 ]] && break
all_data=$(echo -e "${all_data}\n${page_data}" | jq -s 'add' 2>/dev/null)
(( page_count < size )) && break
((page++)) || true
done
local total
total=$(echo "$all_data" | jq 'length' 2>/dev/null || echo 0)
[[ "$total" -eq 0 ]] && die "No instances found"
# Filter by tag if specified
if [[ -n "$TAG_ID" ]]; then
all_data=$(echo "$all_data" | jq --arg tid "$TAG_ID" \
'[.[] | select(.tags[]? | .tagId == ($tid | tonumber))]' 2>/dev/null)
total=$(echo "$all_data" | jq 'length' 2>/dev/null || echo 0)
[[ "$total" -eq 0 ]] && die "No instances found with tag ${TAG_ID}"
fi
case "$OUTPUT_FORMAT" in
json)
echo "$all_data" | jq '.'
;;
ansible)
echo "[contabo]"
echo "$all_data" | jq -r \
'.[] | (.ipConfig.v4.ip // "unknown") + " # " + (.name // .displayName // "unknown") + " id=" + (.instanceId | tostring)' \
2>/dev/null
;;
*)
section_header "Fleet Inventory"
printf " ${BOLD}%-13s %-20s %-11s %-16s %-8s %-8s${RESET}\n" \
"INSTANCE_ID" "NAME" "STATUS" "IP" "REGION" "PRODUCT"
printf " %s\n" "$(printf '%.0s─' {1..78})"
echo "$all_data" | jq -r \
'.[] | "\(.instanceId)\t\(.name // .displayName // "unknown")\t\(.status // "unknown")\t\(.ipConfig.v4.ip // "—")\t\(.region // "—")\t\(.productId // "—")"' \
2>/dev/null \
| while IFS=$'\t' read -r iid name status ip region product; do
printf " %-13s %-20s %-11s %-16s %-8s %-8s\n" \
"$iid" "${name:0:18}" "$status" "$ip" "${region:0:6}" "$product"
done
echo ""
field "Total:" "$total"
;;
esac
}
# ══════════════════════════════════════════════════════════════════════
# HEALTH
# ══════════════════════════════════════════════════════════════════════
do_health() {
local ids
ids=$(get_instance_ids)
[[ -z "$ids" ]] && die "No instances found"
local running=0 stopped=0 errored=0 total_instances=0
local results=""
while IFS= read -r iid; do
[[ -z "$iid" ]] && continue
((total_instances++)) || true
local resp
resp=$(contabo_api GET "/compute/instances/${iid}")
local name status ip
name=$(echo "$resp" | jq -r '.data[0].name // .data[0].displayName // "unknown"' 2>/dev/null)
status=$(echo "$resp" | jq -r '.data[0].status // "unknown"' 2>/dev/null)
ip=$(echo "$resp" | jq -r '.data[0].ipConfig.v4.ip // ""' 2>/dev/null)
local ping_result="—"
if [[ "$PING_CHECK" == "true" && -n "$ip" ]]; then
if ping -c 1 -W 3 "$ip" &>/dev/null; then
ping_result="reachable"
else
ping_result="unreachable"
fi
fi
case "$status" in
running) ((running++)) || true ;;
stopped) ((stopped++)) || true ;;
*) ((errored++)) || true ;;
esac
results="${results}${iid}\t${name}\t${status}\t${ip}\t${ping_result}\n"
done <<< "$ids"
if [[ "$OUTPUT_FORMAT" == "prometheus" ]]; then
cat <<EOF
# HELP contabo_fleet_instances_total Total Contabo instances
# TYPE contabo_fleet_instances_total gauge
contabo_fleet_instances_total ${total_instances}
# HELP contabo_fleet_running Running instances
# TYPE contabo_fleet_running gauge
contabo_fleet_running ${running}
# HELP contabo_fleet_stopped Stopped instances
# TYPE contabo_fleet_stopped gauge
contabo_fleet_stopped ${stopped}
# HELP contabo_fleet_error Instances in error state
# TYPE contabo_fleet_error gauge
contabo_fleet_error ${errored}
EOF
return
fi
section_header "Fleet Health Check"
if [[ "$PING_CHECK" == "true" ]]; then
printf " ${BOLD}%-13s %-20s %-11s %-16s %-12s${RESET}\n" \
"INSTANCE_ID" "NAME" "STATUS" "IP" "PING"
printf " %s\n" "$(printf '%.0s─' {1..74})"
else
printf " ${BOLD}%-13s %-20s %-11s %-16s${RESET}\n" \
"INSTANCE_ID" "NAME" "STATUS" "IP"
printf " %s\n" "$(printf '%.0s─' {1..62})"
fi
echo -e "$results" | while IFS=$'\t' read -r iid name status ip ping_res; do
[[ -z "$iid" ]] && continue
local status_color="$GREEN"
case "$status" in
running) status_color="$GREEN" ;;
stopped) status_color="$YELLOW" ;;
*) status_color="$RED" ;;
esac
if [[ "$PING_CHECK" == "true" ]]; then
local ping_color="$DIM"
case "$ping_res" in
reachable) ping_color="$GREEN" ;;
unreachable) ping_color="$RED" ;;
esac
printf " %-13s %-20s " "$iid" "${name:0:18}"
echo -ne "${status_color}"
printf "%-11s" "$status"
echo -ne "${RESET}"
printf " %-16s " "$ip"
echo -e "${ping_color}${ping_res}${RESET}"
else
printf " %-13s %-20s " "$iid" "${name:0:18}"
echo -ne "${status_color}"
printf "%-11s" "$status"
echo -e "${RESET} ${ip}"
fi
done
echo ""
field "Instances:" "$total_instances"
field_color "Running:" "${GREEN}${running}${RESET}"
if [[ "$stopped" -gt 0 ]]; then
field_color "Stopped:" "${YELLOW}${stopped}${RESET}"
else
field_color "Stopped:" "${GREEN}0${RESET}"
fi
if [[ "$errored" -gt 0 ]]; then
field_color "Error:" "${RED}${errored}${RESET}"
else
field_color "Error:" "${GREEN}0${RESET}"
fi
}
# ══════════════════════════════════════════════════════════════════════
# START / STOP / RESTART
# ══════════════════════════════════════════════════════════════════════
do_action() {
local action="$1"
local ids
ids=$(get_instance_ids)
[[ -z "$ids" ]] && die "No instances found"
if [[ "$action" != "start" && "$FORCE" != "true" ]]; then
die "${action} is destructive — use --force to confirm"
fi
local count
count=$(echo "$ids" | grep -c . || true)
local target_label="instance ${INSTANCE_ID}"
[[ "$TARGET_ALL" == "true" ]] && target_label="all (${count} instances)"
[[ -n "$TAG_ID" ]] && target_label="tag ${TAG_ID} (${count} instances)"
section_header "Bulk ${action^}"
field "Target:" "$target_label"
field "Action:" "$action"
echo ""
while IFS= read -r iid; do
[[ -z "$iid" ]] && continue
local iname
iname=$(get_instance_name "$iid")
verbose "Sending ${action} to ${iname} (${iid})"
if contabo_api POST "/compute/instances/${iid}/actions/${action}" \
-d '{}' > /dev/null 2>&1; then
echo -e " ${GREEN}${RESET} ${iname} (${iid}) ${action} sent"
((ACTION_OK++)) || true
else
echo -e " ${RED}${RESET} ${iname} (${iid}) ${action} failed"
((ACTION_FAIL++)) || true
fi
sleep 1
done <<< "$ids"
echo ""
field_color "Succeeded:" "${GREEN}${ACTION_OK}${RESET}"
if [[ "$ACTION_FAIL" -gt 0 ]]; then
field_color "Failed:" "${RED}${ACTION_FAIL}${RESET}"
fi
}
# ══════════════════════════════════════════════════════════════════════
# TAGS
# ══════════════════════════════════════════════════════════════════════
do_tags() {
if [[ "$TAG_SUB_MODE" == "list" ]]; then
local resp
resp=$(contabo_api GET "/tags?page=1&size=100")
if [[ "$OUTPUT_FORMAT" == "json" ]]; then
echo "$resp" | jq '.data // []'
return
fi
section_header "Tags"
printf " ${BOLD}%-10s %-30s %-10s${RESET}\n" "TAG_ID" "NAME" "COLOR"
printf " %s\n" "$(printf '%.0s─' {1..52})"
echo "$resp" | jq -r '.data[] | "\(.tagId)\t\(.name)\t\(.color // "—")"' 2>/dev/null \
| while IFS=$'\t' read -r tid tname tcolor; do
printf " %-10s %-30s %-10s\n" "$tid" "${tname:0:28}" "$tcolor"
done
elif [[ "$TAG_SUB_MODE" == "filter" ]]; then
[[ -z "$TAG_ID" ]] && die "Specify --filter TAG_ID"
INSTANCE_ID=""
TARGET_ALL="false"
do_inventory
else
die "Specify --list or --filter TAG_ID with --tags"
fi
}
# ══════════════════════════════════════════════════════════════════════
# HELP
# ══════════════════════════════════════════════════════════════════════
show_help() {
cat <<EOF
${BOLD}${SCRIPT_NAME}${RESET} — Contabo Fleet Manager
Inventory, health checks, and bulk operations for Contabo VPS/VDS
instances via the REST API.
${BOLD}MODES${RESET}
--inventory Dump instance inventory (table, ansible, json)
--health Health check all targeted instances
--start Start targeted instances
--stop Stop targeted instances (requires --force)
--restart Restart targeted instances (requires --force)
--tags List tags or filter instances by tag
${BOLD}TARGETING${RESET}
--instance ID Target a specific instance
--all Target all instances
--tag TAG_ID Target instances with a specific tag
${BOLD}OPTIONS${RESET}
--format FMT Output: text, json, ansible, prometheus (default: text)
--ping Include ICMP ping check in health mode
--force Required for stop/restart operations
--list List all tags (with --tags)
--filter TAG_ID Filter instances by tag (with --tags)
--verbose Debug output
--no-color Disable colored output
--help Show this help message
${BOLD}ENVIRONMENT VARIABLES${RESET}
CONTABO_CLIENT_ID OAuth2 Client ID (required)
CONTABO_CLIENT_SECRET OAuth2 Client Secret (required)
CONTABO_API_USER API username / email (required)
CONTABO_API_PASS API password (required)
CFM_FORMAT Default output format
VERBOSE Enable verbose output (true/false)
COLOR Color mode: auto, always, never
${BOLD}EXAMPLES${RESET}
# Full fleet inventory
${SCRIPT_NAME} --inventory --all
# Inventory as Ansible inventory file
${SCRIPT_NAME} --inventory --all --format ansible
# Health check all instances
${SCRIPT_NAME} --health --all
# Health check with ping
${SCRIPT_NAME} --health --all --ping
# Prometheus metrics output
${SCRIPT_NAME} --health --all --format prometheus
# Start a single instance
${SCRIPT_NAME} --start --instance 12345
# Stop all instances (requires --force)
${SCRIPT_NAME} --stop --all --force
# Restart instances by tag
${SCRIPT_NAME} --restart --tag 42 --force
# List all tags
${SCRIPT_NAME} --tags --list
# Show instances with a specific tag
${SCRIPT_NAME} --tags --filter 42
${BOLD}EXIT CODES${RESET}
0 Success
1 Runtime error
EOF
}
# ══════════════════════════════════════════════════════════════════════
# PARSE ARGS
# ══════════════════════════════════════════════════════════════════════
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--inventory) RUN_MODE="inventory"; shift ;;
--health) RUN_MODE="health"; shift ;;
--start) RUN_MODE="start"; shift ;;
--stop) RUN_MODE="stop"; shift ;;
--restart) RUN_MODE="restart"; shift ;;
--tags) RUN_MODE="tags"; shift ;;
--instance) INSTANCE_ID="${2:?--instance requires an ID}"; shift 2 ;;
--all) TARGET_ALL="true"; shift ;;
--tag) TAG_ID="${2:?--tag requires a TAG_ID}"; shift 2 ;;
--format) OUTPUT_FORMAT="${2:?--format requires a value}"; shift 2 ;;
--ping) PING_CHECK="true"; shift ;;
--force) FORCE="true"; shift ;;
--list) TAG_SUB_MODE="list"; shift ;;
--filter) TAG_SUB_MODE="filter"; TAG_ID="${2:?--filter requires a TAG_ID}"; shift 2 ;;
--verbose) VERBOSE="true"; shift ;;
--no-color) COLOR="never"; shift ;;
--help|-h) setup_colors; show_help; exit 0 ;;
*) die "Unknown option: $1 (see --help)" ;;
esac
done
}
# ══════════════════════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════════════════════
main() {
parse_args "$@"
setup_colors
if [[ -z "$RUN_MODE" ]]; then
err "No mode specified"
echo ""
show_help
exit 1
fi
check_deps
check_credentials
START_TIME=$(date +%s)
case "$RUN_MODE" in
inventory) do_inventory ;;
health) do_health ;;
start) do_action "start" ;;
stop) do_action "stop" ;;
restart) do_action "restart" ;;
tags) do_tags ;;
*) die "Unknown mode: ${RUN_MODE}" ;;
esac
if [[ "$OUTPUT_FORMAT" != "prometheus" ]]; then
echo ""
field "Duration:" "$(elapsed)"
fi
}
main "$@"