cmc/cleberg.net
My personal web garden & blog.
clone: git clone https://gitbay.org/cmc/cleberg.net.git
main: content/blog/2023-12-03-unifi-nextdns.org · raw
1#+date: [2023-12-03 Sun 00:00:00]
2#+title: NextDNS on Unifi Dream Machine
3#+description: Installing and configuring NextDNS on a Unifi Dream Machine.
4#+slug: unifi-nextdns
5#+filetags: :linux:self-hosting:
6
7* Overview
8
9I recently installed NextDNS on my Unifi Dream Machine router using the
10[[https://github.com/nextdns/nextdns/wiki/UnifiOS][UnifiOS]] wiki page
11on NextDNS's GitHub repository.
12
13As a result of this, I wanted to write down the process in case the wiki
14or installer ever gets lost.
15
16* Wiki
17
18The following is copied from the wiki page linked above, with one
19difference in the =ssh= command.
20
21Install instructions for Unifi Dream Machine (UDM) standard and pro
22routers.
23
24** Install
25
26Enable SSH:
27
28- Go to your unifi admin interface and select your device (not the
29 controller settings, but the Dream Machine settings)
30- Click on "Settings" at the bottom of the page
31- Go to the "Advanced" section on the left pan
32- Enable SSH
33- Set a SSH password
34
35Connect to your router using =ssh root@xxx.xxx.xxx.xxx= with the
36password you configured.
37
38Run the following command and follow the instructions:
39
40#+begin_src sh
41sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"'
42#+end_src
43
44Note: Queries from the UDM itself won't be routed to NextDNS nor
45encrypted due to current system limitation. All traffic from other
46devices on then network will.
47
48** Upgrade
49
50To upgrade to the last version, simply re-run the installer above. If a
51new version is available, the upgrade action will added to the list of
52possible actions.
53
54** Uninstall
55
56To uninstall, re-run the installer above and select "Remove" in the
57menu.
58
59** Troubleshooting
60
61If the installation fail, please the installer in debug mode and contact
62us at team@nextdns.io with the transcript of the installation:
63
64#+begin_src sh
65sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"'
66#+end_src
67
68*** Content Filtering Conflict
69
70NextDNS CLI and the UDM Content Filtering or the Ad Blocking features
71are incompatible. If you want to use NextDNS CLI, please make sure they
72are disabled.
73
74To disable Content Filtering, go to Settings > Network, then for each
75network, set the Content Filtering feature to None
76
77To disable Ad Blocking, go to Settings > Application Firewall. In the
78General tab, uncheck the Ad Blocking checkbox.
79
80*** APT Error
81
82If you get an apt error as follow:
83
84#+begin_src sh
85E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80]
86#+end_src
87
88You may try to following:
89
90#+begin_src sh
91sed -i -e 's/deb.debian.org/archive.debian.org/g' \
92 -e 's|security.debian.org|archive.debian.org/|g' \
93 -e '/stretch-updates/d' /etc/apt/sources.list
94#+end_src
95
96** install.sh
97
98Here are the contents of the =install.sh= file used above, as of
992023-12-03:
100
101#+begin_src sh
102#!/bin/sh
103
104main() {
105 OS=$(detect_os)
106 GOARCH=$(detect_goarch)
107 GOOS=$(detect_goos)
108 NEXTDNS_BIN=$(bin_location)
109 INSTALL_RELEASE=$(get_release)
110
111 export NEXTDNS_INSTALLER=1
112
113 log_info "OS: $OS"
114 log_info "GOARCH: $GOARCH"
115 log_info "GOOS: $GOOS"
116 log_info "NEXTDNS_BIN: $NEXTDNS_BIN"
117 log_info "INSTALL_RELEASE: $INSTALL_RELEASE"
118
119 if [ -z "$OS" ] || [ -z "$GOARCH" ] || [ -z "$GOOS" ] || [ -z "$NEXTDNS_BIN" ] || [ -z "$INSTALL_RELEASE" ]; then
120 log_error "Cannot detect running environment."
121 exit 1
122 fi
123
124 case "$RUN_COMMAND" in
125 install|upgrade|uninstall|configure) "$RUN_COMMAND"; exit ;;
126 esac
127
128 while true; do
129 CURRENT_RELEASE=$(get_current_release)
130 log_debug "Start install loop with CURRENT_RELEASE=$CURRENT_RELEASE"
131
132 if [ "$CURRENT_RELEASE" ]; then
133 if ! is_version_current; then
134 log_debug "NextDNS is out of date ($CURRENT_RELEASE != $INSTALL_RELEASE)"
135 menu \
136 u "Upgrade NextDNS from $CURRENT_RELEASE to $INSTALL_RELEASE" upgrade \
137 c "Configure NextDNS" configure \
138 r "Remove NextDNS" uninstall \
139 e "Exit" exit
140 else
141 log_debug "NextDNS is up to date ($CURRENT_RELEASE)"
142 menu \
143 c "Configure NextDNS" configure \
144 r "Remove NextDNS" uninstall \
145 e "Exit" exit
146 fi
147 else
148 log_debug "NextDNS is not installed"
149 menu \
150 i "Install NextDNS" install \
151 e "Exit" exit
152 fi
153 done
154}
155
156install() {
157 if [ "$(get_current_release)" ]; then
158 log_info "Already installed"
159 return
160 fi
161 if type=$(install_type); then
162 log_info "Installing NextDNS..."
163 log_debug "Using $type install type"
164 if "install_$type"; then
165 if [ ! -x "$NEXTDNS_BIN" ]; then
166 log_error "Installation failed: binary not installed in $NEXTDNS_BIN"
167 return 1
168 fi
169 configure
170 post_install
171 exit 0
172 fi
173 else
174 return $?
175 fi
176}
177
178upgrade() {
179 if [ "$(get_current_release)" = "$INSTALL_RELEASE" ]; then
180 log_info "Already on the latest version"
181 return
182 fi
183 if type=$(install_type); then
184 log_info "Upgrading NextDNS..."
185 log_debug "Using $type install type"
186 "upgrade_$type"
187 else
188 return $?
189 fi
190}
191
192uninstall() {
193 if type=$(install_type); then
194 log_info "Uninstalling NextDNS..."
195 log_debug "Using $type uninstall type"
196 "uninstall_$type"
197 else
198 return $?
199 fi
200}
201
202precheck() {
203 if [ -e "/data/unifi" ] && [ -f "/run/dnsfilter/dnsfilter" ]; then
204 log_warn "UDM Content Filtering and/or Ad Blocking feature is enabled."
205 log_warn "Please disable it to use NextDNS."
206 log_warn ""
207 log_warn " To disable Content Filtering, go to Settings > Network."
208 log_warn " For each network, set the Content Filtering feature to None."
209 log_warn ""
210 log_warn " To disable Ad Blocking, go to Settings > Application Firewall"
211 log_warn " In the General tab, uncheck the Ad Blocking checkbox."
212 log_warn ""
213 while [ -f "/run/dnsfilter/dnsfilter" ]; do
214 sleep 1
215 done
216 log_info "Content Filtering feature successfuly disabled."
217 fi
218}
219
220configure() {
221 log_debug "Start configure"
222 precheck
223 args=""
224 add_arg() {
225 for value in $2; do
226 log_debug "Add arg -$1=$value"
227 args="$args -$1=$value"
228 done
229 }
230 add_arg_bool_ask() {
231 arg=$1
232 msg=$2
233 default=$3
234 if [ -z "$default" ]; then
235 default=$(get_config_bool "$arg")
236 fi
237 # shellcheck disable=SC2046
238 add_arg "$arg" $(ask_bool "$msg" "$default")
239 }
240 # Use profile from now on
241 add_arg profile "$(get_profile_id)"
242
243 doc "Sending your devices name lets you filter analytics and logs by device."
244 add_arg_bool_ask report-client-info 'Report device name?' true
245
246 case $(guess_host_type) in
247 router)
248 add_arg setup-router true
249 ;;
250 unsure)
251 doc "Accept DNS request from other network hosts."
252 if [ "$(get_config_bool setup-router)" = "true" ]; then
253 router_default=true
254 fi
255 if [ "$(ask_bool 'Setup as a router?' $router_default)" = "true" ]; then
256 add_arg setup-router true
257 fi
258 ;;
259 esac
260
261 doc "Make NextDNS CLI cache responses. This improves latency and reduces the amount"
262 doc "of queries sent to NextDNS."
263 if [ "$(guess_host_type)" = "router" ]; then
264 doc "Note that enabling this feature will disable dnsmasq for DNS to avoid double"
265 doc "caching."
266 fi
267 if [ "$(get_config cache-size)" != "0" ]; then
268 cache_default=true
269 fi
270 if [ "$(ask_bool 'Enable caching?' $cache_default)" = "true" ]; then
271 add_arg cache-size "10MB"
272
273 doc "Instant refresh will force low TTL on responses sent to clients so they rely"
274 doc "on CLI DNS cache. This will allow changes on your NextDNS config to be applied"
275 doc "on your LAN hosts without having to wait for their cache to expire."
276 if [ "$(get_config max-ttl)" = "5s" ]; then
277 instant_refresh_default=true
278 fi
279 if [ "$(ask_bool 'Enable instant refresh?' $instant_refresh_default)" = "true" ]; then
280 add_arg max-ttl "5s"
281 fi
282 fi
283
284 if [ "$(guess_host_type)" != "router" ]; then
285 doc "Changes DNS settings of the host automatically when NextDNS is started."
286 doc "If you say no here, you will have to manually configure DNS to 127.0.0.1."
287 add_arg_bool_ask auto-activate 'Automatically setup local host DNS?' true
288 fi
289 # shellcheck disable=SC2086
290 asroot "$NEXTDNS_BIN" install $args
291}
292
293post_install() {
294 println
295 println "Congratulations! NextDNS is now installed."
296 println
297 println "To upgrade/uninstall, run this command again and select the appropriate option."
298 println
299 println "You can use the NextDNS command to control the daemon."
300 println "Here are a few important commands to know:"
301 println
302 println "# Start, stop, restart the daemon:"
303 println "nextdns start"
304 println "nextdns stop"
305 println "nextdns restart"
306 println
307 println "# Configure the local host to point to NextDNS or not:"
308 println "nextdns activate"
309 println "nextdns deactivate"
310 println
311 println "# Explore daemon logs:"
312 println "nextdns log"
313 println
314 println "# For more commands, use:"
315 println "nextdns help"
316 println
317}
318
319install_bin() {
320 bin_path=$NEXTDNS_BIN
321 if [ "$1" ]; then
322 bin_path=$1
323 fi
324 log_debug "Installing $INSTALL_RELEASE binary for $GOOS/$GOARCH to $bin_path"
325 case "$INSTALL_RELEASE" in
326 ,*/*)
327 # Snapshot
328 branch=${INSTALL_RELEASE%/*}
329 hash=${INSTALL_RELEASE#*/}
330 url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz"
331 ;;
332 ,*)
333 url="https://github.com/nextdns/nextdns/releases/download/v${INSTALL_RELEASE}/nextdns_${INSTALL_RELEASE}_${GOOS}_${GOARCH}.tar.gz"
334 ;;
335 esac
336 log_debug "Downloading $url"
337 asroot mkdir -p "$(dirname "$bin_path")" &&
338 curl -sL "$url" | asroot sh -c "tar Ozxf - nextdns > "$bin_path"" &&
339 asroot chmod 755 "$bin_path"
340}
341
342upgrade_bin() {
343 tmp=$NEXTDNS_BIN.tmp
344 if install_bin "$tmp"; then
345 asroot "$NEXTDNS_BIN" uninstall
346 asroot mv "$tmp" "$NEXTDNS_BIN"
347 asroot "$NEXTDNS_BIN" install
348 fi
349 log_debug "Removing spurious temporary install file"
350 asroot rm -rf "$tmp"
351}
352
353uninstall_bin() {
354 asroot "$NEXTDNS_BIN" uninstall
355 asroot rm -f "$NEXTDNS_BIN"
356}
357
358install_rpm() {
359 asroot curl -Ls https://repo.nextdns.io/nextdns.repo -o /etc/yum.repos.d/nextdns.repo &&
360 asroot yum install -y nextdns
361}
362
363upgrade_rpm() {
364 asroot yum update -y nextdns
365}
366
367uninstall_rpm() {
368 asroot yum remove -y nextdns
369}
370
371install_zypper() {
372 if asroot zypper repos | grep -q nextdns >/dev/null; then
373 echo "Repository nextdns already exists. Skipping adding repository..."
374 else
375 asroot zypper ar -f -r https://repo.nextdns.io/nextdns.repo nextdns
376 fi
377 asroot zypper refresh && asroot zypper in -y nextdns
378}
379
380upgrade_zypper() {
381 asroot zypper up nextdns
382}
383
384uninstall_zypper() {
385 asroot zypper remove -y nextdns
386 case $(ask_bool 'Do you want to remove the repository from the repositories list?' true) in
387 true)
388 asroot zypper removerepo nextdns
389 ;;
390 esac
391}
392
393install_deb() {
394 if [ -f /etc/default/ubnt-dpkg-cache ]; then
395 # On UnifiOS 2, make sure the package is persisted over upgrades
396 sed -e '/^DPKG_CACHE_UBNT_PKGS+=" nextdns"/{:a;n;ba;q}' \
397 -e '$aDPKG_CACHE_UBNT_PKGS+=" nextdns"' \
398 -i /etc/default/ubnt-dpkg-cache
399 fi
400
401 install_deb_keyring &&
402 asroot sh -c 'echo "deb [signed-by=/etc/apt/keyrings/nextdns.gpg] https://repo.nextdns.io/deb stable main" > /etc/apt/sources.list.d/nextdns.list' &&
403 (dpkg --compare-versions $(dpkg-query --showformat='${Version}' --show apt) ge 1.1 ||
404 asroot ln -s /etc/apt/keyrings/nextdns.gpg /etc/apt/trusted.gpg.d/.) &&
405 (test "$OS" = "debian" && asroot apt-get -y install apt-transport-https || true) &&
406 asroot apt-get update &&
407 asroot apt-get install -y nextdns
408}
409
410install_deb_keyring() {
411 # Fallback on curl, some debian based distrib don't have wget while debian
412 # doesn't have curl by default.
413 asroot mkdir -p /etc/apt/keyrings
414 ( asroot wget -qO /etc/apt/keyrings/nextdns.gpg https://repo.nextdns.io/nextdns.gpg ||
415 asroot curl -sfL https://repo.nextdns.io/nextdns.gpg -o /etc/apt/keyrings/nextdns.gpg ) &&
416 asroot chmod 0644 /etc/apt/keyrings/nextdns.gpg
417}
418
419upgrade_deb() {
420 install_deb_keyring &&
421 asroot apt-get update &&
422 asroot apt-get install -y nextdns
423}
424
425uninstall_deb() {
426 asroot apt-get remove -y nextdns
427}
428
429install_apk() {
430 repo=https://repo.nextdns.io/apk
431 asroot wget -O /etc/apk/keys/nextdns.pub https://repo.nextdns.io/nextdns.pub &&
432 (grep -v $repo /etc/apk/repositories; echo $repo) | asroot tee /etc/apk/repositories >/dev/null &&
433 asroot apk update &&
434 asroot apk add nextdns
435}
436
437upgrade_apk() {
438 asroot apk update && asroot apk upgrade nextdns
439}
440
441uninstall_apk() {
442 asroot apk del nextdns
443}
444
445install_arch() {
446 asroot pacman -Sy yay &&
447 yay -Sy nextdns
448}
449
450upgrade_arch() {
451 yay -Suy nextdns
452}
453
454uninstall_arch() {
455 asroot pacman -R nextdns
456}
457
458install_merlin_path() {
459 # Add next to Merlin's path
460 mkdir -p /tmp/opt/sbin
461 ln -sf "$NEXTDNS_BIN" /tmp/opt/sbin/nextdns
462}
463
464install_merlin() {
465 if install_bin; then
466 install_merlin_path
467 fi
468}
469
470uninstall_merlin() {
471 uninstall_bin
472 rm -f /tmp/opt/sbin/nextdns
473}
474
475upgrade_merlin() {
476 if upgrade_bin; then
477 install_merlin_path
478 fi
479}
480
481install_openwrt() {
482 opkg update &&
483 opkg install nextdns
484 rt=$?
485 if [ $rt -eq 0 ]; then
486 case $(ask_bool 'Install the GUI?' true) in
487 true)
488 opkg install luci-app-nextdns
489 rt=$?
490 ;;
491 esac
492 fi
493 return $rt
494}
495
496upgrade_openwrt() {
497 opkg update &&
498 opkg upgrade nextdns
499}
500
501uninstall_openwrt() {
502 opkg remove nextdns
503}
504
505install_ddwrt() {
506 if [ "$(nvram get enable_jffs2)" = "0" ]; then
507 log_error "JFFS support not enabled"
508 log_info "To enabled JFFS:"
509 log_info " 1. On the router web page click on Administration."
510 log_info " 2. Scroll down until you see JFFS2 Support section."
511 log_info " 3. Click Enable JFFS."
512 log_info " 4. Click Save."
513 log_info " 5. Wait couple seconds, then click Apply."
514 log_info " 6. Wait again. Go back to the Enable JFFS section, and enable Clean JFFS."
515 log_info " 7. Do not click Save. Click Apply instead."
516 log_info " 8. Wait till you get the web-GUI back, then disable Clean JFFS again."
517 log_info " 9. Click Save."
518 log_info "10. Relaunch this installer."
519 exit 1
520 fi
521 mkdir -p /jffs/nextdns &&
522 openssl_get https://curl.haxx.se/ca/cacert.pem | http_body > /jffs/nextdns/ca.pem &&
523 install_bin
524}
525
526upgrade_ddwrt() {
527 upgrade_bin
528}
529
530uninstall_ddwrt() {
531 uninstall_bin
532 rm -rf /jffs/nextdns
533}
534
535install_brew() {
536 silent_exec brew install nextdns/tap/nextdns
537}
538
539upgrade_brew() {
540 silent_exec brew upgrade nextdns/tap/nextdns
541 asroot "$NEXTDNS_BIN" install
542}
543
544uninstall_brew() {
545 silent_exec brew uninstall nextdns/tap/nextdns
546}
547
548install_freebsd() {
549 # TODO: port install
550 install_bin
551}
552
553upgrade_freebsd() {
554 # TODO: port upgrade
555 upgrade_bin
556}
557
558uninstall_freebsd() {
559 # TODO: port uninstall
560 uninstall_bin
561}
562
563install_pfsense() {
564 # TODO: port install + UI
565 install_bin
566}
567
568upgrade_pfsense() {
569 # TODO: port upgrade
570 upgrade_bin
571}
572
573uninstall_pfsense() {
574 # TODO: port uninstall
575 uninstall_bin
576}
577
578install_opnsense() {
579 # TODO: port install + UI
580 install_bin
581}
582
583upgrade_opnsense() {
584 # TODO: port upgrade
585 upgrade_bin
586}
587
588uninstall_opnsense() {
589 # TODO: port uninstall
590 uninstall_bin
591}
592
593ubios_install_source() {
594 echo "deb [signed-by=/etc/apt/keyrings/nextdns.gpg] https://repo.nextdns.io/deb stable main" > /data/nextdns.list
595 podman exec unifi-os mv /data/nextdns.list /etc/apt/sources.list.d/nextdns.list
596 rm -f /tmp/nextdns.list
597 podman exec unifi-os apt-get install -y gnupg1 curl
598 podman exec unifi-os mkdir -p /etc/apt/keyrings/
599 podman exec unifi-os curl -sfL https://repo.nextdns.io/nextdns.gpg -o /etc/apt/keyrings/nextdns.gpg
600 podman exec unifi-os apt-get update -o Dir::Etc::sourcelist="sources.list.d/nextdns.list" -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"
601}
602
603install_ubios() {
604 ubios_install_source
605 podman exec unifi-os apt-get install -y nextdns
606}
607
608upgrade_ubios() {
609 ubios_install_source
610 podman exec unifi-os apt-get install --only-upgrade -y nextdns
611}
612
613uninstall_ubios() {
614 podman exec unifi-os apt-get remove -y nextdns
615}
616
617install_ubios_snapshot() {
618 branch=${INSTALL_RELEASE%/*}
619 hash=${INSTALL_RELEASE#*/}
620 url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz"
621 podman exec unifi-os sh -c "curl -o- $url | tar Ozxf - nextdns > /usr/bin/nextdns; /usr/bin/nextdns install"
622}
623
624upgrade_ubios_snapshot() {
625 /data/nextdns uninstall
626 install_ubios_snapshot
627}
628
629install_type() {
630 if [ "$FORCE_INSTALL_TYPE" ]; then
631 echo "$FORCE_INSTALL_TYPE"; return 0
632 fi
633 case "$INSTALL_RELEASE" in
634 ,*/*)
635 case $OS in
636 ubios)
637 echo "ubios_snapshot"; return 0
638 ;;
639 ,*)
640 # Snapshot mode always use binary install
641 echo "bin"; return 0
642 ;;
643 esac
644 esac
645 case $OS in
646 centos|fedora|rhel)
647 echo "rpm"
648 ;;
649 opensuse-tumbleweed|opensuse-leap|opensuse)
650 echo "zypper"
651 ;;
652 debian|ubuntu|elementary|raspbian|linuxmint|pop|neon|sparky|vyos|Deepin)
653 echo "deb"
654 ;;
655 alpine)
656 echo "apk"
657 ;;
658 arch|manjaro|steamos)
659 #echo "arch" # TODO: fix AUR install
660 echo "bin"
661 ;;
662 openwrt)
663 # shellcheck disable=SC1091
664 . /etc/os-release
665 major=$(echo "$VERSION_ID" | cut -d. -f1)
666 case $major in
667 ,*[!0-9]*)
668 if [ "$VERSION_ID" = "19.07.0-rc1" ]; then
669 # No opkg support before 19.07.0-rc2
670 echo "bin"
671 else
672 # Likely 'snapshot' build in this case, but still > major version 19
673 echo "openwrt"
674 fi
675 ;;
676 ,*)
677 if [ "$major" -lt 19 ]; then
678 # No opkg support before 19.07.0-rc2
679 echo "bin"
680 else
681 echo "openwrt"
682 fi
683 ;;
684 esac
685 ;;
686 asuswrt-merlin)
687 echo "merlin"
688 ;;
689 edgeos|synology|clear-linux-os|solus|openbsd|netbsd|overthebox)
690 echo "bin"
691 ;;
692 ddwrt)
693 echo "ddwrt"
694 ;;
695 darwin)
696 if [ -x /usr/local/bin/brew ] || [ -x /opt/homebrew/bin/brew ]; then
697 echo "brew"
698 else
699 log_debug "Homebrew not installed, fallback on binary install"
700 echo "bin"
701 fi
702 ;;
703 freebsd)
704 echo "freebsd"
705 ;;
706 pfsense)
707 echo "pfsense"
708 ;;
709 opnsense)
710 echo "opnsense"
711 ;;
712 ubios)
713 echo "ubios"
714 ;;
715 gentoo)
716 echo "bin"
717 ;;
718 void)
719 # TODO: pkg for xbps
720 echo "bin"
721 ;;
722 ,*)
723 log_error "Unsupported installation for $(detect_os)"
724 return 1
725 ;;
726 esac
727}
728
729get_config() {
730 "$NEXTDNS_BIN" config | grep -E "^$1 " | cut -d' ' -f 2
731}
732
733get_config_bool() {
734 val=$(get_config "$1")
735 case $val in
736 true|false)
737 echo "$val"
738 ;;
739 esac
740 echo "$2"
741}
742
743get_profile_id() {
744 log_debug "Get profile ID"
745 if [ "$CONFIG_ID" ]; then
746 # backward compat
747 PROFILE_ID="$CONFIG_ID"
748 fi
749 while [ -z "$PROFILE_ID" ]; do
750 default=
751 prev_id=$(get_config profile)
752 if [ -z "$prev_id" ]; then
753 # backward compat
754 prev_id=$(get_config config)
755 fi
756 if [ "$prev_id" ]; then
757 log_debug "Previous profile ID: $prev_id"
758 default=" (default=$prev_id)"
759 fi
760 print "NextDNS Profile ID%s: " "$default"
761 read -r id
762 if [ -z "$id" ]; then
763 id=$prev_id
764 fi
765 if echo "$id" | grep -qE '^[0-9a-f]{6}$'; then
766 PROFILE_ID=$id
767 break
768 else
769 log_error "Invalid profile ID."
770 println
771 println "ID format is 6 alphanumerical lowercase characters (example: 123abc)."
772 println "Your ID can be found on the Setup tab of https://my.nextdns.io."
773 println
774 fi
775 done
776 echo "$PROFILE_ID"
777}
778
779log_debug() {
780 if [ "$DEBUG" = "1" ]; then
781 printf "\033[30;1mDEBUG: %s\033[0m\n" "$*" >&2
782 fi
783}
784
785log_info() {
786 printf "INFO: %s\n" "$*" >&2
787}
788
789log_warn() {
790 printf "\033[33mWARN: %s\033[0m\n" "$*" >&2
791}
792
793log_error() {
794 printf "\033[31mERROR: %s\033[0m\n" "$*" >&2
795}
796
797print() {
798 format=$1
799 if [ $# -gt 0 ]; then
800 shift
801 fi
802 # shellcheck disable=SC2059
803 printf "$format" "$@" >&2
804}
805
806println() {
807 format=$1
808 if [ $# -gt 0 ]; then
809 shift
810 fi
811 # shellcheck disable=SC2059
812 printf "$format\n" "$@" >&2
813}
814
815doc() {
816 # shellcheck disable=SC2059
817 printf "\033[30;1m%s\033[0m\n" "$*" >&2
818}
819
820menu() {
821 while true; do
822 n=0
823 default=
824 for item in "$@"; do
825 case $((n%3)) in
826 0)
827 key=$item
828 if [ -z "$default" ]; then
829 default=$key
830 fi
831 ;;
832 1)
833 echo "$key) $item"
834 ;;
835 esac
836 n=$((n+1))
837 done
838 print "Choice (default=%s): " "$default"
839 read -r choice
840 if [ -z "$choice" ]; then
841 choice=$default
842 fi
843 n=0
844 for item in "$@"; do
845 case $((n%3)) in
846 0)
847 key=$item
848 ;;
849 2)
850 if [ "$key" = "$choice" ]; then
851 if ! "$item"; then
852 log_error "$item: exit $?"
853 fi
854 break 2
855 fi
856 ;;
857 esac
858 n=$((n+1))
859 done
860 echo "Invalid choice"
861 done
862}
863
864ask_bool() {
865 msg=$1
866 default=$2
867 case $default in
868 true)
869 msg="$msg [Y|n]: "
870 ;;
871 false)
872 msg="$msg [y|N]: "
873 ;;
874 ,*)
875 msg="$msg (y/n): "
876 esac
877 while true; do
878 print "%s" "$msg"
879 read -r answer
880 if [ -z "$answer" ]; then
881 answer=$default
882 fi
883 case $answer in
884 y|Y|yes|YES|true)
885 echo "true"
886 return 0
887 ;;
888 n|N|no|NO|false)
889 echo "false"
890 return 0
891 ;;
892 ,*)
893 echo "Invalid input, use yes or no"
894 ;;
895 esac
896 done
897}
898
899detect_endiannes() {
900 if ! hexdump /dev/null 2>/dev/null; then
901 # Some firmwares do not contain hexdump, for those, try to detect endianness
902 # differently.
903 case $(cat /proc/cpuinfo) in
904 ,*BCM5300*)
905 # RT-AC66U does not support Merlin version over 380.70 which
906 # lacks hexdump command.
907 echo "le"
908 ;;
909 ,*)
910 log_error "Cannot determine endianness"
911 return 1
912 ;;
913 esac
914 return 0
915 fi
916 case $(hexdump -s 5 -n 1 -e '"%x"' /bin/sh | head -c1) in
917 1)
918 echo "le"
919 ;;
920 2)
921 echo ""
922 ;;
923 esac
924}
925
926detect_goarch() {
927 if [ "$FORCE_GOARCH" ]; then
928 echo "$FORCE_GOARCH"; return 0
929 fi
930 case $(uname -m) in
931 x86_64|amd64)
932 echo "amd64"
933 ;;
934 i386|i686)
935 echo "386"
936 ;;
937 arm)
938 # FreeBSD does not include arm version
939 case "$(sysctl -b hw.model 2>/dev/null)" in
940 ,*A9*)
941 echo "armv7"
942 ;;
943 ,*)
944 # Unknown version, fallback to the lowest
945 echo "armv5"
946 ;;
947 esac
948 ;;
949 armv5*)
950 echo "armv5"
951 ;;
952 armv6*|armv7*)
953 if grep -q vfp /proc/cpuinfo 2>/dev/null; then
954 echo "armv$(uname -m | sed -e 's/[[:alpha:]]//g')"
955 else
956 # Soft floating point
957 echo "armv5"
958 fi
959 ;;
960 aarch64)
961 case "$(uname -o 2>/dev/null)" in
962 ASUSWRT-Merlin*)
963 # XXX when using arm64 build on ASUS AC66U and ACG86U, we get Go error:
964 # "out of memory allocating heap arena metadata".
965 echo "armv7"
966 ;;
967 ,*)
968 echo "arm64"
969 ;;
970 esac
971 ;;
972 armv8*|arm64)
973 echo "arm64"
974 ;;
975 mips*)
976 # TODO: detect hardfloat
977 echo "$(uname -m)$(detect_endiannes)_softfloat"
978 ;;
979 ,*)
980 log_error "Unsupported GOARCH: $(uname -m)"
981 return 1
982 ;;
983 esac
984}
985
986detect_goos() {
987 if [ "$FORCE_GOOS" ]; then
988 echo "$FORCE_GOOS"; return 0
989 fi
990 case $(uname -s) in
991 Linux)
992 echo "linux"
993 ;;
994 Darwin)
995 echo "darwin"
996 ;;
997 FreeBSD)
998 echo "freebsd"
999 ;;
1000 NetBSD)
1001 echo "netbsd"
1002 ;;
1003 OpenBSD)
1004 echo "openbsd"
1005 ;;
1006 ,*)
1007 log_error "Unsupported GOOS: $(uname -s)"
1008 return 1
1009 esac
1010}
1011
1012detect_os() {
1013 if [ "$FORCE_OS" ]; then
1014 echo "$FORCE_OS"; return 0
1015 fi
1016 case $(uname -s) in
1017 Linux)
1018 case $(uname -o) in
1019 GNU/Linux|Linux)
1020 if grep -q -e '^EdgeRouter' -e '^UniFiSecurityGateway' /etc/version 2> /dev/null; then
1021 echo "edgeos"; return 0
1022 fi
1023 if uname -u 2>/dev/null | grep -q '^synology'; then
1024 echo "synology"; return 0
1025 fi
1026 # shellcheck disable=SC1091
1027 dist=$(. /etc/os-release; echo "$ID")
1028 case $dist in
1029 ubios)
1030 if [ -z "$(command -v podman)" ]; then
1031 log_error "This version of UnifiOS is not supported. Make sure you run version 1.7.0 or above."
1032 return 1
1033 fi
1034 echo "$dist"; return 0
1035 ;;
1036 debian|ubuntu|elementary|raspbian|centos|fedora|rhel|arch|manjaro|openwrt|clear-linux-os|linuxmint|opensuse-tumbleweed|opensuse-leap|opensuse|solus|pop|neon|overthebox|sparky|vyos|void|alpine|Deepin|gentoo|steamos)
1037 echo "$dist"; return 0
1038 ;;
1039 esac
1040 # shellcheck disable=SC1091
1041 for dist in $(. /etc/os-release; echo "$ID_LIKE"); do
1042 case $dist in
1043 debian|ubuntu|rhel|fedora|openwrt)
1044 log_debug "Using ID_LIKE"
1045 echo "$dist"; return 0
1046 ;;
1047 esac
1048 done
1049 ;;
1050 ASUSWRT-Merlin*)
1051 echo "asuswrt-merlin"; return 0
1052 ;;
1053 DD-WRT)
1054 echo "ddwrt"; return 0
1055 esac
1056 ;;
1057 Darwin)
1058 echo "darwin"; return 0
1059 ;;
1060 FreeBSD)
1061 if [ -f /etc/platform ]; then
1062 case $(cat /etc/platform) in
1063 pfSense)
1064 echo "pfsense"; return 0
1065 ;;
1066 esac
1067 fi
1068 if [ -x /usr/local/sbin/opnsense-version ]; then
1069 case $(/usr/local/sbin/opnsense-version -N) in
1070 OPNsense)
1071 echo "opnsense"; return 0
1072 ;;
1073 esac
1074 fi
1075 echo "freebsd"; return 0
1076 ;;
1077 NetBSD)
1078 echo "netbsd"; return 0
1079 ;;
1080 OpenBSD)
1081 echo "openbsd"; return 0
1082 ;;
1083 ,*)
1084 esac
1085 log_error "Unsupported OS: $(uname -o) $(grep ID "/etc/os-release" 2>/dev/null | xargs)"
1086 return 1
1087}
1088
1089guess_host_type() {
1090 if [ -d /data/unifi ]; then
1091 # Special case when installer is run from inside the ubios podman
1092 echo "router"; return 0
1093 fi
1094
1095 case $OS in
1096 pfsense|opnsense|openwrt|asuswrt-merlin|edgeos|ddwrt|synology|overthebox|ubios)
1097 echo "router"
1098 ;;
1099 darwin|steamos)
1100 echo "workstation"
1101 ;;
1102 ,*)
1103 echo "unsure"
1104 ;;
1105 esac
1106}
1107
1108asroot() {
1109 # Some platform (Merlin) do not have the "id" command and $USER report a non root username with uid 0.
1110 if [ "$(grep '^Uid:' /proc/$$/status 2>/dev/null|cut -f2)" = "0" ] || [ "$USER" = "root" ] || [ "$(id -u 2>/dev/null)" = "0" ]; then
1111 "$@"
1112 elif [ "$(command -v sudo 2>/dev/null)" ]; then
1113 sudo "$@"
1114 else
1115 echo "Root required"
1116 su -m root -c "$*"
1117 fi
1118}
1119
1120silent_exec() {
1121 if [ "$DEBUG" = 1 ]; then
1122 "$@"
1123 else
1124 if ! out=$("$@" 2>&1); then
1125 rt=$?
1126 println "\033[30;1m%s\033[0m" "$out"
1127 return $rt
1128 fi
1129 fi
1130}
1131
1132bin_location() {
1133 case $OS in
1134 centos|fedora|rhel|debian|ubuntu|elementary|raspbian|arch|manjaro|clear-linux-os|linuxmint|opensuse-tumbleweed|opensuse-leap|opensuse|solus|pop|neon|sparky|vyos|void|alpine|Deepin|gentoo)
1135 echo "/usr/bin/nextdns"
1136 ;;
1137 openwrt|overthebox)
1138 echo "/usr/sbin/nextdns"
1139 ;;
1140 synology)
1141 echo "/usr/local/bin/nextdns"
1142 ;;
1143 darwin)
1144 echo "$(brew --prefix 2>/dev/null || echo /usr/local)/bin/nextdns"
1145 ;;
1146 asuswrt-merlin|ddwrt)
1147 echo "/jffs/nextdns/nextdns"
1148 ;;
1149 freebsd|pfsense|opnsense|netbsd|openbsd)
1150 echo "/usr/local/sbin/nextdns"
1151 ;;
1152 edgeos)
1153 echo "/config/nextdns/nextdns"
1154 ;;
1155 ubios)
1156 echo "/data/nextdns"
1157 ;;
1158 steamos)
1159 echo "$HOME/.local/bin/nextdns"
1160 ;;
1161 ,*)
1162 log_error "Unknown bin location for $OS"
1163 ;;
1164 esac
1165}
1166
1167is_version_current() {
1168 case "$INSTALL_RELEASE" in
1169 ,*/*)
1170 # Snapshot
1171 hash=${INSTALL_RELEASE#*/}
1172 test "0.0.0-$hash" = "$CURRENT_RELEASE"
1173 ;;
1174 ,*)
1175 test "$INSTALL_RELEASE" = "$CURRENT_RELEASE"
1176 ;;
1177 esac
1178}
1179
1180get_current_release() {
1181 if [ -x "$NEXTDNS_BIN" ]; then
1182 $NEXTDNS_BIN version|cut -d' ' -f 3
1183 fi
1184}
1185
1186get_release() {
1187 if [ "$NEXTDNS_VERSION" ]; then
1188 echo "$NEXTDNS_VERSION"
1189 else
1190 for cmd in curl wget openssl true; do
1191 # command is the "right" way but may be compiled out of busybox shell
1192 ! command -v $cmd > /dev/null 2>&1 || break
1193 ! which $cmd > /dev/null 2>&1 || break
1194 done
1195 case "$cmd" in
1196 curl) cmd="curl -A curl -s" ;;
1197 wget) cmd="wget -qO- -U curl" ;;
1198 openssl) cmd="openssl_get" ;;
1199 ,*)
1200 log_error "Cannot retrieve latest version"
1201 return
1202 ;;
1203 esac
1204 v=$($cmd "https://api.github.com/repos/nextdns/nextdns/releases/latest" | \
1205 grep '"tag_name":' | esed 's/.*"([^"]+)".*/\1/' | sed -e 's/^v//')
1206 if [ -z "$v" ]; then
1207 log_error "Cannot get latest version: $out"
1208 fi
1209 echo "$v"
1210 fi
1211}
1212
1213esed() {
1214 if (echo | sed -E '' >/dev/null 2>&1); then
1215 sed -E "$@"
1216 else
1217 sed -r "$@"
1218 fi
1219}
1220
1221http_redirect() {
1222 while read -r header; do
1223 case $header in
1224 Location:*)
1225 echo "${header#Location: }"
1226 return
1227 ;;
1228 esac
1229 if [ "$header" = "" ]; then
1230 break
1231 fi
1232 done
1233 cat > /dev/null
1234 return 1
1235}
1236
1237http_body() {
1238 sed -n '/^\r/,$p' | sed 1d
1239}
1240
1241openssl_get() {
1242 host=${1#https://*} # https://dom.com/path -> dom.com/path
1243 path=/${host#*/} # dom.com/path -> /path
1244 host=${host%$path} # dom.com/path -> dom.com
1245 printf "GET %s HTTP/1.0\nHost: %s\nUser-Agent: curl\n\n" "$path" "$host" |
1246 openssl s_client -quiet -connect "$host:443" 2>/dev/null
1247}
1248
1249umask 0022
1250main
1251#+end_src