Commit 6945e6d1 authored by Vitaly Lipatov's avatar Vitaly Lipatov

route-update.sh: use named groups instead of IP-based directory names

Directory name is now a logical group name (egw, ogw) with gateway and table specified in files, same as routes6.d/. Unify process_v4 and process_v6 into a single process_routes function. Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 72415afa
#!/bin/sh
# Unified route updater: reads directory-based config from routes.d/ and routes6.d/
# Each subdirectory = gateway, .list symlinks inside = domain/IP lists to route through it
# Each subdirectory = route group with gateway/table files and .list symlinks
#
# Usage: route-update.sh [--force] [--show] [--set-rules] [--flush GATEWAY]
# route-update.sh --add IP|DOMAIN GATEWAY
# route-update.sh --del IP|DOMAIN GATEWAY
# Usage: route-update.sh [--force] [--show] [--set-rules]
# route-update.sh --add IP|DOMAIN GROUP
# route-update.sh --del IP|DOMAIN GROUP
# route-update.sh --flush GROUP
cd "$(dirname "$(realpath "$0")")" || exit
......@@ -21,17 +22,17 @@ SET_RULES=
# Parse arguments
ACTION=
ADD_DEL_TARGET=
ADD_DEL_GW=
FLUSH_GW=
ADD_DEL_GROUP=
FLUSH_GROUP=
while [ -n "$1" ] ; do
case "$1" in
--force) FORCE=1 ;;
--show) SHOW=1 ;;
--set-rules) SET_RULES=1 ;;
--flush) FLUSH_GW="$2" ; shift ;;
--add) ACTION=add ; ADD_DEL_TARGET="$2" ; ADD_DEL_GW="$3" ; shift 2 ;;
--del) ACTION=del ; ADD_DEL_TARGET="$2" ; ADD_DEL_GW="$3" ; shift 2 ;;
--flush) FLUSH_GROUP="$2" ; shift ;;
--add) ACTION=add ; ADD_DEL_TARGET="$2" ; ADD_DEL_GROUP="$3" ; shift 2 ;;
--del) ACTION=del ; ADD_DEL_TARGET="$2" ; ADD_DEL_GROUP="$3" ; shift 2 ;;
*) echo "Unknown option: $1" >&2 ; exit 1 ;;
esac
shift
......@@ -42,20 +43,12 @@ log() { echo "$(date '+%H:%M:%S') $*" ; }
md5_lists()
{
# md5 of concatenated content of all .list files (following symlinks)
cat "$@" 2>/dev/null | md5sum | awk '{print $1}'
}
ensure_state_dir()
{
local dir="$1"
mkdir -p "$STATE_DIR/$dir"
}
# Get table number from IPv4 gateway (last octet)
ipv4_table()
{
echo "${1##*.}"
mkdir -p "$STATE_DIR/$1"
}
# Get rule priority from table number (table * 10)
......@@ -64,207 +57,139 @@ rule_pref()
echo $(( $1 * 10 ))
}
# --- Manual add/del ---
if [ "$ACTION" = "add" ] ; then
[ -n "$ADD_DEL_TARGET" ] && [ -n "$ADD_DEL_GW" ] || { echo "Usage: $0 --add IP|DOMAIN GATEWAY" >&2 ; exit 1 ; }
table=$(ipv4_table "$ADD_DEL_GW")
if is_ipv4 "$ADD_DEL_TARGET" ; then
ip route replace "$ADD_DEL_TARGET" via "$ADD_DEL_GW" table "$table"
# Read gateway and table from a group directory
# Sets: gw, table
read_group_config()
{
local dir="$1"
gw="" ; table=""
if [ -f "$dir/gateway" ] ; then
read -r gw < "$dir/gateway"
else
get_ipv4_list "$ADD_DEL_TARGET" | sort -u | while read -r ip ; do
[ -n "$ip" ] || continue
ip route replace "$ip" via "$ADD_DEL_GW" table "$table"
done
echo "[$(basename "$dir")] No gateway file, skipping" >&2
return 1
fi
exit
fi
if [ "$ACTION" = "del" ] ; then
[ -n "$ADD_DEL_TARGET" ] && [ -n "$ADD_DEL_GW" ] || { echo "Usage: $0 --del IP|DOMAIN GATEWAY" >&2 ; exit 1 ; }
table=$(ipv4_table "$ADD_DEL_GW")
if is_ipv4 "$ADD_DEL_TARGET" ; then
ip route del "$ADD_DEL_TARGET" table "$table" 2>/dev/null
if [ -f "$dir/table" ] ; then
read -r table < "$dir/table"
else
get_ipv4_list "$ADD_DEL_TARGET" | sort -u | while read -r ip ; do
[ -n "$ip" ] || continue
ip route del "$ip" table "$table" 2>/dev/null
done
echo "[$(basename "$dir")] No table file, skipping" >&2
return 1
fi
exit
fi
# --- Flush a specific gateway ---
if [ -n "$FLUSH_GW" ] ; then
table=$(ipv4_table "$FLUSH_GW")
log "Flushing table $table (gateway $FLUSH_GW)"
[ -z "$SHOW" ] && ip route flush table "$table" 2>/dev/null
exit
fi
}
# --- Process IPv4 routes.d/ ---
process_v4()
# --- Manual add/del ---
# Find group directory and read its config
find_group()
{
[ -d "$ROUTES_DIR" ] || return 0
for gwdir in "$ROUTES_DIR"/*/ ; do
[ -d "$gwdir" ] || continue
gw=$(basename "$gwdir")
table=$(ipv4_table "$gw")
pref=$(rule_pref "$table")
state="$ROUTES_DIR/$gw"
ensure_state_dir "$state"
# Collect .list files (follow symlinks with ls -L)
lists=$(find -L "$gwdir" -maxdepth 1 -name '*.list' -type f 2>/dev/null | sort)
if [ -z "$lists" ] ; then
log "[$gw] No .list files, flushing table $table"
if [ -z "$SHOW" ] ; then
ip route flush table "$table" 2>/dev/null
ip rule del lookup "$table" 2>/dev/null
rm -f "$STATE_DIR/$state/hash" "$STATE_DIR/$state/resolved"
fi
continue
fi
# Compute hash of all list contents
current_hash=$(md5_lists $lists)
if [ -z "$FORCE" ] && [ -f "$STATE_DIR/$state/hash" ] ; then
read -r saved_hash < "$STATE_DIR/$state/hash"
if [ "$current_hash" = "$saved_hash" ] ; then
[ -n "$SHOW" ] && log "[$gw] No changes (hash match), skipping"
continue
fi
local name="$1"
for base in "$ROUTES_DIR" "$ROUTES6_DIR" ; do
if [ -d "$base/$name" ] ; then
echo "$base/$name"
return 0
fi
done
echo "Group '$name' not found in $ROUTES_DIR or $ROUTES6_DIR" >&2
return 1
}
log "[$gw] Changes detected, resolving..."
if [ "$ACTION" = "add" ] || [ "$ACTION" = "del" ] ; then
[ -n "$ADD_DEL_TARGET" ] && [ -n "$ADD_DEL_GROUP" ] || { echo "Usage: $0 --$ACTION IP|DOMAIN GROUP" >&2 ; exit 1 ; }
groupdir=$(find_group "$ADD_DEL_GROUP") || exit 1
read_group_config "$groupdir" || exit 1
# Fetch lists and run DNS resolution
# Split: pure IP files (no letters except comments) go through cat,
# domain files go through cat_expanded + get_ipv4_list_bulk
resolved_new="$STATE_DIR/$state/resolved.new"
ip_lists=""
domain_lists=""
for f in $lists ; do
if grep -v '^#' "$f" | grep -q '[a-zA-Z]' ; then
domain_lists="$domain_lists $f"
else
ip_lists="$ip_lists $f"
fi
done
# Detect IPv4 vs IPv6
ipcmd="ip"
echo "$gw" | grep -q ':' && ipcmd="ip -6"
{
# Pure IP lists — pass through directly
for f in $ip_lists ; do
grep -v '^#' "$f" | grep -v '^$'
if [ "$ACTION" = "add" ] ; then
if is_ipv4 "$ADD_DEL_TARGET" || is_ipv6 "$ADD_DEL_TARGET" ; then
$ipcmd route replace "$ADD_DEL_TARGET" via "$gw" table "$table"
else
get_ipv4_list "$ADD_DEL_TARGET" | sort -u | while read -r ip ; do
[ -n "$ip" ] || continue
$ipcmd route replace "$ip" via "$gw" table "$table"
done
# Domain lists — expand ranges and resolve
if [ -n "$domain_lists" ] ; then
for f in $domain_lists ; do
cat_expanded "$f"
done | grep -v '^#' | grep -v '^$' | get_ipv4_list_bulk | \
grep -v '^#' | grep -v '^$'
fi
} | sort -u > "$resolved_new"
# Check if resolved IPs actually changed
if [ -z "$FORCE" ] && [ -f "$STATE_DIR/$state/resolved" ] ; then
if diff -q "$STATE_DIR/$state/resolved" "$resolved_new" >/dev/null 2>&1 ; then
log "[$gw] Resolved IPs unchanged, updating hash only"
echo "$current_hash" > "$STATE_DIR/$state/hash"
rm -f "$resolved_new"
continue
fi
fi
count=$(wc -l < "$resolved_new")
log "[$gw] Loading $count routes into table $table via $gw"
if [ -n "$SHOW" ] ; then
echo " Would flush table $table and load $count routes"
head -5 "$resolved_new" | sed 's/^/ /'
[ "$count" -gt 5 ] && echo " ... ($count total)"
rm -f "$resolved_new"
continue
fi
# Flush and load via batch
ip route flush table "$table" 2>/dev/null
sed "s|^|route replace |; s|$| via $gw table $table|" "$resolved_new" | \
ip -batch - 2>&1 | grep -v "^$" | head -5
# Ensure ip rule exists
if ! ip rule show | grep -q "lookup $table.*pref $pref" ; then
ip rule add lookup "$table" pref "$pref" 2>/dev/null
else
if is_ipv4 "$ADD_DEL_TARGET" || is_ipv6 "$ADD_DEL_TARGET" ; then
$ipcmd route del "$ADD_DEL_TARGET" table "$table" 2>/dev/null
else
get_ipv4_list "$ADD_DEL_TARGET" | sort -u | while read -r ip ; do
[ -n "$ip" ] || continue
$ipcmd route del "$ip" table "$table" 2>/dev/null
done
fi
fi
exit
fi
# Save state
echo "$current_hash" > "$STATE_DIR/$state/hash"
mv "$resolved_new" "$STATE_DIR/$state/resolved"
log "[$gw] Done"
done
}
# --- Flush a specific group ---
if [ -n "$FLUSH_GROUP" ] ; then
groupdir=$(find_group "$FLUSH_GROUP") || exit 1
read_group_config "$groupdir" || exit 1
ipcmd="ip"
echo "$gw" | grep -q ':' && ipcmd="ip -6"
log "Flushing table $table (group $FLUSH_GROUP)"
[ -z "$SHOW" ] && $ipcmd route flush table "$table" 2>/dev/null
exit
fi
# --- Process IPv6 routes6.d/ ---
process_v6()
# --- Process a routes directory (works for both IPv4 and IPv6) ---
# Usage: process_routes ROUTES_DIR resolve_func ip_cmd
# resolve_func: get_ipv4_list_bulk or get_ipv6_list_bulk
# ip_cmd: "ip" or "ip -6"
process_routes()
{
[ -d "$ROUTES6_DIR" ] || return 0
local routes_dir="$1"
local resolve_func="$2"
local ipcmd="$3"
local label="$4" # "(v6)" or ""
[ -d "$routes_dir" ] || return 0
for gwdir in "$ROUTES6_DIR"/*/ ; do
for gwdir in "$routes_dir"/*/ ; do
[ -d "$gwdir" ] || continue
dirname=$(basename "$gwdir")
local name=$(basename "$gwdir")
# Read gateway and table from files, or derive from dirname
if [ -f "$gwdir/gateway" ] ; then
read -r gw < "$gwdir/gateway"
else
# Restore IPv6 from dirname: - → :, -- → ::
gw=$(echo "$dirname" | sed 's/--/::/g; s/-/:/g')
fi
read_group_config "$gwdir" || continue
if [ -f "$gwdir/table" ] ; then
read -r table < "$gwdir/table"
else
echo "[$dirname] No table file, skipping" >&2
continue
fi
pref=$(rule_pref "$table")
state="$ROUTES6_DIR/$dirname"
local pref=$(rule_pref "$table")
local state="$routes_dir/$name"
ensure_state_dir "$state"
# Collect .list files
lists=$(find -L "$gwdir" -maxdepth 1 -name '*.list' -type f 2>/dev/null | sort)
# Collect .list files (follow symlinks)
local lists=$(find -L "$gwdir" -maxdepth 1 -name '*.list' -type f 2>/dev/null | sort)
if [ -z "$lists" ] ; then
log "[$dirname] (v6) No .list files, flushing table $table"
log "[$name]$label No .list files, flushing table $table"
if [ -z "$SHOW" ] ; then
ip -6 route flush table "$table" 2>/dev/null
ip -6 rule del lookup "$table" 2>/dev/null
$ipcmd route flush table "$table" 2>/dev/null
$ipcmd rule del lookup "$table" 2>/dev/null
rm -f "$STATE_DIR/$state/hash" "$STATE_DIR/$state/resolved"
fi
continue
fi
current_hash=$(md5_lists $lists)
# Compute hash of all list contents
local current_hash=$(md5_lists $lists)
if [ -z "$FORCE" ] && [ -f "$STATE_DIR/$state/hash" ] ; then
local saved_hash
read -r saved_hash < "$STATE_DIR/$state/hash"
if [ "$current_hash" = "$saved_hash" ] ; then
[ -n "$SHOW" ] && log "[$dirname] (v6) No changes (hash match), skipping"
[ -n "$SHOW" ] && log "[$name]$label No changes (hash match), skipping"
continue
fi
fi
log "[$dirname] (v6) Changes detected, resolving..."
log "[$name]$label Changes detected, resolving..."
resolved_new="$STATE_DIR/$state/resolved.new"
ip_lists=""
domain_lists=""
# Split: pure IP files go through cat, domain files through resolve
local resolved_new="$STATE_DIR/$state/resolved.new"
local ip_lists="" domain_lists=""
for f in $lists ; do
if grep -v '^#' "$f" | grep -q '[a-zA-Z]' ; then
domain_lists="$domain_lists $f"
......@@ -280,22 +205,23 @@ process_v6()
if [ -n "$domain_lists" ] ; then
for f in $domain_lists ; do
cat_expanded "$f"
done | grep -v '^#' | grep -v '^$' | get_ipv6_list_bulk | \
done | grep -v '^#' | grep -v '^$' | $resolve_func | \
grep -v '^#' | grep -v '^$'
fi
} | sort -u > "$resolved_new"
# Check if resolved IPs actually changed
if [ -z "$FORCE" ] && [ -f "$STATE_DIR/$state/resolved" ] ; then
if diff -q "$STATE_DIR/$state/resolved" "$resolved_new" >/dev/null 2>&1 ; then
log "[$dirname] (v6) Resolved IPs unchanged, updating hash only"
log "[$name]$label Resolved IPs unchanged, updating hash only"
echo "$current_hash" > "$STATE_DIR/$state/hash"
rm -f "$resolved_new"
continue
fi
fi
count=$(wc -l < "$resolved_new")
log "[$dirname] (v6) Loading $count routes into table $table via $gw"
local count=$(wc -l < "$resolved_new")
log "[$name]$label Loading $count routes into table $table via $gw"
if [ -n "$SHOW" ] ; then
echo " Would flush table $table and load $count routes"
......@@ -305,84 +231,65 @@ process_v6()
continue
fi
ip -6 route flush table "$table" 2>/dev/null
# Flush and load via batch
$ipcmd route flush table "$table" 2>/dev/null
sed "s|^|route replace |; s|$| via $gw table $table|" "$resolved_new" | \
ip -6 -batch - 2>&1 | grep -v "^$" | head -5
$ipcmd -batch - 2>&1 | grep -v "^$" | head -5
if ! ip -6 rule show | grep -q "lookup $table.*pref $pref" ; then
ip -6 rule add lookup "$table" pref "$pref" 2>/dev/null
# Ensure ip rule exists
if ! $ipcmd rule show | grep -q "lookup $table.*pref $pref" ; then
$ipcmd rule add lookup "$table" pref "$pref" 2>/dev/null
fi
# Save state
echo "$current_hash" > "$STATE_DIR/$state/hash"
echo "$table" > "$STATE_DIR/$state/table"
mv "$resolved_new" "$STATE_DIR/$state/resolved"
log "[$dirname] (v6) Done"
log "[$name]$label Done"
done
}
# --- Cleanup orphaned state dirs ---
cleanup_state()
{
# IPv4
if [ -d "$STATE_DIR/$ROUTES_DIR" ] ; then
for sdir in "$STATE_DIR/$ROUTES_DIR"/*/ ; do
[ -d "$sdir" ] || continue
name=$(basename "$sdir")
if [ ! -d "$ROUTES_DIR/$name" ] ; then
table=$(ipv4_table "$name")
log "Cleaning orphaned state for $name (table $table)"
if [ -z "$SHOW" ] ; then
ip route flush table "$table" 2>/dev/null
ip rule del lookup "$table" 2>/dev/null
rm -rf "$sdir"
fi
fi
done
fi
for routes_dir in "$ROUTES_DIR" "$ROUTES6_DIR" ; do
[ -d "$STATE_DIR/$routes_dir" ] || continue
local ipcmd="ip"
[ "$routes_dir" = "$ROUTES6_DIR" ] && ipcmd="ip -6"
# IPv6
if [ -d "$STATE_DIR/$ROUTES6_DIR" ] ; then
for sdir in "$STATE_DIR/$ROUTES6_DIR"/*/ ; do
for sdir in "$STATE_DIR/$routes_dir"/*/ ; do
[ -d "$sdir" ] || continue
name=$(basename "$sdir")
if [ ! -d "$ROUTES6_DIR/$name" ] ; then
if [ -f "$sdir/table" ] ; then
read -r table < "$sdir/table"
else
table=""
fi
log "Cleaning orphaned v6 state for $name"
local name=$(basename "$sdir")
if [ ! -d "$routes_dir/$name" ] ; then
local table=""
[ -f "$sdir/table" ] && read -r table < "$sdir/table"
log "Cleaning orphaned state for $name"
if [ -z "$SHOW" ] && [ -n "$table" ] ; then
ip -6 route flush table "$table" 2>/dev/null
ip -6 rule del lookup "$table" 2>/dev/null
$ipcmd route flush table "$table" 2>/dev/null
$ipcmd rule del lookup "$table" 2>/dev/null
fi
[ -z "$SHOW" ] && rm -rf "$sdir"
fi
done
fi
done
}
# --- Set-rules only mode ---
if [ -n "$SET_RULES" ] ; then
for gwdir in "$ROUTES_DIR"/*/ ; do
[ -d "$gwdir" ] || continue
gw=$(basename "$gwdir")
table=$(ipv4_table "$gw")
pref=$(rule_pref "$table")
if ! ip rule show | grep -q "lookup $table.*pref $pref" ; then
ip rule add lookup "$table" pref "$pref"
fi
done
for gwdir in "$ROUTES6_DIR"/*/ ; do
[ -d "$gwdir" ] || continue
dirname=$(basename "$gwdir")
[ -f "$gwdir/table" ] || continue
read -r table < "$gwdir/table"
pref=$(rule_pref "$table")
if ! ip -6 rule show | grep -q "lookup $table.*pref $pref" ; then
ip -6 rule add lookup "$table" pref "$pref"
fi
for routes_dir in "$ROUTES_DIR" "$ROUTES6_DIR" ; do
[ -d "$routes_dir" ] || continue
ipcmd="ip"
[ "$routes_dir" = "$ROUTES6_DIR" ] && ipcmd="ip -6"
for gwdir in "$routes_dir"/*/ ; do
[ -d "$gwdir" ] || continue
read_group_config "$gwdir" || continue
pref=$(rule_pref "$table")
if ! $ipcmd rule show | grep -q "lookup $table.*pref $pref" ; then
$ipcmd rule add lookup "$table" pref "$pref"
fi
done
done
exit
fi
......@@ -390,8 +297,8 @@ fi
# --- Main ---
[ -n "$SHOW" ] && log "Dry-run mode (no changes will be made)"
process_v4
process_v6
process_routes "$ROUTES_DIR" get_ipv4_list_bulk "ip" ""
process_routes "$ROUTES6_DIR" get_ipv6_list_bulk "ip -6" " (v6)"
cleanup_state
[ -n "$SHOW" ] || log "All done"
/root/egw-route/egw.list
\ No newline at end of file
/root/antifilter/community.lst
\ No newline at end of file
/root/antifilter/ipresolve.lst
\ No newline at end of file
/root/antifilter/subnet.lst
\ No newline at end of file
/root/egw-route/ogw.list
\ No newline at end of file
/root/egw-route/telegram.list
\ No newline at end of file
/root/egw-route/whatsapp.list
\ No newline at end of file
/root/egw-route/youtube.list
\ No newline at end of file
/root/egw-route/egw.list
\ No newline at end of file
/root/antifilter/community.lst
\ No newline at end of file
/root/antifilter/ipresolve.lst
\ No newline at end of file
/root/antifilter/subnet.lst
\ No newline at end of file
/root/egw-route/ogw.list
\ No newline at end of file
/root/egw-route/telegram.list
\ No newline at end of file
/root/egw-route/whatsapp.list
\ No newline at end of file
/root/egw-route/youtube.list
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment