From d330a0a7a99c9073a28d1aa7fbcbabb89514819e Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Tue, 24 Mar 2026 09:27:35 +0100 Subject: [PATCH 1/6] Add support for host DNS resolution configuration and cleanup in installer and uninstaller scripts --- scripts/dappnode_install.sh | 309 +++++++++++++++++++++++++++++++++- scripts/dappnode_uninstall.sh | 28 +++ 2 files changed, 336 insertions(+), 1 deletion(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 0379827..476d2fa 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -69,6 +69,7 @@ set -Eeuo pipefail : "${MINIMAL:=false}" : "${LITE:=false}" : "${PACKAGES:=}" +: "${RESOLVE_FROM_HOST:=false}" # Enable alias expansion in non-interactive bash scripts. # Required so commands like `dappnode_wireguard` (defined as aliases in `.dappnode_profile`) work. @@ -90,10 +91,11 @@ Options: --minimal Install only BIND DAPPMANAGER NOTIFICATIONS PREMIUM (equivalent: MINIMAL=true) --lite Install reduced package set: BIND VPN WIREGUARD DAPPMANAGER NOTIFICATIONS PREMIUM (equivalent: LITE=true) --packages Override package selection (comma or space separated), e.g. BIND,IPFS,VPN + --resolve-from-host Configure host DNS to resolve .dappnode domains (Linux only) (equivalent: RESOLVE_FROM_HOST=true) -h, --help Show this help Environment variables (also supported): - UPDATE, STATIC_IP, LOCAL_PROFILE_PATH, IPFS_ENDPOINT, PROFILE_URL, MINIMAL, LITE, PACKAGES + UPDATE, STATIC_IP, LOCAL_PROFILE_PATH, IPFS_ENDPOINT, PROFILE_URL, MINIMAL, LITE, PACKAGES, RESOLVE_FROM_HOST EOF } @@ -141,6 +143,10 @@ parse_args() { PACKAGES="${1#*=}" shift ;; + --resolve-from-host) + RESOLVE_FROM_HOST=true + shift + ;; -h|--help) usage exit 0 @@ -1094,6 +1100,306 @@ addUserToDockerGroup() { log "User $user added to the docker group" } +############################## +# Host DNS Resolution # +############################## + +# Install systemd service + timer that configures split DNS via resolvectl +# for .dappnode domains on the dncore_network (and dnprivate_network) bridge interfaces. +setup_resolved_dns() { + local script_path="/usr/local/bin/dappnode-dns.sh" + local service_path="/etc/systemd/system/dappnode-dns.service" + local timer_path="/etc/systemd/system/dappnode-dns.timer" + + log "Setting up host DNS resolution via systemd-resolved..." + + # --- Install the dappnode-dns.sh script --- + cat > "$script_path" << 'DNSEOF' +#!/usr/bin/env bash +set -euo pipefail + +TAG="dappnode-dns" +MAX_RETRIES=5 +SLEEP_SECONDS=2 + +############################## +# Logging # +############################## + +log_info() { + logger -t "$TAG" "[INFO] $1" +} + +log_warn() { + logger -t "$TAG" "[WARN] $1" +} + +log_error() { + logger -t "$TAG" "[ERROR] $1" +} + +# Log unexpected errors with line number +trap 'log_error "Unexpected error on line $LINENO (exit $?)"' ERR + +############################## +# Pre-flight checks # +############################## + +preflight() { + if ! command -v docker &>/dev/null; then + log_error "docker not found. Skipping DNS configuration." + exit 0 + fi + + if ! docker info &>/dev/null; then + log_warn "Docker is not running. Skipping DNS configuration." + exit 0 + fi + + if ! command -v resolvectl &>/dev/null; then + log_error "resolvectl not found. Cannot configure split DNS." + exit 1 + fi +} + +############################## +# Network helpers # +############################## + +network_exists() { + docker network inspect "$1" &>/dev/null +} + +get_bridge_iface() { + local net="$1" + local net_id + net_id=$(docker network inspect -f '{{.Id}}' "$net" 2>/dev/null || true) + [[ -z "$net_id" ]] && return 1 + + local iface="br-${net_id:0:12}" + ip link show "$iface" &>/dev/null || return 1 + echo "$iface" +} + +get_bridge_with_retry() { + local net="$1" + local iface + + for ((i=1; i<=MAX_RETRIES; i++)); do + if iface=$(get_bridge_iface "$net"); then + log_info "Interface $iface found for $net (attempt $i/$MAX_RETRIES)" + echo "$iface" + return 0 + fi + sleep "$SLEEP_SECONDS" + done + + log_error "Failed to find bridge interface for $net after $MAX_RETRIES attempts" + return 1 +} + +############################## +# DNS application # +############################## + +apply_dns() { + local iface="$1" + local dns_ip="$2" + local domain="$3" + + [[ -z "$iface" ]] && return + + log_info "Setting DNS=$dns_ip domain=$domain on interface $iface" + resolvectl dns "$iface" "$dns_ip" || { log_error "resolvectl dns failed on $iface"; return 1; } + resolvectl domain "$iface" "$domain" || { log_error "resolvectl domain failed on $iface"; return 1; } +} + +############################## +# Main # +############################## + +main() { + log_info "===== dappnode-dns.sh started =====" + + preflight + + local core_exists=false + local private_exists=false + + network_exists "dncore_network" && core_exists=true + network_exists "dnprivate_network" && private_exists=true + + # If no DAppNode networks exist, nothing to do. + # Cleanup is handled by the DAppNode uninstall script. + if [[ "$core_exists" == false && "$private_exists" == false ]]; then + log_warn "No DAppNode networks found. Nothing to configure." + exit 0 + fi + + if $core_exists; then + if core_iface=$(get_bridge_with_retry "dncore_network"); then + apply_dns "$core_iface" "172.33.1.2" "~dappnode" + fi + fi + + if $private_exists; then + if private_iface=$(get_bridge_with_retry "dnprivate_network"); then + apply_dns "$private_iface" "10.20.0.2" "~private.dappnode" + fi + fi + + log_info "===== dappnode-dns.sh finished =====" +} + +main +DNSEOF + chmod +x "$script_path" + + # --- Install the systemd service --- + cat > "$service_path" << 'SVCEOF' +[Unit] +Description=Configure DAppNode DNS +After=docker.service network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/dappnode-dns.sh +SVCEOF + + # --- Install the systemd timer --- + cat > "$timer_path" << 'TMREOF' +[Unit] +Description=Run DAppNode DNS periodically + +[Timer] +OnBootSec=30 +OnUnitActiveSec=60 +Persistent=true + +[Install] +WantedBy=timers.target +TMREOF + + systemctl daemon-reload + systemctl enable dappnode-dns.timer + systemctl start dappnode-dns.timer + + log "systemd-resolved DNS setup complete (service + timer installed)" +} + +# Install and configure dnsmasq for split DNS on systems using classic /etc/resolv.conf. +setup_dnsmasq_dns() { + log "Setting up host DNS resolution via dnsmasq..." + + # Check port 53 conflicts before installing + if is_port_listening 53 tcp || is_port_listening 53 udp; then + die "Port 53 is already in use on this host. Cannot set up dnsmasq for .dappnode resolution. Free up port 53 (check for existing dnsmasq, pihole, or other DNS services) and re-run the installer." + fi + + # Install dnsmasq + log "Installing dnsmasq..." + apt-get update -qq + apt-get install -y dnsmasq + + # Write split-DNS config + local dnsmasq_conf="/etc/dnsmasq.d/dappnode.conf" + log "Writing dnsmasq config to ${dnsmasq_conf}..." + cat > "$dnsmasq_conf" << 'DNSMASQEOF' +######################################## +# DAppNode DNS routing (split DNS) +######################################## + +# Route all *.dappnode domains to the DAppNode BIND container +server=/dappnode/172.33.1.2 + +######################################## +# Upstream DNS (fallback) +######################################## + +server=1.1.1.1 +server=8.8.8.8 + +######################################## +# Performance +######################################## + +cache-size=1000 + +######################################## +# Security / Isolation +######################################## + +listen-address=127.0.0.1 +bind-interfaces + +######################################## +# DNS behavior +######################################## + +# Never forward plain names (no dots) +domain-needed + +# Never forward reverse lookups for private IPs +bogus-priv +DNSMASQEOF + + # Backup and update /etc/resolv.conf + if [ -f /etc/resolv.conf ]; then + cp /etc/resolv.conf /etc/resolv.conf.dappnode.bak + log "Backed up /etc/resolv.conf to /etc/resolv.conf.dappnode.bak" + fi + + # Write resolv.conf pointing to dnsmasq, with a public fallback + cat > /etc/resolv.conf << 'RESOLVEOF' +# Managed by DAppNode installer (--resolve-from-host) +# Original backed up to /etc/resolv.conf.dappnode.bak +nameserver 127.0.0.1 +nameserver 1.1.1.1 +RESOLVEOF + + systemctl restart dnsmasq + log "dnsmasq DNS setup complete" +} + +# Main dispatcher: detect the DNS subsystem and apply the appropriate solution. +configure_host_dns_resolution() { + if [[ "${RESOLVE_FROM_HOST}" != "true" ]]; then + return 0 + fi + + if $IS_MACOS; then + warn "Host DNS resolution (--resolve-from-host) is only supported on Linux. Ignoring on macOS." + return 0 + fi + + # Validate BIND is in the package set — both DNS paths forward to 172.33.1.2 + local has_bind=false + local pkg + for pkg in "${PKGS[@]}"; do + if [[ "$pkg" == "BIND" ]]; then + has_bind=true + break + fi + done + if [[ "$has_bind" != "true" ]]; then + die "--resolve-from-host requires the BIND package (DNS server at 172.33.1.2), but BIND is not in the package set. Add BIND to --packages or remove --resolve-from-host." + fi + + log "Configuring host DNS resolution for .dappnode domains..." + + # Detect DNS subsystem + if systemctl is-active --quiet systemd-resolved 2>/dev/null; then + log "Detected systemd-resolved as the active DNS resolver" + setup_resolved_dns + elif [ -f /etc/resolv.conf ] && ! readlink -f /etc/resolv.conf 2>/dev/null | grep -q "systemd"; then + log "Detected classic /etc/resolv.conf (no systemd-resolved)" + setup_dnsmasq_dns + else + die "Unsupported DNS system. --resolve-from-host requires either systemd-resolved (Ubuntu 16.10+) or classic /etc/resolv.conf. Your system uses a different DNS configuration that this installer cannot automatically configure." + fi +} + ############################################## #### SCRIPT START #### ############################################## @@ -1162,6 +1468,7 @@ main() { if [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then log "DAppNode installed" dappnode_core_start + configure_host_dns_resolution print_vpn_access_credentials fi diff --git a/scripts/dappnode_uninstall.sh b/scripts/dappnode_uninstall.sh index 29da4a5..a4593fc 100755 --- a/scripts/dappnode_uninstall.sh +++ b/scripts/dappnode_uninstall.sh @@ -94,6 +94,34 @@ uninstall() { echo "Removing docker dncore_network" docker network remove dncore_network || echo "dncore_network already removed" + # Clean up host DNS resolution artifacts (--resolve-from-host) + if $IS_LINUX; then + # systemd-resolved path: remove service, timer, and script + if [ -f /etc/systemd/system/dappnode-dns.timer ]; then + echo "Removing dappnode-dns systemd timer and service..." + systemctl disable dappnode-dns.timer 2>/dev/null || true + systemctl stop dappnode-dns.timer 2>/dev/null || true + systemctl disable dappnode-dns.service 2>/dev/null || true + systemctl stop dappnode-dns.service 2>/dev/null || true + rm -f /etc/systemd/system/dappnode-dns.service + rm -f /etc/systemd/system/dappnode-dns.timer + rm -f /usr/local/bin/dappnode-dns.sh + systemctl daemon-reload || true + fi + + # dnsmasq path: remove dappnode config and restore resolv.conf + if [ -f /etc/dnsmasq.d/dappnode.conf ]; then + echo "Removing dnsmasq DAppNode config..." + rm -f /etc/dnsmasq.d/dappnode.conf + systemctl restart dnsmasq 2>/dev/null || true + fi + if [ -f /etc/resolv.conf.dappnode.bak ]; then + echo "Restoring /etc/resolv.conf from backup..." + cp /etc/resolv.conf.dappnode.bak /etc/resolv.conf + rm -f /etc/resolv.conf.dappnode.bak + fi + fi + # Remove DAppNode directory echo "Removing DAppNode directory: ${DAPPNODE_DIR}" rm -rf "${DAPPNODE_DIR}" From 928769bb39dce4ee4fee02fccb776950e55f62bb Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Tue, 24 Mar 2026 09:30:29 +0100 Subject: [PATCH 2/6] Enhance uninstaller script to remove additional DAppNode networks and suppress errors --- scripts/dappnode_uninstall.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/dappnode_uninstall.sh b/scripts/dappnode_uninstall.sh index a4593fc..6d0a245 100755 --- a/scripts/dappnode_uninstall.sh +++ b/scripts/dappnode_uninstall.sh @@ -90,9 +90,9 @@ uninstall() { docker volume rm "$volume" &>/dev/null done - # Remove dncore_network - echo "Removing docker dncore_network" - docker network remove dncore_network || echo "dncore_network already removed" + # Remove dncore_network dnprivate_network dnpublic_network gnosis_network holesky_network hoodi_network lukso_network mainnet_network prater_network sepolia_network starknet_network starknet_sepolia_network + echo "Removing dappnode networks..." + docker network remove dncore_network dnprivate_network dnpublic_network gnosis_network holesky_network hoodi_network lukso_network mainnet_network prater_network sepolia_network starknet_network starknet_sepolia_network &>/dev/null || true # Clean up host DNS resolution artifacts (--resolve-from-host) if $IS_LINUX; then From fc1b02175eefecd8d6d9667cf8d5b59de3159568 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Wed, 25 Mar 2026 09:32:04 +0100 Subject: [PATCH 3/6] Add DNS resolution verification for .dappnode domains in installer script --- scripts/dappnode_install.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 476d2fa..baf4bfc 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -1400,6 +1400,34 @@ configure_host_dns_resolution() { fi } +# Verify that .dappnode domains can be resolved from the host after DNS setup. +# Retries a few times to allow DNS propagation, then logs a warning on failure. +verify_host_dns_resolution() { + if [[ "${RESOLVE_FROM_HOST}" != "true" ]]; then + return 0 + fi + + local domain="my.dappnode" + local max_retries=5 + local sleep_seconds=3 + local attempt + + log "Verifying host DNS resolution for ${domain}..." + + for ((attempt = 1; attempt <= max_retries; attempt++)); do + if getent hosts "$domain" >/dev/null 2>&1; then + log "DNS verification succeeded: ${domain} resolves correctly (attempt ${attempt}/${max_retries})" + return 0 + fi + log "DNS verification attempt ${attempt}/${max_retries}: ${domain} not yet resolvable. Retrying in ${sleep_seconds}s..." + sleep "$sleep_seconds" + done + + warn "DNS verification failed: ${domain} could not be resolved after ${max_retries} attempts." + warn "Host DNS resolution for .dappnode domains may not be working correctly." + warn "Ensure the BIND container is running and your DNS configuration is correct." +} + ############################################## #### SCRIPT START #### ############################################## @@ -1469,6 +1497,7 @@ main() { log "DAppNode installed" dappnode_core_start configure_host_dns_resolution + verify_host_dns_resolution print_vpn_access_credentials fi From b741822aa905dd78a2149d2d0a7efd636f99b361 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Wed, 25 Mar 2026 09:36:24 +0100 Subject: [PATCH 4/6] Add creation of dnprivate_network in installer script --- scripts/dappnode_install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index baf4bfc..55b5a73 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -1476,8 +1476,9 @@ main() { fi # --- Common steps (Linux and macOS) --- - log "Creating dncore_network if needed..." + log "Creating dncore_network and dnprivate_network if needed..." docker network create --driver bridge --subnet 172.33.0.0/16 dncore_network 2>&1 | tee -a "$LOGFILE" || true + docker network create --driver bridge --subnet 10.20.0.0/24 dnprivate_network 2>&1 | tee -a "$LOGFILE" || true log "Building DAppNode Core if needed..." dappnode_core_build From edd7e21db59dbc9436bd1c3d70fdd97f2e0a96e1 Mon Sep 17 00:00:00 2001 From: pablo Date: Wed, 25 Mar 2026 09:47:58 +0100 Subject: [PATCH 5/6] increase retries --- scripts/dappnode_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index 55b5a73..cc4e798 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -1408,7 +1408,7 @@ verify_host_dns_resolution() { fi local domain="my.dappnode" - local max_retries=5 + local max_retries=20 local sleep_seconds=3 local attempt From c40eb23304b05617bcf650b1cfb6214dbfe272d0 Mon Sep 17 00:00:00 2001 From: pablo Date: Wed, 25 Mar 2026 09:58:39 +0100 Subject: [PATCH 6/6] Update DNS application for private network to use new domain format --- scripts/dappnode_install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dappnode_install.sh b/scripts/dappnode_install.sh index cc4e798..0962c9f 100755 --- a/scripts/dappnode_install.sh +++ b/scripts/dappnode_install.sh @@ -1244,7 +1244,7 @@ main() { if $private_exists; then if private_iface=$(get_bridge_with_retry "dnprivate_network"); then - apply_dns "$private_iface" "10.20.0.2" "~private.dappnode" + apply_dns "$private_iface" "10.20.0.2" "~dappnode.private" fi fi