#!/bin/bash # ============================================================================= # machine-setup — network configuration # ============================================================================= # # Definitions only, like the other lib/ files. [[ -n "${MACHINE_SETUP_NETWORK_LOADED:-}" ]] && return 0 MACHINE_SETUP_NETWORK_LOADED=1 # ----------------------------------------------------------------------------- # DNS # ----------------------------------------------------------------------------- # # ── What is actually being changed here ── # # On a machine running systemd-resolved there are two layers, and only one of # them is ours to set: # # per-link what DHCP handed each interface, and what Tailscale installs on # its own. These answer for that link's domains — the provider's # internal names, and the tailnet — and are NOT touched here. # Overriding them is how private networking quietly stops resolving. # # global the resolver used when no link claims the query. This is what the # step sets. # # So this changes where public lookups go, and leaves the machine's own networks # resolving exactly as they did. # # ── Drop-in, and note the sort order ── # # systemd reads drop-ins in lexical order and the LAST value wins, so 99- is what # overrides. That is the opposite of sshd, three files away in this same # directory, where the FIRST value wins and the drop-in has to sort early. Worth # stating because getting it backwards fails silently in both directions. # # The original rewrote /etc/systemd/resolved.conf wholesale, which discards # anything else in it — DNSSEC, DNSOverTLS, Domains, Cache — without mentioning # that it had. RESOLVED_DROPIN=/etc/systemd/resolved.conf.d/99-machine-setup.conf resolved_is_active() { systemctl is-active --quiet systemd-resolved 2>/dev/null; } # The global resolvers in force, space separated, or empty if none are set. dns_current_global() { if resolved_is_active; then resolvectl status 2>/dev/null | awk '/^ *DNS Servers:/ { $1 = ""; $2 = ""; print; exit }' | xargs else awk '/^nameserver/ { printf "%s ", $2 }' /etc/resolv.conf 2>/dev/null | xargs fi } # What each interface was handed. Printed, never changed — the point is to show # that this step is not touching them. dns_per_link() { resolved_is_active || return 0 resolvectl status 2>/dev/null | awk '/^Link [0-9]+ \(/ { link = $3; gsub(/[()]/, "", link) } /^ *DNS Servers:/ && link { $1 = ""; $2 = ""; printf "%s:%s\n", link, $0; link = "" }' } dns_set_global() { local primary="$1" fallback="$2" if resolved_is_active; then install -d -m 0755 "$(dirname "$RESOLVED_DROPIN")" cat >"$RESOLVED_DROPIN" </dev/null || true ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf fi systemctl restart systemd-resolved else # No resolved: write resolv.conf directly, and say plainly that anything # managing the interface may put its own back. cp -a /etc/resolv.conf "/etc/resolv.conf.before-machine-setup" 2>/dev/null || true if lsattr /etc/resolv.conf 2>/dev/null | cut -c1-20 | grep -q i; then chattr -i /etc/resolv.conf fi { echo "# Written by machine-setup." local ns for ns in $primary $fallback; do echo "nameserver ${ns}"; done } >/etc/resolv.conf fi } # Does name resolution actually work now? Asked after the change rather than # assumed, because a resolver that does not answer is the one failure that makes # everything after it look broken for unrelated reasons. dns_works() { getent hosts one.one.one.one >/dev/null 2>&1 || getent hosts example.com >/dev/null 2>&1; }