diff --git a/switch-resolver-to-bind9.sh b/switch-resolver-to-bind9.sh index 50f57e4..7a0ea9a 100755 --- a/switch-resolver-to-bind9.sh +++ b/switch-resolver-to-bind9.sh @@ -13,10 +13,31 @@ readonly EXPECTED_NSS='hosts: files myhostname dns' MODE="${1:---check}" case "$MODE" in --check|--apply|--rollback|--help) ;; *) echo "Usage: $0 [--check|--apply|--rollback|--help]" >&2; exit 2;; esac +# Colorize status labels on terminals; respect NO_COLOR and redirected output. +if [[ -t 1 && ! -v NO_COLOR && "${TERM:-}" != dumb ]]; then + GREEN=$'\033[32m' + GREY=$'\033[90m' + RED=$'\033[31m' + YELLOW=$'\033[33m' + RESET=$'\033[0m' +else + GREEN='' GREY='' RED='' YELLOW='' RESET='' +fi + info() { printf '[ Info ] %s\n' "$*"; } -ok() { printf '[ ok ] %s\n' "$*"; } -skipped() { printf '[ skipped ] %s\n' "$*"; } -failed() { printf '[ failed ] %s\n' "$*" >&2; } +ok() { printf '%s[ ok ]%s %s\n' "$GREEN" "$RESET" "$*"; } +skipped() { printf '%s[ skipped ]%s %s\n' "$GREY" "$RESET" "$*"; } +failed() { printf '%s[ failed ]%s %s\n' "$RED" "$RESET" "$*" >&2; } +# Delimited summary at the end of --check / before an interactive --apply. +status_banner(){ + local color="$1" message="$2" detail="${3:-}" + printf '\n%s------------------------------------------------------------%s\n' "$color" "$RESET" + printf '%s%s%s\n' "$color" "$message" "$RESET" + [[ -z "$detail" ]] || printf '%s%s%s\n' "$color" "$detail" "$RESET" + printf '%s------------------------------------------------------------%s\n' "$color" "$RESET" +} +# Every invocation finishes with one additional empty output line. +trap 'printf "\n"' EXIT fatal() { failed "$*"; exit 1; } echononl(){ printf '%-56s' "$*"; } require_root(){ [[ "${EUID}" -eq 0 ]] || fatal 'Please run as root.'; } @@ -47,14 +68,14 @@ is_fully_switched(){ # BIND must reject a known bogus signature, not merely answer normal queries. preflight(){ echononl 'BIND9 on 127.0.0.1:53' - if dig @127.0.0.1 oopen.de A +time=3 +tries=1 +short | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then ok 'reachable'; else echo; fatal 'Local BIND9 does not resolve oopen.de'; fi + if dig @127.0.0.1 oopen.de A +time=3 +tries=1 +short | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'; then ok 'reachable'; else echo; failed 'Local BIND9 does not resolve oopen.de'; return 1; fi echononl 'DNSSEC positive test (AD flag)' local reply reply=$(dig @127.0.0.1 cloudflare.com A +dnssec +adflag +time=5 +tries=1) - if grep -q 'status: NOERROR' <<< "$reply" && grep -Eq 'flags:.*[[:space:]]ad[;[:space:]]' <<< "$reply"; then ok 'validated'; else echo; fatal 'BIND9 DNSSEC positive test failed'; fi + if grep -q 'status: NOERROR' <<< "$reply" && grep -Eq 'flags:.*[[:space:]]ad[;[:space:]]' <<< "$reply"; then ok 'validated'; else echo; failed 'BIND9 DNSSEC positive test failed'; return 1; fi echononl 'DNSSEC negative test (SERVFAIL)' reply=$(dig @127.0.0.1 www.dnssec-failed.org A +time=5 +tries=1) - if grep -q 'status: SERVFAIL' <<< "$reply"; then ok 'rejected as expected'; else echo; fatal 'BIND9 does not reject invalid DNSSEC test zone'; fi + if grep -q 'status: SERVFAIL' <<< "$reply"; then ok 'rejected as expected'; else echo; failed 'BIND9 does not reject invalid DNSSEC test zone'; return 1; fi } show_check(){ @@ -66,11 +87,29 @@ show_check(){ grep -E '^hosts:' "$NSS" || true printf 'systemd-resolved active: %s\n' "$(systemctl is-active systemd-resolved 2>/dev/null || true)" printf 'systemd-resolved enabled: %s\n' "$(systemctl is-enabled systemd-resolved 2>/dev/null || true)" - if is_fully_switched; then ok 'Already directly using BIND9 (resolved disabled)'; - elif is_direct; then info 'BIND9 direct configuration is present; resolved still active/enabled'; - else info 'Not fully configured for direct BIND9'; fi - if [[ -f "$CURRENT_FILE" ]]; then info "Rollback state: $(state_dir_from_marker)"; fi - preflight + local readiness=0 + if preflight; then readiness=1; fi + if is_fully_switched; then + if (( readiness )); then + status_banner "$GREY" 'Umstellung auf BIND9 bereits durchgeführt.' + else + status_banner "$YELLOW" 'Umstellung bereits durchgeführt, aber DNS-Prüfung fehlgeschlagen.' + fi + elif [[ -f "$CURRENT_FILE" ]]; then + status_banner "$YELLOW" 'Umstellung kann nicht ausgeführt werden.' \ + 'Ein vorheriger Rollback-Stand ist vorhanden; zunächst prüfen oder zurückbauen.' + elif (( readiness )); then + status_banner "$GREEN" 'Umstellung kann durchgeführt werden.' + else + status_banner "$YELLOW" 'Umstellung kann nicht ausgeführt werden.' \ + 'BIND9-/DNSSEC-Voraussetzungen nicht erfüllt; bitte Fehler prüfen.' + fi + if [[ -f "$CURRENT_FILE" ]]; then + printf '\n=== Rollback information ===\n' + info 'Backup from before the last --apply is available' + info "Backup: $(state_dir_from_marker)" + info "Restore with: $0 --rollback" + fi } restore_files(){ @@ -113,12 +152,17 @@ apply(){ [[ -f "$NSS" ]] || fatal "$NSS missing" [[ -e "$RESOLV" || -L "$RESOLV" ]] || fatal "$RESOLV missing" [[ ! -f "$CURRENT_FILE" ]] || { - if is_fully_switched; then skipped 'Already installed; no changes'; exit 0; fi + if is_fully_switched; then status_banner "$GREY" 'Umstellung auf BIND9 bereits durchgeführt.'; skipped 'No changes'; exit 0; fi + status_banner "$YELLOW" 'Umstellung kann nicht ausgeführt werden.' 'Vorheriger Rollback-Stand vorhanden.' fatal 'An earlier script run has rollback state. Use --check or --rollback first.'; } - if is_fully_switched; then skipped 'Already directly using BIND9; no changes'; exit 0; fi + if is_fully_switched; then status_banner "$GREY" 'Umstellung auf BIND9 bereits durchgeführt.'; skipped 'No changes'; exit 0; fi info 'Preflight checks (no changes)' - preflight + if ! preflight; then + status_banner "$YELLOW" 'Umstellung kann nicht ausgeführt werden.' 'BIND9-/DNSSEC-Voraussetzungen nicht erfüllt.' + return 1 + fi + status_banner "$GREEN" 'Umstellung kann durchgeführt werden.' printf '\nPlanned changes:\n hosts: files myhostname dns\n resolv.conf: nameserver 127.0.0.1\n stop and disable systemd-resolved\n' info 'Network interfaces, routes, BIND9 and MTA are not changed.' confirm || { skipped 'Abbruch durch User'; exit 0; }