feat(network): add ipInSubnet + IP-only network reset scope
Foundations for network-drift healing: - ipInSubnet(ip, cidr): prefix-aware CIDR membership (pure bash), so stored IPs can be checked against docker's real subnet. Honours the actual prefix, so a healthy /16-subnet + /24-ip-range install is not mistaken for drift. - dockerInstallApp now accepts reset_network="ip": re-roll the static IP from the current subnet but PRESERVE published host ports (clears only IP rows; LIBREPORTAL_RESET_IP_ONLY keeps port_allocate reusing existing ports). This is the heal path — a subnet move strands the IP, not the port, so we don't churn bookmarks/forwards/proxy upstreams. reset="true" still re-rolls both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
55ca1b4270
commit
b7a0743d8b
@ -35,15 +35,24 @@ dockerInstallApp()
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$reset_network" == "true" ]]; then
|
||||
# reset_network: "true" = re-roll IPs AND host ports; "ip" = re-roll the
|
||||
# static IP only and PRESERVE published ports (the network-heal path — a
|
||||
# subnet move strands the IP, not the port, so don't churn bookmarks /
|
||||
# forwards / proxy upstreams). Anything else = preserve both (normal reuse).
|
||||
if [[ "$reset_network" == "true" || "$reset_network" == "ip" ]]; then
|
||||
export LIBREPORTAL_RESET_NETWORK=1
|
||||
[[ "$reset_network" == "ip" ]] && export LIBREPORTAL_RESET_IP_ONLY=1
|
||||
if declare -f ipRemoveFromDatabase >/dev/null 2>&1; then
|
||||
ipRemoveFromDatabase "$app_name"
|
||||
fi
|
||||
if declare -f portsRemoveFromDatabase >/dev/null 2>&1; then
|
||||
if [[ "$reset_network" != "ip" ]] && declare -f portsRemoveFromDatabase >/dev/null 2>&1; then
|
||||
portsRemoveFromDatabase "$app_name"
|
||||
fi
|
||||
isNotice "Network reset: cleared previous IP/port allocations for $app_name."
|
||||
if [[ "$reset_network" == "ip" ]]; then
|
||||
isNotice "Network reset (IP-only): cleared previous IP allocation for $app_name; ports preserved."
|
||||
else
|
||||
isNotice "Network reset: cleared previous IP/port allocations for $app_name."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Silently update the template config so apps.json regen reflects the saved
|
||||
@ -71,5 +80,5 @@ dockerInstallApp()
|
||||
webuiGenerateLibrePortalConfig >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
unset LIBREPORTAL_RESET_NETWORK
|
||||
unset LIBREPORTAL_RESET_NETWORK LIBREPORTAL_RESET_IP_ONLY
|
||||
}
|
||||
|
||||
32
scripts/network/ip/ip_in_subnet.sh
Normal file
32
scripts/network/ip/ip_in_subnet.sh
Normal file
@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# CIDR membership test: is $ip inside $cidr?
|
||||
# ipInSubnet 10.123.154.5 10.123.154.0/24 -> 0 (in subnet)
|
||||
# ipInSubnet 10.100.0.5 10.123.154.0/24 -> 1 (out of subnet)
|
||||
#
|
||||
# Pure-bash 32-bit integer masking that HONOURS the real prefix length, so a /16
|
||||
# genuinely contains its /24s. That matters here: the network is created with a
|
||||
# /24 ip-range but its declared subnet can be /16 (configs/network/network_docker
|
||||
# default), and a healthy "/16 subnet + /24 ip-range" install must NOT read as
|
||||
# drift. Compare stored IPs against docker's actual subnet CIDR, not a forced /24.
|
||||
# Returns 0 (in-subnet) or 1 (out-of-subnet / malformed input).
|
||||
ipInSubnet()
|
||||
{
|
||||
local ip="$1" cidr="$2"
|
||||
[[ "$cidr" == */* ]] || return 1
|
||||
local base="${cidr%/*}" prefix="${cidr#*/}"
|
||||
[[ "$prefix" =~ ^[0-9]+$ ]] && (( prefix >= 0 && prefix <= 32 )) || return 1
|
||||
|
||||
local re='^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$'
|
||||
[[ "$ip" =~ $re ]] || return 1
|
||||
local i1=$((10#${BASH_REMATCH[1]})) i2=$((10#${BASH_REMATCH[2]})) i3=$((10#${BASH_REMATCH[3]})) i4=$((10#${BASH_REMATCH[4]}))
|
||||
[[ "$base" =~ $re ]] || return 1
|
||||
local b1=$((10#${BASH_REMATCH[1]})) b2=$((10#${BASH_REMATCH[2]})) b3=$((10#${BASH_REMATCH[3]})) b4=$((10#${BASH_REMATCH[4]}))
|
||||
|
||||
local ip_int=$(( (i1<<24) | (i2<<16) | (i3<<8) | i4 ))
|
||||
local base_int=$(( (b1<<24) | (b2<<16) | (b3<<8) | b4 ))
|
||||
local mask=0
|
||||
(( prefix > 0 )) && mask=$(( (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF ))
|
||||
|
||||
(( (ip_int & mask) == (base_int & mask) ))
|
||||
}
|
||||
@ -49,7 +49,10 @@ portAllocate()
|
||||
|
||||
if [[ "${port_external_ports[$i]}" == "random" ]]; then
|
||||
local existing_external_port=""
|
||||
if [[ "$LIBREPORTAL_RESET_NETWORK" != "1" ]]; then
|
||||
# Re-roll the port only on a full network reset. In IP-only mode
|
||||
# (network heal) the port rows were left intact — reuse them so
|
||||
# published host ports stay stable.
|
||||
if [[ "$LIBREPORTAL_RESET_NETWORK" != "1" || "$LIBREPORTAL_RESET_IP_ONLY" == "1" ]]; then
|
||||
existing_external_port=$(portLookupExisting "$app_name" "$full_service_name")
|
||||
fi
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user