Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save VSharapov/58d370b37e0491c66b1f8ae6a27ec825 to your computer and use it in GitHub Desktop.

Select an option

Save VSharapov/58d370b37e0491c66b1f8ae6a27ec825 to your computer and use it in GitHub Desktop.
Give same-subnet secondary NICs a distinct DHCP hostname so the bare name tracks the primary
#!/usr/bin/env bash
set -Eeuo pipefail
primary-line() { ip -o route show default | awk '{m=0;for(i=1;i<=NF;i++)if($i=="metric")m=$(i+1);print m"\t"$0}' | sort -n | head -1 | cut -f2-; }
primary-iface() { primary-line | grep -oP 'dev \K\S+'; }
default-gateway() { primary-line | grep -oP 'via \K\S+'; }
iface-subnet() { ip -o -4 route show dev "$1" scope link proto kernel | awk '{print $1; exit}'; }
primary-subnet() { iface-subnet "$(primary-iface)"; }
secondary-ifaces() {
local p net i
p="$(primary-iface)"; net="$(iface-subnet "$p")"
for i in $(ls /sys/class/net); do
[ "$i" = "$p" ] && continue
[ "$(iface-subnet "$i")" = "$net" ] && echo "$i"
done
}
sys-hostname() { hostnamectl --static; }
desired-hostname() { echo "${DESIRED_HOSTNAME:-$(sys-hostname)}-$1"; }
conn-for-iface() { nmcli -t -g GENERAL.CONNECTION device show "$1"; }
current-dhcp-hostname() { nmcli -g ipv4.dhcp-hostname connection show "$1"; }
localdomain() { resolvectl domain "$1" 2>/dev/null | sed 's/.*): //' | awk '{print $1}'; }
run() {
if [ "${MODE:-preview}" = apply ]; then "$@"; return; fi
local a q=''; for a in "$@"; do q+=" ${a@Q}"; done
echo " DRY-RUN: would run:$q"
}
rehost-hint() {
echo " make it live (safe even over SSH on $1): tmux new-session -d -s dhcp-rehost-$1 'sudo nmcli connection down \"$2\"; sleep 2; sudo nmcli connection up \"$2\"'"
}
dot-warn() {
local base p ld
base="${DESIRED_HOSTNAME:-$(sys-hostname)}"
case "$base" in *.*)
p="$(primary-iface)"; ld="$(localdomain "$p")"
echo "WARNING: Your hostname is $base -- depending on the DHCP server / gateway on interface $p, this is likely to resolve as ${base%%.*}.${ld:-<localdomain>}" >&2
;; esac
}
set-plan() {
local secs i conn want have
secs="$(secondary-ifaces)"
[ -z "$secs" ] && { echo "no same-subnet secondary interfaces; nothing to do" >&2; [ "${MODE:-}" = apply ] && exit 3; exit 0; }
dot-warn
for i in $secs; do
conn="$(conn-for-iface "$i")"; want="$(desired-hostname "$i")"; have="$(current-dhcp-hostname "$conn")"
echo "[$i] conn \"$conn\" current=${have:-<unset>} desired=$want"
[ "$have" = "$want" ] && { echo " already set; nothing to do"; continue; }
run sudo nmcli connection modify "$conn" ipv4.dhcp-hostname "$want"
rehost-hint "$i" "$conn"
done
}
preview() { MODE=preview set-plan; }
apply() { MODE=apply set-plan; }
revert() {
local secs i conn have noop=false
secs="$(secondary-ifaces)"
[ -z "$secs" ] && { echo "no same-subnet secondary interfaces; nothing to revert" >&2; exit 3; }
for i in $secs; do
conn="$(conn-for-iface "$i")"; have="$(current-dhcp-hostname "$conn")"
echo "[$i] conn \"$conn\" current=${have:-<unset>}"
[ -z "$have" ] && { echo " already default; nothing to do"; continue; }
MODE=apply run sudo nmcli connection modify "$conn" ipv4.dhcp-hostname ''
rehost-hint "$i" "$conn"; noop=true
done
$noop && { echo "all secondaries already default; nothing to do" >&2; exit 3 ; } || true
}
usage() {
local me; me="$(basename "$0")"
cat >&2 <<EOF
$me - give same-subnet secondary NICs a distinct DHCP hostname so the bare name tracks the primary.
subcommands:
preview show what apply would change (DRY-RUN)
apply set ipv4.dhcp-hostname on each secondary; prints how to make it live
revert clear the override, back to default DHCP hostname
usage [--verbose] this message${1:+ (with plumbing exposed for the curious)}
EOF
[ "${1:-}" = --verbose ] && cat >&2 <<EOF
plumbing:
primary-iface interface owning the lowest-metric default route
default-gateway gateway IP of that route
primary-subnet CIDR of the primary's IPv4
secondary-ifaces same-subnet interfaces (newline-delimited)
desired-hostname <iface> \${DESIRED_HOSTNAME:-\$(hostnamectl --static)}-<iface>
conn-for-iface <iface> NetworkManager connection on a device
current-dhcp-hostname <conn>
localdomain <iface> first search domain (resolvectl)
environment:
DESIRED_HOSTNAME override \$(hostnamectl --static)
EOF
}
[ $# -eq 0 ] && { usage; exit 1; }
"$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment