docker-pi-hole/bash_functions.sh

375 lines
13 KiB
Bash

#!/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=$(</etc/hostname)
elif [ -x "$(command -v hostname)" ]; then
hostname=$(hostname -f)
fi
fi;
if [[ "${IPV4_ADDRESS}" != "" ]]; then
tmp=${IPV4_ADDRESS%/*}
sed -i "s/@IPV4@/$tmp/" ${dnsmasq_pihole_01_location}
else
sed -i '/^address=\/pi.hole\/@IPV4@/d' ${dnsmasq_pihole_01_location}
sed -i '/^address=\/@HOSTNAME@\/@IPV4@/d' ${dnsmasq_pihole_01_location}
fi
if [[ "${IPV6_ADDRESS}" != "" ]]; then
sed -i "s/@IPv6@/$IPV6_ADDRESS/" ${dnsmasq_pihole_01_location}
else
sed -i '/^address=\/pi.hole\/@IPv6@/d' ${dnsmasq_pihole_01_location}
sed -i '/^address=\/@HOSTNAME@\/@IPv6@/d' ${dnsmasq_pihole_01_location}
fi
if [[ "${hostname}" != "" ]]; then
sed -i "s/@HOSTNAME@/$hostname/" ${dnsmasq_pihole_01_location}
else
sed -i '/^address=\/@HOSTNAME@*/d' ${dnsmasq_pihole_01_location}
fi
}
setup_lighttpd_bind() {
local serverip="$1"
# if using '--net=host' only bind lighttpd on $ServerIP and localhost
if grep -q "docker" /proc/net/dev && [[ $serverip != 0.0.0.0 ]]; then #docker (docker0 by default) should only be present on the host system
if ! grep -q "server.bind" /etc/lighttpd/lighttpd.conf ; then # if the declaration is already there, don't add it again
sed -i -E "s/server\.port\s+\=\s+([0-9]+)/server.bind\t\t = \"${serverip}\"\nserver.port\t\t = \1\n"\$SERVER"\[\"socket\"\] == \"127\.0\.0\.1:\1\" \{\}/" /etc/lighttpd/lighttpd.conf
fi
fi
}
setup_php_env() {
if [ -z "$VIRTUAL_HOST" ] ; then
VIRTUAL_HOST="$ServerIP"
fi;
local vhost_line="\t\t\t\"VIRTUAL_HOST\" => \"${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
}