@@ -13,6 +13,22 @@ set -euo pipefail
1313VERSION=" 1.3"
1414TOTAL_STEPS=12
1515
16+ # ─── Safety: ensure DNS is never left broken on exit ──────────────────────────
17+
18+ _dnstm_cleanup_dns () {
19+ # If resolv.conf is empty, missing, or points only at a dead stub, fix it.
20+ if [[ ! -s /etc/resolv.conf ]] || \
21+ ( grep -q ' 127\.0\.0\.53' /etc/resolv.conf 2> /dev/null && \
22+ ! ss -ulnp 2> /dev/null | grep -q ' 127\.0\.0\.53.*systemd-resolve' ); then
23+ chattr -i /etc/resolv.conf 2> /dev/null || true
24+ cat > /etc/resolv.conf 2> /dev/null << 'DNSEOF ' || true
25+ nameserver 8.8.8.8
26+ nameserver 1.1.1.1
27+ DNSEOF
28+ fi
29+ }
30+ trap ' _dnstm_cleanup_dns' EXIT
31+
1632# ─── Colors & Formatting ───────────────────────────────────────────────────────
1733
1834RED=' \033[0;31m'
@@ -1067,14 +1083,20 @@ ensure_resolv_conf_fallback() {
10671083 # After stopping systemd-resolved, /etc/resolv.conf may still point to
10681084 # 127.0.0.53 which is now dead. Write a temporary fallback so the script
10691085 # can still resolve hostnames (e.g. github.com for downloads).
1070- if grep -q ' 127\.0\.0\.53' /etc/resolv.conf 2> /dev/null; then
1086+ if grep -q ' 127\.0\.0\.53' /etc/resolv.conf 2> /dev/null || \
1087+ [[ ! -s /etc/resolv.conf ]]; then
10711088 print_info " Updating /etc/resolv.conf with public DNS fallback"
10721089 chattr -i /etc/resolv.conf 2> /dev/null || true
1090+ rm -f /etc/resolv.conf 2> /dev/null || true
10731091 cat > /etc/resolv.conf << 'RESOLVEOF '
1074- # Temporary fallback written by dnstm-setup (systemd-resolved was stopped)
10751092nameserver 8.8.8.8
10761093nameserver 1.1.1.1
10771094RESOLVEOF
1095+ # Verify the write succeeded
1096+ if ! grep -q ' 8\.8\.8\.8' /etc/resolv.conf 2> /dev/null; then
1097+ # Last resort: try writing via echo
1098+ echo " nameserver 8.8.8.8" > /etc/resolv.conf 2> /dev/null || true
1099+ fi
10781100 fi
10791101}
10801102
@@ -1110,6 +1132,26 @@ EOF
11101132 ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf || true
11111133 fi
11121134
1135+ # After relinking, resolv.conf may still reference 127.0.0.53 (dead stub).
1136+ # Also verify DNS actually works — if not, write direct public nameservers.
1137+ sleep 1
1138+ local dns_ok=false
1139+ if getent hosts github.com & > /dev/null 2>&1 ; then
1140+ dns_ok=true
1141+ elif curl -sf --max-time 3 https://api.ipify.org & > /dev/null 2>&1 ; then
1142+ dns_ok=true
1143+ fi
1144+
1145+ if [[ " $dns_ok " != " true" ]] || grep -q ' 127\.0\.0\.53' /etc/resolv.conf 2> /dev/null; then
1146+ print_warn " DNS broken after disabling stub listener — writing fallback nameservers"
1147+ chattr -i /etc/resolv.conf 2> /dev/null || true
1148+ rm -f /etc/resolv.conf 2> /dev/null || true
1149+ cat > /etc/resolv.conf << 'DNSEOF '
1150+ nameserver 8.8.8.8
1151+ nameserver 1.1.1.1
1152+ DNSEOF
1153+ fi
1154+
11131155 return 0
11141156}
11151157
@@ -1620,7 +1662,7 @@ do_add_tunnel() {
16201662 echo " "
16211663
16221664 # Detect server IP
1623- SERVER_IP=$( curl -4 -s --max-time 10 https://api.ipify.org 2> /dev/null || true)
1665+ SERVER_IP=$( curl -4 -s --max-time 5 https://api.ipify.org 2> /dev/null || curl -4 -s --max-time 5 https://ifconfig.me 2> /dev/null || curl -4 -s --max-time 5 https://icanhazip.com 2> /dev/null || true)
16241666 if [[ -n " $SERVER_IP " ]]; then
16251667 print_ok " Server IP: ${SERVER_IP} "
16261668 fi
@@ -1950,9 +1992,20 @@ do_uninstall() {
19501992 systemctl unmask systemd-resolved.socket systemd-resolved.service 2> /dev/null || true
19511993 systemctl enable systemd-resolved.service 2> /dev/null || true
19521994 systemctl restart systemd-resolved.service 2> /dev/null || true
1995+ sleep 1
19531996 if [[ -e /run/systemd/resolve/stub-resolv.conf ]]; then
19541997 ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf || true
19551998 fi
1999+ # Ensure DNS works after uninstall — if resolv.conf is broken, write fallback
2000+ if ! getent hosts google.com > /dev/null 2>&1 && \
2001+ ! curl -sf --max-time 3 https://api.ipify.org > /dev/null 2>&1 ; then
2002+ print_warn " DNS not working after restore — writing fallback nameservers"
2003+ chattr -i /etc/resolv.conf 2> /dev/null || true
2004+ cat > /etc/resolv.conf << 'DNSEOF '
2005+ nameserver 8.8.8.8
2006+ nameserver 1.1.1.1
2007+ DNSEOF
2008+ fi
19562009 print_ok " Restored systemd-resolved defaults (best effort)"
19572010
19582011 echo " "
@@ -2019,11 +2072,23 @@ do_manage_users() {
20192072 # Run initial configure
20202073 print_info " Applying SSH security configuration..."
20212074 mkdir -p /run/sshd 2> /dev/null || true
2075+ # Back up sshd_config before modification
2076+ if [[ -f /etc/ssh/sshd_config ]]; then
2077+ cp -f /etc/ssh/sshd_config /etc/ssh/sshd_config.dnstm-backup 2> /dev/null || true
2078+ fi
20222079 if timeout 30 sshtun-user configure < /dev/null 2>&1 ; then
20232080 print_ok " SSH configuration applied"
20242081 else
20252082 print_warn " SSH configuration may not have applied fully — user management may have issues"
20262083 fi
2084+ # Validate sshd_config — rollback if broken
2085+ if command -v sshd & > /dev/null && ! sshd -t 2> /dev/null; then
2086+ print_warn " sshd_config validation failed — rolling back"
2087+ if [[ -f /etc/ssh/sshd_config.dnstm-backup ]]; then
2088+ cp -f /etc/ssh/sshd_config.dnstm-backup /etc/ssh/sshd_config
2089+ print_ok " Restored sshd_config from backup"
2090+ fi
2091+ fi
20272092 echo " "
20282093 fi
20292094
@@ -3142,7 +3207,7 @@ do_add_xray() {
31423207 fi
31433208
31443209 # Detect server IP
3145- SERVER_IP=$( curl -4 -s --max-time 10 https://api.ipify.org 2> /dev/null || true)
3210+ SERVER_IP=$( curl -4 -s --max-time 5 https://api.ipify.org 2> /dev/null || curl -4 -s --max-time 5 https://ifconfig.me 2> /dev/null || curl -4 -s --max-time 5 https://icanhazip.com 2> /dev/null || true)
31463211 if [[ -n " $SERVER_IP " ]]; then
31473212 print_ok " Server IP: ${SERVER_IP} "
31483213 else
@@ -3583,6 +3648,11 @@ step_preflight() {
35833648 exit 1
35843649 fi
35853650
3651+ # Back up resolv.conf so we can always recover DNS
3652+ if [[ -f /etc/resolv.conf ]] && [[ ! -f /etc/resolv.conf.dnstm-backup ]]; then
3653+ cp -f /etc/resolv.conf /etc/resolv.conf.dnstm-backup 2> /dev/null || true
3654+ fi
3655+
35863656 # Check OS (read in subshell to avoid overwriting script's VERSION variable)
35873657 if [[ -f /etc/os-release ]]; then
35883658 local os_id os_name
@@ -3617,8 +3687,21 @@ step_preflight() {
36173687 fi
36183688 fi
36193689
3690+ # Ensure DNS resolution works (may be broken after previous uninstall)
3691+ if ! curl -4 -s --max-time 3 https://api.ipify.org > /dev/null 2>&1 ; then
3692+ if grep -q ' 127\.0\.0\.53' /etc/resolv.conf 2> /dev/null; then
3693+ # systemd-resolved stub is dead — replace with public DNS
3694+ print_warn " DNS broken (stub listener dead) — fixing resolv.conf"
3695+ chattr -i /etc/resolv.conf 2> /dev/null || true
3696+ cat > /etc/resolv.conf << 'DNSEOF '
3697+ nameserver 8.8.8.8
3698+ nameserver 1.1.1.1
3699+ DNSEOF
3700+ fi
3701+ fi
3702+
36203703 # Detect server IP
3621- SERVER_IP=$( curl -4 -s --max-time 10 https://api.ipify.org 2> /dev/null || true)
3704+ SERVER_IP=$( curl -4 -s --max-time 5 https://api.ipify.org 2> /dev/null || curl -4 -s --max-time 5 https://ifconfig.me 2> /dev/null || curl -4 -s --max-time 5 https://icanhazip.com 2> /dev/null || true)
36223705 if [[ -n " $SERVER_IP " ]]; then
36233706 print_ok " Server IP: ${SERVER_IP} "
36243707 else
@@ -3670,6 +3753,17 @@ cloudflare_create_dns_records() {
36703753 local server_ip=" $3 "
36713754 local cf_api=" https://api.cloudflare.com/client/v4"
36723755
3756+ # Ensure jq is installed (needed for JSON parsing)
3757+ if ! command -v jq & > /dev/null; then
3758+ print_info " Installing jq (needed for Cloudflare API)..."
3759+ apt-get update -qq > /dev/null 2>&1 || true
3760+ apt-get install -y -qq jq > /dev/null 2>&1 || true
3761+ if ! command -v jq & > /dev/null; then
3762+ print_fail " Could not install jq. Install manually: apt-get install jq"
3763+ return 1
3764+ fi
3765+ fi
3766+
36733767 # Step 1: Get Zone ID
36743768 print_info " Looking up Cloudflare Zone ID for ${domain} ..."
36753769 local zone_resp
@@ -3804,6 +3898,17 @@ step_dns_records() {
38043898 exit 1
38053899 fi
38063900
3901+ # Ensure jq is installed (needed for API JSON parsing)
3902+ if ! command -v jq & > /dev/null; then
3903+ print_info " Installing jq (needed for Cloudflare API)..."
3904+ apt-get update -qq > /dev/null 2>&1 || true
3905+ apt-get install -y -qq jq > /dev/null 2>&1 || true
3906+ if ! command -v jq & > /dev/null; then
3907+ print_fail " Could not install jq. Install manually: apt-get install jq"
3908+ exit 1
3909+ fi
3910+ fi
3911+
38073912 # Validate token
38083913 print_info " Validating API token..."
38093914 local verify_resp
@@ -3902,9 +4007,10 @@ step_free_port53() {
39024007
39034008 # Fallback if stub is still present.
39044009 if echo " $port53_output " | grep -q " systemd-resolve\|127\.0\.0\.53" ; then
3905- print_warn " systemd-resolved still occupies port 53; stopping service as fallback"
4010+ print_warn " systemd-resolved still occupies port 53; stopping + disabling as fallback"
39064011 systemctl stop systemd-resolved.socket 2> /dev/null || true
39074012 systemctl stop systemd-resolved.service 2> /dev/null || true
4013+ systemctl disable systemd-resolved.service 2> /dev/null || true
39084014 ensure_resolv_conf_fallback
39094015 sleep 1
39104016 fi
@@ -4069,9 +4175,10 @@ step_verify_port53() {
40694175 sleep 2
40704176 port53_output=$( ss -ulnp 2> /dev/null | grep -E ' :53\b' || true)
40714177 if echo " $port53_output " | grep -q " systemd-resolve\|127\.0\.0\.53" ; then
4072- print_warn " systemd-resolved still occupies :53; stopping service as fallback"
4178+ print_warn " systemd-resolved still occupies :53; stopping + disabling as fallback"
40734179 systemctl stop systemd-resolved.socket 2> /dev/null || true
40744180 systemctl stop systemd-resolved.service 2> /dev/null || true
4181+ systemctl disable systemd-resolved.service 2> /dev/null || true
40754182 ensure_resolv_conf_fallback
40764183 fi
40774184 sleep 2
@@ -4599,6 +4706,12 @@ step_ssh_user() {
45994706 # Configure SSH (only needed once)
46004707 print_info " Applying SSH security configuration..."
46014708 mkdir -p /run/sshd 2> /dev/null || true
4709+
4710+ # Back up sshd_config before any modification
4711+ if [[ -f /etc/ssh/sshd_config ]]; then
4712+ cp -f /etc/ssh/sshd_config /etc/ssh/sshd_config.dnstm-backup 2> /dev/null || true
4713+ fi
4714+
46024715 local configure_output
46034716 configure_output=$( timeout 30 sshtun-user configure < /dev/null 2>&1 ) || true
46044717 if echo " $configure_output " | grep -qi " already" ; then
@@ -4610,6 +4723,15 @@ step_ssh_user() {
46104723 print_ok " SSH configuration applied"
46114724 fi
46124725
4726+ # Validate sshd_config — rollback if broken
4727+ if command -v sshd & > /dev/null && ! sshd -t 2> /dev/null; then
4728+ print_warn " sshd_config validation failed — rolling back"
4729+ if [[ -f /etc/ssh/sshd_config.dnstm-backup ]]; then
4730+ cp -f /etc/ssh/sshd_config.dnstm-backup /etc/ssh/sshd_config
4731+ print_ok " Restored sshd_config from backup"
4732+ fi
4733+ fi
4734+
46134735 echo " "
46144736
46154737 # Get username
@@ -5051,7 +5173,7 @@ do_add_domain() {
50515173 fi
50525174
50535175 # Detect server IP
5054- SERVER_IP=$( curl -4 -s --max-time 10 https://api.ipify.org 2> /dev/null || true)
5176+ SERVER_IP=$( curl -4 -s --max-time 5 https://api.ipify.org 2> /dev/null || curl -4 -s --max-time 5 https://ifconfig.me 2> /dev/null || curl -4 -s --max-time 5 https://icanhazip.com 2> /dev/null || true)
50555177 if [[ -z " $SERVER_IP " ]]; then
50565178 SERVER_IP=$( prompt_input " Enter your server's public IP" )
50575179 if [[ -z " $SERVER_IP " ]]; then
0 commit comments