update_etc_hosts.sh:
- by default, running in ‘dry run’ mode.
- use
--apply
flag to actually change the /etc/hosts file. - check out the rule in
SUBNET_DOMAINS
to see how subnets are mapped to different domains.
#!/bin/bash
# This script updates /etc/hosts with entries for running PVE guests.
# It runs in DRY-RUN mode by default for safety.
# Use the --apply flag to apply changes.
# --- Configuration ---
HOSTS_FILE="/etc/hosts"
BACKUP_FILE="/etc/hosts.bak"
START_MARKER="# --- BEGIN PVE HOSTS ---"
END_MARKER="# --- END PVE HOSTS ---"
# --- SUBNET TO DOMAIN MAPPING ---
# Define the mapping from subnet prefixes to domain names.
# The script will match the START of an IP address against these prefixes.
declare -A SUBNET_DOMAINS=(
["192.168.1."]="lan"
["192.168.2."]="opn.lan"
# ["10.10.0."]="corp.lan" # <-- Add new mappings here
)
# --- Runtime Variables ---
DRY_RUN=true
TEMP_HOSTS="/tmp/pve_hosts_$$"
FINAL_HOSTS_CONTENT="/tmp/final_hosts_$$"
# --- Argument Parsing ---
if [ "$1" == "--apply" ]; then
DRY_RUN=false
echo "⚡ LIVE RUN MODE ENABLED: /etc/hosts will be modified. ⚡"
else
echo "ⓘ DRY RUN MODE ENABLED (default): No files will be changed. Use --apply to apply."
fi
# --- Display Mappings ---
echo -e "\nNetwork to Domain Mapping Rules:"
for subnet in "${!SUBNET_DOMAINS[@]}"; do
echo " - IPs starting with '${subnet}' will be assigned the '.${SUBNET_DOMAINS[$subnet]}' domain."
done
echo "" # Newline for spacing
# --- Cleanup Function ---
function cleanup {
rm -f "$TEMP_HOSTS" "$FINAL_HOSTS_CONTENT"
}
trap cleanup EXIT
# --- Helper Function for Domain Logic ---
# Iterates through the SUBNET_DOMAINS array to find a matching domain for a given IP.
function get_domain_for_ip {
local ip=$1
for subnet in "${!SUBNET_DOMAINS[@]}"; do
# Check if the IP address starts with the subnet prefix
if [[ $ip == ${subnet}* ]]; then
echo "${SUBNET_DOMAINS[$subnet]}"
return
fi
done
# Return empty if no match is found
}
# --- Main Logic ---
> "$TEMP_HOSTS" # Clear temp file
echo "Processing running guests... ⚙️"
# --- Process KVM Virtual Machines ---
for VMID in $(qm list | awk 'NR>1 {print $1}'); do
STATUS=$(qm status "$VMID" | awk '{print $2}')
if [ "$STATUS" != "running" ]; then continue; fi
VM_NAME=$(qm config "$VMID" | grep 'name:' | awk '{print $2}')
IP_ADDRESS=$(qm guest cmd "$VMID" network-get-interfaces 2>/dev/null | grep '"ip-address"' | grep -v '127.0.0.1' | grep -oE "\b([0-9]{1,3}\.){3}[0-9]{1,3}\b" | head -n 1)
if [ -n "$IP_ADDRESS" ]; then
DOMAIN=$(get_domain_for_ip "$IP_ADDRESS")
HOSTNAME_ENTRY="$VM_NAME"
if [ -n "$DOMAIN" ]; then HOSTNAME_ENTRY="$VM_NAME.$DOMAIN"; fi
echo " ✔️ Found VM: $VM_NAME ($VMID) with IP: $IP_ADDRESS -> Domain: ${DOMAIN:--none-}"
echo "$IP_ADDRESS $HOSTNAME_ENTRY $VM_NAME" >> "$TEMP_HOSTS"
fi
done
# --- Process LXC Containers ---
for CTID in $(pct list | awk 'NR>1 {print $1}'); do
STATUS=$(pct status "$CTID" | awk '{print $2}')
if [ "$STATUS" != "running" ]; then continue; fi
CT_NAME=$(pct config "$CTID" | grep 'hostname:' | awk '{print $2}')
IP_ADDRESS=$(pct exec "$CTID" ip a | grep 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1 | head -n 1)
if [ -n "$IP_ADDRESS" ]; then
DOMAIN=$(get_domain_for_ip "$IP_ADDRESS")
HOSTNAME_ENTRY="$CT_NAME"
if [ -n "$DOMAIN" ]; then HOSTNAME_ENTRY="$CT_NAME.$DOMAIN"; fi
echo " ✔️ Found LXC: $CT_NAME ($CTID) with IP: $IP_ADDRESS -> Domain: ${DOMAIN:--none-}"
echo "$IP_ADDRESS $HOSTNAME_ENTRY $CT_NAME" >> "$TEMP_HOSTS"
fi
done
# --- Build the new hosts file content ---
if grep -qF "$START_MARKER" "$HOSTS_FILE"; then
sed "/$START_MARKER/q" "$HOSTS_FILE" | sed '$d' > "$FINAL_HOSTS_CONTENT"
echo "$START_MARKER" >> "$FINAL_HOSTS_CONTENT"
if [ -s "$TEMP_HOSTS" ]; then cat "$TEMP_HOSTS" >> "$FINAL_HOSTS_CONTENT"; fi
echo "$END_MARKER" >> "$FINAL_HOSTS_CONTENT"
sed -n "/$END_MARKER/,\$p" "$HOSTS_FILE" | sed '1d' >> "$FINAL_HOSTS_CONTENT"
else
IPV6_MARKER_LINE=$(grep -n '::1' "$HOSTS_FILE" | head -n 1 | cut -d: -f1)
NEW_BLOCK_CONTENT="\n$START_MARKER\n"
if [ -s "$TEMP_HOSTS" ]; then NEW_BLOCK_CONTENT+=$(cat "$TEMP_HOSTS")$'\n'; fi
NEW_BLOCK_CONTENT+="$END_MARKER\n"
if [ -n "$IPV6_MARKER_LINE" ]; then
head -n $((IPV6_MARKER_LINE - 1)) "$HOSTS_FILE" > "$FINAL_HOSTS_CONTENT"
echo -e "$NEW_BLOCK_CONTENT" >> "$FINAL_HOSTS_CONTENT"
tail -n +$IPV6_MARKER_LINE "$HOSTS_FILE" >> "$FINAL_HOSTS_CONTENT"
else
cat "$HOSTS_FILE" > "$FINAL_HOSTS_CONTENT"
echo -e "$NEW_BLOCK_CONTENT" >> "$FINAL_HOSTS_CONTENT"
fi
fi
# --- Final Action: Output to screen or file ---
if [ "$DRY_RUN" = true ]; then
echo -e "\n---------- DRY RUN: Full expected content of /etc/hosts ----------"
cat "$FINAL_HOSTS_CONTENT"
echo "--------------------------------------------------------------"
else
echo "Updating $HOSTS_FILE... 📝"
if [ ! -f "$BACKUP_FILE" ]; then
cp "$HOSTS_FILE" "$BACKUP_FILE"
echo "Backup of $HOSTS_FILE created at $BACKUP_FILE"
fi
cat "$FINAL_HOSTS_CONTENT" > "$HOSTS_FILE"
echo "✅ Hosts file updated successfully."
fi