Skip to content

Commit 20dc972

Browse files
authored
Create unlock.sh
1 parent 5a1d28f commit 20dc972

File tree

1 file changed

+308
-0
lines changed

1 file changed

+308
-0
lines changed

script/unlock.sh

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
#!/usr/bin/env bash
2+
# set-dns.sh — Lightweight, polite, bilingual DNS setter ✨
3+
# Works with Debian/Ubuntu, RHEL/CentOS/Rocky, Arch, NixOS (with systemd-resolved), etc.
4+
5+
set -euo pipefail
6+
7+
# ===== Defaults (env or CLI can override) =====
8+
DNS_SERVERS_CSV="${DNS_SERVERS_CSV:-151.240.12.10}"
9+
TEST_DOMAIN="${TEST_DOMAIN:-unlock.isif.net}"
10+
EXPECT_IP="${EXPECT_IP:-1.1.1.1}"
11+
LANG_CHOICE="${LANG_CHOICE:-auto}" # auto|zh|en
12+
FORCE_METHOD="${FORCE_METHOD:-auto}" # auto|resolved|nm|resolv
13+
DRY_RUN="${DRY_RUN:-0}" # 1 to simulate
14+
QUIET="${QUIET:-0}" # 1 for minimal output
15+
ONLY_IPV4="${ONLY_IPV4:-0}" # 1 to keep only IPv4 DNSes
16+
ONLY_IPV6="${ONLY_IPV6:-0}" # 1 to keep only IPv6 DNSes
17+
18+
# ===== i18n =====
19+
pick_lang() {
20+
case "$LANG_CHOICE" in
21+
zh|ZH|cn|CN|zh_CN|zh_TW) echo zh ;;
22+
en|EN|us|US|en_US|en_GB) echo en ;;
23+
auto|Auto|AUTO|*)
24+
case "${LANG:-}" in zh*|ZH*) echo zh ;; *) echo en ;; esac ;;
25+
esac
26+
}
27+
LANG_USE="$(pick_lang)"
28+
29+
say_zh() {
30+
case "$1" in
31+
need_root) echo "请使用 root 运行(sudo $0)。";;
32+
start) echo "开始设置 DNS(优雅模式)。";;
33+
parsed_args) echo "参数就绪:准备出发 🚀";;
34+
using_resolved) echo "检测到 systemd-resolved,采用 drop-in 方式设置。";;
35+
using_nm) echo "检测到 NetworkManager,按活动连接设置手动 DNS。";;
36+
using_resolv) echo "未检测到前两者,直接写入 /etc/resolv.conf(已备份)。";;
37+
backed_up) echo "已备份 /etc/resolv.conf ->";;
38+
flush_cache) echo "刷新 DNS 缓存中…";;
39+
testing) echo "正在解析域名:";;
40+
hits) echo "解析结果如下:";;
41+
success) echo "设置成功 ✅:命中期望 IP";;
42+
fail) echo "可能尚未生效 ❗:未命中期望 IP,请稍后重试或检查网络管理器。";;
43+
dryrun) echo "演练模式(不会真的修改系统),下面只是预览动作:";;
44+
done) echo "全部完成,祝你网络清清爽爽 🌊";;
45+
no_active_nm) echo "未发现活动的 NetworkManager 连接,跳过 nm 配置。";;
46+
restored) echo "已尝试还原最近的 resolv.conf 备份。";;
47+
installing) echo "正在安装为 /usr/local/bin/set-dns …";;
48+
installed) echo "安装完成:现在可以直接运行 set-dns";;
49+
usage)
50+
cat <<'EOF'
51+
用法:
52+
sudo ./set-dns.sh [选项]
53+
54+
选项:
55+
--dns <CSV> 要设置的 DNS,逗号分隔(默认: 1.1.1.1,2606:4700:4700::1111)
56+
--domain <域名> 测试解析的域名(默认: unlock.isif.net)
57+
--expect <IP> 期望命中的 IP(默认: 1.1.1.1)
58+
--lang <auto|zh|en> 语言(默认: auto)
59+
--method <auto|resolved|nm|resolv> 强制方式(默认: auto)
60+
--only-ipv4 仅使用 IPv4 DNS
61+
--only-ipv6 仅使用 IPv6 DNS
62+
--dry-run 演练,不修改系统
63+
--quiet 安静输出
64+
--restore 还原最近的 /etc/resolv.conf 备份
65+
--install 安装为 /usr/local/bin/set-dns
66+
-h,--help 显示帮助
67+
EOF
68+
;;
69+
esac
70+
}
71+
72+
say_en() {
73+
case "$1" in
74+
need_root) echo "Please run as root (sudo $0).";;
75+
start) echo "Starting DNS setup (polite mode).";;
76+
parsed_args) echo "Args parsed: ready to roll 🚀";;
77+
using_resolved) echo "systemd-resolved detected, applying drop-in override.";;
78+
using_nm) echo "NetworkManager detected, setting per active connection.";;
79+
using_resolv) echo "Fallback: writing /etc/resolv.conf directly (backed up).";;
80+
backed_up) echo "Backed up /etc/resolv.conf ->";;
81+
flush_cache) echo "Flushing DNS caches…";;
82+
testing) echo "Querying domain:";;
83+
hits) echo "Answers:";;
84+
success) echo "Success ✅: expected IP matched";;
85+
fail) echo "May not be applied yet ❗: expected IP not found. Retry later / check network manager.";;
86+
dryrun) echo "Dry-run mode (no changes). Previewing actions only:";;
87+
done) echo "All set. May your packets flow smoothly 🌊";;
88+
no_active_nm) echo "No active NetworkManager connections; skipping nm config.";;
89+
restored) echo "Attempted to restore the latest resolv.conf backup.";;
90+
installing) echo "Installing to /usr/local/bin/set-dns …";;
91+
installed) echo "Installed. Now you can run: set-dns";;
92+
usage)
93+
cat <<'EOF'
94+
Usage:
95+
sudo ./set-dns.sh [options]
96+
97+
Options:
98+
--dns <CSV> DNS servers (comma-separated), default: 1.1.1.1,2606:4700:4700::1111
99+
--domain <name> Domain to test (default: unlock.isif.net)
100+
--expect <IP> Expected IP (default: 1.1.1.1)
101+
--lang <auto|zh|en> Language (default: auto)
102+
--method <auto|resolved|nm|resolv> Force method (default: auto)
103+
--only-ipv4 Use IPv4 DNS only
104+
--only-ipv6 Use IPv6 DNS only
105+
--dry-run Simulate only, no system changes
106+
--quiet Minimal output
107+
--restore Restore the latest /etc/resolv.conf backup
108+
--install Install to /usr/local/bin/set-dns
109+
-h,--help Show help
110+
EOF
111+
;;
112+
esac
113+
}
114+
115+
_() { if [ "${LANG_USE}" = zh ]; then say_zh "$@"; else say_en "$@"; fi; }
116+
log() { [ "$QUIET" -eq 1 ] || printf "[%s] %s\n" "$(date +'%F %T')" "$*" >&2; }
117+
have() { command -v "$1" >/dev/null 2>&1; }
118+
is_active() { systemctl is-active --quiet "$1" 2>/dev/null; }
119+
120+
# ===== arg parsing =====
121+
RESTORE=0; INSTALL=0
122+
while [ $# -gt 0 ]; do
123+
case "$1" in
124+
--dns) DNS_SERVERS_CSV="$2"; shift 2;;
125+
--domain) TEST_DOMAIN="$2"; shift 2;;
126+
--expect) EXPECT_IP="$2"; shift 2;;
127+
--lang) LANG_CHOICE="$2"; LANG_USE="$(pick_lang)"; shift 2;;
128+
--method) FORCE_METHOD="$2"; shift 2;;
129+
--dry-run) DRY_RUN=1; shift;;
130+
--quiet) QUIET=1; shift;;
131+
--only-ipv4) ONLY_IPV4=1; shift;;
132+
--only-ipv6) ONLY_IPV6=1; shift;;
133+
--restore) RESTORE=1; shift;;
134+
--install) INSTALL=1; shift;;
135+
-h|--help) _ usage; exit 0;;
136+
*) echo "Unknown arg: $1" >&2; _ usage; exit 1;;
137+
esac
138+
done
139+
[ "$ONLY_IPV4" -eq 1 ] && ONLY_IPV6=0
140+
[ "$ONLY_IPV6" -eq 1 ] && ONLY_IPV4=0
141+
142+
# ===== helpers =====
143+
IFS=',' read -r -a DNS_LIST <<<"$DNS_SERVERS_CSV"
144+
145+
filter_dns_family() {
146+
local out=()
147+
for ns in "${DNS_LIST[@]}"; do
148+
if [ "$ONLY_IPV4" -eq 1 ] && [[ "$ns" != *:* ]]; then out+=("$ns"); fi
149+
if [ "$ONLY_IPV6" -eq 1 ] && [[ "$ns" == *:* ]]; then out+=("$ns"); fi
150+
if [ "$ONLY_IPV4" -eq 0 ] && [ "$ONLY_IPV6" -eq 0 ]; then out+=("$ns"); fi
151+
done
152+
DNS_LIST=("${out[@]}")
153+
}
154+
155+
join_by() { local IFS="$1"; shift; echo "$*"; }
156+
157+
backup_resolv_conf() {
158+
local ts="/etc/resolv.conf.bak.$(date +%Y%m%d%H%M%S)"
159+
if [ -e /etc/resolv.conf ]; then
160+
[ "$DRY_RUN" -eq 1 ] || cp -a /etc/resolv.conf "$ts" || true
161+
log "$(_ backed_up) $ts"
162+
fi
163+
}
164+
165+
restore_latest_resolv() {
166+
local latest
167+
latest="$(ls -1t /etc/resolv.conf.bak.* 2>/dev/null | head -n1 || true)"
168+
if [ -n "$latest" ]; then
169+
[ "$DRY_RUN" -eq 1 ] || cp -a "$latest" /etc/resolv.conf
170+
log "$(_ restored)"
171+
fi
172+
}
173+
174+
write_resolv_conf() {
175+
# Handle immutable / symlink
176+
if have chattr; then [ "$DRY_RUN" -eq 1 ] || chattr -i /etc/resolv.conf 2>/dev/null || true; fi
177+
[ -L /etc/resolv.conf ] && [ "$DRY_RUN" -eq 0 ] && rm -f /etc/resolv.conf
178+
[ "$DRY_RUN" -eq 0 ] || return 0
179+
180+
{
181+
echo "# Generated by set-dns.sh @ $(date)"
182+
for ns in "${DNS_LIST[@]}"; do echo "nameserver $ns"; done
183+
echo "options timeout:2 attempts:2"
184+
} >/etc/resolv.conf
185+
log "$(_ using_resolv)"
186+
}
187+
188+
set_dns_systemd_resolved() {
189+
local dir="/etc/systemd/resolved.conf.d"
190+
local dns_space
191+
dns_space="$(join_by ' ' "${DNS_LIST[@]}")"
192+
[ "$DRY_RUN" -eq 1 ] || mkdir -p "$dir"
193+
if [ "$DRY_RUN" -eq 0 ]; then
194+
{
195+
echo "[Resolve]"
196+
echo "DNS=$dns_space"
197+
echo "FallbackDNS="
198+
} >"$dir/99-override.conf"
199+
systemctl restart systemd-resolved
200+
fi
201+
log "$(_ using_resolved)"
202+
}
203+
204+
set_dns_nmcli() {
205+
local dns4=() dns6=()
206+
for ns in "${DNS_LIST[@]}"; do
207+
case "$ns" in *:*) dns6+=("$ns");; *) dns4+=("$ns");; esac
208+
done
209+
local dns4_csv="$(join_by , "${dns4[@]:-}")"
210+
local dns6_csv="$(join_by , "${dns6[@]:-}")"
211+
212+
local active
213+
active="$(nmcli -t -f NAME,DEVICE connection show --active 2>/dev/null | awk -F: '$2!=""{print $1}')"
214+
if [ -z "$active" ]; then log "$(_ no_active_nm)"; return 0; fi
215+
216+
while IFS= read -r name; do
217+
[ -z "$name" ] && continue
218+
if [ "$DRY_RUN" -eq 0 ]; then
219+
nmcli connection modify "$name" ipv4.ignore-auto-dns yes || true
220+
nmcli connection modify "$name" ipv6.ignore-auto-dns yes || true
221+
[ -n "$dns4_csv" ] && nmcli connection modify "$name" ipv4.dns "$dns4_csv" || true
222+
[ -n "$dns6_csv" ] && nmcli connection modify "$name" ipv6.dns "$dns6_csv" || true
223+
nmcli connection up "$name" >/dev/null || true
224+
fi
225+
log "nm: $name -> dns4=[${dns4_csv:-∅}] dns6=[${dns6_csv:-∅}]"
226+
done <<<"$active"
227+
log "$(_ using_nm)"
228+
}
229+
230+
flush_dns_cache() {
231+
log "$(_ flush_cache)"
232+
if have resolvectl; then
233+
[ "$DRY_RUN" -eq 1 ] || resolvectl flush-caches || true
234+
elif have systemd-resolve; then
235+
[ "$DRY_RUN" -eq 1 ] || systemd-resolve --flush-caches || true
236+
fi
237+
}
238+
239+
test_resolution() {
240+
log "$(_ testing) $TEST_DOMAIN"
241+
local hits
242+
hits="$(getent ahosts "$TEST_DOMAIN" | awk '{print $1}' | sort -u || true)"
243+
[ "$QUIET" -eq 1 ] || { log "$(_ hits)"; printf "%s\n" "$hits" | sed 's/^/ - /' >&2; }
244+
if printf "%s\n" "$hits" | grep -q -F -- "$EXPECT_IP"; then
245+
echo "$(_ success): $TEST_DOMAIN -> $EXPECT_IP"
246+
return 0
247+
else
248+
echo "$(_ fail) ($TEST_DOMAIN -> $EXPECT_IP)"
249+
return 1
250+
fi
251+
}
252+
253+
choose_method() {
254+
case "$FORCE_METHOD" in
255+
resolved|nm|resolv) echo "$FORCE_METHOD"; return;;
256+
auto|*)
257+
if have systemctl && is_active systemd-resolved && have resolvectl; then
258+
echo resolved
259+
elif have nmcli && have systemctl && is_active NetworkManager; then
260+
echo nm
261+
else
262+
echo resolv
263+
fi
264+
;;
265+
esac
266+
}
267+
268+
ensure_root() {
269+
if [ "$(id -u)" -ne 0 ]; then
270+
echo "$(_ need_root)" >&2
271+
exit 1
272+
fi
273+
}
274+
275+
install_self() {
276+
log "$(_ installing)"
277+
[ "$DRY_RUN" -eq 1 ] && return 0
278+
install -m 0755 "$0" /usr/local/bin/set-dns
279+
log "$(_ installed)"
280+
}
281+
282+
main() {
283+
ensure_root
284+
filter_dns_family
285+
286+
[ "$RESTORE" -eq 1 ] && { restore_latest_resolv; exit 0; }
287+
[ "$INSTALL" -eq 1 ] && { install_self; exit 0; }
288+
289+
[ "$QUIET" -eq 1 ] || log "$(_ start)"
290+
[ "$DRY_RUN" -eq 1 ] && log "$(_ dryrun)"
291+
log "$(_ parsed_args) DNS=[${DNS_LIST[*]}] domain=$TEST_DOMAIN expect=$EXPECT_IP lang=$LANG_USE method=$FORCE_METHOD"
292+
293+
local method
294+
method="$(choose_method)"
295+
296+
case "$method" in
297+
resolved) backup_resolv_conf; set_dns_systemd_resolved ;;
298+
nm) backup_resolv_conf; set_dns_nmcli ;;
299+
resolv) backup_resolv_conf; write_resolv_conf ;;
300+
esac
301+
302+
flush_dns_cache
303+
sleep 0.5
304+
test_resolution
305+
[ "$QUIET" -eq 1 ] || log "$(_ done)"
306+
}
307+
308+
main

0 commit comments

Comments
 (0)