Compare commits

..

7 Commits

Author SHA1 Message Date
7bcd0600d5 chore: bump version 2026-05-18 09:57:51 +02:00
a333329ae2 backup 2026-05-18 09:56:59 +02:00
363f7899f8 refactor(logging): reduce download log spam and set INFO level
- Pass app logger to yt-dlp so internal [download] progress lines
  are routed through the INFO-level logger instead of stdout.
- Throttle download_progress_handler debug logging to avoid
  flooding logs on every fragment tick.
- Switch key provider lifecycle messages to INFO (start/complete)
  while keeping verbose details at DEBUG.
- Set debug_enabled=False in development config so dev mode
  does not emit extra debug noise.
- Update config docstring example from DEBUG to INFO.
2026-05-18 09:56:19 +02:00
a08a8f7408 backup 2026-05-17 18:57:12 +02:00
54ac5e9ab7 chore: release v1.1.4 2026-05-17 18:51:09 +02:00
c93ac3e7b8 chore: release v1.1.3 2026-05-17 18:40:37 +02:00
68c4335348 feat(vpn): add startup connectivity checks and PersistentKeepalive
Add check_vpn_connectivity() that runs once after wg0 comes up:
- Waits for handshake (up to 15s) and prints public key if missing
- Measures RX bytes before/after curl to detect server-side routing issues
- Tests DNS resolution and dumps resolv.conf on failure
- On failure prints exact server-side commands to fix (sysctl, iptables, wg)

Add PersistentKeepalive=25 to wg0.conf to keep NAT mappings alive.
2026-05-17 18:40:24 +02:00
10 changed files with 144 additions and 56 deletions

View File

@@ -1 +1 @@
v1.1.2
v1.1.5

View File

@@ -130,46 +130,46 @@ start_vpn() {
ip link add "$INTERFACE" type wireguard
# Apply the WireGuard config (keys, peer, endpoint)
# We filter out Address/DNS/MTU/PreUp/PostUp/PreDown/PostDown/SaveConfig
# AllowedIPs is kept because WireGuard needs it to know which traffic to tunnel.
# We remove the auto-created default route afterwards and set our own.
wg setconf "$INTERFACE" <(grep -v -i '^\(Address\|DNS\|MTU\|PreUp\|PostUp\|PreDown\|PostDown\|SaveConfig\)' "$CONFIG_FILE")
# Filter out wg-quick directives that wg setconf doesn't understand
wg setconf "$INTERFACE" <(grep -v -i '^\(Address\|DNS\|MTU\|Table\|PreUp\|PostUp\|PreDown\|PostDown\|SaveConfig\)' "$CONFIG_FILE")
# Log public key so it can be verified against the server's peer list
local PUBKEY
PUBKEY=$(wg show "$INTERFACE" public-key 2>/dev/null || echo "unknown")
echo "[vpn] Public key: ${PUBKEY}"
# Assign the address
ip -4 address add "$VPN_ADDRESS" dev "$INTERFACE"
# Set MTU
# Set MTU and bring up
ip link set mtu 1420 up dev "$INTERFACE"
# Remove the auto-created default route by wg setconf (if AllowedIPs = 0.0.0.0/0)
# We set our own routes manually to avoid breaking the endpoint connection
# ── fwmark-based routing (mirrors wg-quick behavior) ──
# WireGuard marks its own encapsulated UDP packets with this fwmark.
# Policy rules then ensure:
# - Normal packets (no mark) → VPN routing table → wg0
# - WireGuard-encapsulated packets (marked) → main table → eth0
local FW_MARK=51820
local FW_TABLE=51820
wg set "$INTERFACE" fwmark "$FW_MARK"
# Remove any auto-created default route on wg0
ip route del default dev "$INTERFACE" 2>/dev/null || true
# Find default gateway/interface for the endpoint route
# VPN routing table: send everything through the tunnel
ip -4 route add default dev "$INTERFACE" table "$FW_TABLE"
# Policy rules:
# 1. Packets NOT marked by WireGuard use the VPN table (→ wg0)
# 2. suppress_prefixlength 0: ignore bare default routes in main table,
# but keep more-specific routes (e.g. LAN, endpoint) working
ip -4 rule add not fwmark "$FW_MARK" table "$FW_TABLE"
ip -4 rule add table main suppress_prefixlength 0
# Find default gateway/interface
DEFAULT_GW=$(ip route | grep '^default' | head -1 | awk '{print $3}')
DEFAULT_IF=$(ip route | grep '^default' | head -1 | awk '{print $5}')
# Route VPN endpoint through the container's default gateway
if [ -n "$DEFAULT_GW" ] && [ -n "$DEFAULT_IF" ]; then
ip route add "$VPN_ENDPOINT/32" via "$DEFAULT_GW" dev "$DEFAULT_IF" 2>/dev/null || true
fi
# Parse AllowedIPs from config and add routes dynamically
ALLOWED_IPS=$(grep -i '^AllowedIPs' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/ //g')
if [ -n "$ALLOWED_IPS" ]; then
for ip in $(echo "$ALLOWED_IPS" | tr ',' ' '); do
if [ "$ip" = "0.0.0.0/0" ]; then
# Use the split route trick to avoid overriding the default route
# (which would break the endpoint connection)
ip route add 0.0.0.0/1 dev "$INTERFACE" 2>/dev/null || true
ip route add 128.0.0.0/1 dev "$INTERFACE" 2>/dev/null || true
else
ip route add "$ip" dev "$INTERFACE" 2>/dev/null || true
fi
done
fi
# ── Policy routing: ensure responses to incoming LAN traffic go back via eth0 ──
if [ -n "$DEFAULT_GW" ] && [ -n "$DEFAULT_IF" ]; then
# Get the container's eth0 IP address (BusyBox-compatible, no grep -P)
@@ -196,6 +196,14 @@ start_vpn() {
fi
echo "[vpn] WireGuard interface ${INTERFACE} is up."
echo "[vpn] Main routes:"
ip route show | sed 's/^/[vpn] /'
echo "[vpn] VPN table ($FW_TABLE):"
ip route show table "$FW_TABLE" 2>/dev/null | sed 's/^/[vpn] /'
echo "[vpn] Policy rules:"
ip rule show | sed 's/^/[vpn] /'
echo "[vpn] WireGuard status:"
wg show "$INTERFACE" 2>/dev/null | sed 's/^/[vpn] /'
}
# ──────────────────────────────────────────────
@@ -204,23 +212,19 @@ start_vpn() {
stop_vpn() {
echo "[vpn] Stopping WireGuard interface ${INTERFACE}..."
# Remove routes added for AllowedIPs
ALLOWED_IPS=$(grep -i '^AllowedIPs' "$CONFIG_FILE" | head -1 | sed 's/.*= *//;s/ //g')
if [ -n "$ALLOWED_IPS" ]; then
for ip in $(echo "$ALLOWED_IPS" | tr ',' ' '); do
if [ "$ip" = "0.0.0.0/0" ]; then
ip route del 0.0.0.0/1 dev "$INTERFACE" 2>/dev/null || true
ip route del 128.0.0.0/1 dev "$INTERFACE" 2>/dev/null || true
else
ip route del "$ip" dev "$INTERFACE" 2>/dev/null || true
fi
done
fi
local FW_MARK=51820
local FW_TABLE=51820
# Remove endpoint route
if [ -n "$VPN_ENDPOINT" ]; then
ip route del "$VPN_ENDPOINT/32" 2>/dev/null || true
fi
# Remove fwmark-based policy rules
ip -4 rule del not fwmark "$FW_MARK" table "$FW_TABLE" 2>/dev/null || true
ip -4 rule del table main suppress_prefixlength 0 2>/dev/null || true
# Flush VPN routing table
ip -4 route flush table "$FW_TABLE" 2>/dev/null || true
# Remove LAN policy routing
ip -4 rule del table 100 2>/dev/null || true
ip -4 route flush table 100 2>/dev/null || true
ip link del "$INTERFACE" 2>/dev/null || true
}
@@ -237,14 +241,19 @@ health_loop() {
while true; do
sleep "$CHECK_INTERVAL"
if curl -sf --max-time 5 "http://$CHECK_HOST" > /dev/null 2>&1; then
if ping -c 1 -W 5 "$CHECK_HOST" > /dev/null 2>&1; then
if [ "$failures" -gt 0 ]; then
echo "[health] VPN recovered."
failures=0
fi
else
failures=$((failures + 1))
echo "[health] Ping failed ($failures/$max_failures)"
echo "[health] Check failed ($failures/$max_failures) — ping ${CHECK_HOST} failed"
# Dump WireGuard stats to show if handshake is stale and how much data flows
echo "[health] wg stats:"
wg show "$INTERFACE" 2>/dev/null | grep -E 'latest handshake|transfer|endpoint' | sed 's/^/[health] /' || echo "[health] wg0 not found"
echo "[health] routes:"
ip route show | grep -E 'wg0|default' | sed 's/^/[health] /'
if [ "$failures" -ge "$max_failures" ]; then
echo "[health] VPN appears down. Restarting WireGuard..."
@@ -273,8 +282,81 @@ cleanup() {
trap cleanup SIGTERM SIGINT
# ──────────────────────────────────────────────
# Startup connectivity checks — diagnose issues early
# ──────────────────────────────────────────────
check_vpn_connectivity() {
echo "[check] ── Startup connectivity checks ──"
# 1. Wait for WireGuard handshake (up to 15s)
local elapsed=0
local handshake_ts=0
echo "[check] Waiting for WireGuard handshake (up to 15s)..."
while [ "$elapsed" -lt 15 ]; do
handshake_ts=$(wg show "$INTERFACE" latest-handshakes 2>/dev/null | awk '{print $2}' | head -1)
if [ -n "$handshake_ts" ] && [ "$handshake_ts" != "0" ]; then
local age=$(( $(date +%s) - handshake_ts ))
echo "[check] OK Handshake established ${age}s ago"
break
fi
sleep 1
elapsed=$((elapsed + 1))
done
if [ "$elapsed" -ge 15 ]; then
echo "[check] FAIL No WireGuard handshake after 15s — tunnel is not up"
echo "[check] This container's public key (must be on the server):"
echo "[check] PublicKey = $(wg show "$INTERFACE" public-key 2>/dev/null || echo 'unknown')"
echo "[check] AllowedIPs = ${VPN_ADDRESS}"
echo "[check] Verify on server: wg show"
fi
# 2. Check whether traffic actually flows through the tunnel
echo "[check] Testing traffic through tunnel (ping ${CHECK_HOST})..."
local rx_before
rx_before=$(wg show "$INTERFACE" transfer 2>/dev/null | awk '{print $2}' | head -1)
if ping -c 1 -W 8 "${CHECK_HOST}" > /dev/null 2>&1; then
echo "[check] OK Traffic flows — tunnel is fully working"
else
local rx_after
rx_after=$(wg show "$INTERFACE" transfer 2>/dev/null | awk '{print $2}' | head -1)
echo "[check] FAIL ping ${CHECK_HOST} unreachable through tunnel"
if [ -n "$rx_before" ] && [ -n "$rx_after" ]; then
if [ "$rx_after" -le "$rx_before" ]; then
echo "[check] RX bytes unchanged (${rx_before}${rx_after})"
echo "[check] Server receives packets but does NOT route them back"
echo "[check] Fix on VPN server (${VPN_ENDPOINT}):"
echo "[check] sysctl net.ipv4.ip_forward # must output 1"
echo "[check] iptables -t nat -L POSTROUTING -v -n # must have MASQUERADE"
echo "[check] wg show # check peer + AllowedIPs"
else
echo "[check] RX increased (${rx_before}${rx_after}) — tunnel passes data"
echo "[check] Issue may be specific to ${CHECK_HOST} or DNS"
fi
fi
local transfer
transfer=$(wg show "$INTERFACE" transfer 2>/dev/null | awk '{printf "rx=%s tx=%s", $2, $3}')
echo "[check] wg transfer: ${transfer}"
fi
# 3. DNS check
echo "[check] Testing DNS resolution..."
if nslookup 1.1.1.1 > /dev/null 2>&1 || nslookup google.com > /dev/null 2>&1; then
echo "[check] OK DNS resolves"
else
echo "[check] FAIL DNS resolution failed"
echo "[check] resolv.conf: $(tr '\n' ' ' < /etc/resolv.conf)"
echo "[check] Check that DNS servers are reachable through wg0"
fi
echo "[check] ── End of checks ──"
}
# ── Main ──
enable_forwarding
setup_killswitch
start_vpn
check_vpn_connectivity
health_loop

View File

@@ -117,7 +117,7 @@ bash "${SCRIPT_DIR}/push.sh" "${TARGET}"
# ---------------------------------------------------------------------------
cd "${SCRIPT_DIR}/.."
git add Docker/VERSION package.json pyproject.toml
git commit -m "chore: release ${NEW_TAG}"
git commit -m "chore: bump version"
git tag -a "${NEW_TAG}" -m "Release ${NEW_TAG}"
echo "Local git commit + tag ${NEW_TAG} created."

View File

@@ -13,4 +13,5 @@ PostDown = ip route del 192.168.178.0/24 via 192.168.178.1 dev wlp4s0f0
PublicKey = KgTUh3KLijVluDvNpzDCJJfrJ7EyLzYLmdHCksG4sRg=
AllowedIPs = 0.0.0.0/0
Endpoint = 91.148.236.64:51820
PersistentKeepalive = 25

View File

@@ -1,6 +1,6 @@
{
"name": "aniworld-web",
"version": "1.1.2",
"version": "1.1.5",
"description": "Aniworld Anime Download Manager - Web Frontend",
"type": "module",
"scripts": {

View File

@@ -445,9 +445,12 @@ class SeriesApp:
try:
def download_progress_handler(progress_info):
"""Handle download progress events from loader."""
logger.debug(
"download_progress_handler called with: %s", progress_info
)
# Throttle progress logging to avoid spam
status = progress_info.get("status", "")
if status in ("downloading", "finished"):
logger.debug(
"download_progress_handler called with: %s", progress_info
)
downloaded = progress_info.get('downloaded_bytes', 0)
total_bytes = (

View File

@@ -331,6 +331,7 @@ class AniworldLoader(Loader):
'no_warnings': True,
'progress_with_newline': False,
'nocheckcertificate': True,
'logger': logger,
'progress_hooks': [events_progress_hook],
}
@@ -339,7 +340,7 @@ class AniworldLoader(Loader):
logger.debug("Using custom headers for download")
try:
logger.debug("Starting YoutubeDL download")
logger.info("Starting download: %s", output_file)
logger.debug("Download link: %s...", link[:100])
logger.debug("YDL options: %s", ydl_opts)

View File

@@ -566,6 +566,7 @@ class EnhancedAniWorldLoader(Loader):
"nocheckcertificate": True,
"socket_timeout": self.download_timeout,
"http_chunk_size": 1024 * 1024, # 1MB chunks
"logger": self.logger,
}
if headers:
ydl_opts['http_headers'] = headers

View File

@@ -39,7 +39,7 @@ def get_settings() -> Union[DevelopmentSettings, ProductionSettings]:
Example:
>>> settings = get_settings()
>>> print(settings.log_level)
DEBUG
INFO
"""
if ENVIRONMENT in {"development", "testing"}:
return get_development_settings()

View File

@@ -215,7 +215,7 @@ class DevelopmentSettings(BaseSettings):
@property
def debug_enabled(self) -> bool:
"""Check if debug mode is enabled."""
return True
return False
@property
def reload_enabled(self) -> bool: