Skip to content

Commit d330a0a

Browse files
Add support for host DNS resolution configuration and cleanup in installer and uninstaller scripts
1 parent ef5338e commit d330a0a

2 files changed

Lines changed: 336 additions & 1 deletion

File tree

scripts/dappnode_install.sh

Lines changed: 308 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ set -Eeuo pipefail
6969
: "${MINIMAL:=false}"
7070
: "${LITE:=false}"
7171
: "${PACKAGES:=}"
72+
: "${RESOLVE_FROM_HOST:=false}"
7273

7374
# Enable alias expansion in non-interactive bash scripts.
7475
# Required so commands like `dappnode_wireguard` (defined as aliases in `.dappnode_profile`) work.
@@ -90,10 +91,11 @@ Options:
9091
--minimal Install only BIND DAPPMANAGER NOTIFICATIONS PREMIUM (equivalent: MINIMAL=true)
9192
--lite Install reduced package set: BIND VPN WIREGUARD DAPPMANAGER NOTIFICATIONS PREMIUM (equivalent: LITE=true)
9293
--packages <list> Override package selection (comma or space separated), e.g. BIND,IPFS,VPN
94+
--resolve-from-host Configure host DNS to resolve .dappnode domains (Linux only) (equivalent: RESOLVE_FROM_HOST=true)
9395
-h, --help Show this help
9496
9597
Environment variables (also supported):
96-
UPDATE, STATIC_IP, LOCAL_PROFILE_PATH, IPFS_ENDPOINT, PROFILE_URL, MINIMAL, LITE, PACKAGES
98+
UPDATE, STATIC_IP, LOCAL_PROFILE_PATH, IPFS_ENDPOINT, PROFILE_URL, MINIMAL, LITE, PACKAGES, RESOLVE_FROM_HOST
9799
EOF
98100
}
99101

@@ -141,6 +143,10 @@ parse_args() {
141143
PACKAGES="${1#*=}"
142144
shift
143145
;;
146+
--resolve-from-host)
147+
RESOLVE_FROM_HOST=true
148+
shift
149+
;;
144150
-h|--help)
145151
usage
146152
exit 0
@@ -1094,6 +1100,306 @@ addUserToDockerGroup() {
10941100
log "User $user added to the docker group"
10951101
}
10961102

1103+
##############################
1104+
# Host DNS Resolution #
1105+
##############################
1106+
1107+
# Install systemd service + timer that configures split DNS via resolvectl
1108+
# for .dappnode domains on the dncore_network (and dnprivate_network) bridge interfaces.
1109+
setup_resolved_dns() {
1110+
local script_path="/usr/local/bin/dappnode-dns.sh"
1111+
local service_path="/etc/systemd/system/dappnode-dns.service"
1112+
local timer_path="/etc/systemd/system/dappnode-dns.timer"
1113+
1114+
log "Setting up host DNS resolution via systemd-resolved..."
1115+
1116+
# --- Install the dappnode-dns.sh script ---
1117+
cat > "$script_path" << 'DNSEOF'
1118+
#!/usr/bin/env bash
1119+
set -euo pipefail
1120+
1121+
TAG="dappnode-dns"
1122+
MAX_RETRIES=5
1123+
SLEEP_SECONDS=2
1124+
1125+
##############################
1126+
# Logging #
1127+
##############################
1128+
1129+
log_info() {
1130+
logger -t "$TAG" "[INFO] $1"
1131+
}
1132+
1133+
log_warn() {
1134+
logger -t "$TAG" "[WARN] $1"
1135+
}
1136+
1137+
log_error() {
1138+
logger -t "$TAG" "[ERROR] $1"
1139+
}
1140+
1141+
# Log unexpected errors with line number
1142+
trap 'log_error "Unexpected error on line $LINENO (exit $?)"' ERR
1143+
1144+
##############################
1145+
# Pre-flight checks #
1146+
##############################
1147+
1148+
preflight() {
1149+
if ! command -v docker &>/dev/null; then
1150+
log_error "docker not found. Skipping DNS configuration."
1151+
exit 0
1152+
fi
1153+
1154+
if ! docker info &>/dev/null; then
1155+
log_warn "Docker is not running. Skipping DNS configuration."
1156+
exit 0
1157+
fi
1158+
1159+
if ! command -v resolvectl &>/dev/null; then
1160+
log_error "resolvectl not found. Cannot configure split DNS."
1161+
exit 1
1162+
fi
1163+
}
1164+
1165+
##############################
1166+
# Network helpers #
1167+
##############################
1168+
1169+
network_exists() {
1170+
docker network inspect "$1" &>/dev/null
1171+
}
1172+
1173+
get_bridge_iface() {
1174+
local net="$1"
1175+
local net_id
1176+
net_id=$(docker network inspect -f '{{.Id}}' "$net" 2>/dev/null || true)
1177+
[[ -z "$net_id" ]] && return 1
1178+
1179+
local iface="br-${net_id:0:12}"
1180+
ip link show "$iface" &>/dev/null || return 1
1181+
echo "$iface"
1182+
}
1183+
1184+
get_bridge_with_retry() {
1185+
local net="$1"
1186+
local iface
1187+
1188+
for ((i=1; i<=MAX_RETRIES; i++)); do
1189+
if iface=$(get_bridge_iface "$net"); then
1190+
log_info "Interface $iface found for $net (attempt $i/$MAX_RETRIES)"
1191+
echo "$iface"
1192+
return 0
1193+
fi
1194+
sleep "$SLEEP_SECONDS"
1195+
done
1196+
1197+
log_error "Failed to find bridge interface for $net after $MAX_RETRIES attempts"
1198+
return 1
1199+
}
1200+
1201+
##############################
1202+
# DNS application #
1203+
##############################
1204+
1205+
apply_dns() {
1206+
local iface="$1"
1207+
local dns_ip="$2"
1208+
local domain="$3"
1209+
1210+
[[ -z "$iface" ]] && return
1211+
1212+
log_info "Setting DNS=$dns_ip domain=$domain on interface $iface"
1213+
resolvectl dns "$iface" "$dns_ip" || { log_error "resolvectl dns failed on $iface"; return 1; }
1214+
resolvectl domain "$iface" "$domain" || { log_error "resolvectl domain failed on $iface"; return 1; }
1215+
}
1216+
1217+
##############################
1218+
# Main #
1219+
##############################
1220+
1221+
main() {
1222+
log_info "===== dappnode-dns.sh started ====="
1223+
1224+
preflight
1225+
1226+
local core_exists=false
1227+
local private_exists=false
1228+
1229+
network_exists "dncore_network" && core_exists=true
1230+
network_exists "dnprivate_network" && private_exists=true
1231+
1232+
# If no DAppNode networks exist, nothing to do.
1233+
# Cleanup is handled by the DAppNode uninstall script.
1234+
if [[ "$core_exists" == false && "$private_exists" == false ]]; then
1235+
log_warn "No DAppNode networks found. Nothing to configure."
1236+
exit 0
1237+
fi
1238+
1239+
if $core_exists; then
1240+
if core_iface=$(get_bridge_with_retry "dncore_network"); then
1241+
apply_dns "$core_iface" "172.33.1.2" "~dappnode"
1242+
fi
1243+
fi
1244+
1245+
if $private_exists; then
1246+
if private_iface=$(get_bridge_with_retry "dnprivate_network"); then
1247+
apply_dns "$private_iface" "10.20.0.2" "~private.dappnode"
1248+
fi
1249+
fi
1250+
1251+
log_info "===== dappnode-dns.sh finished ====="
1252+
}
1253+
1254+
main
1255+
DNSEOF
1256+
chmod +x "$script_path"
1257+
1258+
# --- Install the systemd service ---
1259+
cat > "$service_path" << 'SVCEOF'
1260+
[Unit]
1261+
Description=Configure DAppNode DNS
1262+
After=docker.service network-online.target
1263+
Wants=network-online.target
1264+
1265+
[Service]
1266+
Type=oneshot
1267+
ExecStart=/usr/local/bin/dappnode-dns.sh
1268+
SVCEOF
1269+
1270+
# --- Install the systemd timer ---
1271+
cat > "$timer_path" << 'TMREOF'
1272+
[Unit]
1273+
Description=Run DAppNode DNS periodically
1274+
1275+
[Timer]
1276+
OnBootSec=30
1277+
OnUnitActiveSec=60
1278+
Persistent=true
1279+
1280+
[Install]
1281+
WantedBy=timers.target
1282+
TMREOF
1283+
1284+
systemctl daemon-reload
1285+
systemctl enable dappnode-dns.timer
1286+
systemctl start dappnode-dns.timer
1287+
1288+
log "systemd-resolved DNS setup complete (service + timer installed)"
1289+
}
1290+
1291+
# Install and configure dnsmasq for split DNS on systems using classic /etc/resolv.conf.
1292+
setup_dnsmasq_dns() {
1293+
log "Setting up host DNS resolution via dnsmasq..."
1294+
1295+
# Check port 53 conflicts before installing
1296+
if is_port_listening 53 tcp || is_port_listening 53 udp; then
1297+
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."
1298+
fi
1299+
1300+
# Install dnsmasq
1301+
log "Installing dnsmasq..."
1302+
apt-get update -qq
1303+
apt-get install -y dnsmasq
1304+
1305+
# Write split-DNS config
1306+
local dnsmasq_conf="/etc/dnsmasq.d/dappnode.conf"
1307+
log "Writing dnsmasq config to ${dnsmasq_conf}..."
1308+
cat > "$dnsmasq_conf" << 'DNSMASQEOF'
1309+
########################################
1310+
# DAppNode DNS routing (split DNS)
1311+
########################################
1312+
1313+
# Route all *.dappnode domains to the DAppNode BIND container
1314+
server=/dappnode/172.33.1.2
1315+
1316+
########################################
1317+
# Upstream DNS (fallback)
1318+
########################################
1319+
1320+
server=1.1.1.1
1321+
server=8.8.8.8
1322+
1323+
########################################
1324+
# Performance
1325+
########################################
1326+
1327+
cache-size=1000
1328+
1329+
########################################
1330+
# Security / Isolation
1331+
########################################
1332+
1333+
listen-address=127.0.0.1
1334+
bind-interfaces
1335+
1336+
########################################
1337+
# DNS behavior
1338+
########################################
1339+
1340+
# Never forward plain names (no dots)
1341+
domain-needed
1342+
1343+
# Never forward reverse lookups for private IPs
1344+
bogus-priv
1345+
DNSMASQEOF
1346+
1347+
# Backup and update /etc/resolv.conf
1348+
if [ -f /etc/resolv.conf ]; then
1349+
cp /etc/resolv.conf /etc/resolv.conf.dappnode.bak
1350+
log "Backed up /etc/resolv.conf to /etc/resolv.conf.dappnode.bak"
1351+
fi
1352+
1353+
# Write resolv.conf pointing to dnsmasq, with a public fallback
1354+
cat > /etc/resolv.conf << 'RESOLVEOF'
1355+
# Managed by DAppNode installer (--resolve-from-host)
1356+
# Original backed up to /etc/resolv.conf.dappnode.bak
1357+
nameserver 127.0.0.1
1358+
nameserver 1.1.1.1
1359+
RESOLVEOF
1360+
1361+
systemctl restart dnsmasq
1362+
log "dnsmasq DNS setup complete"
1363+
}
1364+
1365+
# Main dispatcher: detect the DNS subsystem and apply the appropriate solution.
1366+
configure_host_dns_resolution() {
1367+
if [[ "${RESOLVE_FROM_HOST}" != "true" ]]; then
1368+
return 0
1369+
fi
1370+
1371+
if $IS_MACOS; then
1372+
warn "Host DNS resolution (--resolve-from-host) is only supported on Linux. Ignoring on macOS."
1373+
return 0
1374+
fi
1375+
1376+
# Validate BIND is in the package set — both DNS paths forward to 172.33.1.2
1377+
local has_bind=false
1378+
local pkg
1379+
for pkg in "${PKGS[@]}"; do
1380+
if [[ "$pkg" == "BIND" ]]; then
1381+
has_bind=true
1382+
break
1383+
fi
1384+
done
1385+
if [[ "$has_bind" != "true" ]]; then
1386+
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."
1387+
fi
1388+
1389+
log "Configuring host DNS resolution for .dappnode domains..."
1390+
1391+
# Detect DNS subsystem
1392+
if systemctl is-active --quiet systemd-resolved 2>/dev/null; then
1393+
log "Detected systemd-resolved as the active DNS resolver"
1394+
setup_resolved_dns
1395+
elif [ -f /etc/resolv.conf ] && ! readlink -f /etc/resolv.conf 2>/dev/null | grep -q "systemd"; then
1396+
log "Detected classic /etc/resolv.conf (no systemd-resolved)"
1397+
setup_dnsmasq_dns
1398+
else
1399+
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."
1400+
fi
1401+
}
1402+
10971403
##############################################
10981404
#### SCRIPT START ####
10991405
##############################################
@@ -1162,6 +1468,7 @@ main() {
11621468
if [ ! -f "${DAPPNODE_DIR}/.firstboot" ]; then
11631469
log "DAppNode installed"
11641470
dappnode_core_start
1471+
configure_host_dns_resolution
11651472
print_vpn_access_credentials
11661473
fi
11671474

scripts/dappnode_uninstall.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,34 @@ uninstall() {
9494
echo "Removing docker dncore_network"
9595
docker network remove dncore_network || echo "dncore_network already removed"
9696

97+
# Clean up host DNS resolution artifacts (--resolve-from-host)
98+
if $IS_LINUX; then
99+
# systemd-resolved path: remove service, timer, and script
100+
if [ -f /etc/systemd/system/dappnode-dns.timer ]; then
101+
echo "Removing dappnode-dns systemd timer and service..."
102+
systemctl disable dappnode-dns.timer 2>/dev/null || true
103+
systemctl stop dappnode-dns.timer 2>/dev/null || true
104+
systemctl disable dappnode-dns.service 2>/dev/null || true
105+
systemctl stop dappnode-dns.service 2>/dev/null || true
106+
rm -f /etc/systemd/system/dappnode-dns.service
107+
rm -f /etc/systemd/system/dappnode-dns.timer
108+
rm -f /usr/local/bin/dappnode-dns.sh
109+
systemctl daemon-reload || true
110+
fi
111+
112+
# dnsmasq path: remove dappnode config and restore resolv.conf
113+
if [ -f /etc/dnsmasq.d/dappnode.conf ]; then
114+
echo "Removing dnsmasq DAppNode config..."
115+
rm -f /etc/dnsmasq.d/dappnode.conf
116+
systemctl restart dnsmasq 2>/dev/null || true
117+
fi
118+
if [ -f /etc/resolv.conf.dappnode.bak ]; then
119+
echo "Restoring /etc/resolv.conf from backup..."
120+
cp /etc/resolv.conf.dappnode.bak /etc/resolv.conf
121+
rm -f /etc/resolv.conf.dappnode.bak
122+
fi
123+
fi
124+
97125
# Remove DAppNode directory
98126
echo "Removing DAppNode directory: ${DAPPNODE_DIR}"
99127
rm -rf "${DAPPNODE_DIR}"

0 commit comments

Comments
 (0)