mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 15:23:11 +02:00
Add custom entries protection to hosts install.sh
- Track custom blocked entries in /etc/hosts.custom-entries.state - Block installation if any previously blocked entries are removed - No bypass option - manual chattr removal required for changes - Protects against impulsive unblocking of sites - State file is also protected with chattr +i
This commit is contained in:
parent
b33385671f
commit
4cb3a62491
289
hosts/install.sh
289
hosts/install.sh
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Re-run with sudo if not root
|
# Re-run with sudo if not root
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
exec sudo -E bash "$0" "$@"
|
exec sudo -E bash "$0" "$@"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
@ -11,25 +11,164 @@ FLUSH_DNS=0
|
|||||||
|
|
||||||
# Parse CLI flags
|
# Parse CLI flags
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
--flush-dns)
|
--flush-dns)
|
||||||
FLUSH_DNS=1
|
FLUSH_DNS=1
|
||||||
;;
|
;;
|
||||||
--no-flush-dns)
|
--no-flush-dns)
|
||||||
FLUSH_DNS=0
|
FLUSH_DNS=0
|
||||||
;;
|
;;
|
||||||
-h | --help)
|
-h | --help)
|
||||||
echo "Usage: $0 [--flush-dns|--no-flush-dns]"
|
echo "Usage: $0 [--flush-dns|--no-flush-dns]"
|
||||||
exit 0
|
exit 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# CUSTOM ENTRIES PROTECTION MECHANISM
|
||||||
|
# ============================================================================
|
||||||
|
# This prevents easy removal of custom blocked entries by requiring that:
|
||||||
|
# 1. New installation has AT LEAST as many custom entries as before, OR
|
||||||
|
# 2. Any removed entries are replaced by NEW entries not previously blocked
|
||||||
|
# If neither condition is met, installation is blocked.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
CUSTOM_ENTRIES_STATE_FILE="/etc/hosts.custom-entries.state"
|
||||||
|
|
||||||
|
# Extract custom blocked entries from a hosts file or heredoc section
|
||||||
|
# Returns only the "0.0.0.0 domain.com" lines (normalized, sorted, unique)
|
||||||
|
extract_custom_entries_from_script() {
|
||||||
|
# Extract entries from the heredoc in this script (between EOF markers after "Custom blocking entries")
|
||||||
|
local script_path="$1"
|
||||||
|
sed -n '/^# Custom blocking entries$/,/^EOF$/p' "$script_path" |
|
||||||
|
grep -E '^0\.0\.0\.0[[:space:]]+' |
|
||||||
|
awk '{print $2}' |
|
||||||
|
sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract custom entries from the current /etc/hosts (entries after "# Custom blocking entries" marker)
|
||||||
|
extract_custom_entries_from_hosts() {
|
||||||
|
local hosts_file="$1"
|
||||||
|
if [[ ! -f "$hosts_file" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
sed -n '/^# Custom blocking entries$/,$p' "$hosts_file" |
|
||||||
|
grep -E '^0\.0\.0\.0[[:space:]]+' |
|
||||||
|
awk '{print $2}' |
|
||||||
|
sort -u
|
||||||
|
}
|
||||||
|
|
||||||
|
# Load previously saved custom entries state
|
||||||
|
load_saved_custom_entries() {
|
||||||
|
if [[ -f "$CUSTOM_ENTRIES_STATE_FILE" ]]; then
|
||||||
|
sort -u "$CUSTOM_ENTRIES_STATE_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save current custom entries to state file
|
||||||
|
save_custom_entries_state() {
|
||||||
|
local entries="$1"
|
||||||
|
echo "$entries" | sort -u >"$CUSTOM_ENTRIES_STATE_FILE"
|
||||||
|
chmod 644 "$CUSTOM_ENTRIES_STATE_FILE"
|
||||||
|
chattr +i "$CUSTOM_ENTRIES_STATE_FILE" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to count non-empty lines
|
||||||
|
count_lines() {
|
||||||
|
local input="$1"
|
||||||
|
if [[ -z "$input" ]]; then
|
||||||
|
echo 0
|
||||||
|
else
|
||||||
|
echo "$input" | grep -c . 2>/dev/null || echo 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main protection check
|
||||||
|
check_custom_entries_protection() {
|
||||||
|
local script_path
|
||||||
|
script_path="$(readlink -f "$0")"
|
||||||
|
|
||||||
|
# Get new entries from the script's heredoc
|
||||||
|
local new_entries
|
||||||
|
new_entries=$(extract_custom_entries_from_script "$script_path")
|
||||||
|
local new_count
|
||||||
|
new_count=$(count_lines "$new_entries")
|
||||||
|
|
||||||
|
# Get saved/existing entries (prefer state file, fall back to current /etc/hosts)
|
||||||
|
local saved_entries
|
||||||
|
saved_entries=$(load_saved_custom_entries)
|
||||||
|
if [[ -z "$saved_entries" ]]; then
|
||||||
|
# First run or state file missing - extract from current /etc/hosts if it has our marker
|
||||||
|
saved_entries=$(extract_custom_entries_from_hosts "/etc/hosts")
|
||||||
|
fi
|
||||||
|
local saved_count
|
||||||
|
saved_count=$(count_lines "$saved_entries")
|
||||||
|
|
||||||
|
# If no saved state exists, this is first installation - allow it
|
||||||
|
if [[ $saved_count -eq 0 ]]; then
|
||||||
|
echo "ℹ️ First installation detected - no protection check needed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find entries that were removed
|
||||||
|
local removed_entries
|
||||||
|
removed_entries=$(comm -23 <(echo "$saved_entries") <(echo "$new_entries"))
|
||||||
|
local removed_count
|
||||||
|
removed_count=$(count_lines "$removed_entries")
|
||||||
|
|
||||||
|
# Find entries that are new
|
||||||
|
local added_entries
|
||||||
|
added_entries=$(comm -13 <(echo "$saved_entries") <(echo "$new_entries"))
|
||||||
|
local added_count
|
||||||
|
added_count=$(count_lines "$added_entries")
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "📊 Custom Entries Protection Check:"
|
||||||
|
echo " Previously blocked: $saved_count entries"
|
||||||
|
echo " Currently in script: $new_count entries"
|
||||||
|
echo " Removed: $removed_count | Added: $added_count"
|
||||||
|
|
||||||
|
# RULE 1: No entries removed - always OK
|
||||||
|
if [[ $removed_count -eq 0 ]]; then
|
||||||
|
echo " ✅ No entries removed - protection check passed."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# RULE 2: Entries were removed - BLOCK INSTALLATION
|
||||||
|
echo ""
|
||||||
|
echo "============================================================"
|
||||||
|
echo " ❌ INSTALLATION BLOCKED - CUSTOM ENTRIES REMOVED"
|
||||||
|
echo "============================================================"
|
||||||
|
echo ""
|
||||||
|
echo "You are attempting to REMOVE the following blocked entries:"
|
||||||
|
while IFS= read -r entry; do
|
||||||
|
echo " - $entry"
|
||||||
|
done <<<"$removed_entries"
|
||||||
|
echo ""
|
||||||
|
echo "This is NOT allowed. The only way to unblock sites is to:"
|
||||||
|
echo ""
|
||||||
|
echo " 1. Manually edit /etc/hosts (requires removing chattr protection)"
|
||||||
|
echo " 2. Delete the state file /etc/hosts.custom-entries.state"
|
||||||
|
echo " (also protected with chattr)"
|
||||||
|
echo ""
|
||||||
|
echo "These manual steps are intentionally difficult to prevent"
|
||||||
|
echo "impulsive unblocking. If you really need to unblock something,"
|
||||||
|
echo "you'll have to work for it."
|
||||||
|
echo ""
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run the protection check
|
||||||
|
if ! check_custom_entries_protection; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Enable systemd-resolved
|
# Enable systemd-resolved
|
||||||
sudo systemctl enable systemd-resolved
|
sudo systemctl enable systemd-resolved
|
||||||
|
|
||||||
# Remove all attributes from /etc/hosts to allow modifications
|
# Remove all attributes from /etc/hosts to allow modifications
|
||||||
sudo chattr -i -a /etc/hosts 2> /dev/null || true
|
sudo chattr -i -a /etc/hosts 2>/dev/null || true
|
||||||
|
|
||||||
# Source and local cache configuration
|
# Source and local cache configuration
|
||||||
URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts"
|
URL="https://raw.githubusercontent.com/StevenBlack/hosts/master/alternates/fakenews-gambling-porn-social/hosts"
|
||||||
@ -38,33 +177,33 @@ LOCAL_CACHE="/etc/hosts.stevenblack"
|
|||||||
|
|
||||||
# Helpers
|
# Helpers
|
||||||
extract_date_epoch_from_file() {
|
extract_date_epoch_from_file() {
|
||||||
# Grep "# Date:" line and convert to epoch seconds (UTC)
|
# Grep "# Date:" line and convert to epoch seconds (UTC)
|
||||||
local f="$1"
|
local f="$1"
|
||||||
local line
|
local line
|
||||||
line=$(grep -m1 '^# Date:' "$f" 2> /dev/null | sed -E 's/^# Date:[[:space:]]*(.*)[[:space:]]*\(UTC\).*/\1 UTC/')
|
line=$(grep -m1 '^# Date:' "$f" 2>/dev/null | sed -E 's/^# Date:[[:space:]]*(.*)[[:space:]]*\(UTC\).*/\1 UTC/')
|
||||||
if [[ -n $line ]]; then
|
if [[ -n $line ]]; then
|
||||||
date -u -d "$line" +%s 2> /dev/null || echo ""
|
date -u -d "$line" +%s 2>/dev/null || echo ""
|
||||||
else
|
else
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_remote_header() {
|
fetch_remote_header() {
|
||||||
# Try to fetch only the first ~4KB using HTTP Range; fallback to piping to head
|
# Try to fetch only the first ~4KB using HTTP Range; fallback to piping to head
|
||||||
local out="$1"
|
local out="$1"
|
||||||
if curl -LfsS --max-time 10 -H 'Range: bytes=0-4095' "$URL" -o "$out"; then
|
if curl -LfsS --max-time 10 -H 'Range: bytes=0-4095' "$URL" -o "$out"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
# Fallback – may download more, but we only keep first lines
|
# Fallback – may download more, but we only keep first lines
|
||||||
if curl -LfsS --max-time 10 "$URL" | head -n 20 > "$out"; then
|
if curl -LfsS --max-time 10 "$URL" | head -n 20 >"$out"; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
download_remote_full_to() {
|
download_remote_full_to() {
|
||||||
local out="$1"
|
local out="$1"
|
||||||
curl -LfsS "$URL" -o "$out"
|
curl -LfsS "$URL" -o "$out"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Decide whether to use cache or update
|
# Decide whether to use cache or update
|
||||||
@ -73,47 +212,47 @@ trap 'rm -f "$TMP_REMOTE_HEAD"' EXIT
|
|||||||
|
|
||||||
REMOTE_AVAILABLE=0
|
REMOTE_AVAILABLE=0
|
||||||
if fetch_remote_header "$TMP_REMOTE_HEAD"; then
|
if fetch_remote_header "$TMP_REMOTE_HEAD"; then
|
||||||
REMOTE_AVAILABLE=1
|
REMOTE_AVAILABLE=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
NEED_UPDATE=0
|
NEED_UPDATE=0
|
||||||
|
|
||||||
if [[ -f $LOCAL_CACHE ]]; then
|
if [[ -f $LOCAL_CACHE ]]; then
|
||||||
local_epoch=$(extract_date_epoch_from_file "$LOCAL_CACHE")
|
local_epoch=$(extract_date_epoch_from_file "$LOCAL_CACHE")
|
||||||
else
|
else
|
||||||
local_epoch=""
|
local_epoch=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $REMOTE_AVAILABLE -eq 1 ]]; then
|
if [[ $REMOTE_AVAILABLE -eq 1 ]]; then
|
||||||
remote_epoch=$(extract_date_epoch_from_file "$TMP_REMOTE_HEAD")
|
remote_epoch=$(extract_date_epoch_from_file "$TMP_REMOTE_HEAD")
|
||||||
if [[ -n $local_epoch && -n $remote_epoch && $local_epoch -ge $remote_epoch ]]; then
|
if [[ -n $local_epoch && -n $remote_epoch && $local_epoch -ge $remote_epoch ]]; then
|
||||||
echo "Using cached StevenBlack hosts (up-to-date)."
|
echo "Using cached StevenBlack hosts (up-to-date)."
|
||||||
else
|
else
|
||||||
echo "Cached version is missing or outdated; downloading latest StevenBlack hosts..."
|
echo "Cached version is missing or outdated; downloading latest StevenBlack hosts..."
|
||||||
NEED_UPDATE=1
|
NEED_UPDATE=1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ -f $LOCAL_CACHE ]]; then
|
if [[ -f $LOCAL_CACHE ]]; then
|
||||||
echo "No internet; using cached StevenBlack hosts."
|
echo "No internet; using cached StevenBlack hosts."
|
||||||
else
|
else
|
||||||
echo "Error: No internet and no cached StevenBlack hosts found." >&2
|
echo "Error: No internet and no cached StevenBlack hosts found." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure we have a fresh cache if needed
|
# Ensure we have a fresh cache if needed
|
||||||
if [[ $NEED_UPDATE -eq 1 ]]; then
|
if [[ $NEED_UPDATE -eq 1 ]]; then
|
||||||
TMP_DL=$(mktemp)
|
TMP_DL=$(mktemp)
|
||||||
if download_remote_full_to "$TMP_DL"; then
|
if download_remote_full_to "$TMP_DL"; then
|
||||||
# Save raw upstream to cache
|
# Save raw upstream to cache
|
||||||
sudo mv "$TMP_DL" "$LOCAL_CACHE"
|
sudo mv "$TMP_DL" "$LOCAL_CACHE"
|
||||||
sudo chmod 644 "$LOCAL_CACHE"
|
sudo chmod 644 "$LOCAL_CACHE"
|
||||||
echo "Saved latest StevenBlack hosts to cache: $LOCAL_CACHE"
|
echo "Saved latest StevenBlack hosts to cache: $LOCAL_CACHE"
|
||||||
else
|
else
|
||||||
rm -f "$TMP_DL"
|
rm -f "$TMP_DL"
|
||||||
echo "Error: Failed to download latest StevenBlack hosts." >&2
|
echo "Error: Failed to download latest StevenBlack hosts." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Install the base hosts from cache into /etc/hosts
|
# Install the base hosts from cache into /etc/hosts
|
||||||
@ -133,7 +272,7 @@ sudo sed -i 's/^0\.0\.0\.0 messenger\.com/#0.0.0.0 messenger.com/' /etc/hosts
|
|||||||
|
|
||||||
# Add custom entries for YouTube and Discord
|
# Add custom entries for YouTube and Discord
|
||||||
echo "Adding custom entries for YouTube and Discord..."
|
echo "Adding custom entries for YouTube and Discord..."
|
||||||
tee -a /etc/hosts > /dev/null << 'EOF'
|
tee -a /etc/hosts >/dev/null <<'EOF'
|
||||||
|
|
||||||
# Custom blocking entries
|
# Custom blocking entries
|
||||||
# YouTube
|
# YouTube
|
||||||
@ -261,11 +400,27 @@ sudo chattr +i /etc/hosts
|
|||||||
sudo chattr -i /etc/hosts
|
sudo chattr -i /etc/hosts
|
||||||
sudo chattr +a /etc/hosts
|
sudo chattr +a /etc/hosts
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SAVE CUSTOM ENTRIES STATE FOR FUTURE PROTECTION CHECKS
|
||||||
|
# ============================================================================
|
||||||
|
echo "Saving custom entries state for protection mechanism..."
|
||||||
|
script_path="$(readlink -f "$0")"
|
||||||
|
current_custom_entries=$(extract_custom_entries_from_script "$script_path")
|
||||||
|
# Remove immutable from state file if it exists
|
||||||
|
chattr -i "$CUSTOM_ENTRIES_STATE_FILE" 2>/dev/null || true
|
||||||
|
save_custom_entries_state "$current_custom_entries"
|
||||||
|
echo "✅ Custom entries state saved to $CUSTOM_ENTRIES_STATE_FILE"
|
||||||
|
|
||||||
# Optionally flush DNS caches
|
# Optionally flush DNS caches
|
||||||
if [[ $FLUSH_DNS -eq 1 ]]; then
|
if [[ $FLUSH_DNS -eq 1 ]]; then
|
||||||
echo "Flushing DNS caches..."
|
echo "Flushing DNS caches..."
|
||||||
sudo systemd-resolve --flush-caches
|
sudo systemd-resolve --flush-caches
|
||||||
sudo systemctl restart NetworkManager.service
|
sudo systemctl restart NetworkManager.service
|
||||||
else
|
else
|
||||||
echo "DNS cache flush skipped (use --flush-dns to enable)."
|
echo "DNS cache flush skipped (use --flush-dns to enable)."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Installation complete!"
|
||||||
|
echo " Custom entries protection is now active."
|
||||||
|
echo " Removing blocked entries from the script will be blocked."
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user