mirror of
https://github.com/unraid/webgui.git
synced 2025-12-30 22:20:23 -06:00
feat: Reliable hosts file management
This commit is contained in:
@@ -226,8 +226,13 @@ if [[ -r /boot/config/ident.cfg ]]; then
|
||||
NAME=${NAME//[^a-zA-Z\-\.0-9]/\-}
|
||||
fi
|
||||
/bin/echo "$NAME" >/etc/HOSTNAME
|
||||
/bin/echo "# Generated" >/etc/hosts
|
||||
/bin/echo "127.0.0.1 $NAME localhost" >>/etc/hosts
|
||||
/bin/mkdir -p /etc/hosts.d
|
||||
{
|
||||
/bin/echo "# Do not edit, generated by rc.S.cont"
|
||||
/bin/echo "127.0.0.1 ${NAME} localhost"
|
||||
/bin/echo "::1 localhost"
|
||||
} >/etc/hosts.d/00-base
|
||||
/usr/local/sbin/rebuild_hosts
|
||||
|
||||
# LimeTech - restore the configured timezone
|
||||
if [[ $timeZone == custom ]]; then
|
||||
|
||||
@@ -30,7 +30,7 @@ DAEMON="Avahi mDNS/DNS-SD daemon"
|
||||
CALLER="avahi"
|
||||
AVAHI="/usr/sbin/avahi-daemon"
|
||||
CONF="/etc/avahi/avahi-daemon.conf"
|
||||
HOSTS="/etc/hosts"
|
||||
HOSTS="/etc/hosts.d/05-avahi"
|
||||
NAME=$(</etc/HOSTNAME)
|
||||
|
||||
# run & log functions
|
||||
@@ -53,17 +53,21 @@ disable(){
|
||||
|
||||
# when starting avahidaemon, add name.local to the hosts file
|
||||
add_local_to_hosts(){
|
||||
local OLD="^127\.0\.0\.1.*"
|
||||
local NEW="127.0.0.1 $NAME $NAME.local localhost"
|
||||
sed -i "s/$OLD/$NEW/gm;t" $HOSTS
|
||||
tmp=$(mktemp "${HOSTS}.XXXXXX")
|
||||
{
|
||||
echo "# Do not edit, generated by rc.avahidaemon"
|
||||
echo "127.0.0.1 ${NAME}.local"
|
||||
} > "$tmp"
|
||||
chmod 644 "$tmp"
|
||||
mv "$tmp" "$HOSTS"
|
||||
/usr/local/sbin/rebuild_hosts
|
||||
return 0
|
||||
}
|
||||
|
||||
# when stopping avahidaemon, remove name.local from the hosts file
|
||||
remove_local_from_hosts(){
|
||||
local OLD="^127\.0\.0\.1.*"
|
||||
local NEW="127.0.0.1 $NAME localhost"
|
||||
sed -i "s/$OLD/$NEW/gm;t" $HOSTS
|
||||
rm -f "$HOSTS"
|
||||
/usr/local/sbin/rebuild_hosts
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
170
sbin/rebuild_hosts
Normal file
170
sbin/rebuild_hosts
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# rebuild_hosts: generate /etc/hosts from /etc/hosts.d fragments.
|
||||
#
|
||||
# Each file in /etc/hosts.d/ should contain lines in standard hosts(5) format:
|
||||
# IPADDR hostname [alias...]
|
||||
#
|
||||
# This script:
|
||||
# - Reads all regular files in /etc/hosts.d, in sorted order.
|
||||
# - Merges entries with the same IP onto a single line.
|
||||
# - Deduplicates hostnames per IP.
|
||||
# - Tracks which fragments contributed to each IP and annotates with '# from ...'.
|
||||
# - Writes the result atomically to /etc/hosts with perms root:root 0644.
|
||||
#
|
||||
# Do NOT edit /etc/hosts directly; edit /etc/hosts.d/* instead.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# run & log functions
|
||||
. /etc/rc.d/rc.runlog
|
||||
|
||||
trap 'log "rebuild_hosts FAILED with status $? (see previous log lines for context)" || true' ERR
|
||||
|
||||
HOSTS_DIR=/etc/hosts.d
|
||||
HOSTS_FILE=/etc/hosts
|
||||
|
||||
# Create temp file in same FS as HOSTS_FILE so mv is atomic
|
||||
tmpfile=$(mktemp "${HOSTS_FILE}.XXXXXX")
|
||||
|
||||
# Header
|
||||
{
|
||||
echo "# $HOSTS_FILE - automatically generated by rebuild_hosts"
|
||||
echo "# Do not edit this file directly; use ${HOSTS_DIR}/* instead."
|
||||
echo "#"
|
||||
echo "# fragments:"
|
||||
shopt -s nullglob
|
||||
for f in "$HOSTS_DIR"/*; do
|
||||
[ -f "$f" ] || continue
|
||||
printf "# %s\n" "$(basename "$f")"
|
||||
done
|
||||
shopt -u nullglob
|
||||
echo
|
||||
} > "$tmpfile"
|
||||
|
||||
# IP -> "host1 host2 ..."
|
||||
declare -A IP_TO_HOSTS
|
||||
|
||||
# IP -> "fragment1 fragment2 ..."
|
||||
declare -A IP_TO_SOURCES
|
||||
|
||||
# Preserve IP order (first time we see an IP, record it here)
|
||||
IP_ORDER=()
|
||||
|
||||
# Name of the fragment currently being processed (basename of file)
|
||||
CURRENT_SOURCE=""
|
||||
|
||||
add_hosts_line() {
|
||||
# $1 = IP address, remaining args = hostnames/aliases
|
||||
local ip="$1"; shift
|
||||
local host
|
||||
|
||||
# If this IP hasn't been seen yet, initialize it and record order
|
||||
if [[ -z "${IP_TO_HOSTS[$ip]+x}" ]]; then
|
||||
IP_TO_HOSTS["$ip"]=""
|
||||
IP_ORDER+=("$ip")
|
||||
fi
|
||||
|
||||
# Append any new hostnames for this IP, skipping duplicates.
|
||||
# We store hostnames as a space-separated string.
|
||||
local current_hosts="${IP_TO_HOSTS[$ip]}"
|
||||
|
||||
# Iterate over all hostnames passed to this function
|
||||
for host in "$@"; do
|
||||
# Skip empty tokens (paranoia / defensive programming)
|
||||
[[ -z "$host" ]] && continue
|
||||
|
||||
# Check if this hostname is already present for this IP.
|
||||
# We wrap the string and hostname with spaces so we can do
|
||||
# a simple substring match without false positives:
|
||||
# " foo bar " contains " bar " but not " ar ".
|
||||
case " $current_hosts " in
|
||||
*" $host "*) ;; # already present, do nothing
|
||||
*) current_hosts="$current_hosts $host" ;; # append new hostname
|
||||
esac
|
||||
done
|
||||
|
||||
# Strip the leading space if we added anything
|
||||
IP_TO_HOSTS["$ip"]="${current_hosts# }"
|
||||
|
||||
# Track which fragment files contributed to this IP.
|
||||
# This is just for the trailing "# from 00-base 10-avahi" comment.
|
||||
if [[ -n "$CURRENT_SOURCE" ]]; then
|
||||
local current_sources="${IP_TO_SOURCES[$ip]:-}"
|
||||
|
||||
case " $current_sources " in
|
||||
*" $CURRENT_SOURCE "*) ;; # already recorded
|
||||
*) current_sources="$current_sources $CURRENT_SOURCE" ;;
|
||||
esac
|
||||
|
||||
# Again, strip leading space
|
||||
IP_TO_SOURCES["$ip"]="${current_sources# }"
|
||||
fi
|
||||
}
|
||||
|
||||
process_file() {
|
||||
local file="$1"
|
||||
local line ip
|
||||
# Remember which fragment we are processing for source tracking
|
||||
CURRENT_SOURCE="$(basename "$file")"
|
||||
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
# Strip comments (anything after '#')
|
||||
line="${line%%#*}"
|
||||
|
||||
# Trim leading whitespace
|
||||
line="${line#"${line%%[![:space:]]*}"}"
|
||||
# Trim trailing whitespace
|
||||
line="${line%"${line##*[![:space:]]}"}"
|
||||
|
||||
# Skip empty lines
|
||||
[[ -z "$line" ]] && continue
|
||||
|
||||
# Split line into fields:
|
||||
# $1 = IP, remaining = hostnames/aliases.
|
||||
# shellcheck disable=SC2086
|
||||
set -- $line
|
||||
ip="$1"; shift || true
|
||||
|
||||
# If IP is empty or no hostnames, skip the line
|
||||
[[ -z "$ip" ]] && continue
|
||||
[[ $# -eq 0 ]] && continue
|
||||
|
||||
add_hosts_line "$ip" "$@"
|
||||
done < "$file"
|
||||
}
|
||||
|
||||
log "Rebuilding $HOSTS_FILE..."
|
||||
|
||||
shopt -s nullglob
|
||||
fragment_files=("$HOSTS_DIR"/*)
|
||||
shopt -u nullglob
|
||||
|
||||
for f in "${fragment_files[@]}"; do
|
||||
[[ -f "$f" ]] || continue
|
||||
process_file "$f"
|
||||
done
|
||||
|
||||
# If no fragments defined any IPs, provide a minimal fallback
|
||||
if [[ ${#IP_ORDER[@]} -eq 0 ]]; then
|
||||
CURRENT_SOURCE="fallback"
|
||||
add_hosts_line "127.0.0.1" "localhost"
|
||||
fi
|
||||
|
||||
# Emit merged lines in original IP discovery order
|
||||
for ip in "${IP_ORDER[@]}"; do
|
||||
hosts="${IP_TO_HOSTS[$ip]}"
|
||||
sources="${IP_TO_SOURCES[$ip]:-}"
|
||||
|
||||
if [[ -n "$sources" ]]; then
|
||||
printf "%-15s %s # from %s\n" "$ip" "$hosts" "$sources" >> "$tmpfile"
|
||||
else
|
||||
printf "%-15s %s\n" "$ip" "$hosts" >> "$tmpfile"
|
||||
fi
|
||||
done
|
||||
|
||||
# Set perms and atomic replace
|
||||
chmod 0644 "$tmpfile" 2>/dev/null || true
|
||||
chown root:root "$tmpfile" 2>/dev/null || true
|
||||
mv "$tmpfile" "$HOSTS_FILE"
|
||||
|
||||
Reference in New Issue
Block a user