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,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 "$@"
|
||||
Reference in New Issue
Block a user