cmc/cleberg.net

My personal web garden & blog.

clone: git clone https://gitbay.org/cmc/cleberg.net.git

18e372d06257e62a1003c563fb26fb4c491f50a9

unsigned

author: Christian Cleberg <hello@cleberg.net> · 2023-12-04T04:43:44Z

feat: add nextdns pot
 blog/2023-11-12-consolefonts.org  |    5 -
 blog/2023-12-03-unifi-nextdns.org | 1275 +++++++++++++++++++++++++++++++++++++
 blog/index.org                    |   25 +
 blog/rss.org                      |    2 +-
 4 files changed, 1301 insertions(+), 6 deletions(-)

diff --git a/blog/2023-11-12-consolefonts.org b/blog/2023-11-12-consolefonts.org
deleted file mode 100644
index 47ac456..0000000
--- a/blog/2023-11-12-consolefonts.org
+++ /dev/null
@@ -1,5 +0,0 @@
-+++
-date = 2023-11-12T02:03:25.924298748+00:00
-title = "How to Configure Console Fonts on Alpine Linux"
-description = ""
-+++
diff --git a/blog/2023-12-03-unifi-nextdns.org b/blog/2023-12-03-unifi-nextdns.org
new file mode 100644
index 0000000..e6eb4b0
--- /dev/null
+++ b/blog/2023-12-03-unifi-nextdns.org
@@ -0,0 +1,1275 @@
+#+title: How to Install NextDNS on the Unifi Dream Machine
+#+date:  2023-12-03
+
+* Overview
+:PROPERTIES:
+:ID:       711309AE-955D-4B2D-B716-CFD700079157
+:PUBDATE:  2023-12-03 Sun 22:32
+:END:
+
+I recently installed NextDNS on my Unifi Dream Machine router using the 
+[[https://github.com/nextdns/nextdns/wiki/UnifiOS][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
+:PROPERTIES:
+:ID:       8457B7A9-AE62-448D-B092-C04759F8D468
+:PUBDATE:  2023-12-03 Sun 22:32
+:END:
+
+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
+:PROPERTIES:
+:ID:       907F595C-6D53-409D-AB97-2B830D09B62E
+:END:
+
+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:
+
+#+begin_src sh
+sh -c 'sh -c "$(curl -sL https://nextdns.io/install)"'
+#+end_src
+
+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
+:PROPERTIES:
+:ID:       3836C070-2C7C-456D-AB3B-15DDE60D2F99
+:END:
+
+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
+:PROPERTIES:
+:ID:       DFD1BA2F-EC4E-4B5C-9CEA-098DFF0AD62F
+:END:
+
+To uninstall, re-run the installer above and select "Remove" in the menu.
+
+** Troubleshooting
+:PROPERTIES:
+:ID:       0B0F4E70-FACC-4C7C-8EB9-55398AA8476F
+:END:
+
+If the installation fail, please the installer in debug mode and contact us at 
+team@nextdns.io with the transcript of the installation:
+
+#+begin_src sh
+sh -c 'DEBUG=1 sh -c "$(curl -sL https://nextdns.io/install)"'
+#+end_Ssrc
+
+*** Content Filtering Conflict
+:PROPERTIES:
+:ID:       BCDD3267-4C04-45AC-BAAD-9D15F414ED4D
+:END:
+
+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
+:PROPERTIES:
+:ID:       83244A13-7154-475A-8193-064A1B81D3AE
+:END:
+
+If you get an apt error as follow:
+
+#+begin_src 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]
+#+end_src
+
+You may try to following:
+
+#+begin_src 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
+#+end_src
+
+* install.sh
+:PROPERTIES:
+:ID:       91DE0DD0-8177-4594-B067-373ABF99C343
+:PUBDATE:  2023-12-03 Sun 22:34
+:END:
+
+Here are the contents of the =install.sh= file used above, as of 2023-12-03:
+
+#+begin_src 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
+#+end_src
diff --git a/blog/index.org b/blog/index.org
index 3c80bbb..a2c4420 100644
--- a/blog/index.org
+++ b/blog/index.org
@@ -4,7 +4,12 @@
 Use =C-f= to search blog post titles for keywords.
 
 * 2023
+:PROPERTIES:
+:ID:       21779F03-E65E-4A80-A958-3D08723EB8AD
+:PUBDATE:  2023-12-03 Sun 22:35
+:END:
 
+- 2023-12-03 [[./2023-12-03-unifi-nextdns.org][Installing NextDNS on the Unifi Dream Machine]]
 - 2023-11-08 [[./2023-11-08-scli.org][Installing scli on Alpine Linux (musl)]]
 - 2023-10-17 [[./2023-10-17-self-hosting-anonymousoverflow.org][Self-Hosting AnonymousOverflow]]
 - 2023-10-15 [[./2023-10-15-alpine-ssh-hardening.org][SSH Hardening for Alpine Linux]]
@@ -33,6 +38,10 @@ Use =C-f= to search blog post titles for keywords.
 - 2023-01-03 [[./2023-01-03-recent-website-changes.org][Recent Website Changes]]
 
 * 2022
+:PROPERTIES:
+:ID:       3B281087-A3B6-46B7-8A29-77F74DAD5F15
+:PUBDATE:  2023-12-03 Sun 22:35
+:END:
 
 - 2022-12-23 [[./2022-12-23-alpine-desktop.org][Alpine Linux as a Desktop OS]]
 - 2022-12-17 [[./2022-12-17-st.org][Simple Terminal]]
@@ -76,6 +85,10 @@ Use =C-f= to search blog post titles for keywords.
 - 2022-02-10 [[./2022-02-10-leaving-the-office.org][Leaving Office-Based Work In the Past]]
 
 * 2021
+:PROPERTIES:
+:ID:       7603D51F-513F-4407-9CDC-F55C555D35D4
+:PUBDATE:  2023-12-03 Sun 22:35
+:END:
 
 - 2021-12-04 [[./2021-12-04-cisa.org][I Passed the CISA!]]
 - 2021-10-09 [[./2021-10-09-apache-redirect.org][Apache Redirect HTML Files to a Directory]]
@@ -95,6 +108,10 @@ Use =C-f= to search blog post titles for keywords.
 - 2021-01-01 [[./2021-01-01-seum.org][SEUM: Speedrunners from Hell]]
 
 * 2020
+:PROPERTIES:
+:ID:       8442803D-FB00-498D-9936-51029E80B367
+:PUBDATE:  2023-12-03 Sun 22:35
+:END:
 
 - 2020-12-29 [[./2020-12-29-zork.org][Zork: Let's Explore a Classic]]
 - 2020-12-28 [[./2020-12-28-neon-drive.org][Neon Drive: A Nostalgic 80s Arcade Racing Game]]
@@ -115,6 +132,10 @@ Use =C-f= to search blog post titles for keywords.
 - 2020-01-25 [[./2020-01-25-linux-software.org][Linux Software]]
 
 * 2019
+:PROPERTIES:
+:ID:       CC0E4E85-6094-48AD-B78A-68DE97E56A17
+:PUBDATE:  2023-12-03 Sun 22:35
+:END:
 
 - 2019-12-16 [[./2019-12-16-password-security.org][Password Security]]
 - 2019-12-03 [[./2019-12-03-the-ansoff-matrix.org][The Ansoff Matrix]]
@@ -122,6 +143,10 @@ Use =C-f= to search blog post titles for keywords.
 - 2019-01-07 [[./2019-01-07-useful-css.org][Useful CSS Snippets]]
 
 * 2018
+:PROPERTIES:
+:ID:       4FE32AD6-AC36-43DD-ACEF-B2D98450FB2D
+:PUBDATE:  2023-12-03 Sun 22:35
+:END:
 
 - 2018-12-08 [[./2018-12-08-aes-encryption.org][AES Encryption]]
 - 2018-11-28 [[./2018-11-28-cpp-compiler.org][The C++ Compiler]]
diff --git a/blog/rss.org b/blog/rss.org
index fdddc88..79d96f6 100644
--- a/blog/rss.org
+++ b/blog/rss.org
@@ -1,5 +1,6 @@
 #+TITLE: cleberg.net
 
+- [[file:2023-12-03-unifi-nextdns.org][How to Install NextDNS on the Unifi Dream Machine]]
 - [[file:2021-03-28-vaporwave-vs-outrun.org][2021-03-28-vaporwave-vs-outrun]]
 - [[file:2023-06-23-self-hosting-convos-irc.org][2023-06-23-self-hosting-convos-irc]]
 - [[file:2020-07-26-business-analysis.org][Algorithmically Analyzing Local Businesses]]
@@ -66,7 +67,6 @@
 - [[file:2022-02-20-nginx-caching.org][2022-02-20-nginx-caching]]
 - [[file:2022-08-31-privacy.com-changes.org][2022-08-31-privacy.com-changes]]
 - [[file:2022-03-02-note-taking.org][2022-03-02-note-taking]]
-- [[file:2023-11-12-consolefonts.org][2023-11-12-consolefonts]]
 - [[file:2021-04-28-photography.org][2021-04-28-photography]]
 - [[file:2020-09-22-internal-audit.org][What is Internal Audit?]]
 - [[file:2022-07-25-curseradio.org][2022-07-25-curseradio]]