Compare commits

18 Commits

Author SHA1 Message Date
bb6634ffc1 Merge pull request 'New image based on python-alpine. Image size reduced by 2. A bit is a bit.' (#5) from wip-python into main
Reviewed-on: #5
2025-06-01 19:28:56 +02:00
1e589ba91a New image based on python-alpine. Image size reduced by 2. A bit is a bit. 2025-06-01 17:27:04 +00:00
2340aea618 Merge branch 'wip-python' 2025-06-01 11:28:15 +00:00
5f54c87cf2 fixed yaml exemple in readme 2025-06-01 11:27:37 +00:00
e3632a84fe Merge pull request 'wip-python -> removed cron for python scheduling, and added pyyaml library.' (#4) from wip-python into main
Reviewed-on: #4
2025-06-01 13:23:22 +02:00
125c4eaed7 fixed yaml dns entry 2025-06-01 11:11:43 +00:00
1236970426 Replaced cron with python scheduler and using python yaml library. Updated readme. 2025-06-01 10:44:06 +00:00
424ce59511 Merge branch 'wip-python' - Image and readme update 2025-05-31 22:05:39 +00:00
7ead6b4154 Image + readme update 2025-05-31 22:04:41 +00:00
77e2a9875f Merge pull request 'wip-python -> Python rewriting + better logs + backup + update at startup' (#3) from wip-python into main
Reviewed-on: #3
2025-05-31 23:22:30 +02:00
31865613cc Fixed bracket issues + readme with backup 2025-05-31 21:21:01 +00:00
a5f39c196e fix indentation in adguardhome.yaml 2025-05-31 21:05:36 +00:00
dea060b4d2 adguard restart 2025-05-31 21:00:12 +00:00
d97348cd17 Fixes missing requests + cross device error + cron folder error 2025-05-31 20:48:05 +00:00
17b80fa446 Added timezone and fixed entrypoint 2025-05-31 20:13:36 +00:00
5afe9c1601 First python commit 2025-05-31 19:46:12 +00:00
b55e462548 Merge branch 'main' of https://git.djeex.fr/Djeex/adguard-cidre 2025-05-31 17:48:09 +00:00
5471371401 Fixed readme 2025-05-31 17:46:16 +00:00
6 changed files with 269 additions and 120 deletions

View File

@ -1,10 +1,14 @@
FROM alpine:latest
FROM python:3.11-alpine
RUN apk add --no-cache curl bash busybox tzdata
ENV TZ=Europe/Paris
COPY update-blocklist.sh /usr/local/bin/update-blocklist.sh
COPY entrypoint.sh /entrypoint.sh
RUN apk add --no-cache tzdata curl \
&& cp /usr/share/zoneinfo/$TZ /etc/localtime \
&& echo $TZ > /etc/timezone \
&& pip install --no-cache-dir requests pyyaml schedule
RUN chmod +x /usr/local/bin/update-blocklist.sh /entrypoint.sh
WORKDIR /app
ENTRYPOINT ["/entrypoint.sh"]
COPY blocklist_scheduler.py .
ENTRYPOINT ["python3", "blocklist_scheduler.py"]

117
README.md
View File

@ -13,38 +13,110 @@
- [Features](#features)
- [Environment Variables](#environment-variables)
- [Volumes](#volumes)
- [File Structure](#file-structure)
- [Installation and Usage](#nstallation-and-usage)
- [Installation and Usage](#installation-and-usage)
## Features
- Automatically downloads IP CIDR blocks for specified countries to block.
- Supports additional manually blocked IPs from a configurable file.
- 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.
- Downloads CIDR lists by country from GitHub
- (Optional) Adds manual IPs from a `manually_blocked_ips.conf` file
- Updates the `AdGuardHome.yaml` file by replacing the `disallowed_clients` list
- Creates a backup of the original config (`AdGuardHome.yaml.first-start.bak`) on first run
- Creates a backup before each update (`AdGuardHome.yaml.last-update.bak`)
- Restarts the AdGuard Home container via Docker API
- Built-in Python scheduler using the `schedule` library, configurable to run updates daily or weekly
## Environment Variables
| Variable | Description | Default |
| ------------------- | ---------------------------------------------------------- | --------------------------------- |
| `TZ` | Your Time Zone | (required) |
| `BLOCK_COUNTRIES` | Comma-separated country codes to block (e.g., `CN,RU,IR`) | (required) |
| `BLOCKLIST_CRON` | Cron expression for update frequency (e.g., `0 6 * * *`) | `0 6 * * *` (at 6:00 everydays) |
| `DOCKER_API_URL` | URL of Docker socket proxy to restart AdGuard container | `http://socket-proxy-adguard:2375` |
| `ADGUARD_CONTAINER_NAME` | Name of your adguard container | `adguardhome` |
| Variable | Description | Example | Possible Values |
|--------------------------|--------------------------------------------------------------------------|-----------------------------|---------------------------------------------|
| `TZ` | Timezone of the container to correctly schedule updates | `Europe/Paris` | Any valid timezone (e.g., `UTC`, `America/New_York`, etc.) |
| `BLOCK_COUNTRIES` | List of country codes for CIDR lists, separated by commas | `cn,ru,ir` | ISO 2-letter country codes |
| `BLOCKLIST_CRON_TYPE` | Scheduling type: `daily` or `weekly` | `daily` | `daily`, `weekly` |
| `BLOCKLIST_CRON_TIME` | Time to run update in `HH:MM` 24-hour format | `06:00` | 24-hour time format |
| `BLOCKLIST_CRON_DAY` | Day of the week for weekly schedule (e.g., `mon`, `tue`, etc.) | `mon` | `mon`, `tue`, `wed`, `thu`, `fri`, `sat`, `sun` |
| `ADGUARD_CONTAINER_NAME` | Name of the AdGuard Home container to restart | `adguardhome` | Valid Docker container name |
| `DOCKER_API_URL` | Docker API URL (used to restart the container) | `http://socket-proxy-adguard:2375` | HTTP URL |
## Volumes
- `/path/to/adguard/confdir` : configuration directory containing `AdGuardHome.yaml` from your adguard container, and optionally `manually_blocked_ips.conf`.
## File Structure
- `update-blocklist.sh`: Main script to download CIDRs, merge manual IPs, update config, and restart AdGuard.
- `entrypoint.sh`: Sets up the cron job to periodically run the update script.
- `Dockerfile`: Builds the lightweight Alpine-based image.
- `blocklist_scheduler.py`: Script to backup, schedule, download CIDRs, merge manual IPs, update config, and restart AdGuard.
- `Dockerfile`: Builds the lightweight python3-slim image.
- `docker-compose.yml`: Example compose file to run the container.
- `manually_blocked_ips.conf`: (Volume mount) Add extra IPs to block manually.
- (optional) `manually_blocked_ips.conf`: Add extra IPs to block manually.
## Installation and Usage
### With our provided docker image
1. **Create `docker-compose.yml` in your `adguard-cidre` folder**
```yaml
---
services:
adguard-cidre:
image: git.djeex.fr/djeex/adguard-cidre:latest
container_name: adguard-cidre
restart: unless-stopped
environment:
- TZ=Europe/Paris # change to your timezone
- BLOCK_COUNTRIES=cn,ru # choose countries listed IP to block. Full lists here https://github.com/vulnebify/cidre/tree/main/output/cidr/ipv4
- BLOCKLIST_CRON_TYPE=daily # daily or weekly
# if weekly, choose the day
# - BLOCKLIST_CRON_DAY=mon
- BLOCKLIST_CRON_TIME=06:00
- DOCKER_API_URL=http://socket-proxy-adguard:2375 # docker socket proxy
- ADGUARD_CONTAINER_NAME=adguardhome # adguard container name
volumes:
- /path/to/adguard/confdir:/adguard
socket-proxy:
image: lscr.io/linuxserver/socket-proxy:latest
container_name: socket-proxy-adguard
security_opt:
- no-new-privileges:true
environment:
- CONTAINERS=1
- ALLOW_RESTARTS=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
read_only: true
tmpfs:
- /run
```
2. **Modify docker-compose.yml**
- Set `BLOCK_COUNTRIES` environment variable with the countries you want to block.
- Adjust `BLOCKLIST_CRON` variables if you want a different update frequency.
- Bind mount your adguard configuration folder (wich contains `AdGuardHome.yaml`) to `/adguard`
- (optionnally) create and edit `manually_blocked_ips.conf` file in your adguard configuration folder to add other IPs you want to block. Only valid IP or CIDR entries will be processed, for exemple :
```bash
192.168.1.100
10.0.0.0/24
# Comments or empty lines are ignored
```
3. **Start the container**
```bash
docker compose up -d
```
4. **Check logs to verify updates**
```bash
docker compose logs -f
```
### With git (developer)
1. **Clone the repository:**
```bash
@ -54,7 +126,7 @@
2. **Modify docker-compose.yml**
- Set `BLOCK_COUNTRIES` environment variable with the countries you want to block.
- Adjust `BLOCKLIST_CRON` if you want a different update frequency.
- Adjust `BLOCKLIST_CRON` variables if you want a different update frequency.
- Bind mount your adguard configuration folder (wich contains `AdGuardHome.yaml`) to `/adguard`
- (optionnally) create and edit `manually_blocked_ips.conf` file in your adguard configuration folder to add other IPs you want to block. Only valid IP or CIDR entries will be processed, for exemple :
@ -64,15 +136,14 @@
# Comments or empty lines are ignored
```
4. **Build and start the container**
3. **Build and start the container**
```bash
docker-compose build
docker-compose up -d
docker compose up -d
```
5. **Check logs to verify updates**
4. **Check logs to verify updates**
```bash
docker-compose logs -f
docker compose logs -f
```

159
blocklist_scheduler.py Normal file
View File

@ -0,0 +1,159 @@
#!/usr/bin/env python3
import os
import sys
import logging
import requests
import yaml
import schedule
import time
from pathlib import Path
logging.basicConfig(
level=logging.INFO,
format='[blocklist] %(levelname)s: %(message)s',
stream=sys.stdout,
)
ADGUARD_YAML = Path("/adguard/AdGuardHome.yaml")
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"
FIRST_BACKUP = ADGUARD_YAML.parent / "AdGuardHome.yaml.first-start.bak"
LAST_UPDATE_BACKUP = ADGUARD_YAML.parent / "AdGuardHome.yaml.last-update.bak"
BLOCK_COUNTRIES = os.getenv("BLOCK_COUNTRIES", "")
BLOCKLIST_CRON_TYPE = os.getenv("BLOCKLIST_CRON_TYPE", "daily").lower() # daily or weekly
BLOCKLIST_CRON_TIME = os.getenv("BLOCKLIST_CRON_TIME", "06:00") # HH:MM format
BLOCKLIST_CRON_DAY = os.getenv("BLOCKLIST_CRON_DAY", "mon").lower() # only if weekly
ADGUARD_CONTAINER_NAME = os.getenv("ADGUARD_CONTAINER_NAME", "adguardhome")
DOCKER_API_URL = os.getenv("DOCKER_API_URL", "http://socket-proxy-adguard:2375")
def backup_first_start():
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.")
def backup_last_update():
logging.info(f"Creating last update backup: {LAST_UPDATE_BACKUP}")
LAST_UPDATE_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()
if line and (line.count('.') == 3 or '/' in line):
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):
if not ADGUARD_YAML.exists():
logging.error(f"{ADGUARD_YAML} does not exist. Cannot update.")
return False
data = None
with ADGUARD_YAML.open() as f:
data = yaml.safe_load(f)
if data is None:
logging.error(f"Failed to parse YAML file {ADGUARD_YAML}")
return False
data['dns']['disallowed_clients'] = ips
with TMP_YAML.open('w') as f:
yaml.safe_dump(data, f)
TMP_YAML.replace(ADGUARD_YAML)
logging.info(f"Updated {ADGUARD_YAML} with new disallowed clients list.")
return True
def restart_adguard_container():
restart_url = f"{DOCKER_API_URL}/containers/{ADGUARD_CONTAINER_NAME}/restart"
logging.info(f"Restarting AdGuard container '{ADGUARD_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 update_blocklist():
if not BLOCK_COUNTRIES:
logging.error("No countries specified in BLOCK_COUNTRIES environment variable. Skipping update.")
return
countries_list = [c.strip() for c in BLOCK_COUNTRIES.split(",") if c.strip()]
cidr_ips = download_cidr_lists(countries_list)
manual_ips = read_manual_ips()
combined_ips = cidr_ips + manual_ips
backup_last_update()
success = update_yaml_with_ips(combined_ips)
if success:
restart_adguard_container()
def schedule_job():
try:
hour, minute = [int(x) for x in BLOCKLIST_CRON_TIME.split(":")]
except Exception:
logging.error(f"Invalid BLOCKLIST_CRON_TIME '{BLOCKLIST_CRON_TIME}', must be HH:MM. Defaulting to 06:00.")
hour, minute = 6, 0
if BLOCKLIST_CRON_TYPE == "daily":
schedule.every().day.at(f"{hour:02d}:{minute:02d}").do(update_blocklist)
logging.info(f"Scheduled daily update at {hour:02d}:{minute:02d}")
elif BLOCKLIST_CRON_TYPE == "weekly":
valid_days = ["mon","tue","wed","thu","fri","sat","sun"]
day = BLOCKLIST_CRON_DAY[:3]
if day not in valid_days:
logging.error(f"Invalid BLOCKLIST_CRON_DAY '{BLOCKLIST_CRON_DAY}', must be one of {valid_days}. Defaulting to Monday.")
day = "mon"
getattr(schedule.every(), day).at(f"{hour:02d}:{minute:02d}").do(update_blocklist)
logging.info(f"Scheduled weekly update on {day.capitalize()} at {hour:02d}:{minute:02d}")
else:
logging.error(f"Invalid BLOCKLIST_CRON_TYPE '{BLOCKLIST_CRON_TYPE}', must be 'daily' or 'weekly'. Defaulting to daily.")
schedule.every().day.at(f"{hour:02d}:{minute:02d}").do(update_blocklist)
logging.info(f"Scheduled daily update at {hour:02d}:{minute:02d}")
def main():
logging.info("Starting blocklist scheduler...")
backup_first_start()
update_blocklist()
schedule_job()
while True:
schedule.run_pending()
time.sleep(10)
if __name__ == "__main__":
main()

View File

@ -3,10 +3,14 @@ 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
- BLOCKLIST_CRON=0 6 * * * # at 6:00 every days
- BLOCK_COUNTRIES=cn,ru # choose countries listed IP to block. Full lists here https://github.com/vulnebify/cidre/tree/main/output/cidr/ipv4
- BLOCKLIST_CRON_TYPE=daily # daily or weekly
# if weekly, choose the day
# - BLOCKLIST_CRON_DAY=mon
- BLOCKLIST_CRON_TIME=06:00
- DOCKER_API_URL=http://socket-proxy-adguard:2375 # docker socket proxy
- ADGUARD_CONTAINER_NAME=adguardhome # adguard container name
volumes:

View File

@ -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

View File

@ -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."