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
+507
View File
@@ -0,0 +1,507 @@
#!/usr/bin/env bash
#########################################################################################
#### config-backup.sh — Snapshot system configs into a timestamped tarball ####
#### Backs up /etc, crontabs, package lists, systemd units, and firewall rules ####
#### Dry-run by default — nothing is written without --force ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version 1.01 ####
#### ####
#### Usage: ####
#### ./config-backup.sh ####
#### ./config-backup.sh --force ####
#### ####
#### See --help for all options. ####
#########################################################################################
set -euo pipefail
# ── Defaults ──────────────────────────────────────────────────────────
BACKUP_DIR="${BACKUP_DIR:-/var/backups/config-snapshots}"
DRY_RUN="${DRY_RUN:-true}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"
# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
INCLUDE_PATHS=()
EXCLUDE_PATHS=()
STAGING_DIR=""
# ── 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 "${DIM}[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"
}
field_color() {
printf " ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
}
human_bytes() {
local bytes="$1"
if [[ "$bytes" -ge 1073741824 ]]; then
awk "BEGIN { printf \"%.1f GiB\", $bytes / 1073741824 }"
elif [[ "$bytes" -ge 1048576 ]]; then
awk "BEGIN { printf \"%.1f MiB\", $bytes / 1048576 }"
elif [[ "$bytes" -ge 1024 ]]; then
awk "BEGIN { printf \"%.1f KiB\", $bytes / 1024 }"
else
echo "${bytes} B"
fi
}
cleanup_staging() {
if [[ -n "$STAGING_DIR" && -d "$STAGING_DIR" ]]; then
rm -rf "$STAGING_DIR"
verbose "Cleaned up staging directory"
fi
}
is_excluded() {
local path="$1"
for exc in "${EXCLUDE_PATHS[@]}"; do
if [[ "$path" == "$exc" || "$path" == "$exc"/* ]]; then
return 0
fi
done
return 1
}
# ══════════════════════════════════════════════════════════════════════
# COLLECT ITEMS
# ══════════════════════════════════════════════════════════════════════
collect_etc() {
section_header "/etc Configuration"
if [[ ! -d /etc ]]; then
warn "/etc not found"
return
fi
if is_excluded "/etc"; then
log "Skipping /etc (excluded)"
return
fi
local etc_size
etc_size=$(du -sb /etc 2>/dev/null | awk '{print $1}' || echo "0")
field "Size:" "$(human_bytes "$etc_size")"
local etc_files
etc_files=$(find /etc -type f 2>/dev/null | wc -l)
field "Files:" "$etc_files"
if [[ "$DRY_RUN" == "false" ]]; then
cp -a /etc "$STAGING_DIR/etc" 2>/dev/null || warn "Some /etc files could not be copied"
log "Collected /etc"
else
log "[DRY-RUN] Would collect /etc"
fi
}
collect_crontabs() {
section_header "User Crontabs"
local crontab_dir="/var/spool/cron/crontabs"
local count=0
if [[ -d "$crontab_dir" ]]; then
count=$(find "$crontab_dir" -type f 2>/dev/null | wc -l)
field "User crontabs:" "$count"
if [[ "$VERBOSE" == "true" && "$count" -gt 0 ]]; then
find "$crontab_dir" -type f 2>/dev/null | while IFS= read -r f; do
printf " %s\n" "$(basename "$f")"
done
fi
if [[ "$DRY_RUN" == "false" && "$count" -gt 0 ]]; then
mkdir -p "$STAGING_DIR/crontabs"
cp -a "$crontab_dir"/* "$STAGING_DIR/crontabs/" 2>/dev/null || warn "Some crontabs could not be copied"
log "Collected user crontabs"
fi
else
field "User crontabs:" "0 (${crontab_dir} not found)"
fi
# Root crontab via crontab -l
if crontab -l &>/dev/null; then
if [[ "$DRY_RUN" == "false" ]]; then
mkdir -p "$STAGING_DIR/crontabs"
crontab -l > "$STAGING_DIR/crontabs/root-crontab-l.txt" 2>/dev/null || true
fi
field "Root crontab:" "present"
else
field "Root crontab:" "none"
fi
if [[ "$DRY_RUN" == "true" && "$count" -gt 0 ]]; then
log "[DRY-RUN] Would collect crontabs"
fi
}
collect_package_list() {
section_header "Package List"
if command -v dpkg &>/dev/null; then
local dpkg_count
dpkg_count=$(dpkg -l 2>/dev/null | grep -c "^ii" || true)
field "dpkg packages:" "$dpkg_count"
if [[ "$DRY_RUN" == "false" ]]; then
mkdir -p "$STAGING_DIR/packages"
dpkg --get-selections > "$STAGING_DIR/packages/dpkg-selections.txt" 2>/dev/null || true
dpkg -l > "$STAGING_DIR/packages/dpkg-list.txt" 2>/dev/null || true
log "Collected dpkg package list"
else
log "[DRY-RUN] Would collect dpkg package list"
fi
fi
if command -v rpm &>/dev/null; then
local rpm_count
rpm_count=$(rpm -qa 2>/dev/null | wc -l || echo "0")
field "rpm packages:" "$rpm_count"
if [[ "$DRY_RUN" == "false" ]]; then
mkdir -p "$STAGING_DIR/packages"
rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}\n' > "$STAGING_DIR/packages/rpm-list.txt" 2>/dev/null || true
log "Collected rpm package list"
else
log "[DRY-RUN] Would collect rpm package list"
fi
fi
if ! command -v dpkg &>/dev/null && ! command -v rpm &>/dev/null; then
log "No package manager detected (dpkg/rpm)"
fi
}
collect_systemd_units() {
section_header "Systemd Units"
if ! command -v systemctl &>/dev/null; then
log "systemd not available"
return
fi
local enabled_count
enabled_count=$(systemctl list-unit-files --state=enabled --no-legend 2>/dev/null | wc -l)
field "Enabled units:" "$enabled_count"
local custom_count=0
for unit_dir in /etc/systemd/system /etc/systemd/user; do
if [[ -d "$unit_dir" ]]; then
local dir_count
dir_count=$(find "$unit_dir" -maxdepth 1 -name "*.service" -o -name "*.timer" 2>/dev/null | wc -l)
custom_count=$((custom_count + dir_count))
fi
done
field "Custom unit files:" "$custom_count"
if [[ "$DRY_RUN" == "false" ]]; then
mkdir -p "$STAGING_DIR/systemd"
systemctl list-unit-files --no-legend > "$STAGING_DIR/systemd/unit-files.txt" 2>/dev/null || true
for unit_dir in /etc/systemd/system /etc/systemd/user; do
if [[ -d "$unit_dir" ]]; then
cp -a "$unit_dir" "$STAGING_DIR/systemd/" 2>/dev/null || true
fi
done
log "Collected systemd units"
else
log "[DRY-RUN] Would collect systemd units"
fi
}
collect_firewall_rules() {
section_header "Firewall Rules"
local fw_found=false
if command -v iptables &>/dev/null; then
fw_found=true
local ipt_rules
ipt_rules=$(iptables -S 2>/dev/null | wc -l || echo "0")
field "iptables rules:" "$ipt_rules"
if [[ "$DRY_RUN" == "false" ]]; then
mkdir -p "$STAGING_DIR/firewall"
iptables-save > "$STAGING_DIR/firewall/iptables.rules" 2>/dev/null || warn "Could not save iptables rules"
log "Collected iptables rules"
fi
fi
if command -v nft &>/dev/null; then
fw_found=true
local nft_tables
nft_tables=$(nft list tables 2>/dev/null | wc -l || echo "0")
field "nftables tables:" "$nft_tables"
if [[ "$DRY_RUN" == "false" ]]; then
mkdir -p "$STAGING_DIR/firewall"
nft list ruleset > "$STAGING_DIR/firewall/nftables.rules" 2>/dev/null || warn "Could not save nftables rules"
log "Collected nftables rules"
fi
fi
if [[ "$fw_found" == "false" ]]; then
log "No firewall tools detected (iptables, nftables)"
elif [[ "$DRY_RUN" == "true" ]]; then
log "[DRY-RUN] Would collect firewall rules"
fi
}
collect_custom_includes() {
if [[ ${#INCLUDE_PATHS[@]} -eq 0 ]]; then
return
fi
section_header "Custom Includes"
for inc_path in "${INCLUDE_PATHS[@]}"; do
if [[ ! -e "$inc_path" ]]; then
warn "Include path not found: $inc_path"
continue
fi
local inc_size
inc_size=$(du -sb "$inc_path" 2>/dev/null | awk '{print $1}' || echo "0")
field "$inc_path:" "$(human_bytes "$inc_size")"
if [[ "$DRY_RUN" == "false" ]]; then
local dest_dir="$STAGING_DIR/custom${inc_path}"
mkdir -p "$(dirname "$dest_dir")"
cp -a "$inc_path" "$dest_dir" 2>/dev/null || warn "Could not copy $inc_path"
fi
done
if [[ "$DRY_RUN" == "true" ]]; then
log "[DRY-RUN] Would collect custom paths"
else
log "Collected custom paths"
fi
}
# ══════════════════════════════════════════════════════════════════════
# CREATE TARBALL
# ══════════════════════════════════════════════════════════════════════
create_tarball() {
local timestamp hostname_val tarball_name tarball_path
timestamp=$(date '+%Y%m%d-%H%M%S')
hostname_val=$(hostname -s 2>/dev/null || hostname)
tarball_name="config-backup-${hostname_val}-${timestamp}.tar.gz"
tarball_path="${BACKUP_DIR}/${tarball_name}"
section_header "Creating Backup"
field "Output directory:" "$BACKUP_DIR"
field "Tarball:" "$tarball_name"
if [[ "$DRY_RUN" == "true" ]]; then
# Estimate total size
local est_size=0
if [[ -d /etc ]] && ! is_excluded "/etc"; then
est_size=$((est_size + $(du -sb /etc 2>/dev/null | awk '{print $1}' || echo 0)))
fi
for inc_path in "${INCLUDE_PATHS[@]}"; do
if [[ -e "$inc_path" ]]; then
est_size=$((est_size + $(du -sb "$inc_path" 2>/dev/null | awk '{print $1}' || echo 0)))
fi
done
field_color "Estimated size:" "${YELLOW}~$(human_bytes "$est_size") (uncompressed)${RESET}"
echo ""
echo -e " ${YELLOW}Dry-run mode — no backup created${RESET}"
echo -e " Run with --force to create the backup"
return
fi
# Create output directory
mkdir -p "$BACKUP_DIR" || { err "Cannot create ${BACKUP_DIR}"; exit 1; }
# Create tarball from staging
local staging_size
staging_size=$(du -sb "$STAGING_DIR" 2>/dev/null | awk '{print $1}' || echo "0")
field "Staging size:" "$(human_bytes "$staging_size")"
tar -czf "$tarball_path" -C "$STAGING_DIR" . 2>/dev/null || { err "Failed to create tarball"; exit 1; }
# Validate tarball
log "Validating tarball..."
local file_count
file_count=$(tar -tzf "$tarball_path" 2>/dev/null | wc -l)
if [[ "$file_count" -eq 0 ]]; then
err "Tarball validation failed — archive appears empty"
exit 1
fi
local tarball_size
tarball_size=$(stat -c%s "$tarball_path" 2>/dev/null || echo "0")
field_color "Status:" "${GREEN}Success${RESET}"
field "Archive size:" "$(human_bytes "$tarball_size")"
field "Files archived:" "$file_count"
field "Location:" "$tarball_path"
}
# ══════════════════════════════════════════════════════════════════════
# USAGE
# ══════════════════════════════════════════════════════════════════════
usage() {
cat <<EOF
${SCRIPT_NAME} — Snapshot system configs into a timestamped tarball
USAGE:
${SCRIPT_NAME} [OPTIONS]
OPTIONS:
--output-dir DIR Output directory (default: ${BACKUP_DIR})
--include PATH Additional path to include (can be repeated)
--exclude PATH Path to exclude (can be repeated)
--force Actually create backup (disables dry-run)
--verbose Enable debug output
--no-color Disable colored output
--help Show this help
WHAT IS BACKED UP:
/etc System configuration files
User crontabs From /var/spool/cron/crontabs/
Package lists dpkg/rpm package lists
Systemd units Custom unit files from /etc/systemd/
Firewall rules iptables/nftables rule dumps
ENVIRONMENT VARIABLES:
BACKUP_DIR Output directory (default: /var/backups/config-snapshots)
DRY_RUN Dry-run mode (default: true)
COLOR Color mode: auto, always, never (default: auto)
EXAMPLES:
# Dry-run (show what would be backed up)
./config-backup.sh
# Create backup
sudo ./config-backup.sh --force
# Custom output directory
sudo ./config-backup.sh --force --output-dir /tmp/backups
# Include additional paths
sudo ./config-backup.sh --force --include /opt/myapp/config
# Exclude paths
sudo ./config-backup.sh --force --exclude /etc/ssl/private
EOF
}
# ══════════════════════════════════════════════════════════════════════
# ARGUMENT PARSING
# ══════════════════════════════════════════════════════════════════════
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--output-dir)
BACKUP_DIR="$2"; shift 2 ;;
--include)
INCLUDE_PATHS+=("$2"); shift 2 ;;
--exclude)
EXCLUDE_PATHS+=("$2"); shift 2 ;;
--force)
DRY_RUN="false"; shift ;;
--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
}
# ══════════════════════════════════════════════════════════════════════
# MAIN
# ══════════════════════════════════════════════════════════════════════
main() {
parse_args "$@"
setup_colors
echo ""
echo -e "${BOLD}Config Backup — $(hostname -f 2>/dev/null || hostname)${RESET}"
if [[ "$DRY_RUN" == "true" ]]; then
echo -e "Safety: ${YELLOW}dry-run (use --force to create backup)${RESET}"
else
echo -e "Safety: ${RED}LIVE — backup will be created${RESET}"
fi
echo -e "Time: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
# Create staging directory for live runs
if [[ "$DRY_RUN" == "false" ]]; then
STAGING_DIR=$(mktemp -d "/tmp/config-backup-XXXXXX")
trap cleanup_staging EXIT
verbose "Staging directory: $STAGING_DIR"
fi
collect_etc
collect_crontabs
collect_package_list
collect_systemd_units
collect_firewall_rules
collect_custom_includes
create_tarball
echo ""
}
main "$@"