Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
13379a3419 | |||
fedbe1e227 | |||
15ba895b04 | |||
7676d34a39 | |||
c8451688ca | |||
bb6634ffc1 | |||
1e589ba91a | |||
2340aea618 | |||
5f54c87cf2 | |||
e3632a84fe | |||
125c4eaed7 | |||
1236970426 |
34
Dockerfile
34
Dockerfile
@ -1,30 +1,14 @@
|
|||||||
FROM python:3.11-slim
|
FROM python:3.11-alpine
|
||||||
|
|
||||||
# Install required utilities
|
ENV TZ=Europe/Paris
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
curl \
|
|
||||||
cron \
|
|
||||||
tzdata \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Install python dependencies
|
RUN apk add --no-cache tzdata curl \
|
||||||
RUN pip install --no-cache-dir requests
|
&& cp /usr/share/zoneinfo/$TZ /etc/localtime \
|
||||||
|
&& echo $TZ > /etc/timezone \
|
||||||
|
&& pip install --no-cache-dir requests pyyaml schedule
|
||||||
|
|
||||||
# Create crontabs directory (if needed)
|
WORKDIR /app
|
||||||
RUN mkdir -p /etc/crontabs
|
|
||||||
|
|
||||||
# Copy scripts
|
COPY blocklist_scheduler.py .
|
||||||
COPY update-blocklist.py /usr/local/bin/update-blocklist.py
|
|
||||||
COPY entrypoint.py /usr/local/bin/entrypoint.py
|
|
||||||
|
|
||||||
# Make scripts executable
|
ENTRYPOINT ["python3", "blocklist_scheduler.py"]
|
||||||
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"]
|
|
||||||
|
65
README.md
65
README.md
@ -7,66 +7,80 @@
|
|||||||
|
|
||||||
**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/adguard-cidre. You'll find full package, history and release note there._
|
||||||
|
|
||||||
## Sommaire
|
## Sommaire
|
||||||
|
|
||||||
- [Features](#features)
|
- [Features](#features)
|
||||||
- [Environment Variables](#environment-variables)
|
- [Environment Variables](#environment-variables)
|
||||||
|
- [Volumes](#volumes)
|
||||||
- [File Structure](#file-structure)
|
- [File Structure](#file-structure)
|
||||||
- [Installation and Usage](#nstallation-and-usage)
|
- [Installation and Usage](#installation-and-usage)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Automatically downloads IP CIDR blocks for specified countries to block.
|
- Downloads CIDR lists by country from GitHub
|
||||||
- Supports additional manually blocked IPs from a configurable file.
|
- (Optional) Adds manual IPs from a `manually_blocked_ips.conf` file
|
||||||
- Updates the disallowed_clients section in the AdGuard Home config.
|
- Updates the `AdGuardHome.yaml` file by replacing the `disallowed_clients` list
|
||||||
- Configurable update frequency via cron expression environment variable.
|
- Creates a backup of the original config (`AdGuardHome.yaml.first-start.bak`) on first run
|
||||||
- Automatically restarts the AdGuard Home container after updates via Docker socket proxy.
|
- Creates a backup before each update (`AdGuardHome.yaml.last-update.bak`)
|
||||||
- Backup `AdguardHome.yaml` at first startup, then create a second backup at each update.
|
- 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
|
## 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
|
## File Structure
|
||||||
|
|
||||||
- `update-blocklist.sh`: Main script to download CIDRs, merge manual IPs, update config, and restart AdGuard.
|
- `blocklist_scheduler.py`: Script to backup, schedule, 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 python3-slim image.
|
||||||
- `Dockerfile`: Builds the lightweight Alpine-based image.
|
|
||||||
- `docker-compose.yml`: Example compose file to run the container.
|
- `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
|
## Installation and Usage
|
||||||
|
|
||||||
### With our docker image
|
### With our provided docker image
|
||||||
|
|
||||||
1. **Create `docker-compose.yml` in your `adguard-cidre` folder**
|
1. **Create `docker-compose.yml` in your `adguard-cidre` folder**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
services:
|
services:
|
||||||
adguard-cidre:
|
adguard-cidre:
|
||||||
image: git.djeex.fr/djeex/adguard-cidre:latest
|
image: git.djeex.fr/djeex/adguard-cidre:latest
|
||||||
container_name: adguard-cidre
|
container_name: adguard-cidre
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Paris # change to your timezone
|
- 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
|
- 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=0 6 * * * # at 6:00 every days
|
- 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
|
- DOCKER_API_URL=http://socket-proxy-adguard:2375 # docker socket proxy
|
||||||
- ADGUARD_CONTAINER_NAME=adguardhome # adguard container name
|
- ADGUARD_CONTAINER_NAME=adguardhome # adguard container name
|
||||||
volumes:
|
volumes:
|
||||||
- /path/to/adguard/confdir:/adguard
|
- /path/to/adguard/confdir:/adguard
|
||||||
|
|
||||||
socket-proxy:
|
socket-proxy:
|
||||||
image: lscr.io/linuxserver/socket-proxy:latest
|
image: lscr.io/linuxserver/socket-proxy:latest
|
||||||
container_name: socket-proxy-adguard
|
container_name: socket-proxy-adguard
|
||||||
security_opt:
|
security_opt:
|
||||||
@ -84,7 +98,7 @@
|
|||||||
2. **Modify docker-compose.yml**
|
2. **Modify docker-compose.yml**
|
||||||
|
|
||||||
- Set `BLOCK_COUNTRIES` environment variable with the countries you want to block.
|
- 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`
|
- 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 :
|
- (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 :
|
||||||
|
|
||||||
@ -115,7 +129,7 @@
|
|||||||
2. **Modify docker-compose.yml**
|
2. **Modify docker-compose.yml**
|
||||||
|
|
||||||
- Set `BLOCK_COUNTRIES` environment variable with the countries you want to block.
|
- 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`
|
- 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 :
|
- (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 :
|
||||||
|
|
||||||
@ -128,7 +142,6 @@
|
|||||||
3. **Build and start the container**
|
3. **Build and start the container**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose build
|
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
4. **Check logs to verify updates**
|
4. **Check logs to verify updates**
|
||||||
|
200
blocklist_scheduler.py
Normal file
200
blocklist_scheduler.py
Normal 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()
|
@ -7,7 +7,10 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- TZ=Europe/Paris # change to your timezone
|
- 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
|
- 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=0 6 * * * # at 6:00 every days
|
- 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
|
- DOCKER_API_URL=http://socket-proxy-adguard:2375 # docker socket proxy
|
||||||
- ADGUARD_CONTAINER_NAME=adguardhome # adguard container name
|
- ADGUARD_CONTAINER_NAME=adguardhome # adguard container name
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
#!/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,148 +0,0 @@
|
|||||||
#!/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()
|
|
Loading…
x
Reference in New Issue
Block a user