aboutsummaryrefslogtreecommitdiff
path: root/content/blog/2023-12-03-unifi-nextdns.md
diff options
context:
space:
mode:
Diffstat (limited to 'content/blog/2023-12-03-unifi-nextdns.md')
-rw-r--r--content/blog/2023-12-03-unifi-nextdns.md1251
1 files changed, 1251 insertions, 0 deletions
diff --git a/content/blog/2023-12-03-unifi-nextdns.md b/content/blog/2023-12-03-unifi-nextdns.md
new file mode 100644
index 0000000..b6bb627
--- /dev/null
+++ b/content/blog/2023-12-03-unifi-nextdns.md
@@ -0,0 +1,1251 @@
++++
+date = 2023-12-03
+title = "How to Install NextDNS on the Unifi Dream Machine"
+description = "A guide to properly install the NextDNS client on the UDM Pro."
++++
+
+# Overview
+
+I recently installed NextDNS on my Unifi Dream Machine router using the
+[UnifiOS](https://github.com/nextdns/nextdns/wiki/UnifiOS) wiki page on
+NextDNS's GitHub repository.
+
+As a result of this, I wanted to write down the process in case the wiki
+or installer ever gets lost.
+
+# Wiki
+
+The following is copied from the wiki page linked above, with one
+difference in the `ssh` command.
+
+Install instructions for Unifi Dream Machine (UDM) standard and pro
+routers.
+
+## Install
+
+Enable SSH:
+
+- Go to your unifi admin interface and select your device (not the
+ controller settings, but the Dream Machine settings)
+- Click on "Settings" at the bottom of the page
+- Go to the "Advanced" section on the left pan
+- Enable SSH
+- Set a SSH password
+
+Connect to your router using `ssh root@xxx.xxx.xxx.xxx` with
+the password you configured.
+
+Run the following command and follow the instructions:
+
+```sh
+sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"'
+```
+
+Note: Queries from the UDM itself won't be routed to NextDNS nor
+encrypted due to current system limitation. All traffic from other
+devices on then network will.
+
+## Upgrade
+
+To upgrade to the last version, simply re-run the installer above. If a
+new version is available, the upgrade action will added to the list of
+possible actions.
+
+## Uninstall
+
+To uninstall, re-run the installer above and select "Remove" in the
+menu.
+
+## Troubleshooting
+
+If the installation fail, please the installer in debug mode and contact
+us at team@nextdns.io with the transcript of the installation:
+
+```sh
+sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"'
+```
+
+### Content Filtering Conflict
+
+NextDNS CLI and the UDM Content Filtering or the Ad Blocking features
+are incompatible. If you want to use NextDNS CLI, please make sure they
+are disabled.
+
+To disable Content Filtering, go to Settings > Network, then for each
+network, set the Content Filtering feature to None
+
+To disable Ad Blocking, go to Settings > Application Firewall. In the
+General tab, uncheck the Ad Blocking checkbox.
+
+### APT Error
+
+If you get an apt error as follow:
+
+```sh
+E: Failed to fetch http://security.debian.org/dists/stretch/updates/main/binary-arm64/Packages 404 Not Found [IP: 151.101.70.132 80]
+```
+
+You may try to following:
+
+```sh
+sed -i -e 's/deb.debian.org/archive.debian.org/g' \
+ -e 's|security.debian.org|archive.debian.org/|g' \
+ -e '/stretch-updates/d' /etc/apt/sources.list
+```
+
+# install.sh
+
+Here are the contents of the `install.sh` file used above, as
+of 2023-12-03:
+
+```sh
+#!/bin/sh
+
+main() {
+ OS=$(detect_os)
+ GOARCH=$(detect_goarch)
+ GOOS=$(detect_goos)
+ NEXTDNS_BIN=$(bin_location)
+ INSTALL_RELEASE=$(get_release)
+
+ export NEXTDNS_INSTALLER=1
+
+ log_info "OS: $OS"
+ log_info "GOARCH: $GOARCH"
+ log_info "GOOS: $GOOS"
+ log_info "NEXTDNS_BIN: $NEXTDNS_BIN"
+ log_info "INSTALL_RELEASE: $INSTALL_RELEASE"
+
+ if [ -z "$OS" ] || [ -z "$GOARCH" ] || [ -z "$GOOS" ] || [ -z "$NEXTDNS_BIN" ] || [ -z "$INSTALL_RELEASE" ]; then
+ log_error "Cannot detect running environment."
+ exit 1
+ fi
+
+ case "$RUN_COMMAND" in
+ install|upgrade|uninstall|configure) "$RUN_COMMAND"; exit ;;
+ esac
+
+ while true; do
+ CURRENT_RELEASE=$(get_current_release)
+ log_debug "Start install loop with CURRENT_RELEASE=$CURRENT_RELEASE"
+
+ if [ "$CURRENT_RELEASE" ]; then
+ if ! is_version_current; then
+ log_debug "NextDNS is out of date ($CURRENT_RELEASE != $INSTALL_RELEASE)"
+ menu \
+ u "Upgrade NextDNS from $CURRENT_RELEASE to $INSTALL_RELEASE" upgrade \
+ c "Configure NextDNS" configure \
+ r "Remove NextDNS" uninstall \
+ e "Exit" exit
+ else
+ log_debug "NextDNS is up to date ($CURRENT_RELEASE)"
+ menu \
+ c "Configure NextDNS" configure \
+ r "Remove NextDNS" uninstall \
+ e "Exit" exit
+ fi
+ else
+ log_debug "NextDNS is not installed"
+ menu \
+ i "Install NextDNS" install \
+ e "Exit" exit
+ fi
+ done
+}
+
+install() {
+ if [ "$(get_current_release)" ]; then
+ log_info "Already installed"
+ return
+ fi
+ if type=$(install_type); then
+ log_info "Installing NextDNS..."
+ log_debug "Using $type install type"
+ if "install_$type"; then
+ if [ ! -x "$NEXTDNS_BIN" ]; then
+ log_error "Installation failed: binary not installed in $NEXTDNS_BIN"
+ return 1
+ fi
+ configure
+ post_install
+ exit 0
+ fi
+ else
+ return $?
+ fi
+}
+
+upgrade() {
+ if [ "$(get_current_release)" = "$INSTALL_RELEASE" ]; then
+ log_info "Already on the latest version"
+ return
+ fi
+ if type=$(install_type); then
+ log_info "Upgrading NextDNS..."
+ log_debug "Using $type install type"
+ "upgrade_$type"
+ else
+ return $?
+ fi
+}
+
+uninstall() {
+ if type=$(install_type); then
+ log_info "Uninstalling NextDNS..."
+ log_debug "Using $type uninstall type"
+ "uninstall_$type"
+ else
+ return $?
+ fi
+}
+
+precheck() {
+ if [ -e "/data/unifi" ] && [ -f "/run/dnsfilter/dnsfilter" ]; then
+ log_warn "UDM Content Filtering and/or Ad Blocking feature is enabled."
+ log_warn "Please disable it to use NextDNS."
+ log_warn ""
+ log_warn " To disable Content Filtering, go to Settings > Network."
+ log_warn " For each network, set the Content Filtering feature to None."
+ log_warn ""
+ log_warn " To disable Ad Blocking, go to Settings > Application Firewall"
+ log_warn " In the General tab, uncheck the Ad Blocking checkbox."
+ log_warn ""
+ while [ -f "/run/dnsfilter/dnsfilter" ]; do
+ sleep 1
+ done
+ log_info "Content Filtering feature successfuly disabled."
+ fi
+}
+
+configure() {
+ log_debug "Start configure"
+ precheck
+ args=""
+ add_arg() {
+ for value in $2; do
+ log_debug "Add arg -$1=$value"
+ args="$args -$1=$value"
+ done
+ }
+ add_arg_bool_ask() {
+ arg=$1
+ msg=$2
+ default=$3
+ if [ -z "$default" ]; then
+ default=$(get_config_bool "$arg")
+ fi
+ # shellcheck disable=SC2046
+ add_arg "$arg" $(ask_bool "$msg" "$default")
+ }
+ # Use profile from now on
+ add_arg profile "$(get_profile_id)"
+
+ doc "Sending your devices name lets you filter analytics and logs by device."
+ add_arg_bool_ask report-client-info 'Report device name?' true
+
+ case $(guess_host_type) in
+ router)
+ add_arg setup-router true
+ ;;
+ unsure)
+ doc "Accept DNS request from other network hosts."
+ if [ "$(get_config_bool setup-router)" = "true" ]; then
+ router_default=true
+ fi
+ if [ "$(ask_bool 'Setup as a router?' $router_default)" = "true" ]; then
+ add_arg setup-router true
+ fi
+ ;;
+ esac
+
+ doc "Make NextDNS CLI cache responses. This improves latency and reduces the amount"
+ doc "of queries sent to NextDNS."
+ if [ "$(guess_host_type)" = "router" ]; then
+ doc "Note that enabling this feature will disable dnsmasq for DNS to avoid double"
+ doc "caching."
+ fi
+ if [ "$(get_config cache-size)" != "0" ]; then
+ cache_default=true
+ fi
+ if [ "$(ask_bool 'Enable caching?' $cache_default)" = "true" ]; then
+ add_arg cache-size "10MB"
+
+ doc "Instant refresh will force low TTL on responses sent to clients so they rely"
+ doc "on CLI DNS cache. This will allow changes on your NextDNS config to be applied"
+ doc "on your LAN hosts without having to wait for their cache to expire."
+ if [ "$(get_config max-ttl)" = "5s" ]; then
+ instant_refresh_default=true
+ fi
+ if [ "$(ask_bool 'Enable instant refresh?' $instant_refresh_default)" = "true" ]; then
+ add_arg max-ttl "5s"
+ fi
+ fi
+
+ if [ "$(guess_host_type)" != "router" ]; then
+ doc "Changes DNS settings of the host automatically when NextDNS is started."
+ doc "If you say no here, you will have to manually configure DNS to 127.0.0.1."
+ add_arg_bool_ask auto-activate 'Automatically setup local host DNS?' true
+ fi
+ # shellcheck disable=SC2086
+ asroot "$NEXTDNS_BIN" install $args
+}
+
+post_install() {
+ println
+ println "Congratulations! NextDNS is now installed."
+ println
+ println "To upgrade/uninstall, run this command again and select the appropriate option."
+ println
+ println "You can use the NextDNS command to control the daemon."
+ println "Here are a few important commands to know:"
+ println
+ println "# Start, stop, restart the daemon:"
+ println "nextdns start"
+ println "nextdns stop"
+ println "nextdns restart"
+ println
+ println "# Configure the local host to point to NextDNS or not:"
+ println "nextdns activate"
+ println "nextdns deactivate"
+ println
+ println "# Explore daemon logs:"
+ println "nextdns log"
+ println
+ println "# For more commands, use:"
+ println "nextdns help"
+ println
+}
+
+install_bin() {
+ bin_path=$NEXTDNS_BIN
+ if [ "$1" ]; then
+ bin_path=$1
+ fi
+ log_debug "Installing $INSTALL_RELEASE binary for $GOOS/$GOARCH to $bin_path"
+ case "$INSTALL_RELEASE" in
+ */*)
+ # Snapshot
+ branch=${INSTALL_RELEASE%/*}
+ hash=${INSTALL_RELEASE#*/}
+ url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz"
+ ;;
+ *)
+ url="https://github.com/nextdns/nextdns/releases/download/v${INSTALL_RELEASE}/nextdns_${INSTALL_RELEASE}_${GOOS}_${GOARCH}.tar.gz"
+ ;;
+ esac
+ log_debug "Downloading $url"
+ asroot mkdir -p "$(dirname "$bin_path")" &&
+ curl -sL "$url" | asroot sh -c "tar Ozxf - nextdns > "$bin_path"" &&
+ asroot chmod 755 "$bin_path"
+}
+
+upgrade_bin() {
+ tmp=$NEXTDNS_BIN.tmp
+ if install_bin "$tmp"; then
+ asroot "$NEXTDNS_BIN" uninstall
+ asroot mv "$tmp" "$NEXTDNS_BIN"
+ asroot "$NEXTDNS_BIN" install
+ fi
+ log_debug "Removing spurious temporary install file"
+ asroot rm -rf "$tmp"
+}
+
+uninstall_bin() {
+ asroot "$NEXTDNS_BIN" uninstall
+ asroot rm -f "$NEXTDNS_BIN"
+}
+
+install_rpm() {
+ asroot curl -Ls https://repo.nextdns.io/nextdns.repo -o /etc/yum.repos.d/nextdns.repo &&
+ asroot yum install -y nextdns
+}
+
+upgrade_rpm() {
+ asroot yum update -y nextdns
+}
+
+uninstall_rpm() {
+ asroot yum remove -y nextdns
+}
+
+install_zypper() {
+ if asroot zypper repos | grep -q nextdns >/dev/null; then
+ echo "Repository nextdns already exists. Skipping adding repository..."
+ else
+ asroot zypper ar -f -r https://repo.nextdns.io/nextdns.repo nextdns
+ fi
+ asroot zypper refresh && asroot zypper in -y nextdns
+}
+
+upgrade_zypper() {
+ asroot zypper up nextdns
+}
+
+uninstall_zypper() {
+ asroot zypper remove -y nextdns
+ case $(ask_bool 'Do you want to remove the repository from the repositories list?' true) in
+ true)
+ asroot zypper removerepo nextdns
+ ;;
+ esac
+}
+
+install_deb() {
+ if [ -f /etc/default/ubnt-dpkg-cache ]; then
+ # On UnifiOS 2, make sure the package is persisted over upgrades
+ sed -e '/^DPKG_CACHE_UBNT_PKGS+=" nextdns"/{:a;n;ba;q}' \
+ -e '$aDPKG_CACHE_UBNT_PKGS+=" nextdns"' \
+ -i /etc/default/ubnt-dpkg-cache
+ fi
+
+ install_deb_keyring &&
+ 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' &&
+ (dpkg --compare-versions $(dpkg-query --showformat='${Version}' --show apt) ge 1.1 ||
+ asroot ln -s /etc/apt/keyrings/nextdns.gpg /etc/apt/trusted.gpg.d/.) &&
+ (test "$OS" = "debian" && asroot apt-get -y install apt-transport-https || true) &&
+ asroot apt-get update &&
+ asroot apt-get install -y nextdns
+}
+
+install_deb_keyring() {
+ # Fallback on curl, some debian based distrib don't have wget while debian
+ # doesn't have curl by default.
+ asroot mkdir -p /etc/apt/keyrings
+ ( asroot wget -qO /etc/apt/keyrings/nextdns.gpg https://repo.nextdns.io/nextdns.gpg ||
+ asroot curl -sfL https://repo.nextdns.io/nextdns.gpg -o /etc/apt/keyrings/nextdns.gpg ) &&
+ asroot chmod 0644 /etc/apt/keyrings/nextdns.gpg
+}
+
+upgrade_deb() {
+ install_deb_keyring &&
+ asroot apt-get update &&
+ asroot apt-get install -y nextdns
+}
+
+uninstall_deb() {
+ asroot apt-get remove -y nextdns
+}
+
+install_apk() {
+ repo=https://repo.nextdns.io/apk
+ asroot wget -O /etc/apk/keys/nextdns.pub https://repo.nextdns.io/nextdns.pub &&
+ (grep -v $repo /etc/apk/repositories; echo $repo) | asroot tee /etc/apk/repositories >/dev/null &&
+ asroot apk update &&
+ asroot apk add nextdns
+}
+
+upgrade_apk() {
+ asroot apk update && asroot apk upgrade nextdns
+}
+
+uninstall_apk() {
+ asroot apk del nextdns
+}
+
+install_arch() {
+ asroot pacman -Sy yay &&
+ yay -Sy nextdns
+}
+
+upgrade_arch() {
+ yay -Suy nextdns
+}
+
+uninstall_arch() {
+ asroot pacman -R nextdns
+}
+
+install_merlin_path() {
+ # Add next to Merlin's path
+ mkdir -p /tmp/opt/sbin
+ ln -sf "$NEXTDNS_BIN" /tmp/opt/sbin/nextdns
+}
+
+install_merlin() {
+ if install_bin; then
+ install_merlin_path
+ fi
+}
+
+uninstall_merlin() {
+ uninstall_bin
+ rm -f /tmp/opt/sbin/nextdns
+}
+
+upgrade_merlin() {
+ if upgrade_bin; then
+ install_merlin_path
+ fi
+}
+
+install_openwrt() {
+ opkg update &&
+ opkg install nextdns
+ rt=$?
+ if [ $rt -eq 0 ]; then
+ case $(ask_bool 'Install the GUI?' true) in
+ true)
+ opkg install luci-app-nextdns
+ rt=$?
+ ;;
+ esac
+ fi
+ return $rt
+}
+
+upgrade_openwrt() {
+ opkg update &&
+ opkg upgrade nextdns
+}
+
+uninstall_openwrt() {
+ opkg remove nextdns
+}
+
+install_ddwrt() {
+ if [ "$(nvram get enable_jffs2)" = "0" ]; then
+ log_error "JFFS support not enabled"
+ log_info "To enabled JFFS:"
+ log_info " 1. On the router web page click on Administration."
+ log_info " 2. Scroll down until you see JFFS2 Support section."
+ log_info " 3. Click Enable JFFS."
+ log_info " 4. Click Save."
+ log_info " 5. Wait couple seconds, then click Apply."
+ log_info " 6. Wait again. Go back to the Enable JFFS section, and enable Clean JFFS."
+ log_info " 7. Do not click Save. Click Apply instead."
+ log_info " 8. Wait till you get the web-GUI back, then disable Clean JFFS again."
+ log_info " 9. Click Save."
+ log_info "10. Relaunch this installer."
+ exit 1
+ fi
+ mkdir -p /jffs/nextdns &&
+ openssl_get https://curl.haxx.se/ca/cacert.pem | http_body > /jffs/nextdns/ca.pem &&
+ install_bin
+}
+
+upgrade_ddwrt() {
+ upgrade_bin
+}
+
+uninstall_ddwrt() {
+ uninstall_bin
+ rm -rf /jffs/nextdns
+}
+
+install_brew() {
+ silent_exec brew install nextdns/tap/nextdns
+}
+
+upgrade_brew() {
+ silent_exec brew upgrade nextdns/tap/nextdns
+ asroot "$NEXTDNS_BIN" install
+}
+
+uninstall_brew() {
+ silent_exec brew uninstall nextdns/tap/nextdns
+}
+
+install_freebsd() {
+ # TODO: port install
+ install_bin
+}
+
+upgrade_freebsd() {
+ # TODO: port upgrade
+ upgrade_bin
+}
+
+uninstall_freebsd() {
+ # TODO: port uninstall
+ uninstall_bin
+}
+
+install_pfsense() {
+ # TODO: port install + UI
+ install_bin
+}
+
+upgrade_pfsense() {
+ # TODO: port upgrade
+ upgrade_bin
+}
+
+uninstall_pfsense() {
+ # TODO: port uninstall
+ uninstall_bin
+}
+
+install_opnsense() {
+ # TODO: port install + UI
+ install_bin
+}
+
+upgrade_opnsense() {
+ # TODO: port upgrade
+ upgrade_bin
+}
+
+uninstall_opnsense() {
+ # TODO: port uninstall
+ uninstall_bin
+}
+
+ubios_install_source() {
+ echo "deb [signed-by=/etc/apt/keyrings/nextdns.gpg] https://repo.nextdns.io/deb stable main" > /data/nextdns.list
+ podman exec unifi-os mv /data/nextdns.list /etc/apt/sources.list.d/nextdns.list
+ rm -f /tmp/nextdns.list
+ podman exec unifi-os apt-get install -y gnupg1 curl
+ podman exec unifi-os mkdir -p /etc/apt/keyrings/
+ podman exec unifi-os curl -sfL https://repo.nextdns.io/nextdns.gpg -o /etc/apt/keyrings/nextdns.gpg
+ 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"
+}
+
+install_ubios() {
+ ubios_install_source
+ podman exec unifi-os apt-get install -y nextdns
+}
+
+upgrade_ubios() {
+ ubios_install_source
+ podman exec unifi-os apt-get install --only-upgrade -y nextdns
+}
+
+uninstall_ubios() {
+ podman exec unifi-os apt-get remove -y nextdns
+}
+
+install_ubios_snapshot() {
+ branch=${INSTALL_RELEASE%/*}
+ hash=${INSTALL_RELEASE#*/}
+ url="https://snapshot.nextdns.io/${branch}/nextdns-${hash}_${GOOS}_${GOARCH}.tar.gz"
+ podman exec unifi-os sh -c "curl -o- $url | tar Ozxf - nextdns > /usr/bin/nextdns; /usr/bin/nextdns install"
+}
+
+upgrade_ubios_snapshot() {
+ /data/nextdns uninstall
+ install_ubios_snapshot
+}
+
+install_type() {
+ if [ "$FORCE_INSTALL_TYPE" ]; then
+ echo "$FORCE_INSTALL_TYPE"; return 0
+ fi
+ case "$INSTALL_RELEASE" in
+ */*)
+ case $OS in
+ ubios)
+ echo "ubios_snapshot"; return 0
+ ;;
+ *)
+ # Snapshot mode always use binary install
+ echo "bin"; return 0
+ ;;
+ esac
+ esac
+ case $OS in
+ centos|fedora|rhel)
+ echo "rpm"
+ ;;
+ opensuse-tumbleweed|opensuse-leap|opensuse)
+ echo "zypper"
+ ;;
+ debian|ubuntu|elementary|raspbian|linuxmint|pop|neon|sparky|vyos|Deepin)
+ echo "deb"
+ ;;
+ alpine)
+ echo "apk"
+ ;;
+ arch|manjaro|steamos)
+ #echo "arch" # TODO: fix AUR install
+ echo "bin"
+ ;;
+ openwrt)
+ # shellcheck disable=SC1091
+ . /etc/os-release
+ major=$(echo "$VERSION_ID" | cut -d. -f1)
+ case $major in
+ *[!0-9]*)
+ if [ "$VERSION_ID" = "19.07.0-rc1" ]; then
+ # No opkg support before 19.07.0-rc2
+ echo "bin"
+ else
+ # Likely 'snapshot' build in this case, but still > major version 19
+ echo "openwrt"
+ fi
+ ;;
+ *)
+ if [ "$major" -lt 19 ]; then
+ # No opkg support before 19.07.0-rc2
+ echo "bin"
+ else
+ echo "openwrt"
+ fi
+ ;;
+ esac
+ ;;
+ asuswrt-merlin)
+ echo "merlin"
+ ;;
+ edgeos|synology|clear-linux-os|solus|openbsd|netbsd|overthebox)
+ echo "bin"
+ ;;
+ ddwrt)
+ echo "ddwrt"
+ ;;
+ darwin)
+ if [ -x /usr/local/bin/brew ] || [ -x /opt/homebrew/bin/brew ]; then
+ echo "brew"
+ else
+ log_debug "Homebrew not installed, fallback on binary install"
+ echo "bin"
+ fi
+ ;;
+ freebsd)
+ echo "freebsd"
+ ;;
+ pfsense)
+ echo "pfsense"
+ ;;
+ opnsense)
+ echo "opnsense"
+ ;;
+ ubios)
+ echo "ubios"
+ ;;
+ gentoo)
+ echo "bin"
+ ;;
+ void)
+ # TODO: pkg for xbps
+ echo "bin"
+ ;;
+ *)
+ log_error "Unsupported installation for $(detect_os)"
+ return 1
+ ;;
+ esac
+}
+
+get_config() {
+ "$NEXTDNS_BIN" config | grep -E "^$1 " | cut -d' ' -f 2
+}
+
+get_config_bool() {
+ val=$(get_config "$1")
+ case $val in
+ true|false)
+ echo "$val"
+ ;;
+ esac
+ echo "$2"
+}
+
+get_profile_id() {
+ log_debug "Get profile ID"
+ if [ "$CONFIG_ID" ]; then
+ # backward compat
+ PROFILE_ID="$CONFIG_ID"
+ fi
+ while [ -z "$PROFILE_ID" ]; do
+ default=
+ prev_id=$(get_config profile)
+ if [ -z "$prev_id" ]; then
+ # backward compat
+ prev_id=$(get_config config)
+ fi
+ if [ "$prev_id" ]; then
+ log_debug "Previous profile ID: $prev_id"
+ default=" (default=$prev_id)"
+ fi
+ print "NextDNS Profile ID%s: " "$default"
+ read -r id
+ if [ -z "$id" ]; then
+ id=$prev_id
+ fi
+ if echo "$id" | grep -qE '^[0-9a-f]{6}$'; then
+ PROFILE_ID=$id
+ break
+ else
+ log_error "Invalid profile ID."
+ println
+ println "ID format is 6 alphanumerical lowercase characters (example: 123abc)."
+ println "Your ID can be found on the Setup tab of https://my.nextdns.io."
+ println
+ fi
+ done
+ echo "$PROFILE_ID"
+}
+
+log_debug() {
+ if [ "$DEBUG" = "1" ]; then
+ printf "\033[30;1mDEBUG: %s\033[0m\n" "$*" >&2
+ fi
+}
+
+log_info() {
+ printf "INFO: %s\n" "$*" >&2
+}
+
+log_warn() {
+ printf "\033[33mWARN: %s\033[0m\n" "$*" >&2
+}
+
+log_error() {
+ printf "\033[31mERROR: %s\033[0m\n" "$*" >&2
+}
+
+print() {
+ format=$1
+ if [ $# -gt 0 ]; then
+ shift
+ fi
+ # shellcheck disable=SC2059
+ printf "$format" "$@" >&2
+}
+
+println() {
+ format=$1
+ if [ $# -gt 0 ]; then
+ shift
+ fi
+ # shellcheck disable=SC2059
+ printf "$format\n" "$@" >&2
+}
+
+doc() {
+ # shellcheck disable=SC2059
+ printf "\033[30;1m%s\033[0m\n" "$*" >&2
+}
+
+menu() {
+ while true; do
+ n=0
+ default=
+ for item in "$@"; do
+ case $((n%3)) in
+ 0)
+ key=$item
+ if [ -z "$default" ]; then
+ default=$key
+ fi
+ ;;
+ 1)
+ echo "$key) $item"
+ ;;
+ esac
+ n=$((n+1))
+ done
+ print "Choice (default=%s): " "$default"
+ read -r choice
+ if [ -z "$choice" ]; then
+ choice=$default
+ fi
+ n=0
+ for item in "$@"; do
+ case $((n%3)) in
+ 0)
+ key=$item
+ ;;
+ 2)
+ if [ "$key" = "$choice" ]; then
+ if ! "$item"; then
+ log_error "$item: exit $?"
+ fi
+ break 2
+ fi
+ ;;
+ esac
+ n=$((n+1))
+ done
+ echo "Invalid choice"
+ done
+}
+
+ask_bool() {
+ msg=$1
+ default=$2
+ case $default in
+ true)
+ msg="$msg [Y|n]: "
+ ;;
+ false)
+ msg="$msg [y|N]: "
+ ;;
+ *)
+ msg="$msg (y/n): "
+ esac
+ while true; do
+ print "%s" "$msg"
+ read -r answer
+ if [ -z "$answer" ]; then
+ answer=$default
+ fi
+ case $answer in
+ y|Y|yes|YES|true)
+ echo "true"
+ return 0
+ ;;
+ n|N|no|NO|false)
+ echo "false"
+ return 0
+ ;;
+ *)
+ echo "Invalid input, use yes or no"
+ ;;
+ esac
+ done
+}
+
+detect_endiannes() {
+ if ! hexdump /dev/null 2>/dev/null; then
+ # Some firmwares do not contain hexdump, for those, try to detect endianness
+ # differently.
+ case $(cat /proc/cpuinfo) in
+ *BCM5300*)
+ # RT-AC66U does not support Merlin version over 380.70 which
+ # lacks hexdump command.
+ echo "le"
+ ;;
+ *)
+ log_error "Cannot determine endianness"
+ return 1
+ ;;
+ esac
+ return 0
+ fi
+ case $(hexdump -s 5 -n 1 -e '"%x"' /bin/sh | head -c1) in
+ 1)
+ echo "le"
+ ;;
+ 2)
+ echo ""
+ ;;
+ esac
+}
+
+detect_goarch() {
+ if [ "$FORCE_GOARCH" ]; then
+ echo "$FORCE_GOARCH"; return 0
+ fi
+ case $(uname -m) in
+ x86_64|amd64)
+ echo "amd64"
+ ;;
+ i386|i686)
+ echo "386"
+ ;;
+ arm)
+ # FreeBSD does not include arm version
+ case "$(sysctl -b hw.model 2>/dev/null)" in
+ *A9*)
+ echo "armv7"
+ ;;
+ *)
+ # Unknown version, fallback to the lowest
+ echo "armv5"
+ ;;
+ esac
+ ;;
+ armv5*)
+ echo "armv5"
+ ;;
+ armv6*|armv7*)
+ if grep -q vfp /proc/cpuinfo 2>/dev/null; then
+ echo "armv$(uname -m | sed -e 's/[[:alpha:]]//g')"
+ else
+ # Soft floating point
+ echo "armv5"
+ fi
+ ;;
+ aarch64)
+ case "$(uname -o 2>/dev/null)" in
+ ASUSWRT-Merlin*)
+ # XXX when using arm64 build on ASUS AC66U and ACG86U, we get Go error:
+ # "out of memory allocating heap arena metadata".
+ echo "armv7"
+ ;;
+ *)
+ echo "arm64"
+ ;;
+ esac
+ ;;
+ armv8*|arm64)
+ echo "arm64"
+ ;;
+ mips*)
+ # TODO: detect hardfloat
+ echo "$(uname -m)$(detect_endiannes)_softfloat"
+ ;;
+ *)
+ log_error "Unsupported GOARCH: $(uname -m)"
+ return 1
+ ;;
+ esac
+}
+
+detect_goos() {
+ if [ "$FORCE_GOOS" ]; then
+ echo "$FORCE_GOOS"; return 0
+ fi
+ case $(uname -s) in
+ Linux)
+ echo "linux"
+ ;;
+ Darwin)
+ echo "darwin"
+ ;;
+ FreeBSD)
+ echo "freebsd"
+ ;;
+ NetBSD)
+ echo "netbsd"
+ ;;
+ OpenBSD)
+ echo "openbsd"
+ ;;
+ *)
+ log_error "Unsupported GOOS: $(uname -s)"
+ return 1
+ esac
+}
+
+detect_os() {
+ if [ "$FORCE_OS" ]; then
+ echo "$FORCE_OS"; return 0
+ fi
+ case $(uname -s) in
+ Linux)
+ case $(uname -o) in
+ GNU/Linux|Linux)
+ if grep -q -e '^EdgeRouter' -e '^UniFiSecurityGateway' /etc/version 2> /dev/null; then
+ echo "edgeos"; return 0
+ fi
+ if uname -u 2>/dev/null | grep -q '^synology'; then
+ echo "synology"; return 0
+ fi
+ # shellcheck disable=SC1091
+ dist=$(. /etc/os-release; echo "$ID")
+ case $dist in
+ ubios)
+ if [ -z "$(command -v podman)" ]; then
+ log_error "This version of UnifiOS is not supported. Make sure you run version 1.7.0 or above."
+ return 1
+ fi
+ echo "$dist"; return 0
+ ;;
+ 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)
+ echo "$dist"; return 0
+ ;;
+ esac
+ # shellcheck disable=SC1091
+ for dist in $(. /etc/os-release; echo "$ID_LIKE"); do
+ case $dist in
+ debian|ubuntu|rhel|fedora|openwrt)
+ log_debug "Using ID_LIKE"
+ echo "$dist"; return 0
+ ;;
+ esac
+ done
+ ;;
+ ASUSWRT-Merlin*)
+ echo "asuswrt-merlin"; return 0
+ ;;
+ DD-WRT)
+ echo "ddwrt"; return 0
+ esac
+ ;;
+ Darwin)
+ echo "darwin"; return 0
+ ;;
+ FreeBSD)
+ if [ -f /etc/platform ]; then
+ case $(cat /etc/platform) in
+ pfSense)
+ echo "pfsense"; return 0
+ ;;
+ esac
+ fi
+ if [ -x /usr/local/sbin/opnsense-version ]; then
+ case $(/usr/local/sbin/opnsense-version -N) in
+ OPNsense)
+ echo "opnsense"; return 0
+ ;;
+ esac
+ fi
+ echo "freebsd"; return 0
+ ;;
+ NetBSD)
+ echo "netbsd"; return 0
+ ;;
+ OpenBSD)
+ echo "openbsd"; return 0
+ ;;
+ *)
+ esac
+ log_error "Unsupported OS: $(uname -o) $(grep ID "/etc/os-release" 2>/dev/null | xargs)"
+ return 1
+}
+
+guess_host_type() {
+ if [ -d /data/unifi ]; then
+ # Special case when installer is run from inside the ubios podman
+ echo "router"; return 0
+ fi
+
+ case $OS in
+ pfsense|opnsense|openwrt|asuswrt-merlin|edgeos|ddwrt|synology|overthebox|ubios)
+ echo "router"
+ ;;
+ darwin|steamos)
+ echo "workstation"
+ ;;
+ *)
+ echo "unsure"
+ ;;
+ esac
+}
+
+asroot() {
+ # Some platform (Merlin) do not have the "id" command and $USER report a non root username with uid 0.
+ if [ "$(grep '^Uid:' /proc/$$/status 2>/dev/null|cut -f2)" = "0" ] || [ "$USER" = "root" ] || [ "$(id -u 2>/dev/null)" = "0" ]; then
+ "$@"
+ elif [ "$(command -v sudo 2>/dev/null)" ]; then
+ sudo "$@"
+ else
+ echo "Root required"
+ su -m root -c "$*"
+ fi
+}
+
+silent_exec() {
+ if [ "$DEBUG" = 1 ]; then
+ "$@"
+ else
+ if ! out=$("$@" 2>&1); then
+ rt=$?
+ println "\033[30;1m%s\033[0m" "$out"
+ return $rt
+ fi
+ fi
+}
+
+bin_location() {
+ case $OS in
+ 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)
+ echo "/usr/bin/nextdns"
+ ;;
+ openwrt|overthebox)
+ echo "/usr/sbin/nextdns"
+ ;;
+ synology)
+ echo "/usr/local/bin/nextdns"
+ ;;
+ darwin)
+ echo "$(brew --prefix 2>/dev/null || echo /usr/local)/bin/nextdns"
+ ;;
+ asuswrt-merlin|ddwrt)
+ echo "/jffs/nextdns/nextdns"
+ ;;
+ freebsd|pfsense|opnsense|netbsd|openbsd)
+ echo "/usr/local/sbin/nextdns"
+ ;;
+ edgeos)
+ echo "/config/nextdns/nextdns"
+ ;;
+ ubios)
+ echo "/data/nextdns"
+ ;;
+ steamos)
+ echo "$HOME/.local/bin/nextdns"
+ ;;
+ *)
+ log_error "Unknown bin location for $OS"
+ ;;
+ esac
+}
+
+is_version_current() {
+ case "$INSTALL_RELEASE" in
+ */*)
+ # Snapshot
+ hash=${INSTALL_RELEASE#*/}
+ test "0.0.0-$hash" = "$CURRENT_RELEASE"
+ ;;
+ *)
+ test "$INSTALL_RELEASE" = "$CURRENT_RELEASE"
+ ;;
+ esac
+}
+
+get_current_release() {
+ if [ -x "$NEXTDNS_BIN" ]; then
+ $NEXTDNS_BIN version|cut -d' ' -f 3
+ fi
+}
+
+get_release() {
+ if [ "$NEXTDNS_VERSION" ]; then
+ echo "$NEXTDNS_VERSION"
+ else
+ for cmd in curl wget openssl true; do
+ # command is the "right" way but may be compiled out of busybox shell
+ ! command -v $cmd > /dev/null 2>&1 || break
+ ! which $cmd > /dev/null 2>&1 || break
+ done
+ case "$cmd" in
+ curl) cmd="curl -A curl -s" ;;
+ wget) cmd="wget -qO- -U curl" ;;
+ openssl) cmd="openssl_get" ;;
+ *)
+ log_error "Cannot retrieve latest version"
+ return
+ ;;
+ esac
+ v=$($cmd "https://api.github.com/repos/nextdns/nextdns/releases/latest" | \
+ grep '"tag_name":' | esed 's/.*"([^"]+)".*/\1/' | sed -e 's/^v//')
+ if [ -z "$v" ]; then
+ log_error "Cannot get latest version: $out"
+ fi
+ echo "$v"
+ fi
+}
+
+esed() {
+ if (echo | sed -E '' >/dev/null 2>&1); then
+ sed -E "$@"
+ else
+ sed -r "$@"
+ fi
+}
+
+http_redirect() {
+ while read -r header; do
+ case $header in
+ Location:*)
+ echo "${header#Location: }"
+ return
+ ;;
+ esac
+ if [ "$header" = "" ]; then
+ break
+ fi
+ done
+ cat > /dev/null
+ return 1
+}
+
+http_body() {
+ sed -n '/^\r/,$p' | sed 1d
+}
+
+openssl_get() {
+ host=${1#https://*} # https://dom.com/path -> dom.com/path
+ path=/${host#*/} # dom.com/path -> /path
+ host=${host%$path} # dom.com/path -> dom.com
+ printf "GET %s HTTP/1.0\nHost: %s\nUser-Agent: curl\n\n" "$path" "$host" |
+ openssl s_client -quiet -connect "$host:443" 2>/dev/null
+}
+
+umask 0022
+main
+```