#!/bin/bash
# to switch from/to systemd-resolved <-> bind9 as the primary dns resolver
VERSION="1.2.3 [01 Jan 2026]"
set -o pipefail
THIS=$(basename "$0")
COLUMNS=$(stty size 2>/dev/null||echo 80); COLUMNS=${COLUMNS##* }

while getopts ":bfhlnrw" optname; do
    case "$optname" in
		"b")	ACTION="from resolved to bind9";;
		#"d")	DEBUG="y"; echo "running: $0 $*";;
		"f")	FORCE="y";;
		"h")	HELP="y";;
		"l")	CHANGELOG="y";;
		"n")	NO_HEADER="-n";;
		"r")	ACTION="from bind9 to resolved";;
		"w")	COLUMNS=30000;;
		"?")	echo "Unknown option $OPTARG">&2; exit 1;;
		":")	echo "No argument value for option $OPTARG">&2; exit 1;;
		*)	# Should not occur
			echo "Unknown error while processing options">&2; exit 1;;
    esac
done
shift $((OPTIND-1))
[[ -z $NO_HEADER ]] && echo -e "\n$THIS v$VERSION - by Dominic (-h for help)\n${THIS//?/=}\n"
if [[ -n $HELP ]]; then
echo -e "A program to test whether bind9 (aka 'named') or systemd-resolved is \
the current active DNS resolver, and to switch permanently between them. \
Tested under Ubuntu 24.04 but should work on other recent systemd-based distros. \
It does not uninstall or break the alternative resolver, but \
sets up resolving so that the preferred resolver is used.

Before using $THIS to switch to using bind9, you must first install bind9.

If run without options, $THIS just shows the current DNS resolver.

$THIS determines the current resolver based on /etc/resolv.conf, but there \
are 2 file changes it makes to enable bind9, or reverses to disable bind9:
1: /etc/resolv.conf           - create static file with 'nameserver 127.0.0.1'
2: /etc/dhcp/dhclient.conf    - add 'prepend domain-name-servers 127.0.0.1;'

Usage: ./$THIS [options]

Options   : -b  switch to using bind9 instead of systemd-resolved
            -f  proceed even if it seems unnecessary (but will still ask before making changes)
            -h  show this help and exit
            -l  show changelog and exit
            -n  hide program title
            -r  switch to using systemd-resolved instead of bind9

License: Copyright © 2026 Dominic Raferd. Licensed under the Apache License, \
Version 2.0 (the \"License\"); you may not use this file except in compliance \
with the License. You may obtain a copy of the License at \
https://www.apache.org/licenses/LICENSE-2.0. Unless required by applicable \
law or agreed to in writing, software distributed under the License is \
distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY \
KIND, either express or implied. See the License for the specific language \
governing permissions and limitations under the License.
"|fold -sw"$COLUMNS"
fi
if [[ -n $CHANGELOG ]]; then
	[[ -n $HELP ]] && echo "Changelog:"
	echo -e "\
1.2 [19 Jun 2024] - work with more recent versions of systemd which have resolvectl command instead of systemd-resolve, make shellcheck compliant
1.1 [11 Jun 2022] - small tweaks
1.0 [01 May 2020] - adapted from former program bind9-setup.sh
0.9 [26 Apr 2019] - add info to help about bind9-resolvconf
0.8 [26 Aug 2018] - fixes and improved text for -r and -s options
0.7 [21 Aug 2018] - add -s option
0.6 [21 Jul 2018] - add -r option, a few other fixes, still not fully tested
0.5 [27 Nov 2016] - fix bind not to use IPv6
0.4 [26 Nov 2016] - install bind from ubuntu repos instead of building
0.3 [17 Feb 2016] - build and attempt to install bind 9.10.3 (doesn't work unless bind already installed)
"|fold -sw"$COLUMNS"
fi
[[ -n $HELP$CHANGELOG ]] && exit 0

[[ -n $ACTION ]] && echo "Action selected: to switch DNS resolver $ACTION"
pidof systemd >/dev/null || { echo "This system does not use systemd, unable to continue."; exit 1; }
SYSTEMD_CMD=$(command -v resolvectl) || SYSTEMD_CMD=$(command -v systemd-resolve) || { echo "Cannot determine systemd-resolved command line tool, unable to continue."; exit 1; }
FOUNDAT=$(type named 2>/dev/null|cut -d" " -f3)
if [[ -n $FOUNDAT ]]; then
	echo -en "bind9 is installed as $FOUNDAT:\n  "
	$FOUNDAT -V|head -n1
else
 if [[ $ACTION == "from resolved to bind9" ]]; then
 	echo "bind9 is not installed, please install it before trying to switch DNS resolver $ACTION" >&2
 	exit 1
 fi
fi

if [[ -L /etc/resolv.conf ]]; then
	USING_SYSTEMD_RESOLVED=y
	echo -e "** systemd-resolved is currently the active DNS resolver (as /etc/resolv.conf is symlink)\n"
	echo "Obtaining systemd-resolved's current name server(s) via $SYSTEMD_CMD status"
	if [[ $SYSTEMD_CMD =~ systemd-resolve ]]; then
		# old syntax
		# shellcheck disable=SC2016
		read -ra DNS=< <($SYSTEMD_CMD --no-pager --status 2>/dev/null|sed -n '/DNS Servers:/,/\(:\|^$\)/{/DNS Domain:/d;/^$rs://;s/^\s*//;/127\.0\.0\.1/d;p}'|tr '\n' ' ')
	else
		read -ra DNS < <($SYSTEMD_CMD --no-pager dns|cut -d: -f1 --complement|tr '\n' ' ')
	fi
fi
if [[ -z ${DNS[*]} ]]; then
	echo "Obtaining current name server(s) from /etc/resolv.conf"
	read -ra DNS < <(sed -n 's/^nameserver\s*//p' /etc/resolv.conf|grep -v '127.0.0.1'|tr '\n' ' ')
fi

if [[ -n ${DNS[*]} ]]; then
	echo "${#DNS[@]} current nameservers: ${DNS[*]}"
else
	if [[ -n $FOUNDAT ]]; then
		echo -e "**bind9 is currently the active DNS resolver (based on the contents of /etc/resolv.conf)\n"
		[[ -n $USING_SYSTEMD_RESOLVED ]] && echo "  ... so which is right?"
	else
		echo -e "Can't find any non-local nameservers\nMaybe a local DNS server other than systemd-resolved is already active?"
	fi
	[[ $ACTION == "from resolved to bind9" && -z $FORCE ]] && { echo "No need for any changes, stopping" >&2; exit 1; }
fi

if [[ -n $ACTION ]]; then
	[[ $($SYSTEMD_CMD --version 2>/dev/null|grep systemd|cut -d" " -f2) -gt 231 ]] || { echo "note: systemd-resolved version <232, can't switch DNS resolver $ACTION, sorry..."; exit 1; }
	[[ $(id -u) -eq 0 ]] || { echo "To continue, you must be root. Aborting" >&2; exit 1; }
	read -rt30 -p "Do you want to switch DNS resolver $ACTION (y/-)? "
	[[ $REPLY == y ]] || { echo "Aborted, no changes made"; exit 0; }
fi

if [[ $ACTION == "from resolved to bind9" ]]; then
	# this method won't work for systemd-resolved earlier than v232
	# https://unix.stackexchange.com/questions/304050/how-to-avoid-conflicts-between-dnsmasq-and-systemd-resolved
	echo "Check and if necessary disable systemd-resolved as primary local DNS:"

	# 1: switch system DNS to straightforward 127.0.0.1 (use a different DNS than systemd-resolved)
	WHEN=$(date +"%F %T")
	echo "  /etc/resolv.conf: "
	if [[ -L /etc/resolv.conf ]]; then
		mv /etc/resolv.conf /etc/resolv.conf4systemd-resolved && echo -e "# $WHEN: set so systemd-resolved does not run DNS\nnameserver 127.0.0.1"  >/etc/resolv.conf && echo "modified so bind9 can function in place of systemd-resolved"
		RESOLVE_CHANGE=y
	else
		echo "already set so system DNS resolution not done by systemd-resolved - nothing to change"
	fi

	# 2: set 127.0.0.1 as primary DNS in DHCP
	echo -n "  /etc/dhcp/dhclient.conf: "
	if ! grep -q "^prepend" /etc/dhcp/dhclient.conf >/dev/null 2>&1; then
		# shellcheck disable=SC2016
		sed -i '/^[# ]*prepend/{h;s/.*/prepend domain-name-servers 127.0.0.1;/};${x;/^$/{s//prepend domain-name-servers 127.0.0.1;/;H};x}' /etc/dhcp/dhclient.conf || { echo "Unable to modify /etc/dhcp/dhclient.conf, aborting" >&2; exit 1; }
		echo "modified so 127.0.0.1 is added as primary DNS"
		RESOLVE_CHANGE=y
	else
		echo "nothing to change"
	fi

	if [[ -n $RESOLVE_CHANGE ]]; then
		echo -n "  (re)starting and enabling bind9 (if required): "
		systemctl restart named 2>/dev/null && systemctl enable named 2>/dev/null && echo "OK" || echo "FAIL"
	else
		echo "  no changes required"
	fi

elif [[ $ACTION == "from bind9 to resolved" ]]; then
	# we are (re-)enabling systemd-resolved (presumably in place of bind9)
	echo "Check and if necessary enable systemd-resolved as primary local DNS:"

	# 1: (in case DNSStubListener=no and/or DNS=127.0.0.1 were set)
	FILE="/etc/systemd/resolved.conf"
	if [[ -f $FILE ]]; then
		echo -n "  $FILE [DNSStubListener]: "
		if grep -q "^DNSStubListener=no" "$FILE"; then
			sed -i "/^DNSStubListener=/{s/^/# $WHEN: disabled so systemd-resolved listens at UDP 127.0.0.53:53\n#/}" "$FILE" && echo "stub listener at 127.0.0.53:53 turned on"
			RESOLVE_CHANGE=y
		else
			echo "stub listener already on - nothing to change"
		fi
		echo -n "  $FILE [DNS]: "
		if grep -qFx "DNS=127.0.0.1" "$FILE"; then
			sed -i "/^DNS=/{s/^/# $WHEN: disabled so systemd-resolved no longer uses local DNS\n#/}" "$FILE" && echo "DNS at 127.0.0.1 turned off - done"
			RESOLVE_CHANGE=y
		else
			echo "DNS at 127.0.0.1 already off - nothing to change"
		fi
	fi

	# 2: switch system DNS to straightforward 127.0.0.1 (use a different DNS than systemd-resolved)
	echo -n "  /etc/resolv.conf: "
	if [[ ! -L /etc/resolv.conf ]]; then
		# create symbolic link file at /etc/resolv.conf
		rm -- /etc/resolv.conf && ln -sr /var/run/resolvconf/resolv.conf /etc/resolv.conf && echo "modified so systemd-resolved functions as system DNS resolver"
		RESOLVE_CHANGE=y
	else
		echo "already a symlink - nothing to change"
	fi

	# 3: disable local DNS (i.e. bind)
	FILE="/etc/dhcp/dhclient.conf"
	if [[ -f $FILE ]]; then
		echo -n "  $FILE: "
		if grep -q "^prepend" "$FILE" >/dev/null 2>&1; then
			sed -i "s/^prepend/# $WHEN: disable bind9 DNS by commenting out prepend line\n#prepend/" "$FILE"
			echo "modified so 127.0.0.1 is not added as primary DNS"
			RESOLVE_CHANGE=y
		else
			echo "127.0.0.1 is not prepended as primary DNS - nothing to change"
		fi
	fi

	if [[ -n $RESOLVE_CHANGE ]]; then
		echo -n "  stopping and disabling bind9: "
		systemctl stop named 2>/dev/null && systemctl disable named 2>/dev/null && echo "OK" || echo "FAIL"
	else
		echo "  no changes required"
	fi
fi
[[ -n $RESOLVE_CHANGE ]] && echo "  restarting systemd-resolved" && systemctl restart systemd-resolved
echo "  showing bind9 status:"
systemctl status named|sed 's/^/    /'
echo "$THIS completed"
exit 0
