Compare commits

21 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
6 changed files with 314 additions and 121 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"]

122
README.md
View File

@ -7,44 +7,119 @@
**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
- [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. 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
### 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 +129,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 +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

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