From 5afe9c1601028db60ad1f7953af8f4b0d8b11466 Mon Sep 17 00:00:00 2001 From: Djeex Date: Sat, 31 May 2025 19:46:12 +0000 Subject: [PATCH 1/6] First python commit --- Dockerfile | 23 +++++-- docker-compose.yml | 1 + entrypoint.py | 61 +++++++++++++++++ entrypoint.sh | 14 ---- update-blocklist.py | 156 ++++++++++++++++++++++++++++++++++++++++++++ update-blocklist.sh | 75 --------------------- 6 files changed, 235 insertions(+), 95 deletions(-) create mode 100644 entrypoint.py delete mode 100644 entrypoint.sh create mode 100644 update-blocklist.py delete mode 100644 update-blocklist.sh diff --git a/Dockerfile b/Dockerfile index 05db38b..fb42004 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,21 @@ -FROM alpine:latest +FROM python:3.11-slim -RUN apk add --no-cache curl bash busybox tzdata +# Install curl and cron +RUN apt-get update && apt-get install -y curl cron && rm -rf /var/lib/apt/lists/* -COPY update-blocklist.sh /usr/local/bin/update-blocklist.sh -COPY entrypoint.sh /entrypoint.sh +# Install Python requests +RUN pip install --no-cache-dir requests -RUN chmod +x /usr/local/bin/update-blocklist.sh /entrypoint.sh +# Create adguard config dir +RUN mkdir -p /adguard -ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file +# Copy update-blocklist script +COPY update-blocklist.py /usr/local/bin/update-blocklist.py +RUN chmod +x /usr/local/bin/update-blocklist.py + +# Copy entrypoint script (on next step) + +# Setup cron config dir +RUN mkdir -p /etc/crontabs + +ENTRYPOINT ["/usr/local/bin/entrypoint.py"] diff --git a/docker-compose.yml b/docker-compose.yml index f56559c..1594fa8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ services: adguard-cidre: build: . container_name: adguard-cidre + restart: unless-stopped environment: - TZ=Europe/Paris # change to your timezone - BLOCK_COUNTRIES=cn,ru,ir # choose countries listed IP to block. Full lists here https://github.com/vulnebify/cidre/tree/main/output/cidr/ipv4 diff --git a/entrypoint.py b/entrypoint.py new file mode 100644 index 0000000..7116c05 --- /dev/null +++ b/entrypoint.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +import os +import sys +import subprocess +import logging +from pathlib import Path + +logging.basicConfig( + level=logging.INFO, + format='[entrypoint] %(message)s', + stream=sys.stdout +) + +ADGUARD_YAML = Path("/adguard/AdGuardHome.yaml") +FIRST_BACKUP = Path("/adguard/AdGuardHome.yaml.first-start.bak") + +def backup_first_start(): + if not FIRST_BACKUP.exists(): + logging.info("Creating first start backup...") + FIRST_BACKUP.write_text(ADGUARD_YAML.read_text()) + else: + logging.info("First start backup already exists.") + +def run_initial_update(): + logging.info("Running initial update-blocklist.py script...") + try: + subprocess.run( + ["/usr/local/bin/update-blocklist.py"], + check=True, + stdout=sys.stdout, + stderr=sys.stderr, + ) + except subprocess.CalledProcessError as e: + logging.error(f"Initial update script failed: {e}") + sys.exit(1) + +def setup_cron(): + cron_expr = os.getenv("BLOCKLIST_CRON", "0 6 * * *") + cron_line = f"{cron_expr} root /usr/local/bin/update-blocklist.py\n" + cron_file = "/etc/crontabs/root" + logging.info(f"Setting cron job: {cron_line.strip()}") + with open(cron_file, "w") as f: + f.write(cron_line) + +def start_cron_foreground(): + logging.info("Starting cron in foreground...") + os.execvp("crond", ["crond", "-f"]) + +def main(): + # Check AdGuardHome.yaml exists + if not ADGUARD_YAML.exists(): + logging.error(f"{ADGUARD_YAML} not found. Exiting.") + sys.exit(1) + + backup_first_start() + run_initial_update() + setup_cron() + start_cron_foreground() + +if __name__ == "__main__": + main() diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index a4a93fa..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -set -e - -if [ -n "$TZ" ]; then - if [ -f "/usr/share/zoneinfo/$TZ" ]; then - cp "/usr/share/zoneinfo/$TZ" /etc/localtime - echo "$TZ" > /etc/timezone - fi -fi - -CRON_EXPR="${BLOCKLIST_CRON:-"0 6 * * *"}" -echo "$CRON_EXPR /usr/local/bin/update-blocklist.sh" > /etc/crontabs/root - -exec crond -f -c /etc/crontabs \ No newline at end of file diff --git a/update-blocklist.py b/update-blocklist.py new file mode 100644 index 0000000..79f5a60 --- /dev/null +++ b/update-blocklist.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +import os +import sys +import shutil +import logging +import re +import requests +from pathlib import Path + +logging.basicConfig( + level=logging.INFO, + format='[update-blocklist] %(levelname)s: %(message)s', + stream=sys.stdout +) + +# Config / variables +ADGUARD_YAML = Path("/adguard/AdGuardHome.yaml") +FIRST_BACKUP = Path("/adguard/AdGuardHome.yaml.first-start.bak") +LAST_CRON_BACKUP = Path("/adguard/AdGuardHome.yaml.last-cron.bak") +MANUAL_IPS_FILE = Path("/adguard/manually_blocked_ips.conf") +CIDR_BASE_URL = "https://raw.githubusercontent.com/vulnebify/cidre/main/output/cidr/ipv4" +COUNTRIES = os.getenv("BLOCK_COUNTRIES", "") +DOCKER_API_URL = os.getenv("DOCKER_API_URL", "http://socket-proxy-adguard:2375") +CONTAINER_NAME = os.getenv("ADGUARD_CONTAINER_NAME", "adguard-home") +TMP_YAML = Path("/tmp/AdGuardHome.yaml") +TMP_DIR = Path("/tmp/cidr") + +def backup_first_start(): + if not FIRST_BACKUP.exists(): + logging.info(f"Creating first-start backup: {FIRST_BACKUP}") + shutil.copy2(ADGUARD_YAML, FIRST_BACKUP) + else: + logging.info("First-start backup already exists, skipping.") + +def backup_last_cron(): + logging.info(f"Creating last-cron backup: {LAST_CRON_BACKUP}") + shutil.copy2(ADGUARD_YAML, LAST_CRON_BACKUP) + +def download_cidr_lists(countries): + if not countries: + logging.error("No countries specified in BLOCK_COUNTRIES environment variable.") + sys.exit(1) + + TMP_DIR.mkdir(parents=True, exist_ok=True) + all_ips = [] + + codes = [c.strip().lower() for c in countries.split(",") if c.strip()] + for code in codes: + url = f"{CIDR_BASE_URL}/{code}.cidr" + logging.info(f"Downloading CIDR list for {code} from {url}") + try: + r = requests.get(url, timeout=15) + r.raise_for_status() + lines = r.text.strip().splitlines() + logging.info(f"Downloaded {len(lines)} CIDR entries for {code}") + all_ips.extend(lines) + except Exception as e: + logging.warning(f"Failed to download {url}: {e}") + + return all_ips + +def read_manual_ips(): + ips = [] + if MANUAL_IPS_FILE.exists(): + logging.info(f"Reading manual IPs from {MANUAL_IPS_FILE}") + try: + with MANUAL_IPS_FILE.open() as f: + for line in f: + line = line.strip() + if re.match(r'^(\d{1,3}\.){3}\d{1,3}(/\d{1,2})?$', line): + ips.append(line) + else: + logging.debug(f"Ignoring invalid manual IP line: {line}") + logging.info(f"Read {len(ips)} valid manual IP entries") + except Exception as e: + logging.warning(f"Error reading manual IPs: {e}") + else: + logging.info("Manual IPs file does not exist, skipping.") + return ips + +def format_ips_yaml_list(ips): + return [f" - {ip}\n" for ip in ips] + +def update_yaml_with_ips(ips): + if not ADGUARD_YAML.exists(): + logging.error(f"AdGuardHome.yaml not found at {ADGUARD_YAML}") + sys.exit(1) + + with ADGUARD_YAML.open() as f: + lines = f.readlines() + + new_lines = [] + inside_disallowed = False + ips_inserted = False + + for line in lines: + stripped = line.rstrip("\n") + + if stripped.startswith(" disallowed_clients:"): + # Write key line without any value (no [] etc) + new_lines.append(" disallowed_clients:\n") + # Insert ips + if ips: + new_lines.extend(format_ips_yaml_list(ips)) + # mark inserted + inside_disallowed = True + ips_inserted = True + continue + + if inside_disallowed: + # skip old IP entries starting with ' - ' + if stripped.startswith(" - "): + continue + else: + inside_disallowed = False + + new_lines.append(line) + + if not ips_inserted: + # disallowed_clients not found - append at end + new_lines.append("\n disallowed_clients:\n") + if ips: + new_lines.extend(format_ips_yaml_list(ips)) + + with TMP_YAML.open("w") as f: + f.writelines(new_lines) + + TMP_YAML.replace(ADGUARD_YAML) + logging.info(f"Updated {ADGUARD_YAML} with {len(ips)} disallowed_clients entries") + +def restart_container(): + url = f"{DOCKER_API_URL}/containers/{CONTAINER_NAME}/restart" + logging.info(f"Restarting container '{CONTAINER_NAME}' via {url}") + try: + r = requests.post(url, timeout=10) + if r.status_code == 204: + logging.info("Container restarted successfully.") + else: + logging.error(f"Failed to restart container. Status: {r.status_code} Response: {r.text}") + except Exception as e: + logging.error(f"Exception during container restart: {e}") + +def main(): + backup_first_start() + backup_last_cron() + cidr_ips = download_cidr_lists(COUNTRIES) + manual_ips = read_manual_ips() + combined_ips = cidr_ips + manual_ips + if not combined_ips: + logging.warning("No IPs to add to disallowed_clients. The list will be empty.") + update_yaml_with_ips(combined_ips) + restart_container() + logging.info("Blocklist update complete.") + +if __name__ == "__main__": + main() diff --git a/update-blocklist.sh b/update-blocklist.sh deleted file mode 100644 index cdddeb1..0000000 --- a/update-blocklist.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/bash - -set -e - -ADGUARD_YAML="/adguard/AdGuardHome.yaml" -TMP_YAML="/tmp/AdGuardHome.yaml" -MANUAL_IPS_FILE="/adguard/manually_blocked_ips.conf" -CIDR_BASE_URL="https://raw.githubusercontent.com/vulnebify/cidre/main/output/cidr/ipv4" -COUNTRIES=${BLOCK_COUNTRIES:-""} -DOCKER_API_URL=${DOCKER_API_URL:-"http://socket-proxy-adguard:2375"} -CONTAINER_NAME=${ADGUARD_CONTAINER_NAME:-"adguard-home"} - -if [ -z "$COUNTRIES" ]; then - echo "No countries specified in BLOCK_COUNTRIES." - exit 1 -fi - -mkdir -p /tmp/cidr -> /tmp/cidr/all.txt - -IFS=',' read -ra CODES <<< "$COUNTRIES" -for CODE in "${CODES[@]}"; do - echo "Downloading CIDR list for $CODE..." - curl -sf "$CIDR_BASE_URL/${CODE,,}.cidr" -o "/tmp/cidr/${CODE}.cidr" || continue - cat "/tmp/cidr/${CODE}.cidr" >> /tmp/cidr/all.txt -done - -if [ -f "$MANUAL_IPS_FILE" ]; then - echo "Validating and adding manually blocked IPs from $MANUAL_IPS_FILE..." - grep -E '^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$' "$MANUAL_IPS_FILE" >> /tmp/cidr/all.txt -fi - -# Format IPs as YAML list items -sed 's/^/ - /' /tmp/cidr/all.txt > /tmp/cidr/ips_formatted.txt - -awk ' -BEGIN { - # Read formatted IPs into array - while ((getline line < "/tmp/cidr/ips_formatted.txt") > 0) { - ips[++count] = line - } - close("/tmp/cidr/ips_formatted.txt") - inside=0 -} - -/^ disallowed_clients:/ { - print - inside=1 - next -} - -/^ [^ ]/ && inside==1 { - # Insert all IPs here - for (i=1; i<=count; i++) print ips[i] - inside=0 -} - -{ - if (!inside) print -} - -END { - # If file ended while still inside disallowed_clients section - if (inside==1) { - for (i=1; i<=count; i++) print ips[i] - } -} -' "$ADGUARD_YAML" > "$TMP_YAML" - -mv "$TMP_YAML" "$ADGUARD_YAML" - -echo "Restarting $CONTAINER_NAME container..." -curl -s -X POST "$DOCKER_API_URL/containers/$CONTAINER_NAME/restart" -o /dev/null - -echo "Done." -- 2.47.2 From 17b80fa4467563a784c6dfbc02d86908f496147d Mon Sep 17 00:00:00 2001 From: Djeex Date: Sat, 31 May 2025 20:13:36 +0000 Subject: [PATCH 2/6] Added timezone and fixed entrypoint --- Dockerfile | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index fb42004..927cb31 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,24 @@ FROM python:3.11-slim -# Install curl and cron -RUN apt-get update && apt-get install -y curl cron && rm -rf /var/lib/apt/lists/* +# Install required utilities +RUN apt-get update && apt-get install -y \ + curl \ + cron \ + tzdata \ + && rm -rf /var/lib/apt/lists/* -# Install Python requests -RUN pip install --no-cache-dir requests - -# Create adguard config dir -RUN mkdir -p /adguard - -# Copy update-blocklist script +# Copy scripts COPY update-blocklist.py /usr/local/bin/update-blocklist.py -RUN chmod +x /usr/local/bin/update-blocklist.py +COPY entrypoint.py /usr/local/bin/entrypoint.py -# Copy entrypoint script (on next step) +# Make scripts executable +RUN chmod +x /usr/local/bin/update-blocklist.py /usr/local/bin/entrypoint.py -# Setup cron config dir -RUN mkdir -p /etc/crontabs +# Set default timezone (can be overridden with TZ env var) +ENV TZ=UTC +# Configure timezone (tzdata) — important for /usr/share/zoneinfo/* +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# Set entrypoint ENTRYPOINT ["/usr/local/bin/entrypoint.py"] -- 2.47.2 From d97348cd17d6141c91a94304ee4822ec8f2f35a7 Mon Sep 17 00:00:00 2001 From: Djeex Date: Sat, 31 May 2025 20:48:05 +0000 Subject: [PATCH 3/6] Fixes missing requests + cross device error + cron folder error --- Dockerfile | 8 ++- README.md | 2 +- entrypoint.py | 24 ++++--- update-blocklist.py | 163 +++++++++++++++++--------------------------- 4 files changed, 85 insertions(+), 112 deletions(-) diff --git a/Dockerfile b/Dockerfile index 927cb31..28bf560 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,12 @@ RUN apt-get update && apt-get install -y \ tzdata \ && rm -rf /var/lib/apt/lists/* +# Install python dependencies +RUN pip install --no-cache-dir requests + +# Create crontabs directory (if needed) +RUN mkdir -p /etc/crontabs + # Copy scripts COPY update-blocklist.py /usr/local/bin/update-blocklist.py COPY entrypoint.py /usr/local/bin/entrypoint.py @@ -17,7 +23,7 @@ RUN chmod +x /usr/local/bin/update-blocklist.py /usr/local/bin/entrypoint.py # Set default timezone (can be overridden with TZ env var) ENV TZ=UTC -# Configure timezone (tzdata) — important for /usr/share/zoneinfo/* +# Configure timezone (tzdata) RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone # Set entrypoint diff --git a/README.md b/README.md index 8345e13..3527d2c 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,6 @@ 5. **Check logs to verify updates** ```bash - docker-compose logs -f + docker compose logs -f ``` diff --git a/entrypoint.py b/entrypoint.py index 7116c05..2dd856d 100644 --- a/entrypoint.py +++ b/entrypoint.py @@ -14,6 +14,20 @@ logging.basicConfig( ADGUARD_YAML = Path("/adguard/AdGuardHome.yaml") FIRST_BACKUP = Path("/adguard/AdGuardHome.yaml.first-start.bak") +def setup_cron(): + cron_expr = os.getenv("BLOCKLIST_CRON", "0 6 * * *") + cron_line = f"{cron_expr} root /usr/local/bin/update-blocklist.py\n" + cron_dir = "/etc/crontabs" + cron_file = f"{cron_dir}/root" + + logging.info(f"Setting cron job: {cron_line.strip()}") + + # Ensure cron directory exists + os.makedirs(cron_dir, exist_ok=True) + + with open(cron_file, "w") as f: + f.write(cron_line) + def backup_first_start(): if not FIRST_BACKUP.exists(): logging.info("Creating first start backup...") @@ -34,17 +48,9 @@ def run_initial_update(): logging.error(f"Initial update script failed: {e}") sys.exit(1) -def setup_cron(): - cron_expr = os.getenv("BLOCKLIST_CRON", "0 6 * * *") - cron_line = f"{cron_expr} root /usr/local/bin/update-blocklist.py\n" - cron_file = "/etc/crontabs/root" - logging.info(f"Setting cron job: {cron_line.strip()}") - with open(cron_file, "w") as f: - f.write(cron_line) - def start_cron_foreground(): logging.info("Starting cron in foreground...") - os.execvp("crond", ["crond", "-f"]) + os.execvp("cron", ["cron", "-f"]) def main(): # Check AdGuardHome.yaml exists diff --git a/update-blocklist.py b/update-blocklist.py index 79f5a60..cb25eba 100644 --- a/update-blocklist.py +++ b/update-blocklist.py @@ -1,156 +1,117 @@ #!/usr/bin/env python3 import os import sys -import shutil import logging -import re import requests from pathlib import Path logging.basicConfig( level=logging.INFO, format='[update-blocklist] %(levelname)s: %(message)s', - stream=sys.stdout + stream=sys.stdout, ) -# Config / variables ADGUARD_YAML = Path("/adguard/AdGuardHome.yaml") FIRST_BACKUP = Path("/adguard/AdGuardHome.yaml.first-start.bak") LAST_CRON_BACKUP = Path("/adguard/AdGuardHome.yaml.last-cron.bak") +TMP_YAML = ADGUARD_YAML.parent / (ADGUARD_YAML.name + ".tmp") MANUAL_IPS_FILE = Path("/adguard/manually_blocked_ips.conf") CIDR_BASE_URL = "https://raw.githubusercontent.com/vulnebify/cidre/main/output/cidr/ipv4" COUNTRIES = os.getenv("BLOCK_COUNTRIES", "") -DOCKER_API_URL = os.getenv("DOCKER_API_URL", "http://socket-proxy-adguard:2375") -CONTAINER_NAME = os.getenv("ADGUARD_CONTAINER_NAME", "adguard-home") -TMP_YAML = Path("/tmp/AdGuardHome.yaml") -TMP_DIR = Path("/tmp/cidr") -def backup_first_start(): +def backup_files(): if not FIRST_BACKUP.exists(): logging.info(f"Creating first-start backup: {FIRST_BACKUP}") - shutil.copy2(ADGUARD_YAML, FIRST_BACKUP) + FIRST_BACKUP.write_text(ADGUARD_YAML.read_text()) else: logging.info("First-start backup already exists, skipping.") -def backup_last_cron(): logging.info(f"Creating last-cron backup: {LAST_CRON_BACKUP}") - shutil.copy2(ADGUARD_YAML, LAST_CRON_BACKUP) + LAST_CRON_BACKUP.write_text(ADGUARD_YAML.read_text()) def download_cidr_lists(countries): - if not countries: - logging.error("No countries specified in BLOCK_COUNTRIES environment variable.") - sys.exit(1) - - TMP_DIR.mkdir(parents=True, exist_ok=True) - all_ips = [] - - codes = [c.strip().lower() for c in countries.split(",") if c.strip()] - for code in codes: - url = f"{CIDR_BASE_URL}/{code}.cidr" + combined_ips = [] + for code in countries: + url = f"{CIDR_BASE_URL}/{code.lower()}.cidr" logging.info(f"Downloading CIDR list for {code} from {url}") try: - r = requests.get(url, timeout=15) + r = requests.get(url, timeout=30) r.raise_for_status() - lines = r.text.strip().splitlines() - logging.info(f"Downloaded {len(lines)} CIDR entries for {code}") - all_ips.extend(lines) + ips = r.text.strip().splitlines() + logging.info(f"Downloaded {len(ips)} CIDR entries for {code}") + combined_ips.extend(ips) except Exception as e: - logging.warning(f"Failed to download {url}: {e}") - - return all_ips + logging.warning(f"Failed to download {code}: {e}") + return combined_ips def read_manual_ips(): - ips = [] if MANUAL_IPS_FILE.exists(): logging.info(f"Reading manual IPs from {MANUAL_IPS_FILE}") - try: - with MANUAL_IPS_FILE.open() as f: - for line in f: - line = line.strip() - if re.match(r'^(\d{1,3}\.){3}\d{1,3}(/\d{1,2})?$', line): - ips.append(line) - else: - logging.debug(f"Ignoring invalid manual IP line: {line}") - logging.info(f"Read {len(ips)} valid manual IP entries") - except Exception as e: - logging.warning(f"Error reading manual IPs: {e}") + valid_ips = [] + with MANUAL_IPS_FILE.open() as f: + for line in f: + line = line.strip() + # Simple regex match for IPv4 or IPv4 CIDR + if line and line.count('.') == 3: + valid_ips.append(line) + logging.info(f"Added {len(valid_ips)} manual IP entries") + return valid_ips else: logging.info("Manual IPs file does not exist, skipping.") - return ips - -def format_ips_yaml_list(ips): - return [f" - {ip}\n" for ip in ips] + return [] def update_yaml_with_ips(ips): - if not ADGUARD_YAML.exists(): - logging.error(f"AdGuardHome.yaml not found at {ADGUARD_YAML}") - sys.exit(1) + # Format IPs for YAML list (4 spaces indent + dash) + formatted_ips = [f" - {ip}" for ip in ips] + + inside_disallowed = False + output_lines = [] with ADGUARD_YAML.open() as f: - lines = f.readlines() - - new_lines = [] - inside_disallowed = False - ips_inserted = False - - for line in lines: - stripped = line.rstrip("\n") - - if stripped.startswith(" disallowed_clients:"): - # Write key line without any value (no [] etc) - new_lines.append(" disallowed_clients:\n") - # Insert ips - if ips: - new_lines.extend(format_ips_yaml_list(ips)) - # mark inserted - inside_disallowed = True - ips_inserted = True - continue - - if inside_disallowed: - # skip old IP entries starting with ' - ' - if stripped.startswith(" - "): - continue + for line in f: + if line.strip().startswith("disallowed_clients:"): + # Replace existing disallowed_clients block + output_lines.append("disallowed_clients:") + output_lines.extend(formatted_ips) + inside_disallowed = True + elif inside_disallowed: + # Skip old lines under disallowed_clients (assuming indentation) + if line.startswith(" ") and not line.startswith(" -"): + # This is a new section, disallowed_clients block ended + inside_disallowed = False + output_lines.append(line.rstrip("\n")) + # Else skip line inside disallowed_clients block else: - inside_disallowed = False + output_lines.append(line.rstrip("\n")) - new_lines.append(line) - - if not ips_inserted: - # disallowed_clients not found - append at end - new_lines.append("\n disallowed_clients:\n") - if ips: - new_lines.extend(format_ips_yaml_list(ips)) + # If the file ended while still inside disallowed_clients block, append nothing more (already done) + # Write to temporary YAML in same folder (to avoid cross-device rename error) with TMP_YAML.open("w") as f: - f.writelines(new_lines) + f.write("\n".join(output_lines) + "\n") + # Atomic replace TMP_YAML.replace(ADGUARD_YAML) - logging.info(f"Updated {ADGUARD_YAML} with {len(ips)} disallowed_clients entries") - -def restart_container(): - url = f"{DOCKER_API_URL}/containers/{CONTAINER_NAME}/restart" - logging.info(f"Restarting container '{CONTAINER_NAME}' via {url}") - try: - r = requests.post(url, timeout=10) - if r.status_code == 204: - logging.info("Container restarted successfully.") - else: - logging.error(f"Failed to restart container. Status: {r.status_code} Response: {r.text}") - except Exception as e: - logging.error(f"Exception during container restart: {e}") + logging.info(f"Updated {ADGUARD_YAML} with new disallowed clients list.") def main(): - backup_first_start() - backup_last_cron() - cidr_ips = download_cidr_lists(COUNTRIES) + if not ADGUARD_YAML.exists(): + logging.error(f"{ADGUARD_YAML} not found, exiting.") + sys.exit(1) + + if not COUNTRIES: + logging.error("No countries specified in BLOCK_COUNTRIES environment variable, exiting.") + sys.exit(1) + + backup_files() + + countries_list = [c.strip() for c in COUNTRIES.split(",") if c.strip()] + cidr_ips = download_cidr_lists(countries_list) manual_ips = read_manual_ips() + combined_ips = cidr_ips + manual_ips - if not combined_ips: - logging.warning("No IPs to add to disallowed_clients. The list will be empty.") + update_yaml_with_ips(combined_ips) - restart_container() - logging.info("Blocklist update complete.") if __name__ == "__main__": main() -- 2.47.2 From dea060b4d25c489d9eca60a61be00ff910b36f84 Mon Sep 17 00:00:00 2001 From: Djeex Date: Sat, 31 May 2025 21:00:12 +0000 Subject: [PATCH 4/6] adguard restart --- update-blocklist.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/update-blocklist.py b/update-blocklist.py index cb25eba..3ab8b6d 100644 --- a/update-blocklist.py +++ b/update-blocklist.py @@ -51,7 +51,7 @@ def read_manual_ips(): with MANUAL_IPS_FILE.open() as f: for line in f: line = line.strip() - # Simple regex match for IPv4 or IPv4 CIDR + # Simple check for IPv4 or IPv4 CIDR format if line and line.count('.') == 3: valid_ips.append(line) logging.info(f"Added {len(valid_ips)} manual IP entries") @@ -75,25 +75,36 @@ def update_yaml_with_ips(ips): output_lines.extend(formatted_ips) inside_disallowed = True elif inside_disallowed: - # Skip old lines under disallowed_clients (assuming indentation) + # Skip old lines under disallowed_clients block if line.startswith(" ") and not line.startswith(" -"): - # This is a new section, disallowed_clients block ended + # New section, disallowed_clients block ended inside_disallowed = False output_lines.append(line.rstrip("\n")) - # Else skip line inside disallowed_clients block + # else skip line else: output_lines.append(line.rstrip("\n")) - # If the file ended while still inside disallowed_clients block, append nothing more (already done) - - # Write to temporary YAML in same folder (to avoid cross-device rename error) with TMP_YAML.open("w") as f: f.write("\n".join(output_lines) + "\n") - # Atomic replace TMP_YAML.replace(ADGUARD_YAML) logging.info(f"Updated {ADGUARD_YAML} with new disallowed clients list.") +def restart_adguard_container(): + docker_api_url = os.getenv("DOCKER_API_URL", "http://socket-proxy-adguard:2375") + container_name = os.getenv("ADGUARD_CONTAINER_NAME", "adguardhome") + restart_url = f"{docker_api_url}/containers/{container_name}/restart" + + logging.info(f"Restarting AdGuard container '{container_name}'...") + try: + resp = requests.post(restart_url, timeout=10) + if resp.status_code == 204: + logging.info("AdGuard container restarted successfully.") + else: + logging.error(f"Failed to restart container: {resp.status_code} {resp.text}") + except Exception as e: + logging.error(f"Error restarting container: {e}") + def main(): if not ADGUARD_YAML.exists(): logging.error(f"{ADGUARD_YAML} not found, exiting.") @@ -113,5 +124,7 @@ def main(): update_yaml_with_ips(combined_ips) + restart_adguard_container() + if __name__ == "__main__": main() -- 2.47.2 From a5f39c196e9d471024f212d3cc3f45aa73e771f1 Mon Sep 17 00:00:00 2001 From: Djeex Date: Sat, 31 May 2025 21:05:36 +0000 Subject: [PATCH 5/6] fix indentation in adguardhome.yaml --- update-blocklist.py | 60 +++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/update-blocklist.py b/update-blocklist.py index 3ab8b6d..50819d8 100644 --- a/update-blocklist.py +++ b/update-blocklist.py @@ -61,35 +61,53 @@ def read_manual_ips(): return [] def update_yaml_with_ips(ips): - # Format IPs for YAML list (4 spaces indent + dash) - formatted_ips = [f" - {ip}" for ip in ips] - - inside_disallowed = False - output_lines = [] - + # Read original lines with ADGUARD_YAML.open() as f: - for line in f: - if line.strip().startswith("disallowed_clients:"): - # Replace existing disallowed_clients block - output_lines.append("disallowed_clients:") - output_lines.extend(formatted_ips) - inside_disallowed = True - elif inside_disallowed: - # Skip old lines under disallowed_clients block - if line.startswith(" ") and not line.startswith(" -"): - # New section, disallowed_clients block ended - inside_disallowed = False - output_lines.append(line.rstrip("\n")) - # else skip line - else: - output_lines.append(line.rstrip("\n")) + lines = f.readlines() + output_lines = [] + inside_disallowed = False + disallowed_indent = "" + + formatted_ips = [] # We'll prepare after knowing indent + + for i, line in enumerate(lines): + stripped = line.lstrip() + indent = line[:len(line) - len(stripped)] + + if stripped.startswith("disallowed_clients:"): + disallowed_indent = indent + # Write the disallowed_clients line with original indent + output_lines.append(line.rstrip("\n")) + inside_disallowed = True + + # Prepare formatted IPs with indentation plus 2 spaces (YAML block indent) + formatted_ips = [f"{disallowed_indent} - {ip}" for ip in ips] + + # Immediately add IP list here (replace old block) + output_lines.extend(formatted_ips) + continue + + if inside_disallowed: + # Detect if the current line is out of the disallowed_clients block by checking indentation + # If line is empty or less indented, disallowed block ended + if (line.strip() == "") or (len(line) - len(line.lstrip()) <= len(disallowed_indent)): + inside_disallowed = False + output_lines.append(line.rstrip("\n")) + else: + # Skip lines inside disallowed_clients block (already replaced) + continue + else: + output_lines.append(line.rstrip("\n")) + + # Write to temporary YAML with TMP_YAML.open("w") as f: f.write("\n".join(output_lines) + "\n") TMP_YAML.replace(ADGUARD_YAML) logging.info(f"Updated {ADGUARD_YAML} with new disallowed clients list.") + def restart_adguard_container(): docker_api_url = os.getenv("DOCKER_API_URL", "http://socket-proxy-adguard:2375") container_name = os.getenv("ADGUARD_CONTAINER_NAME", "adguardhome") -- 2.47.2 From 31865613ccab19fdc0804efd50e6300dbf061cdc Mon Sep 17 00:00:00 2001 From: Djeex Date: Sat, 31 May 2025 21:21:01 +0000 Subject: [PATCH 6/6] Fixed bracket issues + readme with backup --- README.md | 1 + update-blocklist.py | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 3527d2c..c56434b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ - Updates the disallowed_clients section in the AdGuard Home config. - Configurable update frequency via cron expression environment variable. - Automatically restarts the AdGuard Home container after updates via Docker socket proxy. +- Backup `AdguardHome.yaml` at first startup, then create a second backup at each update. ## Environment Variables diff --git a/update-blocklist.py b/update-blocklist.py index 50819d8..f9eee6d 100644 --- a/update-blocklist.py +++ b/update-blocklist.py @@ -61,46 +61,46 @@ def read_manual_ips(): return [] def update_yaml_with_ips(ips): - # Read original lines - with ADGUARD_YAML.open() as f: - lines = f.readlines() - output_lines = [] inside_disallowed = False disallowed_indent = "" - formatted_ips = [] # We'll prepare after knowing indent + with ADGUARD_YAML.open() as f: + lines = f.readlines() - for i, line in enumerate(lines): + for line in lines: stripped = line.lstrip() indent = line[:len(line) - len(stripped)] if stripped.startswith("disallowed_clients:"): + # Capture the indentation of the disallowed_clients key disallowed_indent = indent - # Write the disallowed_clients line with original indent - output_lines.append(line.rstrip("\n")) - inside_disallowed = True - # Prepare formatted IPs with indentation plus 2 spaces (YAML block indent) + # Replace entire line with just 'disallowed_clients:' (remove any []) + output_lines.append(f"{disallowed_indent}disallowed_clients:") + + # Add all IPs indented 2 spaces more than disallowed_clients formatted_ips = [f"{disallowed_indent} - {ip}" for ip in ips] - - # Immediately add IP list here (replace old block) output_lines.extend(formatted_ips) + + inside_disallowed = True continue if inside_disallowed: - # Detect if the current line is out of the disallowed_clients block by checking indentation - # If line is empty or less indented, disallowed block ended - if (line.strip() == "") or (len(line) - len(line.lstrip()) <= len(disallowed_indent)): + # We skip all old lines inside disallowed_clients block. + # The block ends when we find a line with indentation + # less than or equal to disallowed_indent but not the key line itself. + # To detect end of block, compare indent length: + if len(indent) <= len(disallowed_indent) and stripped != "": inside_disallowed = False output_lines.append(line.rstrip("\n")) else: - # Skip lines inside disallowed_clients block (already replaced) + # skip this line (old disallowed_clients content) continue else: output_lines.append(line.rstrip("\n")) - # Write to temporary YAML + # Write temp file in same directory to avoid cross-device rename errors with TMP_YAML.open("w") as f: f.write("\n".join(output_lines) + "\n") -- 2.47.2