Compare commits

31 Commits

Author SHA1 Message Date
15ba895b04 Merge pull request 'v1.3 - Excluding list (all countries except the specified ones)' (#6) from wip-python into main
Reviewed-on: #6
2025-06-06 14:17:11 +02:00
7676d34a39 Excluding list 2025-06-06 12:09:41 +00:00
c8451688ca Github notice 2025-06-06 11:12:15 +00:00
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
e1b43dc895 Merge pull request 'wip - v1 => It's working !!' (#1) from wip into main
Reviewed-on: #1
2025-05-31 19:44:48 +02:00
0da63e9cc0 fixed list issues with awk (too many arguments -> temp file) 2025-05-31 17:42:35 +00:00
02d891b6db fixed cidr list url 2025-05-31 17:30:37 +00:00
93d38900c8 Updated readme and container name 2025-05-31 17:12:11 +00:00
820020f23e Added adguard container name cariable and fixed docker socket proxy permissions to restart container 2025-05-31 17:10:33 +00:00
3d465082a5 Fixed cron issues with cronie 2025-05-31 17:02:27 +00:00
d01fbeb68a Replaced busybox cron with dcron (alpine) 2025-05-31 16:20:51 +00:00
f81b07f528 added timezone and fixed default tcp proxy 2025-05-31 16:12:44 +00:00
cb93a39cc6 fixed readme 2025-05-31 15:57:06 +00:00
52bd85f4bc fixed readme 2025-05-31 15:53:05 +00:00
6 changed files with 330 additions and 99 deletions

View File

@ -1,10 +1,14 @@
FROM alpine:latest
FROM python:3.11-alpine
RUN apk add --no-cache curl bash busybox-cron
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"]

139
README.md
View File

@ -2,54 +2,134 @@
<div align="center">
<a href="https://discord.gg/gxffg3GA96">
<img src="https://img.shields.io/badge/JV%20hardware-rejoindre-green?style=flat-square&logo=discord&logoColor=%23fff" alt="JV Hardware">
</a>
</div>
🤖 **Adguard CIDRE Sync** - A bot to synchronize adguard clients disallow list with countries CIDR list of your choices.
**Adguard CIDRE Sync** - A bot to synchronize adguard clients disallow list with countries CIDR list of your choices.
*The code is partially generated by AI*
> [!NOTE]
>_The code was partially written and structured using a generative AI._
>
>_Github repo is a mirror of https://git.djeex.fr/Djeex/nvidia-stock-bot. You'll find full package, history and release note there._
## 📌 Sommaire
## Sommaire
- [Features](#features)
- [Install with Docker and our image](#install-with-docker)
- [Install with git and build (développeur)](#install-with-git-and-build)
- [Environment Variables](#environment-variables)
- [Volumes](#volumes)
- [File Structure](#file-structure)
- [Installation and Usage](#installation-and-usage)
## Features
## Features
- 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
- 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.
## Environment Variables
| Variable | Description | Default |
| ------------------- | ---------------------------------------------------------- | --------------------------------- |
| `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://docker-socket-proxy:2375` |
| 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. You can also define an exclude list (all countries except the specified ones) by prefixing each country code with !. Mixing inclusion and exclusion codes is not supported. | including list : `cn,ru,ir`, excluding list : `!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
1. **Clone the repository:**
### With our provided docker image
```bash
git clone https://github.com/your-username/adguard-blocklist-updater.git
cd adguard-blocklist-updater
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` 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 :
```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
git clone https://git.djeex.fr/Djeex/adguard-cidre
cd adguard-cidre
```
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 :
@ -59,15 +139,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
```

200
blocklist_scheduler.py Normal file
View File

@ -0,0 +1,200 @@
#!/usr/bin/env python3
import os
import sys
import logging
import requests
import yaml
import schedule
import time
import re
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"
COUNTRY_LIST_URL = "https://raw.githubusercontent.com/vulnebify/cidre/refs/heads/main/cidre/countries.py"
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()
BLOCKLIST_CRON_TIME = os.getenv("BLOCKLIST_CRON_TIME", "06:00")
BLOCKLIST_CRON_DAY = os.getenv("BLOCKLIST_CRON_DAY", "mon").lower()
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 fetch_all_country_codes():
try:
resp = requests.get(COUNTRY_LIST_URL, timeout=15)
resp.raise_for_status()
matches = re.findall(r'"([A-Z]{2})"', resp.text)
return set(code.lower() for code in matches)
except Exception as e:
logging.error(f"Failed to fetch available country codes: {e}")
return set()
def get_selected_countries():
if not BLOCK_COUNTRIES:
logging.error("BLOCK_COUNTRIES is not set. Skipping update.")
return []
raw_codes = [c.strip() for c in BLOCK_COUNTRIES.split(",") if c.strip()]
if not raw_codes:
logging.error("No valid country codes provided.")
return []
is_exclusion = all(c.startswith("!") for c in raw_codes)
is_inclusion = all(not c.startswith("!") for c in raw_codes)
if not (is_exclusion or is_inclusion):
logging.error("Mixed syntax in BLOCK_COUNTRIES. Use only inclusion (e.g. 'fr,de') or only exclusion (e.g. '!fr,!de').")
sys.exit(1)
available = fetch_all_country_codes()
selected = {c.lstrip("!") for c in raw_codes}
unknown = selected - available
if unknown:
logging.warning(f"Unknown country codes: {', '.join(sorted(unknown))}")
if is_exclusion:
return sorted(available - selected)
else:
return sorted(selected & available)
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
try:
with ADGUARD_YAML.open() as f:
data = yaml.safe_load(f)
except Exception as e:
logging.error(f"Failed to parse YAML file: {e}")
return False
if not isinstance(data, dict):
logging.error("Invalid YAML format.")
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():
countries = get_selected_countries()
if not countries:
logging.error("No valid countries to process. Skipping update.")
return
cidr_ips = download_cidr_lists(countries)
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

@ -2,10 +2,17 @@
services:
adguard-cidre:
build: .
container_name: adguard-cidre
restart: unless-stopped
environment:
- 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
- DOCKER_API_URL=http://socket-proxy-adguard:2375
- 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
@ -16,6 +23,7 @@ services:
- no-new-privileges:true
environment:
- CONTAINERS=1
- ALLOW_RESTARTS=1
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped

View File

@ -1,13 +0,0 @@
#!/bin/sh
set -e
CRON_EXPR="${BLOCKLIST_CRON:-"0 6 * * *"}" # default: every hour
SCRIPT_PATH="/usr/local/bin/update-blocklist.sh"
echo "Installing cron job with expression: $CRON_EXPR"
echo "$CRON_EXPR root $SCRIPT_PATH" > /etc/crontabs/root
echo "Starting cron..."
crond -f -L /dev/stdout

View File

@ -1,47 +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://docker-socket-proxy:2375"}
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^^}.txt" -o "/tmp/cidr/${CODE}.txt" || continue
cat "/tmp/cidr/${CODE}.txt" >> /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
IPS_FORMATTED=$(sed 's/^/ - /' /tmp/cidr/all.txt)
awk -v ips="$IPS_FORMATTED" '
BEGIN { inside=0 }
/^ disallowed_clients:/ { print; inside=1; next }
/^ [^ ]/ && inside==1 { print ips; inside=0 }
{ if (!inside) print }
END { if (inside==1) print ips }
' "$ADGUARD_YAML" > "$TMP_YAML"
mv "$TMP_YAML" "$ADGUARD_YAML"
echo "Restarting adguard-home container..."
curl -s -X POST "$DOCKER_API_URL/containers/adguard-home/restart" -o /dev/null
echo "Done."