├── Dockerfile ├── README.md ├── fly.toml ├── s6_run_tailscale_once └── workaround-start.sh /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest as tailscale 2 | WORKDIR /app 3 | ENV TSFILE=tailscale_1.24.2_amd64.tgz 4 | RUN wget https://pkgs.tailscale.com/stable/${TSFILE} && tar xzf ${TSFILE} --strip-components=1 5 | 6 | FROM pihole/pihole:latest 7 | 8 | RUN apt-get update && apt-get install -y iptables 9 | RUN update-alternatives --set iptables /usr/sbin/iptables-legacy 10 | RUN update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy 11 | 12 | COPY --from=tailscale /app/tailscaled /app/tailscaled 13 | COPY --from=tailscale /app/tailscale /app/tailscale 14 | RUN mkdir -p /var/run/tailscale /var/cache/tailscale /var/lib/tailscale 15 | 16 | RUN mkdir -p /etc/services.d/tailscale 17 | COPY ./s6_run_tailscale_once /etc/services.d/tailscale/run 18 | 19 | COPY ./workaround-start.sh /start.sh 20 | 21 | ENV INTERFACE tailscale0 22 | ENV DNSMASQ_LISTENING ALL 23 | ENV TZ America/Chicago 24 | ENV VIRTUAL_HOST pi.hole 25 | ENV PROXY_LOCATION pi.hole 26 | ENV DNSMASQ_USER root 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Pi-hole on Fly.io served via Tailscale 2 | ====================================== 3 | 4 | How to do it: 5 | 6 | 1. [Get set up with Fly.io](https://fly.io/docs/speedrun/) (only install `flyctl` and create an account/login) 7 | 1. [Get set up with Tailscale](https://tailscale.com/kb/1017/install/) 8 | 1. Clone this repo 9 | 1. Run the command `fly launch --name $APP_NAME --no-deploy` and answer the prompts (pick your own $APP_NAME) 10 | 1. [Create an auth key in Tailscale](https://tailscale.com/kb/1085/auth-keys/) and copy it to clipboard 11 | 1. Run the command `fly secrets set TAILSCALE_AUTHKEY=` 12 | 1. Run the command `fly deploy --remote-only` 13 | 1. Test it out with `dig @${APP_NAME}.fly.dev google.com` 14 | 1. If $STEP-1 worked, get the IP address of the Tailscale interface (from the [Tailscale admin](https://login.tailscale.com/admin/machines)) and set that as your DNS resolver (in `/etc/resolv.conf`, Advanced tab of macOS's Networking panel in System Preferences, iOS, etc.) 15 | 16 | 17 | Reading/Reference: 18 | 19 | * [Pi-Hole Docker image](https://github.com/pi-hole/docker-pi-hole) 20 | * [Stuff Your Pi-Hole From Anywhere (Fly.io blog)](https://fly.io/blog/stuff-your-pi-hole-from-anywhere/) 21 | * [Free, private pi-hole hosting with Fly.io and Tailscale](https://arun.be/2021/11/22/private-pi-hole-hosting-fly-tailscale/) 22 | * [Tailscale on fly.io (Tailscale docs)](https://tailscale.com/kb/1132/flydotio/) 23 | * [Need to use legacy iptables when adding Tailscale to Linux (GitHub issue comment)](https://github.com/hassio-addons/addon-tailscale/issues/20#issuecomment-929104783) 24 | * [Trying to follow the "Fly-Hole" tutorial... (Fly.io forum post)](https://community.fly.io/t/trying-to-follow-the-fly-hole-tutorial/3470) 25 | * [Self-hosted RethinkDNS (Pi-Hole alternative)](https://github.com/serverless-dns/serverless-dns/) 26 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | app = "fli-hole-tailscale" 2 | 3 | 4 | [[services]] 5 | internal_port = 80 6 | protocol = "tcp" 7 | 8 | [services.concurrency] 9 | hard_limit = 25 10 | soft_limit = 20 11 | 12 | [[services.ports]] 13 | handlers = [] 14 | port = "80" 15 | 16 | [[services.ports]] 17 | handlers = ["tls"] 18 | port = "443" 19 | 20 | [[services.tcp_checks]] 21 | interval = 10000 22 | timeout = 2000 23 | 24 | [[services]] 25 | internal_port = 53 26 | protocol = "udp" 27 | 28 | [[services.ports]] 29 | port = "53" 30 | -------------------------------------------------------------------------------- /s6_run_tailscale_once: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -m 4 | 5 | # Only run this script once 6 | s6-svc -O /var/run/s6/services/tailscale 7 | 8 | echo "Starting tailscaled" 9 | /app/tailscaled --state=/var/lib/tailscale/tailscaled.state --socket=/var/run/tailscale/tailscaled.sock & 10 | 11 | echo -n "Sleeping for 5 seconds for tailscaled to finish ... " 12 | sleep 5 13 | echo "done" 14 | 15 | echo "Starting tailscale" 16 | /app/tailscale up --authkey=${TAILSCALE_AUTHKEY} --hostname=fli-hole & 17 | 18 | fg 1 19 | -------------------------------------------------------------------------------- /workaround-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # Dockerfile variables 3 | export TAG 4 | export ServerIP 5 | export ServerIPv6 6 | export PYTEST 7 | export PHP_ENV_CONFIG 8 | export PHP_ERROR_LOG 9 | export HOSTNAME 10 | export WEBLOGDIR 11 | export DNS1 12 | export DNS2 13 | export DNSSEC 14 | export DNS_BOGUS_PRIV 15 | export DNS_FQDN_REQUIRED 16 | export INTERFACE 17 | export DNSMASQ_LISTENING_BEHAVIOUR="$DNSMASQ_LISTENING" 18 | export IPv6 19 | export WEB_PORT 20 | export REV_SERVER 21 | export REV_SERVER_DOMAIN 22 | export REV_SERVER_TARGET 23 | export REV_SERVER_CIDR 24 | export CONDITIONAL_FORWARDING 25 | export CONDITIONAL_FORWARDING_IP 26 | export CONDITIONAL_FORWARDING_DOMAIN 27 | export CONDITIONAL_FORWARDING_REVERSE 28 | export TEMPERATUREUNIT 29 | export ADMIN_EMAIL 30 | export WEBUIBOXEDLAYOUT 31 | export QUERY_LOGGING 32 | export PIHOLE_DNS_ 33 | export DHCP_ACTIVE 34 | export DHCP_START 35 | export DHCP_END 36 | export DHCP_ROUTER 37 | export DHCP_LEASETIME 38 | export PIHOLE_DOMAIN 39 | export DHCP_IPv6 40 | export DHCP_rapid_commit 41 | export WEBTHEME 42 | export CUSTOM_CACHE_SIZE 43 | 44 | export adlistFile='/etc/pihole/adlists.list' 45 | 46 | # If user has set QUERY_LOGGING Env Var, copy it out to _OVERRIDE, else it will get reset when we source the next two files 47 | # Come back to it at the end of the file 48 | [ -n "${QUERY_LOGGING}" ] && QUERY_LOGGING_OVERRIDE="${QUERY_LOGGING}" 49 | 50 | # The below functions are all contained in bash_functions.sh 51 | . /bash_functions.sh 52 | 53 | # Ensure we have all functions available to update our configurations 54 | . /opt/pihole/webpage.sh 55 | 56 | # PH_TEST prevents the install from actually running (someone should rename that) 57 | PH_TEST=true . "${PIHOLE_INSTALL}" 58 | 59 | echo " ::: Starting docker specific checks & setup for docker pihole/pihole" 60 | 61 | # TODO: 62 | #if [ ! -f /.piholeFirstBoot ] ; then 63 | # echo " ::: Not first container startup so not running docker's setup, re-create container to run setup again" 64 | #else 65 | # regular_setup_functions 66 | #fi 67 | 68 | #fix_capabilities 69 | load_web_password_secret 70 | generate_password 71 | validate_env || exit 1 72 | prepare_configs 73 | 74 | [ -n "${PIHOLE_INTERFACE}" ] && change_setting "PIHOLE_INTERFACE" "$PIHOLE_INTERFACE" 75 | [ -n "${IPV4_ADDRESS}" ] && change_setting "IPV4_ADDRESS" "$IPV4_ADDRESS" 76 | [ -n "${INSTALL_WEB_SERVER}" ] && change_setting "INSTALL_WEB_SERVER" "$INSTALL_WEB_SERVER" 77 | [ -n "${INSTALL_WEB_INTERFACE}" ] && change_setting "INSTALL_WEB_INTERFACE" "$INSTALL_WEB_INTERFACE" 78 | [ -n "${LIGHTTPD_ENABLED}" ] && change_setting "LIGHTTPD_ENABLED" "$LIGHTTPD_ENABLED" 79 | [ -n "${DNS_BOGUS_PRIV}" ] && change_setting "DNS_BOGUS_PRIV" "$DNS_BOGUS_PRIV" 80 | [ -n "${ServerIP}" ] && changeFTLsetting "REPLY_ADDR4" "$ServerIP" 81 | [ -n "${ServerIPv6}" ] && changeFTLsetting "REPLY_ADDR6" "$ServerIPv6" 82 | [ -n "${DNS_FQDN_REQUIRED}" ] && change_setting "DNS_FQDN_REQUIRED" "$DNS_FQDN_REQUIRED" 83 | [ -n "${DNSSEC}" ] && change_setting "DNSSEC" "$DNSSEC" 84 | [ -n "${REV_SERVER}" ] && change_setting "REV_SERVER" "$REV_SERVER" 85 | [ -n "${REV_SERVER_DOMAIN}" ] && change_setting "REV_SERVER_DOMAIN" "$REV_SERVER_DOMAIN" 86 | [ -n "${REV_SERVER_TARGET}" ] && change_setting "REV_SERVER_TARGET" "$REV_SERVER_TARGET" 87 | [ -n "${REV_SERVER_CIDR}" ] && change_setting "REV_SERVER_CIDR" "$REV_SERVER_CIDR" 88 | 89 | # Get all exported environment variables starting with FTLCONF_ as a prefix and call the changeFTLsetting 90 | # function with the environment variable's suffix as the key. This allows applying any pihole-FTL.conf 91 | # setting defined here: https://docs.pi-hole.net/ftldns/configfile/ 92 | declare -px | grep FTLCONF_ | sed -E 's/declare -x FTLCONF_([^=]+)=\"(.+)\"/\1 \2/' | while read -r name value 93 | do 94 | echo "Applying pihole-FTL.conf setting $name=$value" 95 | changeFTLsetting "$name" "$value" 96 | done 97 | 98 | if [ -z "$REV_SERVER" ];then 99 | # If the REV_SERVER* variables are set, then there is no need to add these. 100 | # If it is not set, then adding these variables is fine, and they will be converted by the Pi-hole install script 101 | [ -n "${CONDITIONAL_FORWARDING}" ] && change_setting "CONDITIONAL_FORWARDING" "$CONDITIONAL_FORWARDING" 102 | [ -n "${CONDITIONAL_FORWARDING_IP}" ] && change_setting "CONDITIONAL_FORWARDING_IP" "$CONDITIONAL_FORWARDING_IP" 103 | [ -n "${CONDITIONAL_FORWARDING_DOMAIN}" ] && change_setting "CONDITIONAL_FORWARDING_DOMAIN" "$CONDITIONAL_FORWARDING_DOMAIN" 104 | [ -n "${CONDITIONAL_FORWARDING_REVERSE}" ] && change_setting "CONDITIONAL_FORWARDING_REVERSE" "$CONDITIONAL_FORWARDING_REVERSE" 105 | fi 106 | 107 | if [ -z "${PIHOLE_DNS_}" ]; then 108 | # For backward compatibility, if DNS1 and/or DNS2 are set, but PIHOLE_DNS_ is not, convert them to 109 | # a semi-colon delimited string and store in PIHOLE_DNS_ 110 | # They are not used anywhere if PIHOLE_DNS_ is set already 111 | [ -n "${DNS1}" ] && echo "Converting DNS1 to PIHOLE_DNS_" && PIHOLE_DNS_="$DNS1" 112 | [[ -n "${DNS2}" && "${DNS2}" != "no" ]] && echo "Converting DNS2 to PIHOLE_DNS_" && PIHOLE_DNS_="$PIHOLE_DNS_;$DNS2" 113 | fi 114 | 115 | # Parse the PIHOLE_DNS variable, if it exists, and apply upstream servers to Pi-hole config 116 | if [ -n "${PIHOLE_DNS_}" ]; then 117 | echo "Setting DNS servers based on PIHOLE_DNS_ variable" 118 | # Remove any PIHOLE_DNS_ entries from setupVars.conf, if they exist 119 | sed -i '/PIHOLE_DNS_/d' /etc/pihole/setupVars.conf 120 | # Split into an array (delimited by ;) 121 | # Loop through and add them one by one to setupVars.conf 122 | PIHOLE_DNS_ARR=(${PIHOLE_DNS_//;/ }) 123 | count=1 124 | valid_entries=0 125 | for i in "${PIHOLE_DNS_ARR[@]}"; do 126 | if valid_ip "$i" || valid_ip6 "$i" ; then 127 | change_setting "PIHOLE_DNS_$count" "$i" 128 | ((count=count+1)) 129 | ((valid_entries=valid_entries+1)) 130 | continue 131 | fi 132 | if [ -n "$(dig +short ${i//#*/})" ]; then 133 | # If the "address" is a domain (for example a docker link) then try to resolve it and add 134 | # the result as a DNS server in setupVars.conf. 135 | resolved_ip="$(dig +short ${i//#*/} | head -n 1)" 136 | if [ -n "${i//*#/}" ] && [ "${i//*#/}" != "${i//#*/}" ]; then 137 | resolved_ip="${resolved_ip}#${i//*#/}" 138 | fi 139 | echo "Resolved ${i} from PIHOLE_DNS_ as: ${resolved_ip}" 140 | if valid_ip "$resolved_ip" || valid_ip6 "$resolved_ip" ; then 141 | change_setting "PIHOLE_DNS_$count" "$resolved_ip" 142 | ((count=count+1)) 143 | ((valid_entries=valid_entries+1)) 144 | continue 145 | fi 146 | fi 147 | # If the above tests fail then this is an invalid DNS server 148 | echo "Invalid entry detected in PIHOLE_DNS_: ${i}" 149 | done 150 | 151 | if [ $valid_entries -eq 0 ]; then 152 | echo "No Valid entries detected in PIHOLE_DNS_. Aborting" 153 | exit 1 154 | fi 155 | else 156 | # Environment variable has not been set, but there may be existing values in an existing setupVars.conf 157 | # if this is the case, we do not want to overwrite these with the defaults of 8.8.8.8 and 8.8.4.4 158 | # Pi-hole can run with only one upstream configured, so we will just check for one. 159 | setupVarsDNS="$(grep 'PIHOLE_DNS_' /etc/pihole/setupVars.conf || true)" 160 | 161 | if [ -z "${setupVarsDNS}" ]; then 162 | echo "Configuring default DNS servers: 8.8.8.8, 8.8.4.4" 163 | change_setting "PIHOLE_DNS_1" "8.8.8.8" 164 | change_setting "PIHOLE_DNS_2" "8.8.4.4" 165 | else 166 | echo "Existing DNS servers detected in setupVars.conf. Leaving them alone" 167 | fi 168 | fi 169 | 170 | # Parse the WEBTHEME variable, if it exists, and set the selected theme if it is one of the supported values. 171 | # If an invalid theme name was supplied, setup WEBTHEME to use the default-light theme. 172 | if [ -n "${WEBTHEME}" ]; then 173 | case "${WEBTHEME}" in 174 | "default-dark" | "default-darker" | "default-light" | "default-auto" | "lcars") 175 | echo "Setting Web Theme based on WEBTHEME variable, using value ${WEBTHEME}" 176 | change_setting "WEBTHEME" "${WEBTHEME}" 177 | ;; 178 | *) 179 | echo "Invalid theme name supplied: ${WEBTHEME}, falling back to default-light." 180 | change_setting "WEBTHEME" "default-light" 181 | ;; 182 | esac 183 | fi 184 | 185 | [[ -n "${DHCP_ACTIVE}" && ${DHCP_ACTIVE} == "true" ]] && echo "Setting DHCP server" && setup_dhcp 186 | 187 | setup_web_port "$WEB_PORT" 188 | setup_web_password "$WEBPASSWORD" 189 | setup_temp_unit "$TEMPERATUREUNIT" 190 | setup_ui_layout "$WEBUIBOXEDLAYOUT" 191 | setup_admin_email "$ADMIN_EMAIL" 192 | setup_dnsmasq "$INTERFACE" "$DNSMASQ_LISTENING_BEHAVIOUR" 193 | setup_php_env 194 | setup_dnsmasq_hostnames "$ServerIP" "$ServerIPv6" "$HOSTNAME" 195 | setup_ipv4_ipv6 196 | setup_lighttpd_bind "$ServerIP" 197 | setup_blocklists 198 | test_configs 199 | 200 | [ -f /.piholeFirstBoot ] && rm /.piholeFirstBoot 201 | 202 | # Set QUERY_LOGGING value in setupVars to be that which the user has passed in as an ENV var (if they have) 203 | [ -n "${QUERY_LOGGING_OVERRIDE}" ] && change_setting "QUERY_LOGGING" "$QUERY_LOGGING_OVERRIDE" 204 | 205 | # Source setupVars.conf to get the true value of QUERY_LOGGING 206 | . ${setupVars} 207 | 208 | if [ ${QUERY_LOGGING} == "false" ]; then 209 | echo "::: Disabling Query Logging" 210 | pihole logging off 211 | else 212 | # If it is anything other than false, set it to true 213 | change_setting "QUERY_LOGGING" "true" 214 | # Set pihole logging on for good measure 215 | echo "::: Enabling Query Logging" 216 | pihole logging on 217 | fi 218 | 219 | echo " ::: Docker start setup complete" 220 | --------------------------------------------------------------------------------