Compare commits
17
Commits
532c3e4646
...
1.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ac20e25e3 | ||
|
|
620d00134c | ||
|
|
d13f79ed82 | ||
|
|
cf96acda92 | ||
|
|
d811faef45 | ||
|
|
85fb4b4e79 | ||
|
|
20ec627515 | ||
|
|
f8e6888d50 | ||
|
|
f9f8506963 | ||
|
|
a63be55cb7 | ||
|
|
0bd6a62eca | ||
|
|
b865da38f3 | ||
|
|
67b4984664 | ||
|
|
5eafd7c7cc | ||
|
|
a72b486b3b | ||
|
|
d051d9deb7 | ||
|
|
267d9e52e0 |
@@ -0,0 +1,22 @@
|
||||
# User/group id the process runs as, matches ownership of the /adguard mount
|
||||
PUID=1000
|
||||
PGID=1000
|
||||
|
||||
# Timezone of the container
|
||||
TZ=Europe/Paris
|
||||
|
||||
# Country codes for CIDR lists, comma separated. Prefix with ! to exclude instead of include.
|
||||
# Full lists here: https://github.com/vulnebify/cidre/tree/main/output/cidr/ipv4
|
||||
BLOCK_COUNTRIES=cn,ru
|
||||
|
||||
# Scheduling: daily or weekly
|
||||
BLOCKLIST_CRON_TYPE=daily
|
||||
# If weekly, choose the day: mon, tue, wed, thu, fri, sat, sun
|
||||
BLOCKLIST_CRON_DAY=mon
|
||||
# Time of day to run the update, 24h HH:MM format
|
||||
BLOCKLIST_CRON_TIME=06:00
|
||||
|
||||
# Docker API URL used to restart the AdGuard container (via socket-proxy)
|
||||
DOCKER_API_URL=http://socket-proxy-adguard:2375
|
||||
# Name of the AdGuard Home container to restart
|
||||
ADGUARD_CONTAINER_NAME=adguardhome
|
||||
+126
-2
@@ -13,7 +13,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
@@ -36,9 +39,14 @@ jobs:
|
||||
exit(1)
|
||||
"
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
docker build --target test -t adguard-cidre:test .
|
||||
docker run --rm adguard-cidre:test pytest -v
|
||||
|
||||
- name: Check deprecation warnings
|
||||
run: |
|
||||
docker run --rm adguard-cidre:ci python -W error::DeprecationWarning -c "import blocklist_scheduler" 2>&1 | tee deprecation.log || true
|
||||
docker run --rm --entrypoint python adguard-cidre:ci -W error::DeprecationWarning -c "import blocklist_scheduler" 2>&1 | tee deprecation.log || true
|
||||
if grep -qi "deprecat" deprecation.log; then
|
||||
echo "::warning::Deprecation warning detected, check logs"
|
||||
fi
|
||||
@@ -56,3 +64,119 @@ jobs:
|
||||
-e DOCKER_HOST=tcp://dockerhost:2375 \
|
||||
--add-host=dockerhost:host-gateway \
|
||||
aquasec/trivy:0.74.0 image --exit-code 0 --severity HIGH adguard-cidre:ci
|
||||
|
||||
- name: Publish tagged image
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
BEFORE="${{ github.event.before }}"
|
||||
if [ -n "$BEFORE" ] && [ "$BEFORE" != "0000000000000000000000000000000000000000" ] && git cat-file -e "$BEFORE" 2>/dev/null; then
|
||||
CHANGED=$(git diff --name-only "$BEFORE" "${{ github.sha }}")
|
||||
else
|
||||
CHANGED=$(git diff --name-only HEAD~1 HEAD)
|
||||
fi
|
||||
echo "Changed files:"
|
||||
echo "$CHANGED"
|
||||
|
||||
if ! echo "$CHANGED" | grep -qE '^(Dockerfile|blocklist_scheduler\.py|VERSION)$'; then
|
||||
echo "No container-relevant file changed, skipping publish."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if echo "$CHANGED" | grep -qE '^VERSION$'; then
|
||||
echo "VERSION was manually edited in this push, using it as-is."
|
||||
else
|
||||
echo "VERSION untouched but container files changed, auto-bumping the build number (Z)."
|
||||
OLD_VERSION=$(tr -d '[:space:]' < VERSION)
|
||||
IFS='.' read -r MAJOR MINOR PATCH <<< "$OLD_VERSION"
|
||||
NEW_VERSION="${MAJOR}.${MINOR}.$((PATCH + 1))"
|
||||
echo "$NEW_VERSION" > VERSION
|
||||
|
||||
git config user.name "adguard-cidre-ci"
|
||||
git config user.email "[email protected]"
|
||||
git add VERSION
|
||||
git commit -m "Bump build version to $NEW_VERSION [skip ci]"
|
||||
|
||||
# Belt and suspenders: actions/checkout can leave its own ephemeral
|
||||
# credential injected as an extraheader, which would silently override
|
||||
# the URL-embedded token below. persist-credentials:false on checkout
|
||||
# should already prevent this, but strip it here too just in case.
|
||||
git config --unset-all http.https://git.djeex.fr/.extraheader || true
|
||||
|
||||
git push "https://Djeex:${{ secrets.CI_PUSH_TOKEN }}@git.djeex.fr/Djeex/adguard-cidre.git" HEAD:main
|
||||
fi
|
||||
|
||||
VERSION=$(tr -d '[:space:]' < VERSION)
|
||||
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
|
||||
MINOR_TAG="${MAJOR}.${MINOR}"
|
||||
|
||||
IMAGE=git.djeex.fr/djeex/adguard-cidre
|
||||
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.djeex.fr -u Djeex --password-stdin
|
||||
|
||||
# Retag the already-built, already-scanned image — never rebuild for publish,
|
||||
# so what ships is byte-for-byte what Trivy just scanned.
|
||||
docker tag adguard-cidre:ci "$IMAGE:latest"
|
||||
docker tag adguard-cidre:ci "$IMAGE:$MINOR_TAG"
|
||||
docker tag adguard-cidre:ci "$IMAGE:$VERSION"
|
||||
docker push "$IMAGE:latest"
|
||||
docker push "$IMAGE:$MINOR_TAG"
|
||||
docker push "$IMAGE:$VERSION"
|
||||
|
||||
TRIGGER_MSG=$(git log -1 --format=%s "${{ github.sha }}")
|
||||
PR_NUM=$(echo "$TRIGGER_MSG" | grep -oE '#[0-9]+' | head -1 | tr -d '#' || true)
|
||||
|
||||
CATEGORY="🔧 Maintenance"
|
||||
CHANGE_TITLE="$TRIGGER_MSG"
|
||||
|
||||
if [ -n "$PR_NUM" ]; then
|
||||
PR_JSON=$(curl -s -H "Authorization: token ${{ secrets.CI_PUSH_TOKEN }}" \
|
||||
"https://git.djeex.fr/api/v1/repos/Djeex/adguard-cidre/pulls/$PR_NUM")
|
||||
PR_TITLE=$(echo "$PR_JSON" | jq -r '.title // empty' 2>/dev/null || true)
|
||||
LABELS=$(echo "$PR_JSON" | jq -r '.labels[]?.name' 2>/dev/null || true)
|
||||
|
||||
if [ -n "$PR_TITLE" ]; then
|
||||
CHANGE_TITLE="$PR_TITLE"
|
||||
fi
|
||||
|
||||
if echo "$LABELS" | grep -qx 'bug'; then
|
||||
CATEGORY="⚠️ Hotfix"
|
||||
elif echo "$LABELS" | grep -qx 'major'; then
|
||||
CATEGORY="💥 Breaking change"
|
||||
elif echo "$LABELS" | grep -qx 'minor'; then
|
||||
CATEGORY="✨ Update"
|
||||
fi
|
||||
fi
|
||||
|
||||
CHANGED_LIST=$(echo "$CHANGED" | sed 's/^/- /')
|
||||
|
||||
REPO_URL="https://git.djeex.fr/Djeex/adguard-cidre"
|
||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
SOURCE_LINE="[${SHORT_SHA}](${REPO_URL}/commit/${{ github.sha }})"
|
||||
if [ -n "$PR_NUM" ]; then
|
||||
SOURCE_LINE="[#${PR_NUM}](${REPO_URL}/pulls/${PR_NUM}) · ${SOURCE_LINE}"
|
||||
fi
|
||||
|
||||
BODY=$(cat <<EOF
|
||||
## Changelog
|
||||
|
||||
### ${CATEGORY}
|
||||
${CHANGE_TITLE}
|
||||
|
||||
**Source:** ${SOURCE_LINE}
|
||||
**Image:** \`${IMAGE}:${VERSION}\`
|
||||
|
||||
**Changed files:**
|
||||
${CHANGED_LIST}
|
||||
EOF
|
||||
)
|
||||
|
||||
JSON_PAYLOAD=$(jq -n \
|
||||
--arg tag "$VERSION" \
|
||||
--arg name "$VERSION" \
|
||||
--arg body "$BODY" \
|
||||
'{tag_name: $tag, name: $name, target_commitish: "main", body: $body}')
|
||||
|
||||
curl -s -o /dev/null -w "Release API response: %{http_code}\n" -X POST \
|
||||
-H "Authorization: token ${{ secrets.CI_PUSH_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$JSON_PAYLOAD" \
|
||||
"https://git.djeex.fr/api/v1/repos/Djeex/adguard-cidre/releases"
|
||||
@@ -1,2 +1,4 @@
|
||||
/adguard/*.log
|
||||
/tmp/
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
|
||||
+15
-6
@@ -1,14 +1,23 @@
|
||||
FROM python:3.13-alpine
|
||||
FROM python:3.14.7-alpine AS base
|
||||
|
||||
ENV TZ=Europe/Paris
|
||||
|
||||
RUN apk add --no-cache tzdata curl \
|
||||
RUN apk add --no-cache tzdata curl su-exec \
|
||||
&& cp /usr/share/zoneinfo/$TZ /etc/localtime \
|
||||
&& echo $TZ > /etc/timezone \
|
||||
&& pip install --no-cache-dir requests pyyaml schedule
|
||||
&& echo $TZ > /etc/timezone
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY blocklist_scheduler.py .
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
ENTRYPOINT ["python3", "blocklist_scheduler.py"]
|
||||
COPY blocklist_scheduler.py entrypoint.sh VERSION ./
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
FROM base AS test
|
||||
RUN pip install --no-cache-dir pytest==9.1.1
|
||||
COPY tests/ tests/
|
||||
COPY pytest.ini .
|
||||
|
||||
FROM base
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
| Variable | Description | Example | Possible Values |
|
||||
|--------------------------|--------------------------------------------------------------------------|-----------------------------|---------------------------------------------|
|
||||
| `PUID` | User ID the process runs as (drops root at startup) | `1000` | Any valid numeric UID |
|
||||
| `PGID` | Group ID the process runs as | `1000` | Any valid numeric GID |
|
||||
| `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` |
|
||||
@@ -64,6 +66,8 @@
|
||||
container_name: adguard-cidre
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000 # user id the process runs as, matches ownership of the /adguard mount
|
||||
- PGID=1000 # group id the process runs as
|
||||
- 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
|
||||
@@ -121,11 +125,12 @@
|
||||
git clone https://git.djeex.fr/Djeex/adguard-cidre
|
||||
cd adguard-cidre
|
||||
```
|
||||
2. **Modify docker-compose.yml**
|
||||
2. **Edit the `.env` file**
|
||||
|
||||
- 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`
|
||||
- A `.env` file is included at the repo root with all environment variables (see [Environment Variables](#environment-variables)). Edit values there instead of `docker-compose.yml`.
|
||||
- Set `BLOCK_COUNTRIES` 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` in `docker-compose.yml`.
|
||||
- (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
|
||||
|
||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='[blocklist] %(levelname)s: %(message)s',
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
stream=sys.stdout,
|
||||
)
|
||||
|
||||
@@ -175,13 +175,16 @@ def schedule_job():
|
||||
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_names = {
|
||||
"mon": "monday", "tue": "tuesday", "wed": "wednesday", "thu": "thursday",
|
||||
"fri": "friday", "sat": "saturday", "sun": "sunday",
|
||||
}
|
||||
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.")
|
||||
if day not in day_names:
|
||||
logging.error(f"Invalid BLOCKLIST_CRON_DAY '{BLOCKLIST_CRON_DAY}', must be one of {list(day_names)}. 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}")
|
||||
getattr(schedule.every(), day_names[day]).at(f"{hour:02d}:{minute:02d}").do(update_blocklist)
|
||||
logging.info(f"Scheduled weekly update on {day_names[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)
|
||||
|
||||
+9
-7
@@ -5,14 +5,16 @@ services:
|
||||
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
|
||||
- PUID=${PUID} # user id the process runs as, matches ownership of the /adguard mount
|
||||
- PGID=${PGID} # group id the process runs as
|
||||
- TZ=${TZ} # change to your timezone
|
||||
- BLOCK_COUNTRIES=${BLOCK_COUNTRIES} # choose countries listed IP to block. Full lists here https://github.com/vulnebify/cidre/tree/main/output/cidr/ipv4
|
||||
- BLOCKLIST_CRON_TYPE=${BLOCKLIST_CRON_TYPE} # 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
|
||||
- BLOCKLIST_CRON_DAY=${BLOCKLIST_CRON_DAY}
|
||||
- BLOCKLIST_CRON_TIME=${BLOCKLIST_CRON_TIME}
|
||||
- DOCKER_API_URL=${DOCKER_API_URL} # docker socket proxy
|
||||
- ADGUARD_CONTAINER_NAME=${ADGUARD_CONTAINER_NAME} # adguard container name
|
||||
volumes:
|
||||
- /path/to/adguard/confdir:/adguard
|
||||
|
||||
|
||||
Executable
+105
@@ -0,0 +1,105 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
CYAN="\033[1;36m"
|
||||
NC="\033[0m"
|
||||
|
||||
log() { echo "$(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
||||
fail() { echo "$(date '+%Y-%m-%d %H:%M:%S') [!] $*" >&2; exit 1; }
|
||||
|
||||
print_banner() {
|
||||
version=$(cat VERSION 2>/dev/null || echo "unknown")
|
||||
title="AdGuard CIDRe - Version ${version}"
|
||||
lines="Source: https://git.djeex.fr/Djeex/adguard-cidre
|
||||
Mirror: https://github.com/Djeex/adguard-cidre"
|
||||
|
||||
width=${#title}
|
||||
old_ifs=$IFS
|
||||
IFS='
|
||||
'
|
||||
for l in $lines; do
|
||||
[ ${#l} -gt "$width" ] && width=${#l}
|
||||
done
|
||||
IFS=$old_ifs
|
||||
width=$((width + 2))
|
||||
|
||||
border=""
|
||||
i=0
|
||||
while [ "$i" -lt "$width" ]; do
|
||||
border="${border}─"
|
||||
i=$((i + 1))
|
||||
done
|
||||
printf "${CYAN}╭%s╮${NC}\n" "$border"
|
||||
|
||||
total_pad=$((width - ${#title}))
|
||||
left=$((total_pad / 2))
|
||||
right=$((total_pad - left))
|
||||
printf "${CYAN}│${NC}%*s%s%*s${CYAN}│${NC}\n" "$left" "" "$title" "$right" ""
|
||||
|
||||
printf "${CYAN}├%s┤${NC}\n" "$border"
|
||||
|
||||
IFS='
|
||||
'
|
||||
for l in $lines; do
|
||||
printf "${CYAN}│${NC} %-*s${CYAN}│${NC}\n" "$((width - 1))" "$l"
|
||||
done
|
||||
IFS=$old_ifs
|
||||
|
||||
printf "${CYAN}╰%s╯${NC}\n" "$border"
|
||||
}
|
||||
|
||||
print_banner
|
||||
|
||||
PUID=${PUID:-911}
|
||||
PGID=${PGID:-911}
|
||||
|
||||
case "$PGID" in
|
||||
''|*[!0-9]*) fail "PGID '$PGID' is not a valid numeric group id." ;;
|
||||
esac
|
||||
case "$PUID" in
|
||||
''|*[!0-9]*) fail "PUID '$PUID' is not a valid numeric user id." ;;
|
||||
esac
|
||||
|
||||
[ -d /adguard ] || fail "/adguard is not mounted — check the volume mapping in docker-compose.yml."
|
||||
|
||||
log "[i] Requested PUID=$PUID, PGID=$PGID"
|
||||
|
||||
log "[~] Checking group for GID $PGID..."
|
||||
GROUP_NAME=$(getent group "$PGID" | cut -d: -f1 || true)
|
||||
if [ -z "$GROUP_NAME" ]; then
|
||||
log "[→] No existing group with GID $PGID, creating 'appgroup'."
|
||||
addgroup -g "$PGID" appgroup || fail "Failed to create group with GID $PGID (addgroup exited $?)."
|
||||
GROUP_NAME=appgroup
|
||||
else
|
||||
log "[i] Reusing existing group '$GROUP_NAME' (GID $PGID)."
|
||||
fi
|
||||
log "[✓] Group ready: $GROUP_NAME"
|
||||
|
||||
log "[~] Checking user for UID $PUID..."
|
||||
USER_NAME=$(getent passwd "$PUID" | cut -d: -f1 || true)
|
||||
if [ -z "$USER_NAME" ]; then
|
||||
log "[→] No existing user with UID $PUID, creating 'appuser'."
|
||||
adduser -D -u "$PUID" -G "$GROUP_NAME" appuser || fail "Failed to create user with UID $PUID (adduser exited $?)."
|
||||
USER_NAME=appuser
|
||||
else
|
||||
log "[i] Reusing existing user '$USER_NAME' (UID $PUID)."
|
||||
fi
|
||||
log "[✓] User ready: $USER_NAME"
|
||||
|
||||
# Grant write access to the shared AdGuard config directory and to the files
|
||||
# this script manages, without touching anything else AdGuardHome owns in
|
||||
# there (its own db/certs/stats). AdGuardHome itself runs as root, so this is
|
||||
# a one-way grant: it keeps full access regardless of what we chown here.
|
||||
log "[~] Setting ownership of /adguard to $USER_NAME:$GROUP_NAME..."
|
||||
chown "$USER_NAME:$GROUP_NAME" /adguard || fail "chown on /adguard failed — check that the host directory permissions allow it."
|
||||
log "[✓] Ownership set on /adguard"
|
||||
|
||||
for f in AdGuardHome.yaml AdGuardHome.yaml.first-start.bak AdGuardHome.yaml.last-update.bak AdGuardHome.yaml.tmp; do
|
||||
if [ -e "/adguard/$f" ]; then
|
||||
chown "$USER_NAME:$GROUP_NAME" "/adguard/$f" || fail "chown on /adguard/$f failed."
|
||||
log "[✓] chown OK: /adguard/$f"
|
||||
fi
|
||||
done
|
||||
|
||||
log "[→] Dropping privileges to $USER_NAME:$GROUP_NAME and starting blocklist_scheduler.py"
|
||||
exec su-exec "$USER_NAME:$GROUP_NAME" python3 blocklist_scheduler.py "$@"
|
||||
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
pythonpath = .
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended"],
|
||||
"timezone": "Europe/Paris",
|
||||
"labels": ["bot"],
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["pip_requirements"],
|
||||
"matchUpdateTypes": ["patch", "minor"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"matchManagers": ["dockerfile"],
|
||||
"matchUpdateTypes": ["patch"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"matchUpdateTypes": ["major"],
|
||||
"addLabels": ["major"]
|
||||
},
|
||||
{
|
||||
"matchUpdateTypes": ["minor"],
|
||||
"addLabels": ["minor"]
|
||||
}
|
||||
],
|
||||
"vulnerabilityAlerts": {
|
||||
"enabled": true,
|
||||
"addLabels": ["bug"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
requests==2.34.2
|
||||
pyyaml==6.0.3
|
||||
schedule==1.2.2
|
||||
@@ -0,0 +1,210 @@
|
||||
import pytest
|
||||
import schedule as schedule_lib
|
||||
import yaml
|
||||
|
||||
import blocklist_scheduler as bs
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, text="", status_code=200, raise_exc=None):
|
||||
self.text = text
|
||||
self.status_code = status_code
|
||||
self._raise_exc = raise_exc
|
||||
|
||||
def raise_for_status(self):
|
||||
if self._raise_exc:
|
||||
raise self._raise_exc
|
||||
|
||||
|
||||
def test_backup_first_start_creates_backup_when_missing(tmp_path, monkeypatch):
|
||||
adguard_yaml = tmp_path / "AdGuardHome.yaml"
|
||||
adguard_yaml.write_text("original: config\n")
|
||||
first_backup = tmp_path / "AdGuardHome.yaml.first-start.bak"
|
||||
|
||||
monkeypatch.setattr(bs, "ADGUARD_YAML", adguard_yaml)
|
||||
monkeypatch.setattr(bs, "FIRST_BACKUP", first_backup)
|
||||
|
||||
bs.backup_first_start()
|
||||
|
||||
assert first_backup.read_text() == "original: config\n"
|
||||
|
||||
|
||||
def test_backup_first_start_does_not_overwrite_existing_backup(tmp_path, monkeypatch):
|
||||
adguard_yaml = tmp_path / "AdGuardHome.yaml"
|
||||
adguard_yaml.write_text("new: config\n")
|
||||
first_backup = tmp_path / "AdGuardHome.yaml.first-start.bak"
|
||||
first_backup.write_text("pristine: original\n")
|
||||
|
||||
monkeypatch.setattr(bs, "ADGUARD_YAML", adguard_yaml)
|
||||
monkeypatch.setattr(bs, "FIRST_BACKUP", first_backup)
|
||||
|
||||
bs.backup_first_start()
|
||||
|
||||
assert first_backup.read_text() == "pristine: original\n"
|
||||
|
||||
|
||||
def test_backup_first_start_raises_if_adguard_yaml_missing(tmp_path, monkeypatch):
|
||||
adguard_yaml = tmp_path / "AdGuardHome.yaml"
|
||||
first_backup = tmp_path / "AdGuardHome.yaml.first-start.bak"
|
||||
|
||||
monkeypatch.setattr(bs, "ADGUARD_YAML", adguard_yaml)
|
||||
monkeypatch.setattr(bs, "FIRST_BACKUP", first_backup)
|
||||
|
||||
with pytest.raises(FileNotFoundError):
|
||||
bs.backup_first_start()
|
||||
|
||||
|
||||
# --- update_yaml_with_ips (pyyaml) ---
|
||||
|
||||
def test_update_yaml_with_ips_writes_disallowed_clients(tmp_path, monkeypatch):
|
||||
adguard_yaml = tmp_path / "AdGuardHome.yaml"
|
||||
adguard_yaml.write_text("dns:\n bind_hosts:\n - 0.0.0.0\n")
|
||||
tmp_yaml = tmp_path / "AdGuardHome.yaml.tmp"
|
||||
|
||||
monkeypatch.setattr(bs, "ADGUARD_YAML", adguard_yaml)
|
||||
monkeypatch.setattr(bs, "TMP_YAML", tmp_yaml)
|
||||
|
||||
result = bs.update_yaml_with_ips(["1.2.3.0/24", "5.6.7.8"])
|
||||
|
||||
assert result is True
|
||||
data = yaml.safe_load(adguard_yaml.read_text())
|
||||
assert data["dns"]["disallowed_clients"] == ["1.2.3.0/24", "5.6.7.8"]
|
||||
assert not tmp_yaml.exists()
|
||||
|
||||
|
||||
def test_update_yaml_with_ips_missing_file_returns_false(tmp_path, monkeypatch):
|
||||
adguard_yaml = tmp_path / "AdGuardHome.yaml"
|
||||
|
||||
monkeypatch.setattr(bs, "ADGUARD_YAML", adguard_yaml)
|
||||
|
||||
assert bs.update_yaml_with_ips(["1.2.3.4"]) is False
|
||||
|
||||
|
||||
def test_update_yaml_with_ips_invalid_yaml_returns_false(tmp_path, monkeypatch):
|
||||
adguard_yaml = tmp_path / "AdGuardHome.yaml"
|
||||
adguard_yaml.write_text("key: [unclosed\n")
|
||||
|
||||
monkeypatch.setattr(bs, "ADGUARD_YAML", adguard_yaml)
|
||||
|
||||
assert bs.update_yaml_with_ips(["1.2.3.4"]) is False
|
||||
|
||||
|
||||
def test_update_yaml_with_ips_missing_dns_key_raises(tmp_path, monkeypatch):
|
||||
adguard_yaml = tmp_path / "AdGuardHome.yaml"
|
||||
adguard_yaml.write_text("some_other_key: true\n")
|
||||
|
||||
monkeypatch.setattr(bs, "ADGUARD_YAML", adguard_yaml)
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
bs.update_yaml_with_ips(["1.2.3.4"])
|
||||
|
||||
|
||||
# --- fetch_all_country_codes / download_cidr_lists / restart_adguard_container (requests) ---
|
||||
|
||||
def test_fetch_all_country_codes_parses_codes(monkeypatch):
|
||||
monkeypatch.setattr(bs.requests, "get", lambda *a, **k: FakeResponse(text='COUNTRIES = ["FR", "DE", "US"]\n'))
|
||||
|
||||
assert bs.fetch_all_country_codes() == {"fr", "de", "us"}
|
||||
|
||||
|
||||
def test_fetch_all_country_codes_returns_empty_set_on_error(monkeypatch):
|
||||
def raise_error(*a, **k):
|
||||
raise bs.requests.exceptions.ConnectionError("boom")
|
||||
|
||||
monkeypatch.setattr(bs.requests, "get", raise_error)
|
||||
|
||||
assert bs.fetch_all_country_codes() == set()
|
||||
|
||||
|
||||
def test_download_cidr_lists_combines_successful_countries_and_skips_failures(monkeypatch):
|
||||
def fake_get(url, timeout=None):
|
||||
if "/fr.cidr" in url:
|
||||
return FakeResponse(text="1.1.1.0/24\n1.1.2.0/24\n")
|
||||
raise bs.requests.exceptions.ConnectionError("boom")
|
||||
|
||||
monkeypatch.setattr(bs.requests, "get", fake_get)
|
||||
|
||||
result = bs.download_cidr_lists(["fr", "de"])
|
||||
|
||||
assert result == ["1.1.1.0/24", "1.1.2.0/24"]
|
||||
|
||||
|
||||
def test_restart_adguard_container_success_does_not_raise(monkeypatch):
|
||||
monkeypatch.setattr(bs.requests, "post", lambda *a, **k: FakeResponse(status_code=204))
|
||||
|
||||
bs.restart_adguard_container()
|
||||
|
||||
|
||||
def test_restart_adguard_container_error_status_does_not_raise(monkeypatch):
|
||||
monkeypatch.setattr(bs.requests, "post", lambda *a, **k: FakeResponse(status_code=500, text="err"))
|
||||
|
||||
bs.restart_adguard_container()
|
||||
|
||||
|
||||
def test_restart_adguard_container_network_error_does_not_raise(monkeypatch):
|
||||
def raise_error(*a, **k):
|
||||
raise bs.requests.exceptions.ConnectionError("boom")
|
||||
|
||||
monkeypatch.setattr(bs.requests, "post", raise_error)
|
||||
|
||||
bs.restart_adguard_container()
|
||||
|
||||
|
||||
# --- schedule_job (schedule) ---
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def clear_schedule():
|
||||
yield
|
||||
schedule_lib.clear()
|
||||
|
||||
|
||||
def test_schedule_job_daily(monkeypatch):
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_TYPE", "daily")
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_TIME", "06:00")
|
||||
|
||||
bs.schedule_job()
|
||||
|
||||
assert len(schedule_lib.jobs) == 1
|
||||
job = schedule_lib.jobs[0]
|
||||
assert job.unit == "days"
|
||||
assert str(job.at_time) == "06:00:00"
|
||||
assert job.job_func.func is bs.update_blocklist
|
||||
|
||||
|
||||
def test_schedule_job_weekly_valid_day(monkeypatch):
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_TYPE", "weekly")
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_TIME", "18:30")
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_DAY", "wed")
|
||||
|
||||
bs.schedule_job()
|
||||
|
||||
job = schedule_lib.jobs[0]
|
||||
assert job.unit == "weeks"
|
||||
assert job.start_day == "wednesday"
|
||||
assert str(job.at_time) == "18:30:00"
|
||||
|
||||
|
||||
def test_schedule_job_weekly_invalid_day_defaults_to_monday(monkeypatch):
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_TYPE", "weekly")
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_DAY", "xxx")
|
||||
|
||||
bs.schedule_job()
|
||||
|
||||
assert schedule_lib.jobs[0].start_day == "monday"
|
||||
|
||||
|
||||
def test_schedule_job_invalid_time_defaults_to_six_am(monkeypatch):
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_TYPE", "daily")
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_TIME", "not-a-time")
|
||||
|
||||
bs.schedule_job()
|
||||
|
||||
assert str(schedule_lib.jobs[0].at_time) == "06:00:00"
|
||||
|
||||
|
||||
def test_schedule_job_invalid_type_defaults_to_daily(monkeypatch):
|
||||
monkeypatch.setattr(bs, "BLOCKLIST_CRON_TYPE", "bogus")
|
||||
|
||||
bs.schedule_job()
|
||||
|
||||
assert schedule_lib.jobs[0].unit == "days"
|
||||
Reference in New Issue
Block a user