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:
Executable
+594
@@ -0,0 +1,594 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#########################################################################################
|
||||
#### service-dependency-mapper.sh — Map systemd service dependencies ####
|
||||
#### Shows what depends on a service and what it requires ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version 1.00 ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### ./service-dependency-mapper.sh nginx ####
|
||||
#### ./service-dependency-mapper.sh --all --format tree ####
|
||||
#### ./service-dependency-mapper.sh postgresql --reverse ####
|
||||
#### ./service-dependency-mapper.sh --critical ####
|
||||
#### ####
|
||||
#### See --help for all options. ####
|
||||
#########################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────────────
|
||||
SERVICE=""
|
||||
MODE="forward"
|
||||
MAP_ALL="false"
|
||||
CRITICAL="false"
|
||||
DEPTH=3
|
||||
FORMAT="tree"
|
||||
COLOR="auto"
|
||||
|
||||
# ── State ─────────────────────────────────────────────────────────────
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
readonly SCRIPT_NAME
|
||||
|
||||
# ── Colors ────────────────────────────────────────────────────────────
|
||||
RED="" GREEN="" YELLOW="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
|
||||
setup_colors() {
|
||||
if [[ "$COLOR" == "never" ]]; then
|
||||
RED="" GREEN="" YELLOW="" BOLD="" DIM="" CYAN="" RESET=""
|
||||
return
|
||||
fi
|
||||
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
DIM='\033[2m'
|
||||
RESET='\033[0m'
|
||||
else
|
||||
RED="" GREEN="" YELLOW="" BOLD="" DIM="" CYAN="" RESET=""
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────
|
||||
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# USAGE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
${SCRIPT_NAME} — Map systemd service dependencies
|
||||
|
||||
USAGE:
|
||||
${SCRIPT_NAME} SERVICE [OPTIONS]
|
||||
${SCRIPT_NAME} --all [OPTIONS]
|
||||
${SCRIPT_NAME} --critical [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--forward Show what this service requires (default)
|
||||
--reverse Show what depends ON this service
|
||||
--all Map all active services
|
||||
--critical Show services with the most reverse dependencies
|
||||
--depth N Depth limit for dependency traversal (default: 3)
|
||||
--format FORMAT Output format: tree, flat, dot, json (default: tree)
|
||||
--no-color Disable colored output
|
||||
--help Show this help
|
||||
|
||||
EXAMPLES:
|
||||
# Show what nginx requires
|
||||
./${SCRIPT_NAME} nginx
|
||||
|
||||
# Show what breaks if postgresql stops
|
||||
./${SCRIPT_NAME} postgresql --reverse
|
||||
|
||||
# Show all active service dependencies as a tree
|
||||
./${SCRIPT_NAME} --all --format tree
|
||||
|
||||
# Find highest-impact services
|
||||
./${SCRIPT_NAME} --critical
|
||||
|
||||
# Generate Graphviz DOT output
|
||||
./${SCRIPT_NAME} nginx --format dot | dot -Tpng -o nginx-deps.png
|
||||
|
||||
# Shallow scan
|
||||
./${SCRIPT_NAME} nginx --depth 1
|
||||
EOF
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# ARGUMENT PARSING
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--forward)
|
||||
MODE="forward"; shift ;;
|
||||
--reverse)
|
||||
MODE="reverse"; shift ;;
|
||||
--all)
|
||||
MAP_ALL="true"; shift ;;
|
||||
--critical)
|
||||
CRITICAL="true"; shift ;;
|
||||
--depth)
|
||||
DEPTH="$2"; shift 2 ;;
|
||||
--format)
|
||||
FORMAT="$2"; shift 2 ;;
|
||||
--no-color)
|
||||
COLOR="never"; shift ;;
|
||||
--help|-h)
|
||||
usage; exit 0 ;;
|
||||
-*)
|
||||
err "Unknown option: $1"; usage; exit 1 ;;
|
||||
*)
|
||||
SERVICE="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$MAP_ALL" == "false" && "$CRITICAL" == "false" && -z "$SERVICE" ]]; then
|
||||
err "No service specified. Use --all, --critical, or provide a service name."
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate format
|
||||
case "$FORMAT" in
|
||||
tree|flat|dot|json) ;;
|
||||
*) err "Unknown format: $FORMAT"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# Validate depth is a positive integer
|
||||
if ! [[ "$DEPTH" =~ ^[0-9]+$ ]] || [[ "$DEPTH" -lt 1 ]]; then
|
||||
err "Depth must be a positive integer"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# HELPERS
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Normalize unit name — append .service if no suffix present
|
||||
normalize_unit() {
|
||||
local unit="$1"
|
||||
if [[ "$unit" != *.* ]]; then
|
||||
echo "${unit}.service"
|
||||
else
|
||||
echo "$unit"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get active state of a unit
|
||||
get_active_state() {
|
||||
systemctl show -p ActiveState --value "$1" 2>/dev/null || echo "unknown"
|
||||
}
|
||||
|
||||
# Get load state of a unit
|
||||
get_load_state() {
|
||||
systemctl show -p LoadState --value "$1" 2>/dev/null || echo "unknown"
|
||||
}
|
||||
|
||||
# Get unit description
|
||||
get_description() {
|
||||
systemctl show -p Description --value "$1" 2>/dev/null || echo ""
|
||||
}
|
||||
|
||||
# Color a unit name based on its active state
|
||||
color_unit() {
|
||||
local unit="$1"
|
||||
local state
|
||||
state="$(get_active_state "$unit")"
|
||||
case "$state" in
|
||||
active) echo -e "${GREEN}${unit}${RESET}" ;;
|
||||
failed) echo -e "${RED}${unit}${RESET}" ;;
|
||||
inactive) echo -e "${YELLOW}${unit}${RESET}" ;;
|
||||
*) echo -e "${DIM}${unit}${RESET}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Get list of all active units (services, sockets, timers, targets)
|
||||
get_active_units() {
|
||||
systemctl list-units --type=service,socket,timer,target --state=active \
|
||||
--no-pager --no-legend --plain 2>/dev/null | awk '{print $1}'
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# DEPENDENCY COLLECTION
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Collect dependencies using systemctl list-dependencies
|
||||
# Args: unit, direction (forward|reverse), max_depth
|
||||
# Outputs lines: "depth unit" (tab-indented raw from systemctl, parsed)
|
||||
collect_deps() {
|
||||
local unit="$1"
|
||||
local direction="$2"
|
||||
local max_depth="$3"
|
||||
local reverse_flag=""
|
||||
|
||||
[[ "$direction" == "reverse" ]] && reverse_flag="--reverse"
|
||||
|
||||
# systemctl list-dependencies outputs an indented tree with unicode chars
|
||||
# We parse the depth from leading whitespace and extract the unit name
|
||||
systemctl list-dependencies "$unit" $reverse_flag --no-pager --plain 2>/dev/null | \
|
||||
tail -n +2 | while IFS= read -r line; do
|
||||
# Count leading spaces (each level = 2 spaces)
|
||||
local stripped="${line#"${line%%[! ]*}"}"
|
||||
local spaces=$(( ${#line} - ${#stripped} ))
|
||||
local depth=$(( spaces / 2 ))
|
||||
|
||||
# Respect depth limit
|
||||
if [[ $depth -ge $max_depth ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Clean unit name (remove any tree-drawing characters)
|
||||
local dep_unit
|
||||
dep_unit="$(echo "$stripped" | sed 's/[●○]//g' | xargs)"
|
||||
|
||||
if [[ -n "$dep_unit" ]]; then
|
||||
echo "${depth} ${dep_unit}"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# SERVICE INFO
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print_service_info() {
|
||||
local unit="$1"
|
||||
local active_state load_state description
|
||||
|
||||
active_state="$(get_active_state "$unit")"
|
||||
load_state="$(get_load_state "$unit")"
|
||||
description="$(get_description "$unit")"
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}Service Info${RESET}"
|
||||
echo " ──────────────────────────────────────────────────────────────"
|
||||
echo -e " Name: ${BOLD}${unit}${RESET}"
|
||||
echo -e " Description: ${description}"
|
||||
echo -e " Load State: ${load_state}"
|
||||
|
||||
case "$active_state" in
|
||||
active) echo -e " Active State: ${GREEN}${active_state}${RESET}" ;;
|
||||
failed) echo -e " Active State: ${RED}${active_state}${RESET}" ;;
|
||||
inactive) echo -e " Active State: ${YELLOW}${active_state}${RESET}" ;;
|
||||
*) echo -e " Active State: ${DIM}${active_state}${RESET}" ;;
|
||||
esac
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# OUTPUT FORMATTERS
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
# ── Tree output ───────────────────────────────────────────────────────
|
||||
output_tree() {
|
||||
local unit="$1"
|
||||
local direction="$2"
|
||||
local deps
|
||||
|
||||
deps="$(collect_deps "$unit" "$direction" "$DEPTH")"
|
||||
|
||||
if [[ "$direction" == "forward" ]]; then
|
||||
echo -e "${BOLD}Forward Dependencies${RESET} (what ${unit} requires):"
|
||||
else
|
||||
echo -e "${BOLD}Reverse Dependencies${RESET} (what depends on ${unit}):"
|
||||
fi
|
||||
echo " ──────────────────────────────────────────────────────────────"
|
||||
|
||||
if [[ -z "$deps" ]]; then
|
||||
echo " (none)"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r line; do
|
||||
local depth="${line%% *}"
|
||||
local dep_unit="${line#* }"
|
||||
local indent=""
|
||||
for (( i=0; i<depth; i++ )); do
|
||||
indent+=" "
|
||||
done
|
||||
local colored
|
||||
colored="$(color_unit "$dep_unit")"
|
||||
echo -e " ${indent}├── ${colored}"
|
||||
done <<< "$deps"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ── Flat output ───────────────────────────────────────────────────────
|
||||
output_flat() {
|
||||
local unit="$1"
|
||||
local direction="$2"
|
||||
local deps
|
||||
|
||||
deps="$(collect_deps "$unit" "$direction" "$DEPTH")"
|
||||
|
||||
if [[ -z "$deps" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r line; do
|
||||
local dep_unit="${line#* }"
|
||||
echo "$dep_unit"
|
||||
done <<< "$deps" | sort -u
|
||||
}
|
||||
|
||||
# ── DOT output ────────────────────────────────────────────────────────
|
||||
output_dot() {
|
||||
local unit="$1"
|
||||
local direction="$2"
|
||||
local deps
|
||||
|
||||
deps="$(collect_deps "$unit" "$direction" "$DEPTH")"
|
||||
|
||||
echo "digraph dependencies {"
|
||||
echo " rankdir=LR;"
|
||||
echo " node [shape=box, fontname=\"monospace\", fontsize=10];"
|
||||
echo " edge [color=\"#555555\"];"
|
||||
echo ""
|
||||
|
||||
# Color the root node
|
||||
local root_state
|
||||
root_state="$(get_active_state "$unit")"
|
||||
local root_color="#4CAF50"
|
||||
[[ "$root_state" == "failed" ]] && root_color="#F44336"
|
||||
[[ "$root_state" == "inactive" ]] && root_color="#FF9800"
|
||||
echo " \"${unit}\" [style=filled, fillcolor=\"${root_color}\", fontcolor=white];"
|
||||
echo ""
|
||||
|
||||
if [[ -n "$deps" ]]; then
|
||||
while IFS= read -r line; do
|
||||
local dep_unit="${line#* }"
|
||||
if [[ "$direction" == "forward" ]]; then
|
||||
echo " \"${unit}\" -> \"${dep_unit}\";"
|
||||
else
|
||||
echo " \"${dep_unit}\" -> \"${unit}\";"
|
||||
fi
|
||||
done <<< "$deps"
|
||||
fi
|
||||
|
||||
echo "}"
|
||||
}
|
||||
|
||||
# ── JSON output ───────────────────────────────────────────────────────
|
||||
output_json() {
|
||||
local unit="$1"
|
||||
local direction="$2"
|
||||
local deps active_state load_state description
|
||||
|
||||
deps="$(collect_deps "$unit" "$direction" "$DEPTH")"
|
||||
active_state="$(get_active_state "$unit")"
|
||||
load_state="$(get_load_state "$unit")"
|
||||
description="$(get_description "$unit")"
|
||||
|
||||
echo "{"
|
||||
echo " \"unit\": \"${unit}\","
|
||||
echo " \"active_state\": \"${active_state}\","
|
||||
echo " \"load_state\": \"${load_state}\","
|
||||
echo " \"description\": \"${description}\","
|
||||
echo " \"direction\": \"${direction}\","
|
||||
echo " \"depth_limit\": ${DEPTH},"
|
||||
echo " \"dependencies\": ["
|
||||
|
||||
local first="true"
|
||||
if [[ -n "$deps" ]]; then
|
||||
while IFS= read -r line; do
|
||||
local depth="${line%% *}"
|
||||
local dep_unit="${line#* }"
|
||||
local dep_state
|
||||
dep_state="$(get_active_state "$dep_unit")"
|
||||
|
||||
if [[ "$first" == "true" ]]; then
|
||||
first="false"
|
||||
else
|
||||
echo ","
|
||||
fi
|
||||
printf ' {"unit": "%s", "depth": %s, "active_state": "%s"}' \
|
||||
"$dep_unit" "$depth" "$dep_state"
|
||||
done <<< "$deps"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo " ]"
|
||||
echo "}"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# IMPACT SUMMARY
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print_impact_summary() {
|
||||
local unit="$1"
|
||||
local reverse_deps
|
||||
|
||||
reverse_deps="$(collect_deps "$unit" "reverse" "$DEPTH")"
|
||||
local count=0
|
||||
|
||||
if [[ -n "$reverse_deps" ]]; then
|
||||
count="$(echo "$reverse_deps" | wc -l)"
|
||||
fi
|
||||
|
||||
echo -e "${BOLD}Impact Summary${RESET}"
|
||||
echo " ──────────────────────────────────────────────────────────────"
|
||||
if [[ $count -eq 0 ]]; then
|
||||
echo -e " Stopping ${BOLD}${unit}${RESET} would affect ${GREEN}0${RESET} other units."
|
||||
elif [[ $count -le 3 ]]; then
|
||||
echo -e " Stopping ${BOLD}${unit}${RESET} would affect ${YELLOW}${count}${RESET} other unit(s)."
|
||||
else
|
||||
echo -e " Stopping ${BOLD}${unit}${RESET} would affect ${RED}${count}${RESET} other unit(s)."
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# CRITICAL MODE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
run_critical() {
|
||||
echo ""
|
||||
echo -e "${BOLD}Critical Services — ranked by reverse dependency count${RESET}"
|
||||
echo -e "${DIM}(services with the most units depending on them)${RESET}"
|
||||
echo " ══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
local units
|
||||
units="$(get_active_units)"
|
||||
|
||||
if [[ -z "$units" ]]; then
|
||||
err "No active units found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Collect reverse dep counts
|
||||
local -a results=()
|
||||
while IFS= read -r unit; do
|
||||
local reverse_deps count=0
|
||||
reverse_deps="$(collect_deps "$unit" "reverse" "$DEPTH" 2>/dev/null)"
|
||||
if [[ -n "$reverse_deps" ]]; then
|
||||
count="$(echo "$reverse_deps" | wc -l)"
|
||||
fi
|
||||
if [[ $count -gt 0 ]]; then
|
||||
results+=("${count} ${unit}")
|
||||
fi
|
||||
done <<< "$units"
|
||||
|
||||
# Sort by count descending, show top 20
|
||||
printf '%s\n' "${results[@]}" | sort -rn | head -20 | while IFS= read -r line; do
|
||||
local count="${line%% *}"
|
||||
local unit="${line#* }"
|
||||
local colored
|
||||
colored="$(color_unit "$unit")"
|
||||
|
||||
if [[ $count -ge 10 ]]; then
|
||||
printf " ${RED}%4d${RESET} %b\n" "$count" "$colored"
|
||||
elif [[ $count -ge 5 ]]; then
|
||||
printf " ${YELLOW}%4d${RESET} %b\n" "$count" "$colored"
|
||||
else
|
||||
printf " ${GREEN}%4d${RESET} %b\n" "$count" "$colored"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${DIM} Top 20 shown. Higher count = more impact if stopped.${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# ALL MODE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
run_all() {
|
||||
local units
|
||||
units="$(get_active_units)"
|
||||
|
||||
if [[ -z "$units" ]]; then
|
||||
err "No active units found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}All Active Service Dependencies${RESET} (${MODE} / depth ${DEPTH})"
|
||||
echo " ══════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
while IFS= read -r unit; do
|
||||
case "$FORMAT" in
|
||||
tree)
|
||||
echo -e "${CYAN}■ ${unit}${RESET}"
|
||||
output_tree "$unit" "$MODE"
|
||||
;;
|
||||
flat)
|
||||
echo -e "# ${unit}"
|
||||
output_flat "$unit" "$MODE"
|
||||
echo ""
|
||||
;;
|
||||
dot)
|
||||
output_dot "$unit" "$MODE"
|
||||
;;
|
||||
json)
|
||||
output_json "$unit" "$MODE"
|
||||
;;
|
||||
esac
|
||||
done <<< "$units"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# SINGLE SERVICE MODE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
run_single() {
|
||||
local unit
|
||||
unit="$(normalize_unit "$SERVICE")"
|
||||
|
||||
# Verify the unit exists
|
||||
local load_state
|
||||
load_state="$(get_load_state "$unit")"
|
||||
if [[ "$load_state" == "not-found" ]]; then
|
||||
err "Unit '${unit}' not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$FORMAT" in
|
||||
tree)
|
||||
echo ""
|
||||
echo -e "${BOLD}Service Dependency Map — ${unit}${RESET}"
|
||||
echo -e "${DIM}$(date -u '+%Y-%m-%d %H:%M:%S UTC')${RESET}"
|
||||
|
||||
print_service_info "$unit"
|
||||
|
||||
if [[ "$MODE" == "forward" || "$MODE" == "both" ]]; then
|
||||
output_tree "$unit" "forward"
|
||||
fi
|
||||
if [[ "$MODE" == "reverse" || "$MODE" == "both" ]]; then
|
||||
output_tree "$unit" "reverse"
|
||||
fi
|
||||
|
||||
# Always show both directions for single-service tree mode
|
||||
if [[ "$MODE" == "forward" ]]; then
|
||||
output_tree "$unit" "reverse"
|
||||
elif [[ "$MODE" == "reverse" ]]; then
|
||||
output_tree "$unit" "forward"
|
||||
fi
|
||||
|
||||
print_impact_summary "$unit"
|
||||
;;
|
||||
flat)
|
||||
output_flat "$unit" "$MODE"
|
||||
;;
|
||||
dot)
|
||||
output_dot "$unit" "$MODE"
|
||||
;;
|
||||
json)
|
||||
output_json "$unit" "$MODE"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
setup_colors
|
||||
|
||||
if [[ "$CRITICAL" == "true" ]]; then
|
||||
run_critical
|
||||
elif [[ "$MAP_ALL" == "true" ]]; then
|
||||
run_all
|
||||
else
|
||||
run_single
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user