#!/bin/bash # Some of the bash_functions use variables these core pi-hole/web scripts . /opt/pihole/webpage.sh fix_capabilities() { setcap CAP_NET_BIND_SERVICE,CAP_NET_RAW,CAP_NET_ADMIN+ei $(which pihole-FTL) || ret=$? if [[ $ret -ne 0 && "${DNSMASQ_USER:-root}" != "root" ]]; then echo "ERROR: Failed to set capabilities for pihole-FTL. Cannot run as non-root." exit 1 fi } prepare_configs() { # Done in /start.sh, don't do twice PH_TEST=true . $PIHOLE_INSTALL distro_check installConfigs touch "$setupVars" set +e mkdir -p /var/run/pihole /var/log/pihole # Re-apply perms from basic-install over any volume mounts that may be present (or not) # Also similar to preflights for FTL https://github.com/pi-hole/pi-hole/blob/master/advanced/Templates/pihole-FTL.service chown pihole:root /etc/lighttpd chown pihole:pihole "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" "/var/log/pihole" "${regexFile}" chmod 644 "${PI_HOLE_CONFIG_DIR}/pihole-FTL.conf" # not sure why pihole:pihole user/group write perms are not enough for web to write...dirty fix: chmod 777 "${regexFile}" touch /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port /var/log/pihole.log chown pihole:pihole /var/run/pihole /var/log/pihole test -f /var/run/pihole/FTL.sock && rm /var/run/pihole/FTL.sock chown pihole:pihole /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port /etc/pihole /etc/pihole/dhcp.leases /var/log/pihole.log chmod 0644 /var/log/pihole-FTL.log /run/pihole-FTL.pid /run/pihole-FTL.port /var/log/pihole.log set -e # Update version numbers pihole updatechecker # Re-write all of the setupVars to ensure required ones are present (like QUERY_LOGGING) # If the setup variable file exists, if [[ -e "${setupVars}" ]]; then cp -f "${setupVars}" "${setupVars}.update.bak" fi } validate_env() { # Optional ServerIP is a valid IP # nc won't throw any text based errors when it times out connecting to a valid IP, otherwise it complains about the DNS name being garbage # if nc doesn't behave as we expect on a valid IP the routing table should be able to look it up and return a 0 retcode if [[ "$(nc -4 -w1 -z "$ServerIP" 53 2>&1)" != "" ]] && ! ip route get "$ServerIP" > /dev/null ; then echo "ERROR: ServerIP Environment variable ($ServerIP) doesn't appear to be a valid IPv4 address" exit 1 fi # Optional IPv6 is a valid address if [[ -n "$ServerIPv6" ]] ; then if [[ "$ServerIPv6" == 'kernel' ]] ; then echo "ERROR: You passed in IPv6 with a value of 'kernel', this maybe beacuse you do not have IPv6 enabled on your network" unset ServerIPv6 exit 1 fi if [[ "$(nc -6 -w1 -z "$ServerIPv6" 53 2>&1)" != "" ]] && ! ip route get "$ServerIPv6" > /dev/null ; then echo "ERROR: ServerIPv6 Environment variable ($ServerIPv6) doesn't appear to be a valid IPv6 address" echo " TIP: If your server is not IPv6 enabled just remove '-e ServerIPv6' from your docker container" exit 1 fi fi; } setup_dnsmasq_dns() { . /opt/pihole/webpage.sh local DNS1="${1:-8.8.8.8}" local DNS2="${2:-8.8.4.4}" local dnsType='default' if [ "$DNS1" != '8.8.8.8' ] || [ "$DNS2" != '8.8.4.4' ] ; then dnsType='custom' fi; # TODO With the addition of this to /start.sh this needs a refactor if [ ! -f /.piholeFirstBoot ] ; then local setupDNS1="$(grep 'PIHOLE_DNS_1' ${setupVars})" local setupDNS2="$(grep 'PIHOLE_DNS_2' ${setupVars})" setupDNS1="${setupDNS1/PIHOLE_DNS_1=/}" setupDNS2="${setupDNS2/PIHOLE_DNS_2=/}" if [[ -n "$DNS1" && -n "$setupDNS1" ]] || \ [[ -n "$DNS2" && -n "$setupDNS2" ]] ; then echo "Docker DNS variables not used" fi echo "Existing DNS servers used (${setupDNS1:-unset} & ${setupDNS2:-unset})" return fi echo "Using $dnsType DNS servers: $DNS1 & $DNS2" if [[ -n "$DNS1" && -z "$setupDNS1" ]] ; then change_setting "PIHOLE_DNS_1" "${DNS1}" fi if [[ -n "$DNS2" && -z "$setupDNS2" ]] ; then if [[ "$DNS2" == "no" ]] ; then delete_setting "PIHOLE_DNS_2" unset PIHOLE_DNS_2 else change_setting "PIHOLE_DNS_2" "${DNS2}" fi fi } setup_dnsmasq_interface() { local interface="${1:-eth0}" local interfaceType='default' if [ "$interface" != 'eth0' ] ; then interfaceType='custom' fi; echo "DNSMasq binding to $interfaceType interface: $interface" [ -n "$interface" ] && change_setting "PIHOLE_INTERFACE" "${interface}" } setup_dnsmasq_listening_behaviour() { local dnsmasq_listening_behaviour="${1}" if [ -n "$dnsmasq_listening_behaviour" ]; then change_setting "DNSMASQ_LISTENING" "${dnsmasq_listening_behaviour}" fi; } setup_dnsmasq_config_if_missing() { # When fresh empty directory volumes are used we miss this file if [ ! -f /etc/dnsmasq.d/01-pihole.conf ] ; then cp /etc/.pihole/advanced/01-pihole.conf /etc/dnsmasq.d/ fi; } setup_dnsmasq() { local dns1="$1" local dns2="$2" local interface="$3" local dnsmasq_listening_behaviour="$4" # Coordinates setup_dnsmasq_config_if_missing setup_dnsmasq_dns "$dns1" "$dns2" setup_dnsmasq_interface "$interface" setup_dnsmasq_listening_behaviour "$dnsmasq_listening_behaviour" setup_dnsmasq_user "${DNSMASQ_USER}" ProcessDNSSettings } setup_dnsmasq_user() { local DNSMASQ_USER="${1}" # Run DNSMASQ as root user to avoid SHM permission issues if grep -r -q '^\s*user=' /etc/dnsmasq.* ; then # Change user that had been set previously to root for f in $(grep -r -l '^\s*user=' /etc/dnsmasq.*); do sed -i "/^\s*user=/ c\user=${DNSMASQ_USER}" "${f}" done else echo -e "\nuser=${DNSMASQ_USER}" >> /etc/dnsmasq.conf fi } setup_dnsmasq_hostnames() { # largely borrowed from automated install/basic-install.sh local IPV4_ADDRESS="${1}" local IPV6_ADDRESS="${2}" local hostname="${3}" local dnsmasq_pihole_01_location="/etc/dnsmasq.d/01-pihole.conf" if [ -z "$hostname" ]; then if [[ -f /etc/hostname ]]; then hostname=$( \"${VIRTUAL_HOST}\"," local serverip_line="\t\t\t\"ServerIP\" => \"${ServerIP}\"," local php_error_line="\t\t\t\"PHP_ERROR_LOG\" => \"${PHP_ERROR_LOG}\"," # idempotent line additions grep -qP "$vhost_line" "$PHP_ENV_CONFIG" || \ sed -i "/bin-environment/ a\\${vhost_line}" "$PHP_ENV_CONFIG" grep -qP "$serverip_line" "$PHP_ENV_CONFIG" || \ sed -i "/bin-environment/ a\\${serverip_line}" "$PHP_ENV_CONFIG" grep -qP "$php_error_line" "$PHP_ENV_CONFIG" || \ sed -i "/bin-environment/ a\\${php_error_line}" "$PHP_ENV_CONFIG" echo "Added ENV to php:" grep -E '(VIRTUAL_HOST|ServerIP|PHP_ERROR_LOG)' "$PHP_ENV_CONFIG" } setup_web_port() { local warning="WARNING: Custom WEB_PORT not used" # Quietly exit early for empty or default if [[ -z "${1}" || "${1}" == '80' ]] ; then return ; fi if ! echo $1 | grep -q '^[0-9][0-9]*$' ; then echo "$warning - $1 is not an integer" return fi local -i web_port="$1" if (( $web_port < 1 || $web_port > 65535 )); then echo "$warning - $web_port is not within valid port range of 1-65535" return fi echo "Custom WEB_PORT set to $web_port" echo "INFO: Without proper router DNAT forwarding to $ServerIP:$web_port, you may not get any blocked websites on ads" # Update lighttpd's port sed -i '/server.port\s*=\s*80\s*$/ s/80/'$WEB_PORT'/g' /etc/lighttpd/lighttpd.conf } load_web_password_secret() { # If WEBPASSWORD is not set at all, attempt to read password from WEBPASSWORD_FILE, # allowing secrets to be passed via docker secrets if [ -z "${WEBPASSWORD+x}" ] && [ -n "${WEBPASSWORD_FILE}" ] && [ -r "${WEBPASSWORD_FILE}" ]; then WEBPASSWORD=$(<"${WEBPASSWORD_FILE}") fi; } generate_password() { if [ -z "${WEBPASSWORD+x}" ] ; then # Not set at all, give the user a random pass WEBPASSWORD=$(tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c 8) echo "Assigning random password: $WEBPASSWORD" fi; } setup_web_password() { setup_var_exists "WEBPASSWORD" && return PASS="$1" # Turn bash debug on while setting up password (to print it) if [[ "$PASS" == "" ]] ; then echo "" | pihole -a -p else echo "Setting password: ${PASS}" set -x pihole -a -p "$PASS" "$PASS" fi # Turn bash debug back off after print password setup # (subshell to null hides printing output) { set +x; } 2>/dev/null # To avoid printing this if conditional in bash debug, turn off debug above.. # then re-enable debug if necessary (more code but cleaner printed output) if [ "${PH_VERBOSE:-0}" -gt 0 ] ; then set -x fi } setup_ipv4_ipv6() { local ip_versions="IPv4 and IPv6" if [ "$IPv6" != "True" ] ; then ip_versions="IPv4" sed -i '/use-ipv6.pl/ d' /etc/lighttpd/lighttpd.conf fi; echo "Using $ip_versions" } test_configs() { set -e echo -n '::: Testing pihole-FTL DNS: ' sudo -u ${DNSMASQ_USER:-root} pihole-FTL test || exit 1 echo -n '::: Testing lighttpd config: ' lighttpd -t -f /etc/lighttpd/lighttpd.conf || exit 1 set +e echo "::: All config checks passed, cleared for startup ..." } setup_blocklists() { local blocklists="$1" # Exit/return early without setting up adlists with defaults for any of the following conditions: # 1. skip_setup_blocklists env is set exit_string="(exiting ${FUNCNAME[0]} early)" if [ -n "${skip_setup_blocklists}" ]; then echo "::: skip_setup_blocklists requested ($exit_string)" return fi # 2. The adlist file exists already (restarted container or volume mounted list) if [ -f "${adlistFile}" ]; then echo "::: Preexisting ad list ${adlistFile} detected ($exit_string)" cat "${adlistFile}" return fi echo "::: ${FUNCNAME[0]} now setting default blocklists up: " echo "::: TIP: Use a docker volume for ${adlistFile} if you want to customize for first boot" installDefaultBlocklists echo "::: Blocklists (${adlistFile}) now set to:" cat "${adlistFile}" } setup_var_exists() { local KEY="$1" if [ -n "$2" ]; then local REQUIRED_VALUE="[^\n]+" fi if grep -Pq "^${KEY}=${REQUIRED_VALUE}" "$setupVars"; then echo "::: Pre existing ${KEY} found" true else false fi } setup_temp_unit() { local UNIT="$1" # check if var is empty if [[ "$UNIT" != "" ]] ; then # check if we have valid units if [[ "$UNIT" == "c" || "$UNIT" == "k" || $UNIT == "f" ]] ; then pihole -a -${UNIT} fi fi } setup_ui_layout() { local LO=$1 # check if var is empty if [[ "$LO" != "" ]] ; then # check if we have valid types boxed | traditional if [[ "$LO" == "traditional" || "$LO" == "boxed" ]] ; then change_setting "WEBUIBOXEDLAYOUT" "$WEBUIBOXEDLAYOUT" fi fi } setup_admin_email() { local EMAIL=$1 # check if var is empty if [[ "$EMAIL" != "" ]] ; then pihole -a -e "$EMAIL" fi }