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:
@@ -0,0 +1,455 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
#########################################################################################
|
||||
#### changelog-diff.sh — Compare installed package versions between snapshots ####
|
||||
#### Supports before/after upgrade diffs, remote host comparison via SSH ####
|
||||
#### Detects additions, removals, upgrades, and downgrades across dpkg and rpm ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### License: MIT ####
|
||||
#### Version 1.00 ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### ./changelog-diff.sh --snapshot ####
|
||||
#### ./changelog-diff.sh --diff snap1.txt snap2.txt ####
|
||||
#### ./changelog-diff.sh --remote user@host ####
|
||||
#### ####
|
||||
#### See --help for all options. ####
|
||||
#########################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ──────────────────────────────────────────────────────────
|
||||
VERBOSE="${VERBOSE:-false}"
|
||||
COLOR="${COLOR:-auto}"
|
||||
SNAPSHOT_DIR="${SNAPSHOT_DIR:-/var/backups/pkg-snapshots}"
|
||||
MODE=""
|
||||
DIFF_FILE_A=""
|
||||
DIFF_FILE_B=""
|
||||
REMOTE_HOST=""
|
||||
|
||||
# ── State ─────────────────────────────────────────────────────────────
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
readonly SCRIPT_NAME
|
||||
|
||||
# ── Colors ────────────────────────────────────────────────────────────
|
||||
setup_colors() {
|
||||
if [[ "$COLOR" == "never" ]]; then
|
||||
RED="" GREEN="" YELLOW="" CYAN="" BOLD="" DIM="" 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="" CYAN="" BOLD="" DIM="" RESET=""
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────
|
||||
log() { echo -e "${CYAN}[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; }
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────
|
||||
section_header() {
|
||||
echo ""
|
||||
echo -e " ${BOLD}${CYAN}── $1 ──${RESET}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
field() {
|
||||
printf " ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
|
||||
}
|
||||
|
||||
detect_package_manager() {
|
||||
if command -v dpkg-query &>/dev/null; then
|
||||
echo "dpkg"
|
||||
elif command -v rpm &>/dev/null; then
|
||||
echo "rpm"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
get_package_list() {
|
||||
local pm
|
||||
pm=$(detect_package_manager)
|
||||
case "$pm" in
|
||||
dpkg)
|
||||
dpkg-query -W -f='${Package} ${Version}\n' 2>/dev/null | sort
|
||||
;;
|
||||
rpm)
|
||||
rpm -qa --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' 2>/dev/null | sort
|
||||
;;
|
||||
*)
|
||||
err "No supported package manager found (need dpkg or rpm)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# SNAPSHOT
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
do_snapshot() {
|
||||
if [[ ! -d "$SNAPSHOT_DIR" ]]; then
|
||||
mkdir -p "$SNAPSHOT_DIR" 2>/dev/null || {
|
||||
err "Cannot create snapshot directory: ${SNAPSHOT_DIR}"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
local timestamp
|
||||
timestamp=$(date +%Y%m%d-%H%M%S)
|
||||
local host
|
||||
host=$(hostname -s 2>/dev/null || hostname)
|
||||
local pm
|
||||
pm=$(detect_package_manager)
|
||||
local snapshot_file="${SNAPSHOT_DIR}/${host}-${pm}-${timestamp}.txt"
|
||||
|
||||
verbose "Package manager: ${pm}"
|
||||
verbose "Snapshot file: ${snapshot_file}"
|
||||
|
||||
get_package_list > "$snapshot_file"
|
||||
|
||||
local pkg_count
|
||||
pkg_count=$(wc -l < "$snapshot_file")
|
||||
|
||||
log "Snapshot saved: ${snapshot_file}"
|
||||
field "Packages:" "$pkg_count"
|
||||
field "Package manager:" "$pm"
|
||||
field "File:" "$snapshot_file"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# DIFF
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
do_diff() {
|
||||
local file_a="$1"
|
||||
local file_b="$2"
|
||||
|
||||
if [[ ! -f "$file_a" ]]; then
|
||||
err "File not found: ${file_a}"
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$file_b" ]]; then
|
||||
err "File not found: ${file_b}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}Package Diff${RESET}"
|
||||
field "Before:" "$file_a"
|
||||
field "After:" "$file_b"
|
||||
|
||||
local added=0 removed=0 upgraded=0 downgraded=0
|
||||
|
||||
# Build associative arrays
|
||||
local tmp_added tmp_removed tmp_changed
|
||||
tmp_added=$(mktemp)
|
||||
tmp_removed=$(mktemp)
|
||||
tmp_changed=$(mktemp)
|
||||
trap 'rm -f "$tmp_added" "$tmp_removed" "$tmp_changed"' EXIT
|
||||
|
||||
# Find added packages (in B but not A)
|
||||
while IFS=' ' read -r pkg ver; do
|
||||
if ! grep -q "^${pkg} " "$file_a"; then
|
||||
echo "${pkg} ${ver}" >> "$tmp_added"
|
||||
fi
|
||||
done < "$file_b"
|
||||
|
||||
# Find removed packages (in A but not B)
|
||||
while IFS=' ' read -r pkg ver; do
|
||||
if ! grep -q "^${pkg} " "$file_b"; then
|
||||
echo "${pkg} ${ver}" >> "$tmp_removed"
|
||||
fi
|
||||
done < "$file_a"
|
||||
|
||||
# Find changed packages
|
||||
while IFS=' ' read -r pkg ver_b; do
|
||||
local ver_a
|
||||
ver_a=$(grep "^${pkg} " "$file_a" 2>/dev/null | head -1 | cut -d' ' -f2-)
|
||||
if [[ -n "$ver_a" && "$ver_a" != "$ver_b" ]]; then
|
||||
echo "${pkg} ${ver_a} ${ver_b}" >> "$tmp_changed"
|
||||
fi
|
||||
done < "$file_b"
|
||||
|
||||
# Display additions
|
||||
if [[ -s "$tmp_added" ]]; then
|
||||
section_header "Added Packages"
|
||||
while IFS=' ' read -r pkg ver; do
|
||||
printf " ${CYAN}+${RESET} %-40s %s\n" "$pkg" "$ver"
|
||||
added=$((added + 1))
|
||||
done < "$tmp_added"
|
||||
fi
|
||||
|
||||
# Display removals
|
||||
if [[ -s "$tmp_removed" ]]; then
|
||||
section_header "Removed Packages"
|
||||
while IFS=' ' read -r pkg ver; do
|
||||
printf " ${RED}-${RESET} %-40s %s\n" "$pkg" "$ver"
|
||||
removed=$((removed + 1))
|
||||
done < "$tmp_removed"
|
||||
fi
|
||||
|
||||
# Display upgrades and downgrades
|
||||
if [[ -s "$tmp_changed" ]]; then
|
||||
local has_upgrades=false
|
||||
local has_downgrades=false
|
||||
|
||||
# First pass: categorize
|
||||
while IFS=' ' read -r pkg ver_a ver_b; do
|
||||
if dpkg --compare-versions "$ver_b" gt "$ver_a" 2>/dev/null; then
|
||||
has_upgrades=true
|
||||
elif dpkg --compare-versions "$ver_b" lt "$ver_a" 2>/dev/null; then
|
||||
has_downgrades=true
|
||||
else
|
||||
# Fallback: string comparison
|
||||
if [[ "$ver_b" > "$ver_a" ]]; then
|
||||
has_upgrades=true
|
||||
else
|
||||
has_downgrades=true
|
||||
fi
|
||||
fi
|
||||
done < "$tmp_changed"
|
||||
|
||||
if [[ "$has_upgrades" == "true" ]]; then
|
||||
section_header "Upgraded Packages"
|
||||
while IFS=' ' read -r pkg ver_a ver_b; do
|
||||
local is_upgrade=false
|
||||
if dpkg --compare-versions "$ver_b" gt "$ver_a" 2>/dev/null; then
|
||||
is_upgrade=true
|
||||
elif ! dpkg --compare-versions "$ver_b" lt "$ver_a" 2>/dev/null && [[ "$ver_b" > "$ver_a" ]]; then
|
||||
is_upgrade=true
|
||||
fi
|
||||
if [[ "$is_upgrade" == "true" ]]; then
|
||||
printf " ${GREEN}↑${RESET} %-35s %s → %s\n" "$pkg" "$ver_a" "$ver_b"
|
||||
upgraded=$((upgraded + 1))
|
||||
fi
|
||||
done < "$tmp_changed"
|
||||
fi
|
||||
|
||||
if [[ "$has_downgrades" == "true" ]]; then
|
||||
section_header "Downgraded Packages"
|
||||
while IFS=' ' read -r pkg ver_a ver_b; do
|
||||
local is_downgrade=false
|
||||
if dpkg --compare-versions "$ver_b" lt "$ver_a" 2>/dev/null; then
|
||||
is_downgrade=true
|
||||
elif ! dpkg --compare-versions "$ver_b" gt "$ver_a" 2>/dev/null && [[ "$ver_b" < "$ver_a" ]]; then
|
||||
is_downgrade=true
|
||||
fi
|
||||
if [[ "$is_downgrade" == "true" ]]; then
|
||||
printf " ${YELLOW}↓${RESET} %-35s %s → %s\n" "$pkg" "$ver_a" "$ver_b"
|
||||
downgraded=$((downgraded + 1))
|
||||
fi
|
||||
done < "$tmp_changed"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Summary
|
||||
local total=$((added + removed + upgraded + downgraded))
|
||||
|
||||
echo ""
|
||||
echo -e " ${BOLD}══════════════════════════════════════════${RESET}"
|
||||
echo -e " ${BOLD}Change Summary${RESET}"
|
||||
echo -e " ${BOLD}══════════════════════════════════════════${RESET}"
|
||||
field "Total changes:" "$total"
|
||||
if [[ "$added" -gt 0 ]]; then
|
||||
printf " ${BOLD}%-22s${RESET} ${CYAN}%s${RESET}\n" "Additions:" "$added"
|
||||
else
|
||||
field "Additions:" "0"
|
||||
fi
|
||||
if [[ "$removed" -gt 0 ]]; then
|
||||
printf " ${BOLD}%-22s${RESET} ${RED}%s${RESET}\n" "Removals:" "$removed"
|
||||
else
|
||||
field "Removals:" "0"
|
||||
fi
|
||||
if [[ "$upgraded" -gt 0 ]]; then
|
||||
printf " ${BOLD}%-22s${RESET} ${GREEN}%s${RESET}\n" "Upgrades:" "$upgraded"
|
||||
else
|
||||
field "Upgrades:" "0"
|
||||
fi
|
||||
if [[ "$downgraded" -gt 0 ]]; then
|
||||
printf " ${BOLD}%-22s${RESET} ${YELLOW}%s${RESET}\n" "Downgrades:" "$downgraded"
|
||||
else
|
||||
field "Downgrades:" "0"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# REMOTE COMPARE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
do_remote() {
|
||||
local remote="$1"
|
||||
|
||||
if ! command -v ssh &>/dev/null; then
|
||||
err "ssh is required for remote comparison"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Fetching local package list..."
|
||||
local local_file
|
||||
local_file=$(mktemp)
|
||||
|
||||
log "Fetching remote package list from ${remote}..."
|
||||
local remote_file
|
||||
remote_file=$(mktemp)
|
||||
trap 'rm -f "$local_file" "$remote_file"' EXIT
|
||||
|
||||
get_package_list > "$local_file"
|
||||
|
||||
local pm
|
||||
pm=$(detect_package_manager)
|
||||
case "$pm" in
|
||||
dpkg)
|
||||
ssh "$remote" "dpkg-query -W -f='\${Package} \${Version}\n' 2>/dev/null | sort" > "$remote_file" || {
|
||||
err "Failed to fetch package list from ${remote}"
|
||||
exit 1
|
||||
}
|
||||
;;
|
||||
rpm)
|
||||
ssh "$remote" "rpm -qa --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' 2>/dev/null | sort" > "$remote_file" || {
|
||||
err "Failed to fetch package list from ${remote}"
|
||||
exit 1
|
||||
}
|
||||
;;
|
||||
esac
|
||||
|
||||
log "Comparing local vs ${remote}..."
|
||||
do_diff "$local_file" "$remote_file"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# USAGE
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
${SCRIPT_NAME} — Compare installed package versions between snapshots
|
||||
|
||||
USAGE:
|
||||
${SCRIPT_NAME} --snapshot
|
||||
${SCRIPT_NAME} --diff <before.txt> <after.txt>
|
||||
${SCRIPT_NAME} --remote <user@host>
|
||||
|
||||
MODES:
|
||||
--snapshot Save current package list to a timestamped file
|
||||
--diff FILE1 FILE2 Compare two snapshot files
|
||||
--remote HOST Compare local packages with a remote host via SSH
|
||||
|
||||
OPTIONS:
|
||||
--snapshot-dir DIR Directory for snapshots (default: ${SNAPSHOT_DIR})
|
||||
--verbose Enable debug output
|
||||
--no-color Disable colored output
|
||||
--help Show this help
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
SNAPSHOT_DIR Snapshot directory (default: /var/backups/pkg-snapshots)
|
||||
COLOR Color mode: auto, always, never (default: auto)
|
||||
|
||||
EXAMPLES:
|
||||
# Take a snapshot before upgrade
|
||||
./changelog-diff.sh --snapshot
|
||||
|
||||
# Upgrade packages, take another snapshot, then diff
|
||||
./changelog-diff.sh --snapshot
|
||||
sudo apt upgrade -y
|
||||
./changelog-diff.sh --snapshot
|
||||
./changelog-diff.sh --diff /var/backups/pkg-snapshots/host-*.txt
|
||||
|
||||
# Compare with a remote server
|
||||
./changelog-diff.sh --remote admin@prod-server
|
||||
|
||||
# Custom snapshot directory
|
||||
./changelog-diff.sh --snapshot --snapshot-dir /tmp/snapshots
|
||||
EOF
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# ARGUMENT PARSING
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--snapshot)
|
||||
MODE="snapshot"; shift ;;
|
||||
--diff)
|
||||
MODE="diff"
|
||||
if [[ $# -lt 3 ]]; then
|
||||
err "--diff requires two file arguments"
|
||||
exit 1
|
||||
fi
|
||||
DIFF_FILE_A="$2"
|
||||
DIFF_FILE_B="$3"
|
||||
shift 3 ;;
|
||||
--remote)
|
||||
MODE="remote"
|
||||
if [[ $# -lt 2 ]]; then
|
||||
err "--remote requires a host argument"
|
||||
exit 1
|
||||
fi
|
||||
REMOTE_HOST="$2"
|
||||
shift 2 ;;
|
||||
--snapshot-dir)
|
||||
SNAPSHOT_DIR="$2"; shift 2 ;;
|
||||
--verbose)
|
||||
VERBOSE="true"; shift ;;
|
||||
--no-color)
|
||||
COLOR="never"; shift ;;
|
||||
--help|-h)
|
||||
setup_colors
|
||||
usage
|
||||
exit 0 ;;
|
||||
*)
|
||||
err "Unknown option: $1"
|
||||
echo "Run ${SCRIPT_NAME} --help for usage" >&2
|
||||
exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$MODE" ]]; then
|
||||
err "No mode specified. Use --snapshot, --diff, or --remote"
|
||||
echo "Run ${SCRIPT_NAME} --help for usage" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# MAIN
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
setup_colors
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}Package Changelog Diff — $(hostname -f 2>/dev/null || hostname)${RESET}"
|
||||
echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}"
|
||||
|
||||
case "$MODE" in
|
||||
snapshot)
|
||||
do_snapshot
|
||||
;;
|
||||
diff)
|
||||
do_diff "$DIFF_FILE_A" "$DIFF_FILE_B"
|
||||
;;
|
||||
remote)
|
||||
do_remote "$REMOTE_HOST"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user