Merge pull request 'wip-python -> Python rewriting + better logs + backup + update at startup' (#3) from wip-python into main
Reviewed-on: #3
This commit is contained in:
commit
77e2a9875f
32
Dockerfile
32
Dockerfile
@ -1,10 +1,30 @@
|
|||||||
FROM alpine:latest
|
FROM python:3.11-slim
|
||||||
|
|
||||||
RUN apk add --no-cache curl bash busybox tzdata
|
# Install required utilities
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
cron \
|
||||||
|
tzdata \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY update-blocklist.sh /usr/local/bin/update-blocklist.sh
|
# Install python dependencies
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
RUN pip install --no-cache-dir requests
|
||||||
|
|
||||||
RUN chmod +x /usr/local/bin/update-blocklist.sh /entrypoint.sh
|
# Create crontabs directory (if needed)
|
||||||
|
RUN mkdir -p /etc/crontabs
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
# Copy scripts
|
||||||
|
COPY update-blocklist.py /usr/local/bin/update-blocklist.py
|
||||||
|
COPY entrypoint.py /usr/local/bin/entrypoint.py
|
||||||
|
|
||||||
|
# Make scripts executable
|
||||||
|
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)
|
||||||
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
# Set entrypoint
|
||||||
|
ENTRYPOINT ["/usr/local/bin/entrypoint.py"]
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
- Updates the disallowed_clients section in the AdGuard Home config.
|
- Updates the disallowed_clients section in the AdGuard Home config.
|
||||||
- Configurable update frequency via cron expression environment variable.
|
- Configurable update frequency via cron expression environment variable.
|
||||||
- Automatically restarts the AdGuard Home container after updates via Docker socket proxy.
|
- 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
|
## Environment Variables
|
||||||
|
|
||||||
@ -73,6 +74,6 @@
|
|||||||
5. **Check logs to verify updates**
|
5. **Check logs to verify updates**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose logs -f
|
docker compose logs -f
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ services:
|
|||||||
adguard-cidre:
|
adguard-cidre:
|
||||||
build: .
|
build: .
|
||||||
container_name: adguard-cidre
|
container_name: adguard-cidre
|
||||||
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Paris # change to your timezone
|
- 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
|
- BLOCK_COUNTRIES=cn,ru,ir # choose countries listed IP to block. Full lists here https://github.com/vulnebify/cidre/tree/main/output/cidr/ipv4
|
||||||
|
67
entrypoint.py
Normal file
67
entrypoint.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#!/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 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...")
|
||||||
|
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 start_cron_foreground():
|
||||||
|
logging.info("Starting cron in foreground...")
|
||||||
|
os.execvp("cron", ["cron", "-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()
|
@ -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
|
|
148
update-blocklist.py
Normal file
148
update-blocklist.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='[update-blocklist] %(levelname)s: %(message)s',
|
||||||
|
stream=sys.stdout,
|
||||||
|
)
|
||||||
|
|
||||||
|
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", "")
|
||||||
|
|
||||||
|
def backup_files():
|
||||||
|
if not FIRST_BACKUP.exists():
|
||||||
|
logging.info(f"Creating first-start backup: {FIRST_BACKUP}")
|
||||||
|
FIRST_BACKUP.write_text(ADGUARD_YAML.read_text())
|
||||||
|
else:
|
||||||
|
logging.info("First-start backup already exists, skipping.")
|
||||||
|
|
||||||
|
logging.info(f"Creating last-cron backup: {LAST_CRON_BACKUP}")
|
||||||
|
LAST_CRON_BACKUP.write_text(ADGUARD_YAML.read_text())
|
||||||
|
|
||||||
|
def download_cidr_lists(countries):
|
||||||
|
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=30)
|
||||||
|
r.raise_for_status()
|
||||||
|
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 {code}: {e}")
|
||||||
|
return combined_ips
|
||||||
|
|
||||||
|
def read_manual_ips():
|
||||||
|
if MANUAL_IPS_FILE.exists():
|
||||||
|
logging.info(f"Reading manual IPs from {MANUAL_IPS_FILE}")
|
||||||
|
valid_ips = []
|
||||||
|
with MANUAL_IPS_FILE.open() as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
# 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")
|
||||||
|
return valid_ips
|
||||||
|
else:
|
||||||
|
logging.info("Manual IPs file does not exist, skipping.")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def update_yaml_with_ips(ips):
|
||||||
|
output_lines = []
|
||||||
|
inside_disallowed = False
|
||||||
|
disallowed_indent = ""
|
||||||
|
|
||||||
|
with ADGUARD_YAML.open() as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# 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]
|
||||||
|
output_lines.extend(formatted_ips)
|
||||||
|
|
||||||
|
inside_disallowed = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if inside_disallowed:
|
||||||
|
# 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 this line (old disallowed_clients content)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
output_lines.append(line.rstrip("\n"))
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
|
||||||
|
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.")
|
||||||
|
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
|
||||||
|
|
||||||
|
update_yaml_with_ips(combined_ips)
|
||||||
|
|
||||||
|
restart_adguard_container()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -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."
|
|
Loading…
x
Reference in New Issue
Block a user