diff --git a/dns-override.sh b/dns-override.sh index fd6ab22..f71fa18 100644 --- a/dns-override.sh +++ b/dns-override.sh @@ -3,7 +3,8 @@ set -euo pipefail readonly SCRIPT_NAME="dns-override.sh" -readonly SCRIPT_VERSION="1.0.0" +readonly SCRIPT_VERSION="1.0.2" +readonly IMMUTABLE_PROMPT_TIMEOUT_SEC=20 readonly NC='\033[0m' readonly RED='\033[1;31m' @@ -22,6 +23,8 @@ readonly EMOJI_NET='🌐' readonly EMOJI_LOCK='🔒' SUDO_BIN="" +IS_REMOTE_SSH=0 +IMMUTABLE_LOCK_REQUESTED=0 declare -a DNS_SERVERS=() declare -a DETECTED_MANAGERS=() declare -a ACTIONS_ATTEMPTED=() @@ -44,6 +47,27 @@ command_exists() { command -v "$1" >/dev/null 2>&1 } +binary_or_path_exists() { + local bin_name="$1" + shift + + if command_exists "$bin_name"; then + return 0 + fi + + local path_candidate + for path_candidate in "$@"; do + [[ -x "$path_candidate" ]] && return 0 + done + return 1 +} + +systemd_unit_exists() { + local unit_name="$1" + command_exists systemctl || return 1 + systemctl list-unit-files "$unit_name" >/dev/null 2>&1 +} + trim() { local value="$1" value="${value#"${value%%[![:space:]]*}"}" @@ -98,6 +122,15 @@ read_tty_input() { printf '%s' "$(trim "$value")" } +read_tty_input_timeout() { + local prompt="$1" + local timeout_sec="$2" + local value + printf "%b" "$prompt" >/dev/tty + IFS= read -r -t "$timeout_sec" value /dev/null; then - add_manager "systemd-resolved" - elif [[ -L /etc/resolv.conf ]] && readlink /etc/resolv.conf 2>/dev/null | grep -Eq 'systemd/resolve'; then + if has_systemd_resolved; then add_manager "systemd-resolved" fi - if command_exists nmcli || [[ -d /etc/NetworkManager ]]; then + if has_networkmanager; then add_manager "NetworkManager" fi - if command_exists resolvconf || [[ -d /etc/resolvconf ]] || [[ -f /etc/resolvconf.conf ]]; then + if has_resolvconf_openresolv; then add_manager "resolvconf/openresolv" fi - if command_exists dhclient || [[ -f /etc/dhcp/dhclient.conf ]]; then + if has_dhclient; then add_manager "dhclient" fi - if command_exists systemctl && systemctl is-active --quiet systemd-networkd 2>/dev/null; then - add_manager "systemd-networkd" - elif [[ -d /etc/systemd/network ]]; then + if has_systemd_networkd; then add_manager "systemd-networkd" fi - if [[ -f /etc/network/interfaces ]] || [[ -d /etc/network/interfaces.d ]]; then + if has_ifupdown; then add_manager "ifupdown" fi } @@ -336,7 +408,7 @@ run_action() { rc=$? if [[ "$rc" -eq 2 ]]; then ACTIONS_SKIPPED+=("$action") - log_info "$action not applicable on this system" + log_info "$action skipped: target manager is not present on this host" return 0 fi @@ -374,7 +446,8 @@ apply_static_resolv_conf() { } apply_systemd_resolved() { - if ! command_exists systemctl && ! command_exists resolvectl && [[ ! -d /etc/systemd ]]; then + if ! has_systemd_resolved; then + log_info "Skipping systemd-resolved override: systemd-resolved is not present." return 2 fi @@ -409,7 +482,8 @@ apply_systemd_resolved() { } apply_networkmanager() { - if ! command_exists nmcli && [[ ! -d /etc/NetworkManager ]]; then + if ! has_networkmanager; then + log_info "Skipping NetworkManager override: NetworkManager is not present." return 2 fi @@ -430,21 +504,37 @@ apply_networkmanager() { run_root nmcli connection modify "$conn" ipv6.ignore-auto-dns yes >/dev/null 2>&1 || true run_root nmcli connection modify "$conn" ipv4.dns "$dns_csv" >/dev/null 2>&1 || true run_root nmcli connection modify "$conn" ipv6.dns "$dns_csv" >/dev/null 2>&1 || true - run_root nmcli connection up "$conn" >/dev/null 2>&1 || true done < <(nmcli -g NAME connection show --active 2>/dev/null || true) + run_root nmcli connection reload >/dev/null 2>&1 || true + + if [[ "$IS_REMOTE_SSH" -eq 1 ]]; then + log_warning "Skipping NetworkManager reconnect/restart operations in SSH session." + local dev + while IFS= read -r dev; do + [[ -n "$dev" && "$dev" != "lo" ]] || continue + run_root nmcli device reapply "$dev" >/dev/null 2>&1 || true + done < <(nmcli -t -f DEVICE,STATE device status 2>/dev/null | awk -F: '$2=="connected" || $2=="connecting" {print $1}') + else + while IFS= read -r conn; do + [[ -n "$conn" ]] || continue + run_root nmcli connection up "$conn" >/dev/null 2>&1 || true + done < <(nmcli -g NAME connection show --active 2>/dev/null || true) + fi + run_root nmcli general reload >/dev/null 2>&1 || true fi if command_exists systemctl; then - run_root systemctl reload NetworkManager >/dev/null 2>&1 || run_root systemctl restart NetworkManager >/dev/null 2>&1 || true + run_root systemctl reload NetworkManager >/dev/null 2>&1 || true fi return 0 } apply_resolvconf_openresolv() { - if ! command_exists resolvconf && [[ ! -d /etc/resolvconf ]] && [[ ! -f /etc/resolvconf.conf ]]; then + if ! has_resolvconf_openresolv; then + log_info "Skipping resolvconf/openresolv override: resolvconf/openresolv is not present." return 2 fi @@ -464,7 +554,8 @@ apply_resolvconf_openresolv() { } apply_dhclient() { - if ! command_exists dhclient && [[ ! -f /etc/dhcp/dhclient.conf ]] && [[ ! -d /etc/dhcp ]]; then + if ! has_dhclient; then + log_info "Skipping dhclient override: dhclient is not present." return 2 fi @@ -485,7 +576,8 @@ apply_dhclient() { } apply_systemd_networkd() { - if ! command_exists systemctl && [[ ! -d /etc/systemd/network ]]; then + if ! has_systemd_networkd; then + log_info "Skipping systemd-networkd override: systemd-networkd is not present." return 2 fi @@ -515,7 +607,18 @@ apply_systemd_networkd() { if command_exists systemctl; then run_root systemctl daemon-reload >/dev/null 2>&1 || true - run_root systemctl restart systemd-networkd >/dev/null 2>&1 || run_root systemctl try-restart systemd-networkd >/dev/null 2>&1 || true + if command_exists networkctl; then + run_root networkctl reload >/dev/null 2>&1 || true + if [[ "$IS_REMOTE_SSH" -eq 0 ]]; then + run_root networkctl reconfigure --all >/dev/null 2>&1 || true + else + log_warning "Skipping networkctl reconfigure --all in SSH session." + fi + elif [[ "$IS_REMOTE_SSH" -eq 0 ]]; then + run_root systemctl restart systemd-networkd >/dev/null 2>&1 || run_root systemctl try-restart systemd-networkd >/dev/null 2>&1 || true + else + log_warning "Skipping systemd-networkd restart in SSH session." + fi fi return 0 @@ -567,7 +670,8 @@ patch_ifupdown_file() { } apply_ifupdown() { - if [[ ! -f /etc/network/interfaces ]] && [[ ! -d /etc/network/interfaces.d ]]; then + if ! has_ifupdown; then + log_info "Skipping ifupdown override: ifupdown is not present." return 2 fi @@ -611,22 +715,41 @@ prompt_immutable_lock() { } local answer - answer="$(read_tty_input "${YELLOW}${EMOJI_LOCK} Make /etc/resolv.conf immutable with chattr +i? [y/N]: ${NC}")" || return 1 + if ! answer="$(read_tty_input_timeout "${YELLOW}${EMOJI_LOCK} Make /etc/resolv.conf immutable with chattr +i? [y/N] (auto No in ${IMMUTABLE_PROMPT_TIMEOUT_SEC}s): ${NC}" "$IMMUTABLE_PROMPT_TIMEOUT_SEC")"; then + local rc=$? + if [[ "$rc" -gt 128 ]]; then + log_warning "No response received for immutable lock prompt; defaulting to No." + IMMUTABLE_LOCK_REQUESTED=0 + return 0 + fi + return 1 + fi + case "$answer" in y|Y|yes|YES) - if run_root chattr +i /etc/resolv.conf; then - log_success "Immutable lock applied to /etc/resolv.conf." - log_warning "To edit DNS later, run: sudo chattr -i /etc/resolv.conf" - else - log_warning "Failed to apply immutable lock." - fi + IMMUTABLE_LOCK_REQUESTED=1 + log_info "Immutable lock requested. It will be applied after DNS updates." ;; *) + IMMUTABLE_LOCK_REQUESTED=0 log_info "Immutable lock skipped." ;; esac } +apply_immutable_lock_if_requested() { + if [[ "$IMMUTABLE_LOCK_REQUESTED" -ne 1 ]]; then + return 0 + fi + + if run_root chattr +i /etc/resolv.conf; then + log_success "Immutable lock applied to /etc/resolv.conf." + log_warning "To edit DNS later, run: sudo chattr -i /etc/resolv.conf" + else + log_warning "Failed to apply immutable lock." + fi +} + print_summary() { echo log_step "Execution summary" @@ -669,7 +792,9 @@ main() { require_tty ensure_root_context "$@" + detect_runtime_context prompt_dns_servers + prompt_immutable_lock || log_warning "Immutable lock prompt failed; defaulting to unlocked." run_connectivity_checks detect_managers @@ -681,7 +806,7 @@ main() { run_action "Apply ifupdown override" apply_ifupdown run_action "Write /etc/resolv.conf directly" apply_static_resolv_conf - prompt_immutable_lock || log_warning "Immutable lock prompt failed; continuing." + run_action "Apply immutable /etc/resolv.conf lock (if requested)" apply_immutable_lock_if_requested print_summary echo